editor_tests.rs

    1use super::*;
    2use crate::{
    3    JoinLines,
    4    code_context_menus::CodeContextMenu,
    5    edit_prediction_tests::FakeEditPredictionProvider,
    6    linked_editing_ranges::LinkedEditingRanges,
    7    scroll::scroll_amount::ScrollAmount,
    8    test::{
    9        assert_text_with_selections, build_editor,
   10        editor_lsp_test_context::{EditorLspTestContext, git_commit_lang},
   11        editor_test_context::EditorTestContext,
   12        select_ranges,
   13    },
   14};
   15use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
   16use futures::StreamExt;
   17use gpui::{
   18    BackgroundExecutor, DismissEvent, Rgba, SemanticVersion, TestAppContext, UpdateGlobal,
   19    VisualTestContext, WindowBounds, WindowOptions, div,
   20};
   21use indoc::indoc;
   22use language::{
   23    BracketPairConfig,
   24    Capability::ReadWrite,
   25    DiagnosticSourceKind, FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageMatcher,
   26    LanguageName, Override, Point,
   27    language_settings::{
   28        AllLanguageSettings, AllLanguageSettingsContent, CompletionSettings, FormatterList,
   29        LanguageSettingsContent, LspInsertMode, PrettierSettings, SelectedFormatter,
   30    },
   31    tree_sitter_python,
   32};
   33use language_settings::{Formatter, IndentGuideSettings};
   34use lsp::CompletionParams;
   35use multi_buffer::{IndentGuide, PathKey};
   36use parking_lot::Mutex;
   37use pretty_assertions::{assert_eq, assert_ne};
   38use project::{
   39    FakeFs,
   40    debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
   41    project_settings::{LspSettings, ProjectSettings},
   42};
   43use serde_json::{self, json};
   44use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
   45use std::{
   46    iter,
   47    sync::atomic::{self, AtomicUsize},
   48};
   49use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
   50use text::ToPoint as _;
   51use unindent::Unindent;
   52use util::{
   53    assert_set_eq, path,
   54    test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text},
   55    uri,
   56};
   57use workspace::{
   58    CloseActiveItem, CloseAllItems, CloseOtherItems, MoveItemToPaneInDirection, NavigationEntry,
   59    OpenOptions, ViewId,
   60    item::{FollowEvent, FollowableItem, Item, ItemHandle, SaveOptions},
   61};
   62
   63#[gpui::test]
   64fn test_edit_events(cx: &mut TestAppContext) {
   65    init_test(cx, |_| {});
   66
   67    let buffer = cx.new(|cx| {
   68        let mut buffer = language::Buffer::local("123456", cx);
   69        buffer.set_group_interval(Duration::from_secs(1));
   70        buffer
   71    });
   72
   73    let events = Rc::new(RefCell::new(Vec::new()));
   74    let editor1 = cx.add_window({
   75        let events = events.clone();
   76        |window, cx| {
   77            let entity = cx.entity().clone();
   78            cx.subscribe_in(
   79                &entity,
   80                window,
   81                move |_, _, event: &EditorEvent, _, _| match event {
   82                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
   83                    EditorEvent::BufferEdited => {
   84                        events.borrow_mut().push(("editor1", "buffer edited"))
   85                    }
   86                    _ => {}
   87                },
   88            )
   89            .detach();
   90            Editor::for_buffer(buffer.clone(), None, window, cx)
   91        }
   92    });
   93
   94    let editor2 = cx.add_window({
   95        let events = events.clone();
   96        |window, cx| {
   97            cx.subscribe_in(
   98                &cx.entity().clone(),
   99                window,
  100                move |_, _, event: &EditorEvent, _, _| match event {
  101                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
  102                    EditorEvent::BufferEdited => {
  103                        events.borrow_mut().push(("editor2", "buffer edited"))
  104                    }
  105                    _ => {}
  106                },
  107            )
  108            .detach();
  109            Editor::for_buffer(buffer.clone(), None, window, cx)
  110        }
  111    });
  112
  113    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  114
  115    // Mutating editor 1 will emit an `Edited` event only for that editor.
  116    _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
  117    assert_eq!(
  118        mem::take(&mut *events.borrow_mut()),
  119        [
  120            ("editor1", "edited"),
  121            ("editor1", "buffer edited"),
  122            ("editor2", "buffer edited"),
  123        ]
  124    );
  125
  126    // Mutating editor 2 will emit an `Edited` event only for that editor.
  127    _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
  128    assert_eq!(
  129        mem::take(&mut *events.borrow_mut()),
  130        [
  131            ("editor2", "edited"),
  132            ("editor1", "buffer edited"),
  133            ("editor2", "buffer edited"),
  134        ]
  135    );
  136
  137    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  138    _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  139    assert_eq!(
  140        mem::take(&mut *events.borrow_mut()),
  141        [
  142            ("editor1", "edited"),
  143            ("editor1", "buffer edited"),
  144            ("editor2", "buffer edited"),
  145        ]
  146    );
  147
  148    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  149    _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  150    assert_eq!(
  151        mem::take(&mut *events.borrow_mut()),
  152        [
  153            ("editor1", "edited"),
  154            ("editor1", "buffer edited"),
  155            ("editor2", "buffer edited"),
  156        ]
  157    );
  158
  159    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  160    _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  161    assert_eq!(
  162        mem::take(&mut *events.borrow_mut()),
  163        [
  164            ("editor2", "edited"),
  165            ("editor1", "buffer edited"),
  166            ("editor2", "buffer edited"),
  167        ]
  168    );
  169
  170    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  171    _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  172    assert_eq!(
  173        mem::take(&mut *events.borrow_mut()),
  174        [
  175            ("editor2", "edited"),
  176            ("editor1", "buffer edited"),
  177            ("editor2", "buffer edited"),
  178        ]
  179    );
  180
  181    // No event is emitted when the mutation is a no-op.
  182    _ = editor2.update(cx, |editor, window, cx| {
  183        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  184            s.select_ranges([0..0])
  185        });
  186
  187        editor.backspace(&Backspace, window, cx);
  188    });
  189    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  190}
  191
  192#[gpui::test]
  193fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  194    init_test(cx, |_| {});
  195
  196    let mut now = Instant::now();
  197    let group_interval = Duration::from_millis(1);
  198    let buffer = cx.new(|cx| {
  199        let mut buf = language::Buffer::local("123456", cx);
  200        buf.set_group_interval(group_interval);
  201        buf
  202    });
  203    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  204    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
  205
  206    _ = editor.update(cx, |editor, window, cx| {
  207        editor.start_transaction_at(now, window, cx);
  208        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  209            s.select_ranges([2..4])
  210        });
  211
  212        editor.insert("cd", window, cx);
  213        editor.end_transaction_at(now, cx);
  214        assert_eq!(editor.text(cx), "12cd56");
  215        assert_eq!(editor.selections.ranges(cx), vec![4..4]);
  216
  217        editor.start_transaction_at(now, window, cx);
  218        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  219            s.select_ranges([4..5])
  220        });
  221        editor.insert("e", window, cx);
  222        editor.end_transaction_at(now, cx);
  223        assert_eq!(editor.text(cx), "12cde6");
  224        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  225
  226        now += group_interval + Duration::from_millis(1);
  227        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  228            s.select_ranges([2..2])
  229        });
  230
  231        // Simulate an edit in another editor
  232        buffer.update(cx, |buffer, cx| {
  233            buffer.start_transaction_at(now, cx);
  234            buffer.edit([(0..1, "a")], None, cx);
  235            buffer.edit([(1..1, "b")], None, cx);
  236            buffer.end_transaction_at(now, cx);
  237        });
  238
  239        assert_eq!(editor.text(cx), "ab2cde6");
  240        assert_eq!(editor.selections.ranges(cx), vec![3..3]);
  241
  242        // Last transaction happened past the group interval in a different editor.
  243        // Undo it individually and don't restore selections.
  244        editor.undo(&Undo, window, cx);
  245        assert_eq!(editor.text(cx), "12cde6");
  246        assert_eq!(editor.selections.ranges(cx), vec![2..2]);
  247
  248        // First two transactions happened within the group interval in this editor.
  249        // Undo them together and restore selections.
  250        editor.undo(&Undo, window, cx);
  251        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  252        assert_eq!(editor.text(cx), "123456");
  253        assert_eq!(editor.selections.ranges(cx), vec![0..0]);
  254
  255        // Redo the first two transactions together.
  256        editor.redo(&Redo, window, cx);
  257        assert_eq!(editor.text(cx), "12cde6");
  258        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  259
  260        // Redo the last transaction on its own.
  261        editor.redo(&Redo, window, cx);
  262        assert_eq!(editor.text(cx), "ab2cde6");
  263        assert_eq!(editor.selections.ranges(cx), vec![6..6]);
  264
  265        // Test empty transactions.
  266        editor.start_transaction_at(now, window, cx);
  267        editor.end_transaction_at(now, cx);
  268        editor.undo(&Undo, window, cx);
  269        assert_eq!(editor.text(cx), "12cde6");
  270    });
  271}
  272
  273#[gpui::test]
  274fn test_ime_composition(cx: &mut TestAppContext) {
  275    init_test(cx, |_| {});
  276
  277    let buffer = cx.new(|cx| {
  278        let mut buffer = language::Buffer::local("abcde", cx);
  279        // Ensure automatic grouping doesn't occur.
  280        buffer.set_group_interval(Duration::ZERO);
  281        buffer
  282    });
  283
  284    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  285    cx.add_window(|window, cx| {
  286        let mut editor = build_editor(buffer.clone(), window, cx);
  287
  288        // Start a new IME composition.
  289        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  290        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
  291        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  292        assert_eq!(editor.text(cx), "äbcde");
  293        assert_eq!(
  294            editor.marked_text_ranges(cx),
  295            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  296        );
  297
  298        // Finalize IME composition.
  299        editor.replace_text_in_range(None, "ā", window, cx);
  300        assert_eq!(editor.text(cx), "ābcde");
  301        assert_eq!(editor.marked_text_ranges(cx), None);
  302
  303        // IME composition edits are grouped and are undone/redone at once.
  304        editor.undo(&Default::default(), window, cx);
  305        assert_eq!(editor.text(cx), "abcde");
  306        assert_eq!(editor.marked_text_ranges(cx), None);
  307        editor.redo(&Default::default(), window, cx);
  308        assert_eq!(editor.text(cx), "ābcde");
  309        assert_eq!(editor.marked_text_ranges(cx), None);
  310
  311        // Start a new IME composition.
  312        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  313        assert_eq!(
  314            editor.marked_text_ranges(cx),
  315            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  316        );
  317
  318        // Undoing during an IME composition cancels it.
  319        editor.undo(&Default::default(), window, cx);
  320        assert_eq!(editor.text(cx), "ābcde");
  321        assert_eq!(editor.marked_text_ranges(cx), None);
  322
  323        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  324        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  325        assert_eq!(editor.text(cx), "ābcdè");
  326        assert_eq!(
  327            editor.marked_text_ranges(cx),
  328            Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
  329        );
  330
  331        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  332        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  333        assert_eq!(editor.text(cx), "ābcdę");
  334        assert_eq!(editor.marked_text_ranges(cx), None);
  335
  336        // Start a new IME composition with multiple cursors.
  337        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  338            s.select_ranges([
  339                OffsetUtf16(1)..OffsetUtf16(1),
  340                OffsetUtf16(3)..OffsetUtf16(3),
  341                OffsetUtf16(5)..OffsetUtf16(5),
  342            ])
  343        });
  344        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  345        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  346        assert_eq!(
  347            editor.marked_text_ranges(cx),
  348            Some(vec![
  349                OffsetUtf16(0)..OffsetUtf16(3),
  350                OffsetUtf16(4)..OffsetUtf16(7),
  351                OffsetUtf16(8)..OffsetUtf16(11)
  352            ])
  353        );
  354
  355        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  356        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  357        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  358        assert_eq!(
  359            editor.marked_text_ranges(cx),
  360            Some(vec![
  361                OffsetUtf16(1)..OffsetUtf16(2),
  362                OffsetUtf16(5)..OffsetUtf16(6),
  363                OffsetUtf16(9)..OffsetUtf16(10)
  364            ])
  365        );
  366
  367        // Finalize IME composition with multiple cursors.
  368        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  369        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  370        assert_eq!(editor.marked_text_ranges(cx), None);
  371
  372        editor
  373    });
  374}
  375
  376#[gpui::test]
  377fn test_selection_with_mouse(cx: &mut TestAppContext) {
  378    init_test(cx, |_| {});
  379
  380    let editor = cx.add_window(|window, cx| {
  381        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  382        build_editor(buffer, window, cx)
  383    });
  384
  385    _ = editor.update(cx, |editor, window, cx| {
  386        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  387    });
  388    assert_eq!(
  389        editor
  390            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  391            .unwrap(),
  392        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  393    );
  394
  395    _ = editor.update(cx, |editor, window, cx| {
  396        editor.update_selection(
  397            DisplayPoint::new(DisplayRow(3), 3),
  398            0,
  399            gpui::Point::<f32>::default(),
  400            window,
  401            cx,
  402        );
  403    });
  404
  405    assert_eq!(
  406        editor
  407            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  408            .unwrap(),
  409        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  410    );
  411
  412    _ = editor.update(cx, |editor, window, cx| {
  413        editor.update_selection(
  414            DisplayPoint::new(DisplayRow(1), 1),
  415            0,
  416            gpui::Point::<f32>::default(),
  417            window,
  418            cx,
  419        );
  420    });
  421
  422    assert_eq!(
  423        editor
  424            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  425            .unwrap(),
  426        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  427    );
  428
  429    _ = editor.update(cx, |editor, window, cx| {
  430        editor.end_selection(window, cx);
  431        editor.update_selection(
  432            DisplayPoint::new(DisplayRow(3), 3),
  433            0,
  434            gpui::Point::<f32>::default(),
  435            window,
  436            cx,
  437        );
  438    });
  439
  440    assert_eq!(
  441        editor
  442            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  443            .unwrap(),
  444        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  445    );
  446
  447    _ = editor.update(cx, |editor, window, cx| {
  448        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  449        editor.update_selection(
  450            DisplayPoint::new(DisplayRow(0), 0),
  451            0,
  452            gpui::Point::<f32>::default(),
  453            window,
  454            cx,
  455        );
  456    });
  457
  458    assert_eq!(
  459        editor
  460            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  461            .unwrap(),
  462        [
  463            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  464            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  465        ]
  466    );
  467
  468    _ = editor.update(cx, |editor, window, cx| {
  469        editor.end_selection(window, cx);
  470    });
  471
  472    assert_eq!(
  473        editor
  474            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  475            .unwrap(),
  476        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  477    );
  478}
  479
  480#[gpui::test]
  481fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  482    init_test(cx, |_| {});
  483
  484    let editor = cx.add_window(|window, cx| {
  485        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  486        build_editor(buffer, window, cx)
  487    });
  488
  489    _ = editor.update(cx, |editor, window, cx| {
  490        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  491    });
  492
  493    _ = editor.update(cx, |editor, window, cx| {
  494        editor.end_selection(window, cx);
  495    });
  496
  497    _ = editor.update(cx, |editor, window, cx| {
  498        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  499    });
  500
  501    _ = editor.update(cx, |editor, window, cx| {
  502        editor.end_selection(window, cx);
  503    });
  504
  505    assert_eq!(
  506        editor
  507            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  508            .unwrap(),
  509        [
  510            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  511            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  512        ]
  513    );
  514
  515    _ = editor.update(cx, |editor, window, cx| {
  516        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  517    });
  518
  519    _ = editor.update(cx, |editor, window, cx| {
  520        editor.end_selection(window, cx);
  521    });
  522
  523    assert_eq!(
  524        editor
  525            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  526            .unwrap(),
  527        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  528    );
  529}
  530
  531#[gpui::test]
  532fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  533    init_test(cx, |_| {});
  534
  535    let editor = cx.add_window(|window, cx| {
  536        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  537        build_editor(buffer, window, cx)
  538    });
  539
  540    _ = editor.update(cx, |editor, window, cx| {
  541        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  542        assert_eq!(
  543            editor.selections.display_ranges(cx),
  544            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  545        );
  546    });
  547
  548    _ = editor.update(cx, |editor, window, cx| {
  549        editor.update_selection(
  550            DisplayPoint::new(DisplayRow(3), 3),
  551            0,
  552            gpui::Point::<f32>::default(),
  553            window,
  554            cx,
  555        );
  556        assert_eq!(
  557            editor.selections.display_ranges(cx),
  558            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  559        );
  560    });
  561
  562    _ = editor.update(cx, |editor, window, cx| {
  563        editor.cancel(&Cancel, window, cx);
  564        editor.update_selection(
  565            DisplayPoint::new(DisplayRow(1), 1),
  566            0,
  567            gpui::Point::<f32>::default(),
  568            window,
  569            cx,
  570        );
  571        assert_eq!(
  572            editor.selections.display_ranges(cx),
  573            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  574        );
  575    });
  576}
  577
  578#[gpui::test]
  579fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  580    init_test(cx, |_| {});
  581
  582    let editor = cx.add_window(|window, cx| {
  583        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  584        build_editor(buffer, window, cx)
  585    });
  586
  587    _ = editor.update(cx, |editor, window, cx| {
  588        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  589        assert_eq!(
  590            editor.selections.display_ranges(cx),
  591            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  592        );
  593
  594        editor.move_down(&Default::default(), window, cx);
  595        assert_eq!(
  596            editor.selections.display_ranges(cx),
  597            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  598        );
  599
  600        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  601        assert_eq!(
  602            editor.selections.display_ranges(cx),
  603            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  604        );
  605
  606        editor.move_up(&Default::default(), window, cx);
  607        assert_eq!(
  608            editor.selections.display_ranges(cx),
  609            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  610        );
  611    });
  612}
  613
  614#[gpui::test]
  615fn test_clone(cx: &mut TestAppContext) {
  616    init_test(cx, |_| {});
  617
  618    let (text, selection_ranges) = marked_text_ranges(
  619        indoc! {"
  620            one
  621            two
  622            threeˇ
  623            four
  624            fiveˇ
  625        "},
  626        true,
  627    );
  628
  629    let editor = cx.add_window(|window, cx| {
  630        let buffer = MultiBuffer::build_simple(&text, cx);
  631        build_editor(buffer, window, cx)
  632    });
  633
  634    _ = editor.update(cx, |editor, window, cx| {
  635        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  636            s.select_ranges(selection_ranges.clone())
  637        });
  638        editor.fold_creases(
  639            vec![
  640                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  641                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  642            ],
  643            true,
  644            window,
  645            cx,
  646        );
  647    });
  648
  649    let cloned_editor = editor
  650        .update(cx, |editor, _, cx| {
  651            cx.open_window(Default::default(), |window, cx| {
  652                cx.new(|cx| editor.clone(window, cx))
  653            })
  654        })
  655        .unwrap()
  656        .unwrap();
  657
  658    let snapshot = editor
  659        .update(cx, |e, window, cx| e.snapshot(window, cx))
  660        .unwrap();
  661    let cloned_snapshot = cloned_editor
  662        .update(cx, |e, window, cx| e.snapshot(window, cx))
  663        .unwrap();
  664
  665    assert_eq!(
  666        cloned_editor
  667            .update(cx, |e, _, cx| e.display_text(cx))
  668            .unwrap(),
  669        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  670    );
  671    assert_eq!(
  672        cloned_snapshot
  673            .folds_in_range(0..text.len())
  674            .collect::<Vec<_>>(),
  675        snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
  676    );
  677    assert_set_eq!(
  678        cloned_editor
  679            .update(cx, |editor, _, cx| editor.selections.ranges::<Point>(cx))
  680            .unwrap(),
  681        editor
  682            .update(cx, |editor, _, cx| editor.selections.ranges(cx))
  683            .unwrap()
  684    );
  685    assert_set_eq!(
  686        cloned_editor
  687            .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
  688            .unwrap(),
  689        editor
  690            .update(cx, |e, _, cx| e.selections.display_ranges(cx))
  691            .unwrap()
  692    );
  693}
  694
  695#[gpui::test]
  696async fn test_navigation_history(cx: &mut TestAppContext) {
  697    init_test(cx, |_| {});
  698
  699    use workspace::item::Item;
  700
  701    let fs = FakeFs::new(cx.executor());
  702    let project = Project::test(fs, [], cx).await;
  703    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  704    let pane = workspace
  705        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  706        .unwrap();
  707
  708    _ = workspace.update(cx, |_v, window, cx| {
  709        cx.new(|cx| {
  710            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  711            let mut editor = build_editor(buffer.clone(), window, cx);
  712            let handle = cx.entity();
  713            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  714
  715            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  716                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  717            }
  718
  719            // Move the cursor a small distance.
  720            // Nothing is added to the navigation history.
  721            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  722                s.select_display_ranges([
  723                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  724                ])
  725            });
  726            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  727                s.select_display_ranges([
  728                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  729                ])
  730            });
  731            assert!(pop_history(&mut editor, cx).is_none());
  732
  733            // Move the cursor a large distance.
  734            // The history can jump back to the previous position.
  735            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  736                s.select_display_ranges([
  737                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  738                ])
  739            });
  740            let nav_entry = pop_history(&mut editor, cx).unwrap();
  741            editor.navigate(nav_entry.data.unwrap(), window, cx);
  742            assert_eq!(nav_entry.item.id(), cx.entity_id());
  743            assert_eq!(
  744                editor.selections.display_ranges(cx),
  745                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  746            );
  747            assert!(pop_history(&mut editor, cx).is_none());
  748
  749            // Move the cursor a small distance via the mouse.
  750            // Nothing is added to the navigation history.
  751            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  752            editor.end_selection(window, cx);
  753            assert_eq!(
  754                editor.selections.display_ranges(cx),
  755                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  756            );
  757            assert!(pop_history(&mut editor, cx).is_none());
  758
  759            // Move the cursor a large distance via the mouse.
  760            // The history can jump back to the previous position.
  761            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  762            editor.end_selection(window, cx);
  763            assert_eq!(
  764                editor.selections.display_ranges(cx),
  765                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  766            );
  767            let nav_entry = pop_history(&mut editor, cx).unwrap();
  768            editor.navigate(nav_entry.data.unwrap(), window, cx);
  769            assert_eq!(nav_entry.item.id(), cx.entity_id());
  770            assert_eq!(
  771                editor.selections.display_ranges(cx),
  772                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  773            );
  774            assert!(pop_history(&mut editor, cx).is_none());
  775
  776            // Set scroll position to check later
  777            editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), window, cx);
  778            let original_scroll_position = editor.scroll_manager.anchor();
  779
  780            // Jump to the end of the document and adjust scroll
  781            editor.move_to_end(&MoveToEnd, window, cx);
  782            editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), window, cx);
  783            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  784
  785            let nav_entry = pop_history(&mut editor, cx).unwrap();
  786            editor.navigate(nav_entry.data.unwrap(), window, cx);
  787            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  788
  789            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  790            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  791            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  792            let invalid_point = Point::new(9999, 0);
  793            editor.navigate(
  794                Box::new(NavigationData {
  795                    cursor_anchor: invalid_anchor,
  796                    cursor_position: invalid_point,
  797                    scroll_anchor: ScrollAnchor {
  798                        anchor: invalid_anchor,
  799                        offset: Default::default(),
  800                    },
  801                    scroll_top_row: invalid_point.row,
  802                }),
  803                window,
  804                cx,
  805            );
  806            assert_eq!(
  807                editor.selections.display_ranges(cx),
  808                &[editor.max_point(cx)..editor.max_point(cx)]
  809            );
  810            assert_eq!(
  811                editor.scroll_position(cx),
  812                gpui::Point::new(0., editor.max_point(cx).row().as_f32())
  813            );
  814
  815            editor
  816        })
  817    });
  818}
  819
  820#[gpui::test]
  821fn test_cancel(cx: &mut TestAppContext) {
  822    init_test(cx, |_| {});
  823
  824    let editor = cx.add_window(|window, cx| {
  825        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  826        build_editor(buffer, window, cx)
  827    });
  828
  829    _ = editor.update(cx, |editor, window, cx| {
  830        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
  831        editor.update_selection(
  832            DisplayPoint::new(DisplayRow(1), 1),
  833            0,
  834            gpui::Point::<f32>::default(),
  835            window,
  836            cx,
  837        );
  838        editor.end_selection(window, cx);
  839
  840        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
  841        editor.update_selection(
  842            DisplayPoint::new(DisplayRow(0), 3),
  843            0,
  844            gpui::Point::<f32>::default(),
  845            window,
  846            cx,
  847        );
  848        editor.end_selection(window, cx);
  849        assert_eq!(
  850            editor.selections.display_ranges(cx),
  851            [
  852                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
  853                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
  854            ]
  855        );
  856    });
  857
  858    _ = editor.update(cx, |editor, window, cx| {
  859        editor.cancel(&Cancel, window, cx);
  860        assert_eq!(
  861            editor.selections.display_ranges(cx),
  862            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
  863        );
  864    });
  865
  866    _ = editor.update(cx, |editor, window, cx| {
  867        editor.cancel(&Cancel, window, cx);
  868        assert_eq!(
  869            editor.selections.display_ranges(cx),
  870            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
  871        );
  872    });
  873}
  874
  875#[gpui::test]
  876fn test_fold_action(cx: &mut TestAppContext) {
  877    init_test(cx, |_| {});
  878
  879    let editor = cx.add_window(|window, cx| {
  880        let buffer = MultiBuffer::build_simple(
  881            &"
  882                impl Foo {
  883                    // Hello!
  884
  885                    fn a() {
  886                        1
  887                    }
  888
  889                    fn b() {
  890                        2
  891                    }
  892
  893                    fn c() {
  894                        3
  895                    }
  896                }
  897            "
  898            .unindent(),
  899            cx,
  900        );
  901        build_editor(buffer.clone(), window, cx)
  902    });
  903
  904    _ = editor.update(cx, |editor, window, cx| {
  905        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  906            s.select_display_ranges([
  907                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
  908            ]);
  909        });
  910        editor.fold(&Fold, window, cx);
  911        assert_eq!(
  912            editor.display_text(cx),
  913            "
  914                impl Foo {
  915                    // Hello!
  916
  917                    fn a() {
  918                        1
  919                    }
  920
  921                    fn b() {⋯
  922                    }
  923
  924                    fn c() {⋯
  925                    }
  926                }
  927            "
  928            .unindent(),
  929        );
  930
  931        editor.fold(&Fold, window, cx);
  932        assert_eq!(
  933            editor.display_text(cx),
  934            "
  935                impl Foo {⋯
  936                }
  937            "
  938            .unindent(),
  939        );
  940
  941        editor.unfold_lines(&UnfoldLines, window, cx);
  942        assert_eq!(
  943            editor.display_text(cx),
  944            "
  945                impl Foo {
  946                    // Hello!
  947
  948                    fn a() {
  949                        1
  950                    }
  951
  952                    fn b() {⋯
  953                    }
  954
  955                    fn c() {⋯
  956                    }
  957                }
  958            "
  959            .unindent(),
  960        );
  961
  962        editor.unfold_lines(&UnfoldLines, window, cx);
  963        assert_eq!(
  964            editor.display_text(cx),
  965            editor.buffer.read(cx).read(cx).text()
  966        );
  967    });
  968}
  969
  970#[gpui::test]
  971fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
  972    init_test(cx, |_| {});
  973
  974    let editor = cx.add_window(|window, cx| {
  975        let buffer = MultiBuffer::build_simple(
  976            &"
  977                class Foo:
  978                    # Hello!
  979
  980                    def a():
  981                        print(1)
  982
  983                    def b():
  984                        print(2)
  985
  986                    def c():
  987                        print(3)
  988            "
  989            .unindent(),
  990            cx,
  991        );
  992        build_editor(buffer.clone(), window, cx)
  993    });
  994
  995    _ = editor.update(cx, |editor, window, cx| {
  996        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  997            s.select_display_ranges([
  998                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
  999            ]);
 1000        });
 1001        editor.fold(&Fold, window, cx);
 1002        assert_eq!(
 1003            editor.display_text(cx),
 1004            "
 1005                class Foo:
 1006                    # Hello!
 1007
 1008                    def a():
 1009                        print(1)
 1010
 1011                    def b():⋯
 1012
 1013                    def c():⋯
 1014            "
 1015            .unindent(),
 1016        );
 1017
 1018        editor.fold(&Fold, window, cx);
 1019        assert_eq!(
 1020            editor.display_text(cx),
 1021            "
 1022                class Foo:⋯
 1023            "
 1024            .unindent(),
 1025        );
 1026
 1027        editor.unfold_lines(&UnfoldLines, window, cx);
 1028        assert_eq!(
 1029            editor.display_text(cx),
 1030            "
 1031                class Foo:
 1032                    # Hello!
 1033
 1034                    def a():
 1035                        print(1)
 1036
 1037                    def b():⋯
 1038
 1039                    def c():⋯
 1040            "
 1041            .unindent(),
 1042        );
 1043
 1044        editor.unfold_lines(&UnfoldLines, window, cx);
 1045        assert_eq!(
 1046            editor.display_text(cx),
 1047            editor.buffer.read(cx).read(cx).text()
 1048        );
 1049    });
 1050}
 1051
 1052#[gpui::test]
 1053fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1054    init_test(cx, |_| {});
 1055
 1056    let editor = cx.add_window(|window, cx| {
 1057        let buffer = MultiBuffer::build_simple(
 1058            &"
 1059                class Foo:
 1060                    # Hello!
 1061
 1062                    def a():
 1063                        print(1)
 1064
 1065                    def b():
 1066                        print(2)
 1067
 1068
 1069                    def c():
 1070                        print(3)
 1071
 1072
 1073            "
 1074            .unindent(),
 1075            cx,
 1076        );
 1077        build_editor(buffer.clone(), window, cx)
 1078    });
 1079
 1080    _ = editor.update(cx, |editor, window, cx| {
 1081        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1082            s.select_display_ranges([
 1083                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1084            ]);
 1085        });
 1086        editor.fold(&Fold, window, cx);
 1087        assert_eq!(
 1088            editor.display_text(cx),
 1089            "
 1090                class Foo:
 1091                    # Hello!
 1092
 1093                    def a():
 1094                        print(1)
 1095
 1096                    def b():⋯
 1097
 1098
 1099                    def c():⋯
 1100
 1101
 1102            "
 1103            .unindent(),
 1104        );
 1105
 1106        editor.fold(&Fold, window, cx);
 1107        assert_eq!(
 1108            editor.display_text(cx),
 1109            "
 1110                class Foo:⋯
 1111
 1112
 1113            "
 1114            .unindent(),
 1115        );
 1116
 1117        editor.unfold_lines(&UnfoldLines, window, cx);
 1118        assert_eq!(
 1119            editor.display_text(cx),
 1120            "
 1121                class Foo:
 1122                    # Hello!
 1123
 1124                    def a():
 1125                        print(1)
 1126
 1127                    def b():⋯
 1128
 1129
 1130                    def c():⋯
 1131
 1132
 1133            "
 1134            .unindent(),
 1135        );
 1136
 1137        editor.unfold_lines(&UnfoldLines, window, cx);
 1138        assert_eq!(
 1139            editor.display_text(cx),
 1140            editor.buffer.read(cx).read(cx).text()
 1141        );
 1142    });
 1143}
 1144
 1145#[gpui::test]
 1146fn test_fold_at_level(cx: &mut TestAppContext) {
 1147    init_test(cx, |_| {});
 1148
 1149    let editor = cx.add_window(|window, cx| {
 1150        let buffer = MultiBuffer::build_simple(
 1151            &"
 1152                class Foo:
 1153                    # Hello!
 1154
 1155                    def a():
 1156                        print(1)
 1157
 1158                    def b():
 1159                        print(2)
 1160
 1161
 1162                class Bar:
 1163                    # World!
 1164
 1165                    def a():
 1166                        print(1)
 1167
 1168                    def b():
 1169                        print(2)
 1170
 1171
 1172            "
 1173            .unindent(),
 1174            cx,
 1175        );
 1176        build_editor(buffer.clone(), window, cx)
 1177    });
 1178
 1179    _ = editor.update(cx, |editor, window, cx| {
 1180        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1181        assert_eq!(
 1182            editor.display_text(cx),
 1183            "
 1184                class Foo:
 1185                    # Hello!
 1186
 1187                    def a():⋯
 1188
 1189                    def b():⋯
 1190
 1191
 1192                class Bar:
 1193                    # World!
 1194
 1195                    def a():⋯
 1196
 1197                    def b():⋯
 1198
 1199
 1200            "
 1201            .unindent(),
 1202        );
 1203
 1204        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1205        assert_eq!(
 1206            editor.display_text(cx),
 1207            "
 1208                class Foo:⋯
 1209
 1210
 1211                class Bar:⋯
 1212
 1213
 1214            "
 1215            .unindent(),
 1216        );
 1217
 1218        editor.unfold_all(&UnfoldAll, window, cx);
 1219        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1220        assert_eq!(
 1221            editor.display_text(cx),
 1222            "
 1223                class Foo:
 1224                    # Hello!
 1225
 1226                    def a():
 1227                        print(1)
 1228
 1229                    def b():
 1230                        print(2)
 1231
 1232
 1233                class Bar:
 1234                    # World!
 1235
 1236                    def a():
 1237                        print(1)
 1238
 1239                    def b():
 1240                        print(2)
 1241
 1242
 1243            "
 1244            .unindent(),
 1245        );
 1246
 1247        assert_eq!(
 1248            editor.display_text(cx),
 1249            editor.buffer.read(cx).read(cx).text()
 1250        );
 1251    });
 1252}
 1253
 1254#[gpui::test]
 1255fn test_move_cursor(cx: &mut TestAppContext) {
 1256    init_test(cx, |_| {});
 1257
 1258    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1259    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1260
 1261    buffer.update(cx, |buffer, cx| {
 1262        buffer.edit(
 1263            vec![
 1264                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1265                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1266            ],
 1267            None,
 1268            cx,
 1269        );
 1270    });
 1271    _ = editor.update(cx, |editor, window, cx| {
 1272        assert_eq!(
 1273            editor.selections.display_ranges(cx),
 1274            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1275        );
 1276
 1277        editor.move_down(&MoveDown, window, cx);
 1278        assert_eq!(
 1279            editor.selections.display_ranges(cx),
 1280            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1281        );
 1282
 1283        editor.move_right(&MoveRight, window, cx);
 1284        assert_eq!(
 1285            editor.selections.display_ranges(cx),
 1286            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1287        );
 1288
 1289        editor.move_left(&MoveLeft, window, cx);
 1290        assert_eq!(
 1291            editor.selections.display_ranges(cx),
 1292            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1293        );
 1294
 1295        editor.move_up(&MoveUp, window, cx);
 1296        assert_eq!(
 1297            editor.selections.display_ranges(cx),
 1298            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1299        );
 1300
 1301        editor.move_to_end(&MoveToEnd, window, cx);
 1302        assert_eq!(
 1303            editor.selections.display_ranges(cx),
 1304            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1305        );
 1306
 1307        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1308        assert_eq!(
 1309            editor.selections.display_ranges(cx),
 1310            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1311        );
 1312
 1313        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1314            s.select_display_ranges([
 1315                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1316            ]);
 1317        });
 1318        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1319        assert_eq!(
 1320            editor.selections.display_ranges(cx),
 1321            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1322        );
 1323
 1324        editor.select_to_end(&SelectToEnd, window, cx);
 1325        assert_eq!(
 1326            editor.selections.display_ranges(cx),
 1327            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1328        );
 1329    });
 1330}
 1331
 1332#[gpui::test]
 1333fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1334    init_test(cx, |_| {});
 1335
 1336    let editor = cx.add_window(|window, cx| {
 1337        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1338        build_editor(buffer.clone(), window, cx)
 1339    });
 1340
 1341    assert_eq!('🟥'.len_utf8(), 4);
 1342    assert_eq!('α'.len_utf8(), 2);
 1343
 1344    _ = editor.update(cx, |editor, window, cx| {
 1345        editor.fold_creases(
 1346            vec![
 1347                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1348                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1349                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1350            ],
 1351            true,
 1352            window,
 1353            cx,
 1354        );
 1355        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1356
 1357        editor.move_right(&MoveRight, window, cx);
 1358        assert_eq!(
 1359            editor.selections.display_ranges(cx),
 1360            &[empty_range(0, "🟥".len())]
 1361        );
 1362        editor.move_right(&MoveRight, window, cx);
 1363        assert_eq!(
 1364            editor.selections.display_ranges(cx),
 1365            &[empty_range(0, "🟥🟧".len())]
 1366        );
 1367        editor.move_right(&MoveRight, window, cx);
 1368        assert_eq!(
 1369            editor.selections.display_ranges(cx),
 1370            &[empty_range(0, "🟥🟧⋯".len())]
 1371        );
 1372
 1373        editor.move_down(&MoveDown, window, cx);
 1374        assert_eq!(
 1375            editor.selections.display_ranges(cx),
 1376            &[empty_range(1, "ab⋯e".len())]
 1377        );
 1378        editor.move_left(&MoveLeft, window, cx);
 1379        assert_eq!(
 1380            editor.selections.display_ranges(cx),
 1381            &[empty_range(1, "ab⋯".len())]
 1382        );
 1383        editor.move_left(&MoveLeft, window, cx);
 1384        assert_eq!(
 1385            editor.selections.display_ranges(cx),
 1386            &[empty_range(1, "ab".len())]
 1387        );
 1388        editor.move_left(&MoveLeft, window, cx);
 1389        assert_eq!(
 1390            editor.selections.display_ranges(cx),
 1391            &[empty_range(1, "a".len())]
 1392        );
 1393
 1394        editor.move_down(&MoveDown, window, cx);
 1395        assert_eq!(
 1396            editor.selections.display_ranges(cx),
 1397            &[empty_range(2, "α".len())]
 1398        );
 1399        editor.move_right(&MoveRight, window, cx);
 1400        assert_eq!(
 1401            editor.selections.display_ranges(cx),
 1402            &[empty_range(2, "αβ".len())]
 1403        );
 1404        editor.move_right(&MoveRight, window, cx);
 1405        assert_eq!(
 1406            editor.selections.display_ranges(cx),
 1407            &[empty_range(2, "αβ⋯".len())]
 1408        );
 1409        editor.move_right(&MoveRight, window, cx);
 1410        assert_eq!(
 1411            editor.selections.display_ranges(cx),
 1412            &[empty_range(2, "αβ⋯ε".len())]
 1413        );
 1414
 1415        editor.move_up(&MoveUp, window, cx);
 1416        assert_eq!(
 1417            editor.selections.display_ranges(cx),
 1418            &[empty_range(1, "ab⋯e".len())]
 1419        );
 1420        editor.move_down(&MoveDown, window, cx);
 1421        assert_eq!(
 1422            editor.selections.display_ranges(cx),
 1423            &[empty_range(2, "αβ⋯ε".len())]
 1424        );
 1425        editor.move_up(&MoveUp, window, cx);
 1426        assert_eq!(
 1427            editor.selections.display_ranges(cx),
 1428            &[empty_range(1, "ab⋯e".len())]
 1429        );
 1430
 1431        editor.move_up(&MoveUp, window, cx);
 1432        assert_eq!(
 1433            editor.selections.display_ranges(cx),
 1434            &[empty_range(0, "🟥🟧".len())]
 1435        );
 1436        editor.move_left(&MoveLeft, window, cx);
 1437        assert_eq!(
 1438            editor.selections.display_ranges(cx),
 1439            &[empty_range(0, "🟥".len())]
 1440        );
 1441        editor.move_left(&MoveLeft, window, cx);
 1442        assert_eq!(
 1443            editor.selections.display_ranges(cx),
 1444            &[empty_range(0, "".len())]
 1445        );
 1446    });
 1447}
 1448
 1449#[gpui::test]
 1450fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1451    init_test(cx, |_| {});
 1452
 1453    let editor = cx.add_window(|window, cx| {
 1454        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1455        build_editor(buffer.clone(), window, cx)
 1456    });
 1457    _ = editor.update(cx, |editor, window, cx| {
 1458        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1459            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1460        });
 1461
 1462        // moving above start of document should move selection to start of document,
 1463        // but the next move down should still be at the original goal_x
 1464        editor.move_up(&MoveUp, window, cx);
 1465        assert_eq!(
 1466            editor.selections.display_ranges(cx),
 1467            &[empty_range(0, "".len())]
 1468        );
 1469
 1470        editor.move_down(&MoveDown, window, cx);
 1471        assert_eq!(
 1472            editor.selections.display_ranges(cx),
 1473            &[empty_range(1, "abcd".len())]
 1474        );
 1475
 1476        editor.move_down(&MoveDown, window, cx);
 1477        assert_eq!(
 1478            editor.selections.display_ranges(cx),
 1479            &[empty_range(2, "αβγ".len())]
 1480        );
 1481
 1482        editor.move_down(&MoveDown, window, cx);
 1483        assert_eq!(
 1484            editor.selections.display_ranges(cx),
 1485            &[empty_range(3, "abcd".len())]
 1486        );
 1487
 1488        editor.move_down(&MoveDown, window, cx);
 1489        assert_eq!(
 1490            editor.selections.display_ranges(cx),
 1491            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1492        );
 1493
 1494        // moving past end of document should not change goal_x
 1495        editor.move_down(&MoveDown, window, cx);
 1496        assert_eq!(
 1497            editor.selections.display_ranges(cx),
 1498            &[empty_range(5, "".len())]
 1499        );
 1500
 1501        editor.move_down(&MoveDown, window, cx);
 1502        assert_eq!(
 1503            editor.selections.display_ranges(cx),
 1504            &[empty_range(5, "".len())]
 1505        );
 1506
 1507        editor.move_up(&MoveUp, window, cx);
 1508        assert_eq!(
 1509            editor.selections.display_ranges(cx),
 1510            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1511        );
 1512
 1513        editor.move_up(&MoveUp, window, cx);
 1514        assert_eq!(
 1515            editor.selections.display_ranges(cx),
 1516            &[empty_range(3, "abcd".len())]
 1517        );
 1518
 1519        editor.move_up(&MoveUp, window, cx);
 1520        assert_eq!(
 1521            editor.selections.display_ranges(cx),
 1522            &[empty_range(2, "αβγ".len())]
 1523        );
 1524    });
 1525}
 1526
 1527#[gpui::test]
 1528fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1529    init_test(cx, |_| {});
 1530    let move_to_beg = MoveToBeginningOfLine {
 1531        stop_at_soft_wraps: true,
 1532        stop_at_indent: true,
 1533    };
 1534
 1535    let delete_to_beg = DeleteToBeginningOfLine {
 1536        stop_at_indent: false,
 1537    };
 1538
 1539    let move_to_end = MoveToEndOfLine {
 1540        stop_at_soft_wraps: true,
 1541    };
 1542
 1543    let editor = cx.add_window(|window, cx| {
 1544        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1545        build_editor(buffer, window, cx)
 1546    });
 1547    _ = editor.update(cx, |editor, window, cx| {
 1548        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1549            s.select_display_ranges([
 1550                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1551                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1552            ]);
 1553        });
 1554    });
 1555
 1556    _ = editor.update(cx, |editor, window, cx| {
 1557        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1558        assert_eq!(
 1559            editor.selections.display_ranges(cx),
 1560            &[
 1561                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1562                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1563            ]
 1564        );
 1565    });
 1566
 1567    _ = editor.update(cx, |editor, window, cx| {
 1568        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1569        assert_eq!(
 1570            editor.selections.display_ranges(cx),
 1571            &[
 1572                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1573                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1574            ]
 1575        );
 1576    });
 1577
 1578    _ = editor.update(cx, |editor, window, cx| {
 1579        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1580        assert_eq!(
 1581            editor.selections.display_ranges(cx),
 1582            &[
 1583                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1584                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1585            ]
 1586        );
 1587    });
 1588
 1589    _ = editor.update(cx, |editor, window, cx| {
 1590        editor.move_to_end_of_line(&move_to_end, window, cx);
 1591        assert_eq!(
 1592            editor.selections.display_ranges(cx),
 1593            &[
 1594                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1595                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1596            ]
 1597        );
 1598    });
 1599
 1600    // Moving to the end of line again is a no-op.
 1601    _ = editor.update(cx, |editor, window, cx| {
 1602        editor.move_to_end_of_line(&move_to_end, window, cx);
 1603        assert_eq!(
 1604            editor.selections.display_ranges(cx),
 1605            &[
 1606                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1607                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1608            ]
 1609        );
 1610    });
 1611
 1612    _ = editor.update(cx, |editor, window, cx| {
 1613        editor.move_left(&MoveLeft, window, cx);
 1614        editor.select_to_beginning_of_line(
 1615            &SelectToBeginningOfLine {
 1616                stop_at_soft_wraps: true,
 1617                stop_at_indent: true,
 1618            },
 1619            window,
 1620            cx,
 1621        );
 1622        assert_eq!(
 1623            editor.selections.display_ranges(cx),
 1624            &[
 1625                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1626                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1627            ]
 1628        );
 1629    });
 1630
 1631    _ = editor.update(cx, |editor, window, cx| {
 1632        editor.select_to_beginning_of_line(
 1633            &SelectToBeginningOfLine {
 1634                stop_at_soft_wraps: true,
 1635                stop_at_indent: true,
 1636            },
 1637            window,
 1638            cx,
 1639        );
 1640        assert_eq!(
 1641            editor.selections.display_ranges(cx),
 1642            &[
 1643                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1644                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1645            ]
 1646        );
 1647    });
 1648
 1649    _ = editor.update(cx, |editor, window, cx| {
 1650        editor.select_to_beginning_of_line(
 1651            &SelectToBeginningOfLine {
 1652                stop_at_soft_wraps: true,
 1653                stop_at_indent: true,
 1654            },
 1655            window,
 1656            cx,
 1657        );
 1658        assert_eq!(
 1659            editor.selections.display_ranges(cx),
 1660            &[
 1661                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1662                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1663            ]
 1664        );
 1665    });
 1666
 1667    _ = editor.update(cx, |editor, window, cx| {
 1668        editor.select_to_end_of_line(
 1669            &SelectToEndOfLine {
 1670                stop_at_soft_wraps: true,
 1671            },
 1672            window,
 1673            cx,
 1674        );
 1675        assert_eq!(
 1676            editor.selections.display_ranges(cx),
 1677            &[
 1678                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1679                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1680            ]
 1681        );
 1682    });
 1683
 1684    _ = editor.update(cx, |editor, window, cx| {
 1685        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1686        assert_eq!(editor.display_text(cx), "ab\n  de");
 1687        assert_eq!(
 1688            editor.selections.display_ranges(cx),
 1689            &[
 1690                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1691                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1692            ]
 1693        );
 1694    });
 1695
 1696    _ = editor.update(cx, |editor, window, cx| {
 1697        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1698        assert_eq!(editor.display_text(cx), "\n");
 1699        assert_eq!(
 1700            editor.selections.display_ranges(cx),
 1701            &[
 1702                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1703                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1704            ]
 1705        );
 1706    });
 1707}
 1708
 1709#[gpui::test]
 1710fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1711    init_test(cx, |_| {});
 1712    let move_to_beg = MoveToBeginningOfLine {
 1713        stop_at_soft_wraps: false,
 1714        stop_at_indent: false,
 1715    };
 1716
 1717    let move_to_end = MoveToEndOfLine {
 1718        stop_at_soft_wraps: false,
 1719    };
 1720
 1721    let editor = cx.add_window(|window, cx| {
 1722        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1723        build_editor(buffer, window, cx)
 1724    });
 1725
 1726    _ = editor.update(cx, |editor, window, cx| {
 1727        editor.set_wrap_width(Some(140.0.into()), cx);
 1728
 1729        // We expect the following lines after wrapping
 1730        // ```
 1731        // thequickbrownfox
 1732        // jumpedoverthelazydo
 1733        // gs
 1734        // ```
 1735        // The final `gs` was soft-wrapped onto a new line.
 1736        assert_eq!(
 1737            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1738            editor.display_text(cx),
 1739        );
 1740
 1741        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1742        // Start the cursor at the `k` on the first line
 1743        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1744            s.select_display_ranges([
 1745                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1746            ]);
 1747        });
 1748
 1749        // Moving to the beginning of the line should put us at the beginning of the line.
 1750        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1751        assert_eq!(
 1752            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1753            editor.selections.display_ranges(cx)
 1754        );
 1755
 1756        // Moving to the end of the line should put us at the end of the line.
 1757        editor.move_to_end_of_line(&move_to_end, window, cx);
 1758        assert_eq!(
 1759            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1760            editor.selections.display_ranges(cx)
 1761        );
 1762
 1763        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1764        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1765        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1766            s.select_display_ranges([
 1767                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1768            ]);
 1769        });
 1770
 1771        // Moving to the beginning of the line should put us at the start of the second line of
 1772        // display text, i.e., the `j`.
 1773        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1774        assert_eq!(
 1775            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1776            editor.selections.display_ranges(cx)
 1777        );
 1778
 1779        // Moving to the beginning of the line again should be a no-op.
 1780        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1781        assert_eq!(
 1782            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1783            editor.selections.display_ranges(cx)
 1784        );
 1785
 1786        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1787        // next display line.
 1788        editor.move_to_end_of_line(&move_to_end, window, cx);
 1789        assert_eq!(
 1790            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1791            editor.selections.display_ranges(cx)
 1792        );
 1793
 1794        // Moving to the end of the line again should be a no-op.
 1795        editor.move_to_end_of_line(&move_to_end, window, cx);
 1796        assert_eq!(
 1797            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1798            editor.selections.display_ranges(cx)
 1799        );
 1800    });
 1801}
 1802
 1803#[gpui::test]
 1804fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1805    init_test(cx, |_| {});
 1806
 1807    let move_to_beg = MoveToBeginningOfLine {
 1808        stop_at_soft_wraps: true,
 1809        stop_at_indent: true,
 1810    };
 1811
 1812    let select_to_beg = SelectToBeginningOfLine {
 1813        stop_at_soft_wraps: true,
 1814        stop_at_indent: true,
 1815    };
 1816
 1817    let delete_to_beg = DeleteToBeginningOfLine {
 1818        stop_at_indent: true,
 1819    };
 1820
 1821    let move_to_end = MoveToEndOfLine {
 1822        stop_at_soft_wraps: false,
 1823    };
 1824
 1825    let editor = cx.add_window(|window, cx| {
 1826        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1827        build_editor(buffer, window, cx)
 1828    });
 1829
 1830    _ = editor.update(cx, |editor, window, cx| {
 1831        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1832            s.select_display_ranges([
 1833                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1834                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1835            ]);
 1836        });
 1837
 1838        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 1839        // and the second cursor at the first non-whitespace character in the line.
 1840        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1841        assert_eq!(
 1842            editor.selections.display_ranges(cx),
 1843            &[
 1844                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1845                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1846            ]
 1847        );
 1848
 1849        // Moving to the beginning of the line again should be a no-op for the first cursor,
 1850        // and should move the second cursor to the beginning of the line.
 1851        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1852        assert_eq!(
 1853            editor.selections.display_ranges(cx),
 1854            &[
 1855                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1856                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1857            ]
 1858        );
 1859
 1860        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 1861        // and should move the second cursor back to the first non-whitespace character in the line.
 1862        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1863        assert_eq!(
 1864            editor.selections.display_ranges(cx),
 1865            &[
 1866                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1867                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1868            ]
 1869        );
 1870
 1871        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 1872        // and to the first non-whitespace character in the line for the second cursor.
 1873        editor.move_to_end_of_line(&move_to_end, window, cx);
 1874        editor.move_left(&MoveLeft, window, cx);
 1875        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1876        assert_eq!(
 1877            editor.selections.display_ranges(cx),
 1878            &[
 1879                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1880                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1881            ]
 1882        );
 1883
 1884        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 1885        // and should select to the beginning of the line for the second cursor.
 1886        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1887        assert_eq!(
 1888            editor.selections.display_ranges(cx),
 1889            &[
 1890                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1891                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1892            ]
 1893        );
 1894
 1895        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 1896        // and should delete to the first non-whitespace character in the line for the second cursor.
 1897        editor.move_to_end_of_line(&move_to_end, window, cx);
 1898        editor.move_left(&MoveLeft, window, cx);
 1899        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1900        assert_eq!(editor.text(cx), "c\n  f");
 1901    });
 1902}
 1903
 1904#[gpui::test]
 1905fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 1906    init_test(cx, |_| {});
 1907
 1908    let editor = cx.add_window(|window, cx| {
 1909        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 1910        build_editor(buffer, window, cx)
 1911    });
 1912    _ = editor.update(cx, |editor, window, cx| {
 1913        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1914            s.select_display_ranges([
 1915                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 1916                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 1917            ])
 1918        });
 1919        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1920        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 1921
 1922        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1923        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 1924
 1925        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1926        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1927
 1928        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1929        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1930
 1931        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1932        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 1933
 1934        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1935        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1936
 1937        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1938        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 1939
 1940        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1941        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1942
 1943        editor.move_right(&MoveRight, window, cx);
 1944        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1945        assert_selection_ranges(
 1946            "use std::«ˇs»tr::{foo, bar}\n«ˇ\n»  {baz.qux()}",
 1947            editor,
 1948            cx,
 1949        );
 1950
 1951        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1952        assert_selection_ranges(
 1953            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 1954            editor,
 1955            cx,
 1956        );
 1957
 1958        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 1959        assert_selection_ranges(
 1960            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 1961            editor,
 1962            cx,
 1963        );
 1964    });
 1965}
 1966
 1967#[gpui::test]
 1968fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 1969    init_test(cx, |_| {});
 1970
 1971    let editor = cx.add_window(|window, cx| {
 1972        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 1973        build_editor(buffer, window, cx)
 1974    });
 1975
 1976    _ = editor.update(cx, |editor, window, cx| {
 1977        editor.set_wrap_width(Some(140.0.into()), cx);
 1978        assert_eq!(
 1979            editor.display_text(cx),
 1980            "use one::{\n    two::three::\n    four::five\n};"
 1981        );
 1982
 1983        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1984            s.select_display_ranges([
 1985                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 1986            ]);
 1987        });
 1988
 1989        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1990        assert_eq!(
 1991            editor.selections.display_ranges(cx),
 1992            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 1993        );
 1994
 1995        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1996        assert_eq!(
 1997            editor.selections.display_ranges(cx),
 1998            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 1999        );
 2000
 2001        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2002        assert_eq!(
 2003            editor.selections.display_ranges(cx),
 2004            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2005        );
 2006
 2007        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2008        assert_eq!(
 2009            editor.selections.display_ranges(cx),
 2010            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2011        );
 2012
 2013        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2014        assert_eq!(
 2015            editor.selections.display_ranges(cx),
 2016            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2017        );
 2018
 2019        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2020        assert_eq!(
 2021            editor.selections.display_ranges(cx),
 2022            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2023        );
 2024    });
 2025}
 2026
 2027#[gpui::test]
 2028async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2029    init_test(cx, |_| {});
 2030    let mut cx = EditorTestContext::new(cx).await;
 2031
 2032    let line_height = cx.editor(|editor, window, _| {
 2033        editor
 2034            .style()
 2035            .unwrap()
 2036            .text
 2037            .line_height_in_pixels(window.rem_size())
 2038    });
 2039    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2040
 2041    cx.set_state(
 2042        &r#"ˇone
 2043        two
 2044
 2045        three
 2046        fourˇ
 2047        five
 2048
 2049        six"#
 2050            .unindent(),
 2051    );
 2052
 2053    cx.update_editor(|editor, window, cx| {
 2054        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2055    });
 2056    cx.assert_editor_state(
 2057        &r#"one
 2058        two
 2059        ˇ
 2060        three
 2061        four
 2062        five
 2063        ˇ
 2064        six"#
 2065            .unindent(),
 2066    );
 2067
 2068    cx.update_editor(|editor, window, cx| {
 2069        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2070    });
 2071    cx.assert_editor_state(
 2072        &r#"one
 2073        two
 2074
 2075        three
 2076        four
 2077        five
 2078        ˇ
 2079        sixˇ"#
 2080            .unindent(),
 2081    );
 2082
 2083    cx.update_editor(|editor, window, cx| {
 2084        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2085    });
 2086    cx.assert_editor_state(
 2087        &r#"one
 2088        two
 2089
 2090        three
 2091        four
 2092        five
 2093
 2094        sixˇ"#
 2095            .unindent(),
 2096    );
 2097
 2098    cx.update_editor(|editor, window, cx| {
 2099        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2100    });
 2101    cx.assert_editor_state(
 2102        &r#"one
 2103        two
 2104
 2105        three
 2106        four
 2107        five
 2108        ˇ
 2109        six"#
 2110            .unindent(),
 2111    );
 2112
 2113    cx.update_editor(|editor, window, cx| {
 2114        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2115    });
 2116    cx.assert_editor_state(
 2117        &r#"one
 2118        two
 2119        ˇ
 2120        three
 2121        four
 2122        five
 2123
 2124        six"#
 2125            .unindent(),
 2126    );
 2127
 2128    cx.update_editor(|editor, window, cx| {
 2129        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2130    });
 2131    cx.assert_editor_state(
 2132        &r#"ˇone
 2133        two
 2134
 2135        three
 2136        four
 2137        five
 2138
 2139        six"#
 2140            .unindent(),
 2141    );
 2142}
 2143
 2144#[gpui::test]
 2145async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2146    init_test(cx, |_| {});
 2147    let mut cx = EditorTestContext::new(cx).await;
 2148    let line_height = cx.editor(|editor, window, _| {
 2149        editor
 2150            .style()
 2151            .unwrap()
 2152            .text
 2153            .line_height_in_pixels(window.rem_size())
 2154    });
 2155    let window = cx.window;
 2156    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2157
 2158    cx.set_state(
 2159        r#"ˇone
 2160        two
 2161        three
 2162        four
 2163        five
 2164        six
 2165        seven
 2166        eight
 2167        nine
 2168        ten
 2169        "#,
 2170    );
 2171
 2172    cx.update_editor(|editor, window, cx| {
 2173        assert_eq!(
 2174            editor.snapshot(window, cx).scroll_position(),
 2175            gpui::Point::new(0., 0.)
 2176        );
 2177        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2178        assert_eq!(
 2179            editor.snapshot(window, cx).scroll_position(),
 2180            gpui::Point::new(0., 3.)
 2181        );
 2182        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2183        assert_eq!(
 2184            editor.snapshot(window, cx).scroll_position(),
 2185            gpui::Point::new(0., 6.)
 2186        );
 2187        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2188        assert_eq!(
 2189            editor.snapshot(window, cx).scroll_position(),
 2190            gpui::Point::new(0., 3.)
 2191        );
 2192
 2193        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2194        assert_eq!(
 2195            editor.snapshot(window, cx).scroll_position(),
 2196            gpui::Point::new(0., 1.)
 2197        );
 2198        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2199        assert_eq!(
 2200            editor.snapshot(window, cx).scroll_position(),
 2201            gpui::Point::new(0., 3.)
 2202        );
 2203    });
 2204}
 2205
 2206#[gpui::test]
 2207async fn test_autoscroll(cx: &mut TestAppContext) {
 2208    init_test(cx, |_| {});
 2209    let mut cx = EditorTestContext::new(cx).await;
 2210
 2211    let line_height = cx.update_editor(|editor, window, cx| {
 2212        editor.set_vertical_scroll_margin(2, cx);
 2213        editor
 2214            .style()
 2215            .unwrap()
 2216            .text
 2217            .line_height_in_pixels(window.rem_size())
 2218    });
 2219    let window = cx.window;
 2220    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2221
 2222    cx.set_state(
 2223        r#"ˇone
 2224            two
 2225            three
 2226            four
 2227            five
 2228            six
 2229            seven
 2230            eight
 2231            nine
 2232            ten
 2233        "#,
 2234    );
 2235    cx.update_editor(|editor, window, cx| {
 2236        assert_eq!(
 2237            editor.snapshot(window, cx).scroll_position(),
 2238            gpui::Point::new(0., 0.0)
 2239        );
 2240    });
 2241
 2242    // Add a cursor below the visible area. Since both cursors cannot fit
 2243    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2244    // allows the vertical scroll margin below that cursor.
 2245    cx.update_editor(|editor, window, cx| {
 2246        editor.change_selections(Default::default(), window, cx, |selections| {
 2247            selections.select_ranges([
 2248                Point::new(0, 0)..Point::new(0, 0),
 2249                Point::new(6, 0)..Point::new(6, 0),
 2250            ]);
 2251        })
 2252    });
 2253    cx.update_editor(|editor, window, cx| {
 2254        assert_eq!(
 2255            editor.snapshot(window, cx).scroll_position(),
 2256            gpui::Point::new(0., 3.0)
 2257        );
 2258    });
 2259
 2260    // Move down. The editor cursor scrolls down to track the newest cursor.
 2261    cx.update_editor(|editor, window, cx| {
 2262        editor.move_down(&Default::default(), window, cx);
 2263    });
 2264    cx.update_editor(|editor, window, cx| {
 2265        assert_eq!(
 2266            editor.snapshot(window, cx).scroll_position(),
 2267            gpui::Point::new(0., 4.0)
 2268        );
 2269    });
 2270
 2271    // Add a cursor above the visible area. Since both cursors fit on screen,
 2272    // the editor scrolls to show both.
 2273    cx.update_editor(|editor, window, cx| {
 2274        editor.change_selections(Default::default(), window, cx, |selections| {
 2275            selections.select_ranges([
 2276                Point::new(1, 0)..Point::new(1, 0),
 2277                Point::new(6, 0)..Point::new(6, 0),
 2278            ]);
 2279        })
 2280    });
 2281    cx.update_editor(|editor, window, cx| {
 2282        assert_eq!(
 2283            editor.snapshot(window, cx).scroll_position(),
 2284            gpui::Point::new(0., 1.0)
 2285        );
 2286    });
 2287}
 2288
 2289#[gpui::test]
 2290async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2291    init_test(cx, |_| {});
 2292    let mut cx = EditorTestContext::new(cx).await;
 2293
 2294    let line_height = cx.editor(|editor, window, _cx| {
 2295        editor
 2296            .style()
 2297            .unwrap()
 2298            .text
 2299            .line_height_in_pixels(window.rem_size())
 2300    });
 2301    let window = cx.window;
 2302    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2303    cx.set_state(
 2304        &r#"
 2305        ˇone
 2306        two
 2307        threeˇ
 2308        four
 2309        five
 2310        six
 2311        seven
 2312        eight
 2313        nine
 2314        ten
 2315        "#
 2316        .unindent(),
 2317    );
 2318
 2319    cx.update_editor(|editor, window, cx| {
 2320        editor.move_page_down(&MovePageDown::default(), window, cx)
 2321    });
 2322    cx.assert_editor_state(
 2323        &r#"
 2324        one
 2325        two
 2326        three
 2327        ˇfour
 2328        five
 2329        sixˇ
 2330        seven
 2331        eight
 2332        nine
 2333        ten
 2334        "#
 2335        .unindent(),
 2336    );
 2337
 2338    cx.update_editor(|editor, window, cx| {
 2339        editor.move_page_down(&MovePageDown::default(), window, cx)
 2340    });
 2341    cx.assert_editor_state(
 2342        &r#"
 2343        one
 2344        two
 2345        three
 2346        four
 2347        five
 2348        six
 2349        ˇseven
 2350        eight
 2351        nineˇ
 2352        ten
 2353        "#
 2354        .unindent(),
 2355    );
 2356
 2357    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2358    cx.assert_editor_state(
 2359        &r#"
 2360        one
 2361        two
 2362        three
 2363        ˇfour
 2364        five
 2365        sixˇ
 2366        seven
 2367        eight
 2368        nine
 2369        ten
 2370        "#
 2371        .unindent(),
 2372    );
 2373
 2374    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2375    cx.assert_editor_state(
 2376        &r#"
 2377        ˇone
 2378        two
 2379        threeˇ
 2380        four
 2381        five
 2382        six
 2383        seven
 2384        eight
 2385        nine
 2386        ten
 2387        "#
 2388        .unindent(),
 2389    );
 2390
 2391    // Test select collapsing
 2392    cx.update_editor(|editor, window, cx| {
 2393        editor.move_page_down(&MovePageDown::default(), window, cx);
 2394        editor.move_page_down(&MovePageDown::default(), window, cx);
 2395        editor.move_page_down(&MovePageDown::default(), window, cx);
 2396    });
 2397    cx.assert_editor_state(
 2398        &r#"
 2399        one
 2400        two
 2401        three
 2402        four
 2403        five
 2404        six
 2405        seven
 2406        eight
 2407        nine
 2408        ˇten
 2409        ˇ"#
 2410        .unindent(),
 2411    );
 2412}
 2413
 2414#[gpui::test]
 2415async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2416    init_test(cx, |_| {});
 2417    let mut cx = EditorTestContext::new(cx).await;
 2418    cx.set_state("one «two threeˇ» four");
 2419    cx.update_editor(|editor, window, cx| {
 2420        editor.delete_to_beginning_of_line(
 2421            &DeleteToBeginningOfLine {
 2422                stop_at_indent: false,
 2423            },
 2424            window,
 2425            cx,
 2426        );
 2427        assert_eq!(editor.text(cx), " four");
 2428    });
 2429}
 2430
 2431#[gpui::test]
 2432fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2433    init_test(cx, |_| {});
 2434
 2435    let editor = cx.add_window(|window, cx| {
 2436        let buffer = MultiBuffer::build_simple("one two three four", cx);
 2437        build_editor(buffer.clone(), window, cx)
 2438    });
 2439
 2440    _ = editor.update(cx, |editor, window, cx| {
 2441        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2442            s.select_display_ranges([
 2443                // an empty selection - the preceding word fragment is deleted
 2444                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2445                // characters selected - they are deleted
 2446                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
 2447            ])
 2448        });
 2449        editor.delete_to_previous_word_start(
 2450            &DeleteToPreviousWordStart {
 2451                ignore_newlines: false,
 2452            },
 2453            window,
 2454            cx,
 2455        );
 2456        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e two te four");
 2457    });
 2458
 2459    _ = editor.update(cx, |editor, window, cx| {
 2460        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2461            s.select_display_ranges([
 2462                // an empty selection - the following word fragment is deleted
 2463                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 2464                // characters selected - they are deleted
 2465                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
 2466            ])
 2467        });
 2468        editor.delete_to_next_word_end(
 2469            &DeleteToNextWordEnd {
 2470                ignore_newlines: false,
 2471            },
 2472            window,
 2473            cx,
 2474        );
 2475        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e t te our");
 2476    });
 2477}
 2478
 2479#[gpui::test]
 2480fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2481    init_test(cx, |_| {});
 2482
 2483    let editor = cx.add_window(|window, cx| {
 2484        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 2485        build_editor(buffer.clone(), window, cx)
 2486    });
 2487    let del_to_prev_word_start = DeleteToPreviousWordStart {
 2488        ignore_newlines: false,
 2489    };
 2490    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 2491        ignore_newlines: true,
 2492    };
 2493
 2494    _ = editor.update(cx, |editor, window, cx| {
 2495        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2496            s.select_display_ranges([
 2497                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 2498            ])
 2499        });
 2500        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2501        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 2502        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2503        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 2504        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2505        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 2506        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2507        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 2508        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2509        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 2510        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2511        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2512    });
 2513}
 2514
 2515#[gpui::test]
 2516fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 2517    init_test(cx, |_| {});
 2518
 2519    let editor = cx.add_window(|window, cx| {
 2520        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 2521        build_editor(buffer.clone(), window, cx)
 2522    });
 2523    let del_to_next_word_end = DeleteToNextWordEnd {
 2524        ignore_newlines: false,
 2525    };
 2526    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 2527        ignore_newlines: true,
 2528    };
 2529
 2530    _ = editor.update(cx, |editor, window, cx| {
 2531        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2532            s.select_display_ranges([
 2533                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 2534            ])
 2535        });
 2536        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2537        assert_eq!(
 2538            editor.buffer.read(cx).read(cx).text(),
 2539            "one\n   two\nthree\n   four"
 2540        );
 2541        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2542        assert_eq!(
 2543            editor.buffer.read(cx).read(cx).text(),
 2544            "\n   two\nthree\n   four"
 2545        );
 2546        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2547        assert_eq!(
 2548            editor.buffer.read(cx).read(cx).text(),
 2549            "two\nthree\n   four"
 2550        );
 2551        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2552        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 2553        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2554        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 2555        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2556        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2557    });
 2558}
 2559
 2560#[gpui::test]
 2561fn test_newline(cx: &mut TestAppContext) {
 2562    init_test(cx, |_| {});
 2563
 2564    let editor = cx.add_window(|window, cx| {
 2565        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 2566        build_editor(buffer.clone(), window, cx)
 2567    });
 2568
 2569    _ = editor.update(cx, |editor, window, cx| {
 2570        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2571            s.select_display_ranges([
 2572                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2573                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2574                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 2575            ])
 2576        });
 2577
 2578        editor.newline(&Newline, window, cx);
 2579        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 2580    });
 2581}
 2582
 2583#[gpui::test]
 2584fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 2585    init_test(cx, |_| {});
 2586
 2587    let editor = cx.add_window(|window, cx| {
 2588        let buffer = MultiBuffer::build_simple(
 2589            "
 2590                a
 2591                b(
 2592                    X
 2593                )
 2594                c(
 2595                    X
 2596                )
 2597            "
 2598            .unindent()
 2599            .as_str(),
 2600            cx,
 2601        );
 2602        let mut editor = build_editor(buffer.clone(), window, cx);
 2603        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2604            s.select_ranges([
 2605                Point::new(2, 4)..Point::new(2, 5),
 2606                Point::new(5, 4)..Point::new(5, 5),
 2607            ])
 2608        });
 2609        editor
 2610    });
 2611
 2612    _ = editor.update(cx, |editor, window, cx| {
 2613        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 2614        editor.buffer.update(cx, |buffer, cx| {
 2615            buffer.edit(
 2616                [
 2617                    (Point::new(1, 2)..Point::new(3, 0), ""),
 2618                    (Point::new(4, 2)..Point::new(6, 0), ""),
 2619                ],
 2620                None,
 2621                cx,
 2622            );
 2623            assert_eq!(
 2624                buffer.read(cx).text(),
 2625                "
 2626                    a
 2627                    b()
 2628                    c()
 2629                "
 2630                .unindent()
 2631            );
 2632        });
 2633        assert_eq!(
 2634            editor.selections.ranges(cx),
 2635            &[
 2636                Point::new(1, 2)..Point::new(1, 2),
 2637                Point::new(2, 2)..Point::new(2, 2),
 2638            ],
 2639        );
 2640
 2641        editor.newline(&Newline, window, cx);
 2642        assert_eq!(
 2643            editor.text(cx),
 2644            "
 2645                a
 2646                b(
 2647                )
 2648                c(
 2649                )
 2650            "
 2651            .unindent()
 2652        );
 2653
 2654        // The selections are moved after the inserted newlines
 2655        assert_eq!(
 2656            editor.selections.ranges(cx),
 2657            &[
 2658                Point::new(2, 0)..Point::new(2, 0),
 2659                Point::new(4, 0)..Point::new(4, 0),
 2660            ],
 2661        );
 2662    });
 2663}
 2664
 2665#[gpui::test]
 2666async fn test_newline_above(cx: &mut TestAppContext) {
 2667    init_test(cx, |settings| {
 2668        settings.defaults.tab_size = NonZeroU32::new(4)
 2669    });
 2670
 2671    let language = Arc::new(
 2672        Language::new(
 2673            LanguageConfig::default(),
 2674            Some(tree_sitter_rust::LANGUAGE.into()),
 2675        )
 2676        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2677        .unwrap(),
 2678    );
 2679
 2680    let mut cx = EditorTestContext::new(cx).await;
 2681    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2682    cx.set_state(indoc! {"
 2683        const a: ˇA = (
 2684 2685                «const_functionˇ»(ˇ),
 2686                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2687 2688        ˇ);ˇ
 2689    "});
 2690
 2691    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 2692    cx.assert_editor_state(indoc! {"
 2693        ˇ
 2694        const a: A = (
 2695            ˇ
 2696            (
 2697                ˇ
 2698                ˇ
 2699                const_function(),
 2700                ˇ
 2701                ˇ
 2702                ˇ
 2703                ˇ
 2704                something_else,
 2705                ˇ
 2706            )
 2707            ˇ
 2708            ˇ
 2709        );
 2710    "});
 2711}
 2712
 2713#[gpui::test]
 2714async fn test_newline_below(cx: &mut TestAppContext) {
 2715    init_test(cx, |settings| {
 2716        settings.defaults.tab_size = NonZeroU32::new(4)
 2717    });
 2718
 2719    let language = Arc::new(
 2720        Language::new(
 2721            LanguageConfig::default(),
 2722            Some(tree_sitter_rust::LANGUAGE.into()),
 2723        )
 2724        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2725        .unwrap(),
 2726    );
 2727
 2728    let mut cx = EditorTestContext::new(cx).await;
 2729    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2730    cx.set_state(indoc! {"
 2731        const a: ˇA = (
 2732 2733                «const_functionˇ»(ˇ),
 2734                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2735 2736        ˇ);ˇ
 2737    "});
 2738
 2739    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 2740    cx.assert_editor_state(indoc! {"
 2741        const a: A = (
 2742            ˇ
 2743            (
 2744                ˇ
 2745                const_function(),
 2746                ˇ
 2747                ˇ
 2748                something_else,
 2749                ˇ
 2750                ˇ
 2751                ˇ
 2752                ˇ
 2753            )
 2754            ˇ
 2755        );
 2756        ˇ
 2757        ˇ
 2758    "});
 2759}
 2760
 2761#[gpui::test]
 2762async fn test_newline_comments(cx: &mut TestAppContext) {
 2763    init_test(cx, |settings| {
 2764        settings.defaults.tab_size = NonZeroU32::new(4)
 2765    });
 2766
 2767    let language = Arc::new(Language::new(
 2768        LanguageConfig {
 2769            line_comments: vec!["// ".into()],
 2770            ..LanguageConfig::default()
 2771        },
 2772        None,
 2773    ));
 2774    {
 2775        let mut cx = EditorTestContext::new(cx).await;
 2776        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2777        cx.set_state(indoc! {"
 2778        // Fooˇ
 2779    "});
 2780
 2781        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2782        cx.assert_editor_state(indoc! {"
 2783        // Foo
 2784        // ˇ
 2785    "});
 2786        // Ensure that we add comment prefix when existing line contains space
 2787        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2788        cx.assert_editor_state(
 2789            indoc! {"
 2790        // Foo
 2791        //s
 2792        // ˇ
 2793    "}
 2794            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2795            .as_str(),
 2796        );
 2797        // Ensure that we add comment prefix when existing line does not contain space
 2798        cx.set_state(indoc! {"
 2799        // Foo
 2800        //ˇ
 2801    "});
 2802        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2803        cx.assert_editor_state(indoc! {"
 2804        // Foo
 2805        //
 2806        // ˇ
 2807    "});
 2808        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 2809        cx.set_state(indoc! {"
 2810        ˇ// Foo
 2811    "});
 2812        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2813        cx.assert_editor_state(indoc! {"
 2814
 2815        ˇ// Foo
 2816    "});
 2817    }
 2818    // Ensure that comment continuations can be disabled.
 2819    update_test_language_settings(cx, |settings| {
 2820        settings.defaults.extend_comment_on_newline = Some(false);
 2821    });
 2822    let mut cx = EditorTestContext::new(cx).await;
 2823    cx.set_state(indoc! {"
 2824        // Fooˇ
 2825    "});
 2826    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2827    cx.assert_editor_state(indoc! {"
 2828        // Foo
 2829        ˇ
 2830    "});
 2831}
 2832
 2833#[gpui::test]
 2834async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 2835    init_test(cx, |settings| {
 2836        settings.defaults.tab_size = NonZeroU32::new(4)
 2837    });
 2838
 2839    let language = Arc::new(Language::new(
 2840        LanguageConfig {
 2841            line_comments: vec!["// ".into(), "/// ".into()],
 2842            ..LanguageConfig::default()
 2843        },
 2844        None,
 2845    ));
 2846    {
 2847        let mut cx = EditorTestContext::new(cx).await;
 2848        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2849        cx.set_state(indoc! {"
 2850        //ˇ
 2851    "});
 2852        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2853        cx.assert_editor_state(indoc! {"
 2854        //
 2855        // ˇ
 2856    "});
 2857
 2858        cx.set_state(indoc! {"
 2859        ///ˇ
 2860    "});
 2861        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2862        cx.assert_editor_state(indoc! {"
 2863        ///
 2864        /// ˇ
 2865    "});
 2866    }
 2867}
 2868
 2869#[gpui::test]
 2870async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 2871    init_test(cx, |settings| {
 2872        settings.defaults.tab_size = NonZeroU32::new(4)
 2873    });
 2874
 2875    let language = Arc::new(
 2876        Language::new(
 2877            LanguageConfig {
 2878                documentation_comment: Some(language::BlockCommentConfig {
 2879                    start: "/**".into(),
 2880                    end: "*/".into(),
 2881                    prefix: "* ".into(),
 2882                    tab_size: 1,
 2883                }),
 2884
 2885                ..LanguageConfig::default()
 2886            },
 2887            Some(tree_sitter_rust::LANGUAGE.into()),
 2888        )
 2889        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 2890        .unwrap(),
 2891    );
 2892
 2893    {
 2894        let mut cx = EditorTestContext::new(cx).await;
 2895        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2896        cx.set_state(indoc! {"
 2897        /**ˇ
 2898    "});
 2899
 2900        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2901        cx.assert_editor_state(indoc! {"
 2902        /**
 2903         * ˇ
 2904    "});
 2905        // Ensure that if cursor is before the comment start,
 2906        // we do not actually insert a comment prefix.
 2907        cx.set_state(indoc! {"
 2908        ˇ/**
 2909    "});
 2910        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2911        cx.assert_editor_state(indoc! {"
 2912
 2913        ˇ/**
 2914    "});
 2915        // Ensure that if cursor is between it doesn't add comment prefix.
 2916        cx.set_state(indoc! {"
 2917        /*ˇ*
 2918    "});
 2919        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2920        cx.assert_editor_state(indoc! {"
 2921        /*
 2922        ˇ*
 2923    "});
 2924        // Ensure that if suffix exists on same line after cursor it adds new line.
 2925        cx.set_state(indoc! {"
 2926        /**ˇ*/
 2927    "});
 2928        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2929        cx.assert_editor_state(indoc! {"
 2930        /**
 2931         * ˇ
 2932         */
 2933    "});
 2934        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2935        cx.set_state(indoc! {"
 2936        /**ˇ */
 2937    "});
 2938        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2939        cx.assert_editor_state(indoc! {"
 2940        /**
 2941         * ˇ
 2942         */
 2943    "});
 2944        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2945        cx.set_state(indoc! {"
 2946        /** ˇ*/
 2947    "});
 2948        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2949        cx.assert_editor_state(
 2950            indoc! {"
 2951        /**s
 2952         * ˇ
 2953         */
 2954    "}
 2955            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2956            .as_str(),
 2957        );
 2958        // Ensure that delimiter space is preserved when newline on already
 2959        // spaced delimiter.
 2960        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2961        cx.assert_editor_state(
 2962            indoc! {"
 2963        /**s
 2964         *s
 2965         * ˇ
 2966         */
 2967    "}
 2968            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2969            .as_str(),
 2970        );
 2971        // Ensure that delimiter space is preserved when space is not
 2972        // on existing delimiter.
 2973        cx.set_state(indoc! {"
 2974        /**
 2975 2976         */
 2977    "});
 2978        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2979        cx.assert_editor_state(indoc! {"
 2980        /**
 2981         *
 2982         * ˇ
 2983         */
 2984    "});
 2985        // Ensure that if suffix exists on same line after cursor it
 2986        // doesn't add extra new line if prefix is not on same line.
 2987        cx.set_state(indoc! {"
 2988        /**
 2989        ˇ*/
 2990    "});
 2991        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2992        cx.assert_editor_state(indoc! {"
 2993        /**
 2994
 2995        ˇ*/
 2996    "});
 2997        // Ensure that it detects suffix after existing prefix.
 2998        cx.set_state(indoc! {"
 2999        /**ˇ/
 3000    "});
 3001        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3002        cx.assert_editor_state(indoc! {"
 3003        /**
 3004        ˇ/
 3005    "});
 3006        // Ensure that if suffix exists on same line before
 3007        // cursor it does not add comment prefix.
 3008        cx.set_state(indoc! {"
 3009        /** */ˇ
 3010    "});
 3011        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3012        cx.assert_editor_state(indoc! {"
 3013        /** */
 3014        ˇ
 3015    "});
 3016        // Ensure that if suffix exists on same line before
 3017        // cursor it does not add comment prefix.
 3018        cx.set_state(indoc! {"
 3019        /**
 3020         *
 3021         */ˇ
 3022    "});
 3023        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3024        cx.assert_editor_state(indoc! {"
 3025        /**
 3026         *
 3027         */
 3028         ˇ
 3029    "});
 3030
 3031        // Ensure that inline comment followed by code
 3032        // doesn't add comment prefix on newline
 3033        cx.set_state(indoc! {"
 3034        /** */ textˇ
 3035    "});
 3036        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3037        cx.assert_editor_state(indoc! {"
 3038        /** */ text
 3039        ˇ
 3040    "});
 3041
 3042        // Ensure that text after comment end tag
 3043        // doesn't add comment prefix on newline
 3044        cx.set_state(indoc! {"
 3045        /**
 3046         *
 3047         */ˇtext
 3048    "});
 3049        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3050        cx.assert_editor_state(indoc! {"
 3051        /**
 3052         *
 3053         */
 3054         ˇtext
 3055    "});
 3056
 3057        // Ensure if not comment block it doesn't
 3058        // add comment prefix on newline
 3059        cx.set_state(indoc! {"
 3060        * textˇ
 3061    "});
 3062        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3063        cx.assert_editor_state(indoc! {"
 3064        * text
 3065        ˇ
 3066    "});
 3067    }
 3068    // Ensure that comment continuations can be disabled.
 3069    update_test_language_settings(cx, |settings| {
 3070        settings.defaults.extend_comment_on_newline = Some(false);
 3071    });
 3072    let mut cx = EditorTestContext::new(cx).await;
 3073    cx.set_state(indoc! {"
 3074        /**ˇ
 3075    "});
 3076    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3077    cx.assert_editor_state(indoc! {"
 3078        /**
 3079        ˇ
 3080    "});
 3081}
 3082
 3083#[gpui::test]
 3084async fn test_newline_comments_with_block_comment(cx: &mut TestAppContext) {
 3085    init_test(cx, |settings| {
 3086        settings.defaults.tab_size = NonZeroU32::new(4)
 3087    });
 3088
 3089    let lua_language = Arc::new(Language::new(
 3090        LanguageConfig {
 3091            line_comments: vec!["--".into()],
 3092            block_comment: Some(language::BlockCommentConfig {
 3093                start: "--[[".into(),
 3094                prefix: "".into(),
 3095                end: "]]".into(),
 3096                tab_size: 0,
 3097            }),
 3098            ..LanguageConfig::default()
 3099        },
 3100        None,
 3101    ));
 3102
 3103    let mut cx = EditorTestContext::new(cx).await;
 3104    cx.update_buffer(|buffer, cx| buffer.set_language(Some(lua_language), cx));
 3105
 3106    // Line with line comment should extend
 3107    cx.set_state(indoc! {"
 3108        --ˇ
 3109    "});
 3110    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3111    cx.assert_editor_state(indoc! {"
 3112        --
 3113        --ˇ
 3114    "});
 3115
 3116    // Line with block comment that matches line comment should not extend
 3117    cx.set_state(indoc! {"
 3118        --[[ˇ
 3119    "});
 3120    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3121    cx.assert_editor_state(indoc! {"
 3122        --[[
 3123        ˇ
 3124    "});
 3125}
 3126
 3127#[gpui::test]
 3128fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3129    init_test(cx, |_| {});
 3130
 3131    let editor = cx.add_window(|window, cx| {
 3132        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3133        let mut editor = build_editor(buffer.clone(), window, cx);
 3134        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3135            s.select_ranges([3..4, 11..12, 19..20])
 3136        });
 3137        editor
 3138    });
 3139
 3140    _ = editor.update(cx, |editor, window, cx| {
 3141        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3142        editor.buffer.update(cx, |buffer, cx| {
 3143            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 3144            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3145        });
 3146        assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
 3147
 3148        editor.insert("Z", window, cx);
 3149        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3150
 3151        // The selections are moved after the inserted characters
 3152        assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
 3153    });
 3154}
 3155
 3156#[gpui::test]
 3157async fn test_tab(cx: &mut TestAppContext) {
 3158    init_test(cx, |settings| {
 3159        settings.defaults.tab_size = NonZeroU32::new(3)
 3160    });
 3161
 3162    let mut cx = EditorTestContext::new(cx).await;
 3163    cx.set_state(indoc! {"
 3164        ˇabˇc
 3165        ˇ🏀ˇ🏀ˇefg
 3166 3167    "});
 3168    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3169    cx.assert_editor_state(indoc! {"
 3170           ˇab ˇc
 3171           ˇ🏀  ˇ🏀  ˇefg
 3172        d  ˇ
 3173    "});
 3174
 3175    cx.set_state(indoc! {"
 3176        a
 3177        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3178    "});
 3179    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3180    cx.assert_editor_state(indoc! {"
 3181        a
 3182           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3183    "});
 3184}
 3185
 3186#[gpui::test]
 3187async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3188    init_test(cx, |_| {});
 3189
 3190    let mut cx = EditorTestContext::new(cx).await;
 3191    let language = Arc::new(
 3192        Language::new(
 3193            LanguageConfig::default(),
 3194            Some(tree_sitter_rust::LANGUAGE.into()),
 3195        )
 3196        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3197        .unwrap(),
 3198    );
 3199    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3200
 3201    // test when all cursors are not at suggested indent
 3202    // then simply move to their suggested indent location
 3203    cx.set_state(indoc! {"
 3204        const a: B = (
 3205            c(
 3206        ˇ
 3207        ˇ    )
 3208        );
 3209    "});
 3210    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3211    cx.assert_editor_state(indoc! {"
 3212        const a: B = (
 3213            c(
 3214                ˇ
 3215            ˇ)
 3216        );
 3217    "});
 3218
 3219    // test cursor already at suggested indent not moving when
 3220    // other cursors are yet to reach their suggested indents
 3221    cx.set_state(indoc! {"
 3222        ˇ
 3223        const a: B = (
 3224            c(
 3225                d(
 3226        ˇ
 3227                )
 3228        ˇ
 3229        ˇ    )
 3230        );
 3231    "});
 3232    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3233    cx.assert_editor_state(indoc! {"
 3234        ˇ
 3235        const a: B = (
 3236            c(
 3237                d(
 3238                    ˇ
 3239                )
 3240                ˇ
 3241            ˇ)
 3242        );
 3243    "});
 3244    // test when all cursors are at suggested indent then tab is inserted
 3245    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3246    cx.assert_editor_state(indoc! {"
 3247            ˇ
 3248        const a: B = (
 3249            c(
 3250                d(
 3251                        ˇ
 3252                )
 3253                    ˇ
 3254                ˇ)
 3255        );
 3256    "});
 3257
 3258    // test when current indent is less than suggested indent,
 3259    // we adjust line to match suggested indent and move cursor to it
 3260    //
 3261    // when no other cursor is at word boundary, all of them should move
 3262    cx.set_state(indoc! {"
 3263        const a: B = (
 3264            c(
 3265                d(
 3266        ˇ
 3267        ˇ   )
 3268        ˇ   )
 3269        );
 3270    "});
 3271    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3272    cx.assert_editor_state(indoc! {"
 3273        const a: B = (
 3274            c(
 3275                d(
 3276                    ˇ
 3277                ˇ)
 3278            ˇ)
 3279        );
 3280    "});
 3281
 3282    // test when current indent is less than suggested indent,
 3283    // we adjust line to match suggested indent and move cursor to it
 3284    //
 3285    // when some other cursor is at word boundary, it should not move
 3286    cx.set_state(indoc! {"
 3287        const a: B = (
 3288            c(
 3289                d(
 3290        ˇ
 3291        ˇ   )
 3292           ˇ)
 3293        );
 3294    "});
 3295    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3296    cx.assert_editor_state(indoc! {"
 3297        const a: B = (
 3298            c(
 3299                d(
 3300                    ˇ
 3301                ˇ)
 3302            ˇ)
 3303        );
 3304    "});
 3305
 3306    // test when current indent is more than suggested indent,
 3307    // we just move cursor to current indent instead of suggested indent
 3308    //
 3309    // when no other cursor is at word boundary, all of them should move
 3310    cx.set_state(indoc! {"
 3311        const a: B = (
 3312            c(
 3313                d(
 3314        ˇ
 3315        ˇ                )
 3316        ˇ   )
 3317        );
 3318    "});
 3319    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3320    cx.assert_editor_state(indoc! {"
 3321        const a: B = (
 3322            c(
 3323                d(
 3324                    ˇ
 3325                        ˇ)
 3326            ˇ)
 3327        );
 3328    "});
 3329    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3330    cx.assert_editor_state(indoc! {"
 3331        const a: B = (
 3332            c(
 3333                d(
 3334                        ˇ
 3335                            ˇ)
 3336                ˇ)
 3337        );
 3338    "});
 3339
 3340    // test when current indent is more than suggested indent,
 3341    // we just move cursor to current indent instead of suggested indent
 3342    //
 3343    // when some other cursor is at word boundary, it doesn't move
 3344    cx.set_state(indoc! {"
 3345        const a: B = (
 3346            c(
 3347                d(
 3348        ˇ
 3349        ˇ                )
 3350            ˇ)
 3351        );
 3352    "});
 3353    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3354    cx.assert_editor_state(indoc! {"
 3355        const a: B = (
 3356            c(
 3357                d(
 3358                    ˇ
 3359                        ˇ)
 3360            ˇ)
 3361        );
 3362    "});
 3363
 3364    // handle auto-indent when there are multiple cursors on the same line
 3365    cx.set_state(indoc! {"
 3366        const a: B = (
 3367            c(
 3368        ˇ    ˇ
 3369        ˇ    )
 3370        );
 3371    "});
 3372    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3373    cx.assert_editor_state(indoc! {"
 3374        const a: B = (
 3375            c(
 3376                ˇ
 3377            ˇ)
 3378        );
 3379    "});
 3380}
 3381
 3382#[gpui::test]
 3383async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 3384    init_test(cx, |settings| {
 3385        settings.defaults.tab_size = NonZeroU32::new(3)
 3386    });
 3387
 3388    let mut cx = EditorTestContext::new(cx).await;
 3389    cx.set_state(indoc! {"
 3390         ˇ
 3391        \t ˇ
 3392        \t  ˇ
 3393        \t   ˇ
 3394         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 3395    "});
 3396
 3397    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3398    cx.assert_editor_state(indoc! {"
 3399           ˇ
 3400        \t   ˇ
 3401        \t   ˇ
 3402        \t      ˇ
 3403         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 3404    "});
 3405}
 3406
 3407#[gpui::test]
 3408async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 3409    init_test(cx, |settings| {
 3410        settings.defaults.tab_size = NonZeroU32::new(4)
 3411    });
 3412
 3413    let language = Arc::new(
 3414        Language::new(
 3415            LanguageConfig::default(),
 3416            Some(tree_sitter_rust::LANGUAGE.into()),
 3417        )
 3418        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 3419        .unwrap(),
 3420    );
 3421
 3422    let mut cx = EditorTestContext::new(cx).await;
 3423    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3424    cx.set_state(indoc! {"
 3425        fn a() {
 3426            if b {
 3427        \t ˇc
 3428            }
 3429        }
 3430    "});
 3431
 3432    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3433    cx.assert_editor_state(indoc! {"
 3434        fn a() {
 3435            if b {
 3436                ˇc
 3437            }
 3438        }
 3439    "});
 3440}
 3441
 3442#[gpui::test]
 3443async fn test_indent_outdent(cx: &mut TestAppContext) {
 3444    init_test(cx, |settings| {
 3445        settings.defaults.tab_size = NonZeroU32::new(4);
 3446    });
 3447
 3448    let mut cx = EditorTestContext::new(cx).await;
 3449
 3450    cx.set_state(indoc! {"
 3451          «oneˇ» «twoˇ»
 3452        three
 3453         four
 3454    "});
 3455    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3456    cx.assert_editor_state(indoc! {"
 3457            «oneˇ» «twoˇ»
 3458        three
 3459         four
 3460    "});
 3461
 3462    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3463    cx.assert_editor_state(indoc! {"
 3464        «oneˇ» «twoˇ»
 3465        three
 3466         four
 3467    "});
 3468
 3469    // select across line ending
 3470    cx.set_state(indoc! {"
 3471        one two
 3472        t«hree
 3473        ˇ» four
 3474    "});
 3475    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3476    cx.assert_editor_state(indoc! {"
 3477        one two
 3478            t«hree
 3479        ˇ» four
 3480    "});
 3481
 3482    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3483    cx.assert_editor_state(indoc! {"
 3484        one two
 3485        t«hree
 3486        ˇ» four
 3487    "});
 3488
 3489    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3490    cx.set_state(indoc! {"
 3491        one two
 3492        ˇthree
 3493            four
 3494    "});
 3495    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3496    cx.assert_editor_state(indoc! {"
 3497        one two
 3498            ˇthree
 3499            four
 3500    "});
 3501
 3502    cx.set_state(indoc! {"
 3503        one two
 3504        ˇ    three
 3505            four
 3506    "});
 3507    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3508    cx.assert_editor_state(indoc! {"
 3509        one two
 3510        ˇthree
 3511            four
 3512    "});
 3513}
 3514
 3515#[gpui::test]
 3516async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3517    // This is a regression test for issue #33761
 3518    init_test(cx, |_| {});
 3519
 3520    let mut cx = EditorTestContext::new(cx).await;
 3521    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3522    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3523
 3524    cx.set_state(
 3525        r#"ˇ#     ingress:
 3526ˇ#         api:
 3527ˇ#             enabled: false
 3528ˇ#             pathType: Prefix
 3529ˇ#           console:
 3530ˇ#               enabled: false
 3531ˇ#               pathType: Prefix
 3532"#,
 3533    );
 3534
 3535    // Press tab to indent all lines
 3536    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3537
 3538    cx.assert_editor_state(
 3539        r#"    ˇ#     ingress:
 3540    ˇ#         api:
 3541    ˇ#             enabled: false
 3542    ˇ#             pathType: Prefix
 3543    ˇ#           console:
 3544    ˇ#               enabled: false
 3545    ˇ#               pathType: Prefix
 3546"#,
 3547    );
 3548}
 3549
 3550#[gpui::test]
 3551async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3552    // This is a test to make sure our fix for issue #33761 didn't break anything
 3553    init_test(cx, |_| {});
 3554
 3555    let mut cx = EditorTestContext::new(cx).await;
 3556    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3557    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3558
 3559    cx.set_state(
 3560        r#"ˇingress:
 3561ˇ  api:
 3562ˇ    enabled: false
 3563ˇ    pathType: Prefix
 3564"#,
 3565    );
 3566
 3567    // Press tab to indent all lines
 3568    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3569
 3570    cx.assert_editor_state(
 3571        r#"ˇingress:
 3572    ˇapi:
 3573        ˇenabled: false
 3574        ˇpathType: Prefix
 3575"#,
 3576    );
 3577}
 3578
 3579#[gpui::test]
 3580async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 3581    init_test(cx, |settings| {
 3582        settings.defaults.hard_tabs = Some(true);
 3583    });
 3584
 3585    let mut cx = EditorTestContext::new(cx).await;
 3586
 3587    // select two ranges on one line
 3588    cx.set_state(indoc! {"
 3589        «oneˇ» «twoˇ»
 3590        three
 3591        four
 3592    "});
 3593    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3594    cx.assert_editor_state(indoc! {"
 3595        \t«oneˇ» «twoˇ»
 3596        three
 3597        four
 3598    "});
 3599    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3600    cx.assert_editor_state(indoc! {"
 3601        \t\t«oneˇ» «twoˇ»
 3602        three
 3603        four
 3604    "});
 3605    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3606    cx.assert_editor_state(indoc! {"
 3607        \t«oneˇ» «twoˇ»
 3608        three
 3609        four
 3610    "});
 3611    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3612    cx.assert_editor_state(indoc! {"
 3613        «oneˇ» «twoˇ»
 3614        three
 3615        four
 3616    "});
 3617
 3618    // select across a line ending
 3619    cx.set_state(indoc! {"
 3620        one two
 3621        t«hree
 3622        ˇ»four
 3623    "});
 3624    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3625    cx.assert_editor_state(indoc! {"
 3626        one two
 3627        \tt«hree
 3628        ˇ»four
 3629    "});
 3630    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3631    cx.assert_editor_state(indoc! {"
 3632        one two
 3633        \t\tt«hree
 3634        ˇ»four
 3635    "});
 3636    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3637    cx.assert_editor_state(indoc! {"
 3638        one two
 3639        \tt«hree
 3640        ˇ»four
 3641    "});
 3642    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3643    cx.assert_editor_state(indoc! {"
 3644        one two
 3645        t«hree
 3646        ˇ»four
 3647    "});
 3648
 3649    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3650    cx.set_state(indoc! {"
 3651        one two
 3652        ˇthree
 3653        four
 3654    "});
 3655    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3656    cx.assert_editor_state(indoc! {"
 3657        one two
 3658        ˇthree
 3659        four
 3660    "});
 3661    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3662    cx.assert_editor_state(indoc! {"
 3663        one two
 3664        \tˇthree
 3665        four
 3666    "});
 3667    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3668    cx.assert_editor_state(indoc! {"
 3669        one two
 3670        ˇthree
 3671        four
 3672    "});
 3673}
 3674
 3675#[gpui::test]
 3676fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 3677    init_test(cx, |settings| {
 3678        settings.languages.0.extend([
 3679            (
 3680                "TOML".into(),
 3681                LanguageSettingsContent {
 3682                    tab_size: NonZeroU32::new(2),
 3683                    ..Default::default()
 3684                },
 3685            ),
 3686            (
 3687                "Rust".into(),
 3688                LanguageSettingsContent {
 3689                    tab_size: NonZeroU32::new(4),
 3690                    ..Default::default()
 3691                },
 3692            ),
 3693        ]);
 3694    });
 3695
 3696    let toml_language = Arc::new(Language::new(
 3697        LanguageConfig {
 3698            name: "TOML".into(),
 3699            ..Default::default()
 3700        },
 3701        None,
 3702    ));
 3703    let rust_language = Arc::new(Language::new(
 3704        LanguageConfig {
 3705            name: "Rust".into(),
 3706            ..Default::default()
 3707        },
 3708        None,
 3709    ));
 3710
 3711    let toml_buffer =
 3712        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 3713    let rust_buffer =
 3714        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 3715    let multibuffer = cx.new(|cx| {
 3716        let mut multibuffer = MultiBuffer::new(ReadWrite);
 3717        multibuffer.push_excerpts(
 3718            toml_buffer.clone(),
 3719            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 3720            cx,
 3721        );
 3722        multibuffer.push_excerpts(
 3723            rust_buffer.clone(),
 3724            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 3725            cx,
 3726        );
 3727        multibuffer
 3728    });
 3729
 3730    cx.add_window(|window, cx| {
 3731        let mut editor = build_editor(multibuffer, window, cx);
 3732
 3733        assert_eq!(
 3734            editor.text(cx),
 3735            indoc! {"
 3736                a = 1
 3737                b = 2
 3738
 3739                const c: usize = 3;
 3740            "}
 3741        );
 3742
 3743        select_ranges(
 3744            &mut editor,
 3745            indoc! {"
 3746                «aˇ» = 1
 3747                b = 2
 3748
 3749                «const c:ˇ» usize = 3;
 3750            "},
 3751            window,
 3752            cx,
 3753        );
 3754
 3755        editor.tab(&Tab, window, cx);
 3756        assert_text_with_selections(
 3757            &mut editor,
 3758            indoc! {"
 3759                  «aˇ» = 1
 3760                b = 2
 3761
 3762                    «const c:ˇ» usize = 3;
 3763            "},
 3764            cx,
 3765        );
 3766        editor.backtab(&Backtab, window, cx);
 3767        assert_text_with_selections(
 3768            &mut editor,
 3769            indoc! {"
 3770                «aˇ» = 1
 3771                b = 2
 3772
 3773                «const c:ˇ» usize = 3;
 3774            "},
 3775            cx,
 3776        );
 3777
 3778        editor
 3779    });
 3780}
 3781
 3782#[gpui::test]
 3783async fn test_backspace(cx: &mut TestAppContext) {
 3784    init_test(cx, |_| {});
 3785
 3786    let mut cx = EditorTestContext::new(cx).await;
 3787
 3788    // Basic backspace
 3789    cx.set_state(indoc! {"
 3790        onˇe two three
 3791        fou«rˇ» five six
 3792        seven «ˇeight nine
 3793        »ten
 3794    "});
 3795    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3796    cx.assert_editor_state(indoc! {"
 3797        oˇe two three
 3798        fouˇ five six
 3799        seven ˇten
 3800    "});
 3801
 3802    // Test backspace inside and around indents
 3803    cx.set_state(indoc! {"
 3804        zero
 3805            ˇone
 3806                ˇtwo
 3807            ˇ ˇ ˇ  three
 3808        ˇ  ˇ  four
 3809    "});
 3810    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3811    cx.assert_editor_state(indoc! {"
 3812        zero
 3813        ˇone
 3814            ˇtwo
 3815        ˇ  threeˇ  four
 3816    "});
 3817}
 3818
 3819#[gpui::test]
 3820async fn test_delete(cx: &mut TestAppContext) {
 3821    init_test(cx, |_| {});
 3822
 3823    let mut cx = EditorTestContext::new(cx).await;
 3824    cx.set_state(indoc! {"
 3825        onˇe two three
 3826        fou«rˇ» five six
 3827        seven «ˇeight nine
 3828        »ten
 3829    "});
 3830    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 3831    cx.assert_editor_state(indoc! {"
 3832        onˇ two three
 3833        fouˇ five six
 3834        seven ˇten
 3835    "});
 3836}
 3837
 3838#[gpui::test]
 3839fn test_delete_line(cx: &mut TestAppContext) {
 3840    init_test(cx, |_| {});
 3841
 3842    let editor = cx.add_window(|window, cx| {
 3843        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3844        build_editor(buffer, window, cx)
 3845    });
 3846    _ = editor.update(cx, |editor, window, cx| {
 3847        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3848            s.select_display_ranges([
 3849                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 3850                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 3851                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 3852            ])
 3853        });
 3854        editor.delete_line(&DeleteLine, window, cx);
 3855        assert_eq!(editor.display_text(cx), "ghi");
 3856        assert_eq!(
 3857            editor.selections.display_ranges(cx),
 3858            vec![
 3859                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 3860                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
 3861            ]
 3862        );
 3863    });
 3864
 3865    let editor = cx.add_window(|window, cx| {
 3866        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3867        build_editor(buffer, window, cx)
 3868    });
 3869    _ = editor.update(cx, |editor, window, cx| {
 3870        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3871            s.select_display_ranges([
 3872                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 3873            ])
 3874        });
 3875        editor.delete_line(&DeleteLine, window, cx);
 3876        assert_eq!(editor.display_text(cx), "ghi\n");
 3877        assert_eq!(
 3878            editor.selections.display_ranges(cx),
 3879            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 3880        );
 3881    });
 3882}
 3883
 3884#[gpui::test]
 3885fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 3886    init_test(cx, |_| {});
 3887
 3888    cx.add_window(|window, cx| {
 3889        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3890        let mut editor = build_editor(buffer.clone(), window, cx);
 3891        let buffer = buffer.read(cx).as_singleton().unwrap();
 3892
 3893        assert_eq!(
 3894            editor.selections.ranges::<Point>(cx),
 3895            &[Point::new(0, 0)..Point::new(0, 0)]
 3896        );
 3897
 3898        // When on single line, replace newline at end by space
 3899        editor.join_lines(&JoinLines, window, cx);
 3900        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3901        assert_eq!(
 3902            editor.selections.ranges::<Point>(cx),
 3903            &[Point::new(0, 3)..Point::new(0, 3)]
 3904        );
 3905
 3906        // When multiple lines are selected, remove newlines that are spanned by the selection
 3907        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3908            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 3909        });
 3910        editor.join_lines(&JoinLines, window, cx);
 3911        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 3912        assert_eq!(
 3913            editor.selections.ranges::<Point>(cx),
 3914            &[Point::new(0, 11)..Point::new(0, 11)]
 3915        );
 3916
 3917        // Undo should be transactional
 3918        editor.undo(&Undo, window, cx);
 3919        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3920        assert_eq!(
 3921            editor.selections.ranges::<Point>(cx),
 3922            &[Point::new(0, 5)..Point::new(2, 2)]
 3923        );
 3924
 3925        // When joining an empty line don't insert a space
 3926        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3927            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 3928        });
 3929        editor.join_lines(&JoinLines, window, cx);
 3930        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 3931        assert_eq!(
 3932            editor.selections.ranges::<Point>(cx),
 3933            [Point::new(2, 3)..Point::new(2, 3)]
 3934        );
 3935
 3936        // We can remove trailing newlines
 3937        editor.join_lines(&JoinLines, window, cx);
 3938        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3939        assert_eq!(
 3940            editor.selections.ranges::<Point>(cx),
 3941            [Point::new(2, 3)..Point::new(2, 3)]
 3942        );
 3943
 3944        // We don't blow up on the last line
 3945        editor.join_lines(&JoinLines, window, cx);
 3946        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3947        assert_eq!(
 3948            editor.selections.ranges::<Point>(cx),
 3949            [Point::new(2, 3)..Point::new(2, 3)]
 3950        );
 3951
 3952        // reset to test indentation
 3953        editor.buffer.update(cx, |buffer, cx| {
 3954            buffer.edit(
 3955                [
 3956                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 3957                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 3958                ],
 3959                None,
 3960                cx,
 3961            )
 3962        });
 3963
 3964        // We remove any leading spaces
 3965        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 3966        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3967            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 3968        });
 3969        editor.join_lines(&JoinLines, window, cx);
 3970        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 3971
 3972        // We don't insert a space for a line containing only spaces
 3973        editor.join_lines(&JoinLines, window, cx);
 3974        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 3975
 3976        // We ignore any leading tabs
 3977        editor.join_lines(&JoinLines, window, cx);
 3978        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 3979
 3980        editor
 3981    });
 3982}
 3983
 3984#[gpui::test]
 3985fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 3986    init_test(cx, |_| {});
 3987
 3988    cx.add_window(|window, cx| {
 3989        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3990        let mut editor = build_editor(buffer.clone(), window, cx);
 3991        let buffer = buffer.read(cx).as_singleton().unwrap();
 3992
 3993        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3994            s.select_ranges([
 3995                Point::new(0, 2)..Point::new(1, 1),
 3996                Point::new(1, 2)..Point::new(1, 2),
 3997                Point::new(3, 1)..Point::new(3, 2),
 3998            ])
 3999        });
 4000
 4001        editor.join_lines(&JoinLines, window, cx);
 4002        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 4003
 4004        assert_eq!(
 4005            editor.selections.ranges::<Point>(cx),
 4006            [
 4007                Point::new(0, 7)..Point::new(0, 7),
 4008                Point::new(1, 3)..Point::new(1, 3)
 4009            ]
 4010        );
 4011        editor
 4012    });
 4013}
 4014
 4015#[gpui::test]
 4016async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 4017    init_test(cx, |_| {});
 4018
 4019    let mut cx = EditorTestContext::new(cx).await;
 4020
 4021    let diff_base = r#"
 4022        Line 0
 4023        Line 1
 4024        Line 2
 4025        Line 3
 4026        "#
 4027    .unindent();
 4028
 4029    cx.set_state(
 4030        &r#"
 4031        ˇLine 0
 4032        Line 1
 4033        Line 2
 4034        Line 3
 4035        "#
 4036        .unindent(),
 4037    );
 4038
 4039    cx.set_head_text(&diff_base);
 4040    executor.run_until_parked();
 4041
 4042    // Join lines
 4043    cx.update_editor(|editor, window, cx| {
 4044        editor.join_lines(&JoinLines, window, cx);
 4045    });
 4046    executor.run_until_parked();
 4047
 4048    cx.assert_editor_state(
 4049        &r#"
 4050        Line 0ˇ Line 1
 4051        Line 2
 4052        Line 3
 4053        "#
 4054        .unindent(),
 4055    );
 4056    // Join again
 4057    cx.update_editor(|editor, window, cx| {
 4058        editor.join_lines(&JoinLines, window, cx);
 4059    });
 4060    executor.run_until_parked();
 4061
 4062    cx.assert_editor_state(
 4063        &r#"
 4064        Line 0 Line 1ˇ Line 2
 4065        Line 3
 4066        "#
 4067        .unindent(),
 4068    );
 4069}
 4070
 4071#[gpui::test]
 4072async fn test_custom_newlines_cause_no_false_positive_diffs(
 4073    executor: BackgroundExecutor,
 4074    cx: &mut TestAppContext,
 4075) {
 4076    init_test(cx, |_| {});
 4077    let mut cx = EditorTestContext::new(cx).await;
 4078    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 4079    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 4080    executor.run_until_parked();
 4081
 4082    cx.update_editor(|editor, window, cx| {
 4083        let snapshot = editor.snapshot(window, cx);
 4084        assert_eq!(
 4085            snapshot
 4086                .buffer_snapshot
 4087                .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
 4088                .collect::<Vec<_>>(),
 4089            Vec::new(),
 4090            "Should not have any diffs for files with custom newlines"
 4091        );
 4092    });
 4093}
 4094
 4095#[gpui::test]
 4096async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 4097    init_test(cx, |_| {});
 4098
 4099    let mut cx = EditorTestContext::new(cx).await;
 4100
 4101    // Test sort_lines_case_insensitive()
 4102    cx.set_state(indoc! {"
 4103        «z
 4104        y
 4105        x
 4106        Z
 4107        Y
 4108        Xˇ»
 4109    "});
 4110    cx.update_editor(|e, window, cx| {
 4111        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4112    });
 4113    cx.assert_editor_state(indoc! {"
 4114        «x
 4115        X
 4116        y
 4117        Y
 4118        z
 4119        Zˇ»
 4120    "});
 4121
 4122    // Test sort_lines_by_length()
 4123    //
 4124    // Demonstrates:
 4125    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 4126    // - sort is stable
 4127    cx.set_state(indoc! {"
 4128        «123
 4129        æ
 4130        12
 4131 4132        1
 4133        æˇ»
 4134    "});
 4135    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 4136    cx.assert_editor_state(indoc! {"
 4137        «æ
 4138 4139        1
 4140        æ
 4141        12
 4142        123ˇ»
 4143    "});
 4144
 4145    // Test reverse_lines()
 4146    cx.set_state(indoc! {"
 4147        «5
 4148        4
 4149        3
 4150        2
 4151        1ˇ»
 4152    "});
 4153    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4154    cx.assert_editor_state(indoc! {"
 4155        «1
 4156        2
 4157        3
 4158        4
 4159        5ˇ»
 4160    "});
 4161
 4162    // Skip testing shuffle_line()
 4163
 4164    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4165    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4166
 4167    // Don't manipulate when cursor is on single line, but expand the selection
 4168    cx.set_state(indoc! {"
 4169        ddˇdd
 4170        ccc
 4171        bb
 4172        a
 4173    "});
 4174    cx.update_editor(|e, window, cx| {
 4175        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4176    });
 4177    cx.assert_editor_state(indoc! {"
 4178        «ddddˇ»
 4179        ccc
 4180        bb
 4181        a
 4182    "});
 4183
 4184    // Basic manipulate case
 4185    // Start selection moves to column 0
 4186    // End of selection shrinks to fit shorter line
 4187    cx.set_state(indoc! {"
 4188        dd«d
 4189        ccc
 4190        bb
 4191        aaaaaˇ»
 4192    "});
 4193    cx.update_editor(|e, window, cx| {
 4194        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4195    });
 4196    cx.assert_editor_state(indoc! {"
 4197        «aaaaa
 4198        bb
 4199        ccc
 4200        dddˇ»
 4201    "});
 4202
 4203    // Manipulate case with newlines
 4204    cx.set_state(indoc! {"
 4205        dd«d
 4206        ccc
 4207
 4208        bb
 4209        aaaaa
 4210
 4211        ˇ»
 4212    "});
 4213    cx.update_editor(|e, window, cx| {
 4214        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4215    });
 4216    cx.assert_editor_state(indoc! {"
 4217        «
 4218
 4219        aaaaa
 4220        bb
 4221        ccc
 4222        dddˇ»
 4223
 4224    "});
 4225
 4226    // Adding new line
 4227    cx.set_state(indoc! {"
 4228        aa«a
 4229        bbˇ»b
 4230    "});
 4231    cx.update_editor(|e, window, cx| {
 4232        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4233    });
 4234    cx.assert_editor_state(indoc! {"
 4235        «aaa
 4236        bbb
 4237        added_lineˇ»
 4238    "});
 4239
 4240    // Removing line
 4241    cx.set_state(indoc! {"
 4242        aa«a
 4243        bbbˇ»
 4244    "});
 4245    cx.update_editor(|e, window, cx| {
 4246        e.manipulate_immutable_lines(window, cx, |lines| {
 4247            lines.pop();
 4248        })
 4249    });
 4250    cx.assert_editor_state(indoc! {"
 4251        «aaaˇ»
 4252    "});
 4253
 4254    // Removing all lines
 4255    cx.set_state(indoc! {"
 4256        aa«a
 4257        bbbˇ»
 4258    "});
 4259    cx.update_editor(|e, window, cx| {
 4260        e.manipulate_immutable_lines(window, cx, |lines| {
 4261            lines.drain(..);
 4262        })
 4263    });
 4264    cx.assert_editor_state(indoc! {"
 4265        ˇ
 4266    "});
 4267}
 4268
 4269#[gpui::test]
 4270async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4271    init_test(cx, |_| {});
 4272
 4273    let mut cx = EditorTestContext::new(cx).await;
 4274
 4275    // Consider continuous selection as single selection
 4276    cx.set_state(indoc! {"
 4277        Aaa«aa
 4278        cˇ»c«c
 4279        bb
 4280        aaaˇ»aa
 4281    "});
 4282    cx.update_editor(|e, window, cx| {
 4283        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4284    });
 4285    cx.assert_editor_state(indoc! {"
 4286        «Aaaaa
 4287        ccc
 4288        bb
 4289        aaaaaˇ»
 4290    "});
 4291
 4292    cx.set_state(indoc! {"
 4293        Aaa«aa
 4294        cˇ»c«c
 4295        bb
 4296        aaaˇ»aa
 4297    "});
 4298    cx.update_editor(|e, window, cx| {
 4299        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4300    });
 4301    cx.assert_editor_state(indoc! {"
 4302        «Aaaaa
 4303        ccc
 4304        bbˇ»
 4305    "});
 4306
 4307    // Consider non continuous selection as distinct dedup operations
 4308    cx.set_state(indoc! {"
 4309        «aaaaa
 4310        bb
 4311        aaaaa
 4312        aaaaaˇ»
 4313
 4314        aaa«aaˇ»
 4315    "});
 4316    cx.update_editor(|e, window, cx| {
 4317        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4318    });
 4319    cx.assert_editor_state(indoc! {"
 4320        «aaaaa
 4321        bbˇ»
 4322
 4323        «aaaaaˇ»
 4324    "});
 4325}
 4326
 4327#[gpui::test]
 4328async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4329    init_test(cx, |_| {});
 4330
 4331    let mut cx = EditorTestContext::new(cx).await;
 4332
 4333    cx.set_state(indoc! {"
 4334        «Aaa
 4335        aAa
 4336        Aaaˇ»
 4337    "});
 4338    cx.update_editor(|e, window, cx| {
 4339        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4340    });
 4341    cx.assert_editor_state(indoc! {"
 4342        «Aaa
 4343        aAaˇ»
 4344    "});
 4345
 4346    cx.set_state(indoc! {"
 4347        «Aaa
 4348        aAa
 4349        aaAˇ»
 4350    "});
 4351    cx.update_editor(|e, window, cx| {
 4352        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4353    });
 4354    cx.assert_editor_state(indoc! {"
 4355        «Aaaˇ»
 4356    "});
 4357}
 4358
 4359#[gpui::test]
 4360async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 4361    init_test(cx, |_| {});
 4362
 4363    let mut cx = EditorTestContext::new(cx).await;
 4364
 4365    // Manipulate with multiple selections on a single line
 4366    cx.set_state(indoc! {"
 4367        dd«dd
 4368        cˇ»c«c
 4369        bb
 4370        aaaˇ»aa
 4371    "});
 4372    cx.update_editor(|e, window, cx| {
 4373        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4374    });
 4375    cx.assert_editor_state(indoc! {"
 4376        «aaaaa
 4377        bb
 4378        ccc
 4379        ddddˇ»
 4380    "});
 4381
 4382    // Manipulate with multiple disjoin selections
 4383    cx.set_state(indoc! {"
 4384 4385        4
 4386        3
 4387        2
 4388        1ˇ»
 4389
 4390        dd«dd
 4391        ccc
 4392        bb
 4393        aaaˇ»aa
 4394    "});
 4395    cx.update_editor(|e, window, cx| {
 4396        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4397    });
 4398    cx.assert_editor_state(indoc! {"
 4399        «1
 4400        2
 4401        3
 4402        4
 4403        5ˇ»
 4404
 4405        «aaaaa
 4406        bb
 4407        ccc
 4408        ddddˇ»
 4409    "});
 4410
 4411    // Adding lines on each selection
 4412    cx.set_state(indoc! {"
 4413 4414        1ˇ»
 4415
 4416        bb«bb
 4417        aaaˇ»aa
 4418    "});
 4419    cx.update_editor(|e, window, cx| {
 4420        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 4421    });
 4422    cx.assert_editor_state(indoc! {"
 4423        «2
 4424        1
 4425        added lineˇ»
 4426
 4427        «bbbb
 4428        aaaaa
 4429        added lineˇ»
 4430    "});
 4431
 4432    // Removing lines on each selection
 4433    cx.set_state(indoc! {"
 4434 4435        1ˇ»
 4436
 4437        bb«bb
 4438        aaaˇ»aa
 4439    "});
 4440    cx.update_editor(|e, window, cx| {
 4441        e.manipulate_immutable_lines(window, cx, |lines| {
 4442            lines.pop();
 4443        })
 4444    });
 4445    cx.assert_editor_state(indoc! {"
 4446        «2ˇ»
 4447
 4448        «bbbbˇ»
 4449    "});
 4450}
 4451
 4452#[gpui::test]
 4453async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 4454    init_test(cx, |settings| {
 4455        settings.defaults.tab_size = NonZeroU32::new(3)
 4456    });
 4457
 4458    let mut cx = EditorTestContext::new(cx).await;
 4459
 4460    // MULTI SELECTION
 4461    // Ln.1 "«" tests empty lines
 4462    // Ln.9 tests just leading whitespace
 4463    cx.set_state(indoc! {"
 4464        «
 4465        abc                 // No indentationˇ»
 4466        «\tabc              // 1 tabˇ»
 4467        \t\tabc «      ˇ»   // 2 tabs
 4468        \t ab«c             // Tab followed by space
 4469         \tabc              // Space followed by tab (3 spaces should be the result)
 4470        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4471           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 4472        \t
 4473        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4474    "});
 4475    cx.update_editor(|e, window, cx| {
 4476        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4477    });
 4478    cx.assert_editor_state(
 4479        indoc! {"
 4480            «
 4481            abc                 // No indentation
 4482               abc              // 1 tab
 4483                  abc          // 2 tabs
 4484                abc             // Tab followed by space
 4485               abc              // Space followed by tab (3 spaces should be the result)
 4486                           abc   // Mixed indentation (tab conversion depends on the column)
 4487               abc         // Already space indented
 4488               ·
 4489               abc\tdef          // Only the leading tab is manipulatedˇ»
 4490        "}
 4491        .replace("·", "")
 4492        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4493    );
 4494
 4495    // Test on just a few lines, the others should remain unchanged
 4496    // Only lines (3, 5, 10, 11) should change
 4497    cx.set_state(
 4498        indoc! {"
 4499            ·
 4500            abc                 // No indentation
 4501            \tabcˇ               // 1 tab
 4502            \t\tabc             // 2 tabs
 4503            \t abcˇ              // Tab followed by space
 4504             \tabc              // Space followed by tab (3 spaces should be the result)
 4505            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4506               abc              // Already space indented
 4507            «\t
 4508            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4509        "}
 4510        .replace("·", "")
 4511        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4512    );
 4513    cx.update_editor(|e, window, cx| {
 4514        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4515    });
 4516    cx.assert_editor_state(
 4517        indoc! {"
 4518            ·
 4519            abc                 // No indentation
 4520            «   abc               // 1 tabˇ»
 4521            \t\tabc             // 2 tabs
 4522            «    abc              // Tab followed by spaceˇ»
 4523             \tabc              // Space followed by tab (3 spaces should be the result)
 4524            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4525               abc              // Already space indented
 4526            «   ·
 4527               abc\tdef          // Only the leading tab is manipulatedˇ»
 4528        "}
 4529        .replace("·", "")
 4530        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4531    );
 4532
 4533    // SINGLE SELECTION
 4534    // Ln.1 "«" tests empty lines
 4535    // Ln.9 tests just leading whitespace
 4536    cx.set_state(indoc! {"
 4537        «
 4538        abc                 // No indentation
 4539        \tabc               // 1 tab
 4540        \t\tabc             // 2 tabs
 4541        \t abc              // Tab followed by space
 4542         \tabc              // Space followed by tab (3 spaces should be the result)
 4543        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4544           abc              // Already space indented
 4545        \t
 4546        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4547    "});
 4548    cx.update_editor(|e, window, cx| {
 4549        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4550    });
 4551    cx.assert_editor_state(
 4552        indoc! {"
 4553            «
 4554            abc                 // No indentation
 4555               abc               // 1 tab
 4556                  abc             // 2 tabs
 4557                abc              // Tab followed by space
 4558               abc              // Space followed by tab (3 spaces should be the result)
 4559                           abc   // Mixed indentation (tab conversion depends on the column)
 4560               abc              // Already space indented
 4561               ·
 4562               abc\tdef          // Only the leading tab is manipulatedˇ»
 4563        "}
 4564        .replace("·", "")
 4565        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4566    );
 4567}
 4568
 4569#[gpui::test]
 4570async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 4571    init_test(cx, |settings| {
 4572        settings.defaults.tab_size = NonZeroU32::new(3)
 4573    });
 4574
 4575    let mut cx = EditorTestContext::new(cx).await;
 4576
 4577    // MULTI SELECTION
 4578    // Ln.1 "«" tests empty lines
 4579    // Ln.11 tests just leading whitespace
 4580    cx.set_state(indoc! {"
 4581        «
 4582        abˇ»ˇc                 // No indentation
 4583         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 4584          abc  «             // 2 spaces (< 3 so dont convert)
 4585           abc              // 3 spaces (convert)
 4586             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 4587        «\tˇ»\t«\tˇ»abc           // Already tab indented
 4588        «\t abc              // Tab followed by space
 4589         \tabc              // Space followed by tab (should be consumed due to tab)
 4590        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4591           \tˇ»  «\t
 4592           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 4593    "});
 4594    cx.update_editor(|e, window, cx| {
 4595        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4596    });
 4597    cx.assert_editor_state(indoc! {"
 4598        «
 4599        abc                 // No indentation
 4600         abc                // 1 space (< 3 so dont convert)
 4601          abc               // 2 spaces (< 3 so dont convert)
 4602        \tabc              // 3 spaces (convert)
 4603        \t  abc            // 5 spaces (1 tab + 2 spaces)
 4604        \t\t\tabc           // Already tab indented
 4605        \t abc              // Tab followed by space
 4606        \tabc              // Space followed by tab (should be consumed due to tab)
 4607        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4608        \t\t\t
 4609        \tabc   \t         // Only the leading spaces should be convertedˇ»
 4610    "});
 4611
 4612    // Test on just a few lines, the other should remain unchanged
 4613    // Only lines (4, 8, 11, 12) should change
 4614    cx.set_state(
 4615        indoc! {"
 4616            ·
 4617            abc                 // No indentation
 4618             abc                // 1 space (< 3 so dont convert)
 4619              abc               // 2 spaces (< 3 so dont convert)
 4620            «   abc              // 3 spaces (convert)ˇ»
 4621                 abc            // 5 spaces (1 tab + 2 spaces)
 4622            \t\t\tabc           // Already tab indented
 4623            \t abc              // Tab followed by space
 4624             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 4625               \t\t  \tabc      // Mixed indentation
 4626            \t \t  \t   \tabc   // Mixed indentation
 4627               \t  \tˇ
 4628            «   abc   \t         // Only the leading spaces should be convertedˇ»
 4629        "}
 4630        .replace("·", "")
 4631        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4632    );
 4633    cx.update_editor(|e, window, cx| {
 4634        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4635    });
 4636    cx.assert_editor_state(
 4637        indoc! {"
 4638            ·
 4639            abc                 // No indentation
 4640             abc                // 1 space (< 3 so dont convert)
 4641              abc               // 2 spaces (< 3 so dont convert)
 4642            «\tabc              // 3 spaces (convert)ˇ»
 4643                 abc            // 5 spaces (1 tab + 2 spaces)
 4644            \t\t\tabc           // Already tab indented
 4645            \t abc              // Tab followed by space
 4646            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 4647               \t\t  \tabc      // Mixed indentation
 4648            \t \t  \t   \tabc   // Mixed indentation
 4649            «\t\t\t
 4650            \tabc   \t         // Only the leading spaces should be convertedˇ»
 4651        "}
 4652        .replace("·", "")
 4653        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4654    );
 4655
 4656    // SINGLE SELECTION
 4657    // Ln.1 "«" tests empty lines
 4658    // Ln.11 tests just leading whitespace
 4659    cx.set_state(indoc! {"
 4660        «
 4661        abc                 // No indentation
 4662         abc                // 1 space (< 3 so dont convert)
 4663          abc               // 2 spaces (< 3 so dont convert)
 4664           abc              // 3 spaces (convert)
 4665             abc            // 5 spaces (1 tab + 2 spaces)
 4666        \t\t\tabc           // Already tab indented
 4667        \t abc              // Tab followed by space
 4668         \tabc              // Space followed by tab (should be consumed due to tab)
 4669        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4670           \t  \t
 4671           abc   \t         // Only the leading spaces should be convertedˇ»
 4672    "});
 4673    cx.update_editor(|e, window, cx| {
 4674        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4675    });
 4676    cx.assert_editor_state(indoc! {"
 4677        «
 4678        abc                 // No indentation
 4679         abc                // 1 space (< 3 so dont convert)
 4680          abc               // 2 spaces (< 3 so dont convert)
 4681        \tabc              // 3 spaces (convert)
 4682        \t  abc            // 5 spaces (1 tab + 2 spaces)
 4683        \t\t\tabc           // Already tab indented
 4684        \t abc              // Tab followed by space
 4685        \tabc              // Space followed by tab (should be consumed due to tab)
 4686        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4687        \t\t\t
 4688        \tabc   \t         // Only the leading spaces should be convertedˇ»
 4689    "});
 4690}
 4691
 4692#[gpui::test]
 4693async fn test_toggle_case(cx: &mut TestAppContext) {
 4694    init_test(cx, |_| {});
 4695
 4696    let mut cx = EditorTestContext::new(cx).await;
 4697
 4698    // If all lower case -> upper case
 4699    cx.set_state(indoc! {"
 4700        «hello worldˇ»
 4701    "});
 4702    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4703    cx.assert_editor_state(indoc! {"
 4704        «HELLO WORLDˇ»
 4705    "});
 4706
 4707    // If all upper case -> lower case
 4708    cx.set_state(indoc! {"
 4709        «HELLO WORLDˇ»
 4710    "});
 4711    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4712    cx.assert_editor_state(indoc! {"
 4713        «hello worldˇ»
 4714    "});
 4715
 4716    // If any upper case characters are identified -> lower case
 4717    // This matches JetBrains IDEs
 4718    cx.set_state(indoc! {"
 4719        «hEllo worldˇ»
 4720    "});
 4721    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4722    cx.assert_editor_state(indoc! {"
 4723        «hello worldˇ»
 4724    "});
 4725}
 4726
 4727#[gpui::test]
 4728async fn test_convert_to_sentence_case(cx: &mut TestAppContext) {
 4729    init_test(cx, |_| {});
 4730
 4731    let mut cx = EditorTestContext::new(cx).await;
 4732
 4733    cx.set_state(indoc! {"
 4734        «implement-windows-supportˇ»
 4735    "});
 4736    cx.update_editor(|e, window, cx| {
 4737        e.convert_to_sentence_case(&ConvertToSentenceCase, window, cx)
 4738    });
 4739    cx.assert_editor_state(indoc! {"
 4740        «Implement windows supportˇ»
 4741    "});
 4742}
 4743
 4744#[gpui::test]
 4745async fn test_manipulate_text(cx: &mut TestAppContext) {
 4746    init_test(cx, |_| {});
 4747
 4748    let mut cx = EditorTestContext::new(cx).await;
 4749
 4750    // Test convert_to_upper_case()
 4751    cx.set_state(indoc! {"
 4752        «hello worldˇ»
 4753    "});
 4754    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4755    cx.assert_editor_state(indoc! {"
 4756        «HELLO WORLDˇ»
 4757    "});
 4758
 4759    // Test convert_to_lower_case()
 4760    cx.set_state(indoc! {"
 4761        «HELLO WORLDˇ»
 4762    "});
 4763    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 4764    cx.assert_editor_state(indoc! {"
 4765        «hello worldˇ»
 4766    "});
 4767
 4768    // Test multiple line, single selection case
 4769    cx.set_state(indoc! {"
 4770        «The quick brown
 4771        fox jumps over
 4772        the lazy dogˇ»
 4773    "});
 4774    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 4775    cx.assert_editor_state(indoc! {"
 4776        «The Quick Brown
 4777        Fox Jumps Over
 4778        The Lazy Dogˇ»
 4779    "});
 4780
 4781    // Test multiple line, single selection case
 4782    cx.set_state(indoc! {"
 4783        «The quick brown
 4784        fox jumps over
 4785        the lazy dogˇ»
 4786    "});
 4787    cx.update_editor(|e, window, cx| {
 4788        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 4789    });
 4790    cx.assert_editor_state(indoc! {"
 4791        «TheQuickBrown
 4792        FoxJumpsOver
 4793        TheLazyDogˇ»
 4794    "});
 4795
 4796    // From here on out, test more complex cases of manipulate_text()
 4797
 4798    // Test no selection case - should affect words cursors are in
 4799    // Cursor at beginning, middle, and end of word
 4800    cx.set_state(indoc! {"
 4801        ˇhello big beauˇtiful worldˇ
 4802    "});
 4803    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4804    cx.assert_editor_state(indoc! {"
 4805        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 4806    "});
 4807
 4808    // Test multiple selections on a single line and across multiple lines
 4809    cx.set_state(indoc! {"
 4810        «Theˇ» quick «brown
 4811        foxˇ» jumps «overˇ»
 4812        the «lazyˇ» dog
 4813    "});
 4814    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4815    cx.assert_editor_state(indoc! {"
 4816        «THEˇ» quick «BROWN
 4817        FOXˇ» jumps «OVERˇ»
 4818        the «LAZYˇ» dog
 4819    "});
 4820
 4821    // Test case where text length grows
 4822    cx.set_state(indoc! {"
 4823        «tschüߡ»
 4824    "});
 4825    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4826    cx.assert_editor_state(indoc! {"
 4827        «TSCHÜSSˇ»
 4828    "});
 4829
 4830    // Test to make sure we don't crash when text shrinks
 4831    cx.set_state(indoc! {"
 4832        aaa_bbbˇ
 4833    "});
 4834    cx.update_editor(|e, window, cx| {
 4835        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4836    });
 4837    cx.assert_editor_state(indoc! {"
 4838        «aaaBbbˇ»
 4839    "});
 4840
 4841    // Test to make sure we all aware of the fact that each word can grow and shrink
 4842    // Final selections should be aware of this fact
 4843    cx.set_state(indoc! {"
 4844        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 4845    "});
 4846    cx.update_editor(|e, window, cx| {
 4847        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4848    });
 4849    cx.assert_editor_state(indoc! {"
 4850        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 4851    "});
 4852
 4853    cx.set_state(indoc! {"
 4854        «hElLo, WoRld!ˇ»
 4855    "});
 4856    cx.update_editor(|e, window, cx| {
 4857        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 4858    });
 4859    cx.assert_editor_state(indoc! {"
 4860        «HeLlO, wOrLD!ˇ»
 4861    "});
 4862}
 4863
 4864#[gpui::test]
 4865fn test_duplicate_line(cx: &mut TestAppContext) {
 4866    init_test(cx, |_| {});
 4867
 4868    let editor = cx.add_window(|window, cx| {
 4869        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4870        build_editor(buffer, window, cx)
 4871    });
 4872    _ = editor.update(cx, |editor, window, cx| {
 4873        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4874            s.select_display_ranges([
 4875                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4876                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4877                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4878                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4879            ])
 4880        });
 4881        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4882        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4883        assert_eq!(
 4884            editor.selections.display_ranges(cx),
 4885            vec![
 4886                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4887                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 4888                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4889                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4890            ]
 4891        );
 4892    });
 4893
 4894    let editor = cx.add_window(|window, cx| {
 4895        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4896        build_editor(buffer, window, cx)
 4897    });
 4898    _ = editor.update(cx, |editor, window, cx| {
 4899        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4900            s.select_display_ranges([
 4901                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4902                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4903            ])
 4904        });
 4905        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4906        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4907        assert_eq!(
 4908            editor.selections.display_ranges(cx),
 4909            vec![
 4910                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 4911                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 4912            ]
 4913        );
 4914    });
 4915
 4916    // With `move_upwards` the selections stay in place, except for
 4917    // the lines inserted above them
 4918    let editor = cx.add_window(|window, cx| {
 4919        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4920        build_editor(buffer, window, cx)
 4921    });
 4922    _ = editor.update(cx, |editor, window, cx| {
 4923        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4924            s.select_display_ranges([
 4925                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4926                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4927                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4928                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4929            ])
 4930        });
 4931        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4932        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4933        assert_eq!(
 4934            editor.selections.display_ranges(cx),
 4935            vec![
 4936                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4937                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4938                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 4939                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4940            ]
 4941        );
 4942    });
 4943
 4944    let editor = cx.add_window(|window, cx| {
 4945        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4946        build_editor(buffer, window, cx)
 4947    });
 4948    _ = editor.update(cx, |editor, window, cx| {
 4949        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4950            s.select_display_ranges([
 4951                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4952                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4953            ])
 4954        });
 4955        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4956        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4957        assert_eq!(
 4958            editor.selections.display_ranges(cx),
 4959            vec![
 4960                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4961                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4962            ]
 4963        );
 4964    });
 4965
 4966    let editor = cx.add_window(|window, cx| {
 4967        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4968        build_editor(buffer, window, cx)
 4969    });
 4970    _ = editor.update(cx, |editor, window, cx| {
 4971        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4972            s.select_display_ranges([
 4973                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4974                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4975            ])
 4976        });
 4977        editor.duplicate_selection(&DuplicateSelection, window, cx);
 4978        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 4979        assert_eq!(
 4980            editor.selections.display_ranges(cx),
 4981            vec![
 4982                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4983                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 4984            ]
 4985        );
 4986    });
 4987}
 4988
 4989#[gpui::test]
 4990fn test_move_line_up_down(cx: &mut TestAppContext) {
 4991    init_test(cx, |_| {});
 4992
 4993    let editor = cx.add_window(|window, cx| {
 4994        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 4995        build_editor(buffer, window, cx)
 4996    });
 4997    _ = editor.update(cx, |editor, window, cx| {
 4998        editor.fold_creases(
 4999            vec![
 5000                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5001                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5002                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5003            ],
 5004            true,
 5005            window,
 5006            cx,
 5007        );
 5008        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5009            s.select_display_ranges([
 5010                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5011                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5012                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5013                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 5014            ])
 5015        });
 5016        assert_eq!(
 5017            editor.display_text(cx),
 5018            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 5019        );
 5020
 5021        editor.move_line_up(&MoveLineUp, window, cx);
 5022        assert_eq!(
 5023            editor.display_text(cx),
 5024            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 5025        );
 5026        assert_eq!(
 5027            editor.selections.display_ranges(cx),
 5028            vec![
 5029                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5030                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5031                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5032                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5033            ]
 5034        );
 5035    });
 5036
 5037    _ = editor.update(cx, |editor, window, cx| {
 5038        editor.move_line_down(&MoveLineDown, window, cx);
 5039        assert_eq!(
 5040            editor.display_text(cx),
 5041            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 5042        );
 5043        assert_eq!(
 5044            editor.selections.display_ranges(cx),
 5045            vec![
 5046                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5047                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5048                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5049                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5050            ]
 5051        );
 5052    });
 5053
 5054    _ = editor.update(cx, |editor, window, cx| {
 5055        editor.move_line_down(&MoveLineDown, window, cx);
 5056        assert_eq!(
 5057            editor.display_text(cx),
 5058            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 5059        );
 5060        assert_eq!(
 5061            editor.selections.display_ranges(cx),
 5062            vec![
 5063                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5064                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5065                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5066                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5067            ]
 5068        );
 5069    });
 5070
 5071    _ = editor.update(cx, |editor, window, cx| {
 5072        editor.move_line_up(&MoveLineUp, window, cx);
 5073        assert_eq!(
 5074            editor.display_text(cx),
 5075            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 5076        );
 5077        assert_eq!(
 5078            editor.selections.display_ranges(cx),
 5079            vec![
 5080                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5081                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5082                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5083                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5084            ]
 5085        );
 5086    });
 5087}
 5088
 5089#[gpui::test]
 5090fn test_move_line_up_selection_at_end_of_fold(cx: &mut TestAppContext) {
 5091    init_test(cx, |_| {});
 5092    let editor = cx.add_window(|window, cx| {
 5093        let buffer = MultiBuffer::build_simple("\n\n\n\n\n\naaaa\nbbbb\ncccc", cx);
 5094        build_editor(buffer, window, cx)
 5095    });
 5096    _ = editor.update(cx, |editor, window, cx| {
 5097        editor.fold_creases(
 5098            vec![Crease::simple(
 5099                Point::new(6, 4)..Point::new(7, 4),
 5100                FoldPlaceholder::test(),
 5101            )],
 5102            true,
 5103            window,
 5104            cx,
 5105        );
 5106        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5107            s.select_ranges([Point::new(7, 4)..Point::new(7, 4)])
 5108        });
 5109        assert_eq!(editor.display_text(cx), "\n\n\n\n\n\naaaa⋯\ncccc");
 5110        editor.move_line_up(&MoveLineUp, window, cx);
 5111        let buffer_text = editor.buffer.read(cx).snapshot(cx).text();
 5112        assert_eq!(buffer_text, "\n\n\n\n\naaaa\nbbbb\n\ncccc");
 5113    });
 5114}
 5115
 5116#[gpui::test]
 5117fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 5118    init_test(cx, |_| {});
 5119
 5120    let editor = cx.add_window(|window, cx| {
 5121        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5122        build_editor(buffer, window, cx)
 5123    });
 5124    _ = editor.update(cx, |editor, window, cx| {
 5125        let snapshot = editor.buffer.read(cx).snapshot(cx);
 5126        editor.insert_blocks(
 5127            [BlockProperties {
 5128                style: BlockStyle::Fixed,
 5129                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 5130                height: Some(1),
 5131                render: Arc::new(|_| div().into_any()),
 5132                priority: 0,
 5133            }],
 5134            Some(Autoscroll::fit()),
 5135            cx,
 5136        );
 5137        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5138            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 5139        });
 5140        editor.move_line_down(&MoveLineDown, window, cx);
 5141    });
 5142}
 5143
 5144#[gpui::test]
 5145async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 5146    init_test(cx, |_| {});
 5147
 5148    let mut cx = EditorTestContext::new(cx).await;
 5149    cx.set_state(
 5150        &"
 5151            ˇzero
 5152            one
 5153            two
 5154            three
 5155            four
 5156            five
 5157        "
 5158        .unindent(),
 5159    );
 5160
 5161    // Create a four-line block that replaces three lines of text.
 5162    cx.update_editor(|editor, window, cx| {
 5163        let snapshot = editor.snapshot(window, cx);
 5164        let snapshot = &snapshot.buffer_snapshot;
 5165        let placement = BlockPlacement::Replace(
 5166            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 5167        );
 5168        editor.insert_blocks(
 5169            [BlockProperties {
 5170                placement,
 5171                height: Some(4),
 5172                style: BlockStyle::Sticky,
 5173                render: Arc::new(|_| gpui::div().into_any_element()),
 5174                priority: 0,
 5175            }],
 5176            None,
 5177            cx,
 5178        );
 5179    });
 5180
 5181    // Move down so that the cursor touches the block.
 5182    cx.update_editor(|editor, window, cx| {
 5183        editor.move_down(&Default::default(), window, cx);
 5184    });
 5185    cx.assert_editor_state(
 5186        &"
 5187            zero
 5188            «one
 5189            two
 5190            threeˇ»
 5191            four
 5192            five
 5193        "
 5194        .unindent(),
 5195    );
 5196
 5197    // Move down past the block.
 5198    cx.update_editor(|editor, window, cx| {
 5199        editor.move_down(&Default::default(), window, cx);
 5200    });
 5201    cx.assert_editor_state(
 5202        &"
 5203            zero
 5204            one
 5205            two
 5206            three
 5207            ˇfour
 5208            five
 5209        "
 5210        .unindent(),
 5211    );
 5212}
 5213
 5214#[gpui::test]
 5215fn test_transpose(cx: &mut TestAppContext) {
 5216    init_test(cx, |_| {});
 5217
 5218    _ = cx.add_window(|window, cx| {
 5219        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 5220        editor.set_style(EditorStyle::default(), window, cx);
 5221        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5222            s.select_ranges([1..1])
 5223        });
 5224        editor.transpose(&Default::default(), window, cx);
 5225        assert_eq!(editor.text(cx), "bac");
 5226        assert_eq!(editor.selections.ranges(cx), [2..2]);
 5227
 5228        editor.transpose(&Default::default(), window, cx);
 5229        assert_eq!(editor.text(cx), "bca");
 5230        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5231
 5232        editor.transpose(&Default::default(), window, cx);
 5233        assert_eq!(editor.text(cx), "bac");
 5234        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5235
 5236        editor
 5237    });
 5238
 5239    _ = cx.add_window(|window, cx| {
 5240        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5241        editor.set_style(EditorStyle::default(), window, cx);
 5242        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5243            s.select_ranges([3..3])
 5244        });
 5245        editor.transpose(&Default::default(), window, cx);
 5246        assert_eq!(editor.text(cx), "acb\nde");
 5247        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5248
 5249        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5250            s.select_ranges([4..4])
 5251        });
 5252        editor.transpose(&Default::default(), window, cx);
 5253        assert_eq!(editor.text(cx), "acbd\ne");
 5254        assert_eq!(editor.selections.ranges(cx), [5..5]);
 5255
 5256        editor.transpose(&Default::default(), window, cx);
 5257        assert_eq!(editor.text(cx), "acbde\n");
 5258        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5259
 5260        editor.transpose(&Default::default(), window, cx);
 5261        assert_eq!(editor.text(cx), "acbd\ne");
 5262        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5263
 5264        editor
 5265    });
 5266
 5267    _ = cx.add_window(|window, cx| {
 5268        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5269        editor.set_style(EditorStyle::default(), window, cx);
 5270        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5271            s.select_ranges([1..1, 2..2, 4..4])
 5272        });
 5273        editor.transpose(&Default::default(), window, cx);
 5274        assert_eq!(editor.text(cx), "bacd\ne");
 5275        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 5276
 5277        editor.transpose(&Default::default(), window, cx);
 5278        assert_eq!(editor.text(cx), "bcade\n");
 5279        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 5280
 5281        editor.transpose(&Default::default(), window, cx);
 5282        assert_eq!(editor.text(cx), "bcda\ne");
 5283        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5284
 5285        editor.transpose(&Default::default(), window, cx);
 5286        assert_eq!(editor.text(cx), "bcade\n");
 5287        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5288
 5289        editor.transpose(&Default::default(), window, cx);
 5290        assert_eq!(editor.text(cx), "bcaed\n");
 5291        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 5292
 5293        editor
 5294    });
 5295
 5296    _ = cx.add_window(|window, cx| {
 5297        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 5298        editor.set_style(EditorStyle::default(), window, cx);
 5299        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5300            s.select_ranges([4..4])
 5301        });
 5302        editor.transpose(&Default::default(), window, cx);
 5303        assert_eq!(editor.text(cx), "🏀🍐✋");
 5304        assert_eq!(editor.selections.ranges(cx), [8..8]);
 5305
 5306        editor.transpose(&Default::default(), window, cx);
 5307        assert_eq!(editor.text(cx), "🏀✋🍐");
 5308        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5309
 5310        editor.transpose(&Default::default(), window, cx);
 5311        assert_eq!(editor.text(cx), "🏀🍐✋");
 5312        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5313
 5314        editor
 5315    });
 5316}
 5317
 5318#[gpui::test]
 5319async fn test_rewrap(cx: &mut TestAppContext) {
 5320    init_test(cx, |settings| {
 5321        settings.languages.0.extend([
 5322            (
 5323                "Markdown".into(),
 5324                LanguageSettingsContent {
 5325                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5326                    preferred_line_length: Some(40),
 5327                    ..Default::default()
 5328                },
 5329            ),
 5330            (
 5331                "Plain Text".into(),
 5332                LanguageSettingsContent {
 5333                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5334                    preferred_line_length: Some(40),
 5335                    ..Default::default()
 5336                },
 5337            ),
 5338            (
 5339                "C++".into(),
 5340                LanguageSettingsContent {
 5341                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5342                    preferred_line_length: Some(40),
 5343                    ..Default::default()
 5344                },
 5345            ),
 5346            (
 5347                "Python".into(),
 5348                LanguageSettingsContent {
 5349                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5350                    preferred_line_length: Some(40),
 5351                    ..Default::default()
 5352                },
 5353            ),
 5354            (
 5355                "Rust".into(),
 5356                LanguageSettingsContent {
 5357                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5358                    preferred_line_length: Some(40),
 5359                    ..Default::default()
 5360                },
 5361            ),
 5362        ])
 5363    });
 5364
 5365    let mut cx = EditorTestContext::new(cx).await;
 5366
 5367    let cpp_language = Arc::new(Language::new(
 5368        LanguageConfig {
 5369            name: "C++".into(),
 5370            line_comments: vec!["// ".into()],
 5371            ..LanguageConfig::default()
 5372        },
 5373        None,
 5374    ));
 5375    let python_language = Arc::new(Language::new(
 5376        LanguageConfig {
 5377            name: "Python".into(),
 5378            line_comments: vec!["# ".into()],
 5379            ..LanguageConfig::default()
 5380        },
 5381        None,
 5382    ));
 5383    let markdown_language = Arc::new(Language::new(
 5384        LanguageConfig {
 5385            name: "Markdown".into(),
 5386            rewrap_prefixes: vec![
 5387                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 5388                regex::Regex::new("[-*+]\\s+").unwrap(),
 5389            ],
 5390            ..LanguageConfig::default()
 5391        },
 5392        None,
 5393    ));
 5394    let rust_language = Arc::new(Language::new(
 5395        LanguageConfig {
 5396            name: "Rust".into(),
 5397            line_comments: vec!["// ".into(), "/// ".into()],
 5398            ..LanguageConfig::default()
 5399        },
 5400        Some(tree_sitter_rust::LANGUAGE.into()),
 5401    ));
 5402
 5403    let plaintext_language = Arc::new(Language::new(
 5404        LanguageConfig {
 5405            name: "Plain Text".into(),
 5406            ..LanguageConfig::default()
 5407        },
 5408        None,
 5409    ));
 5410
 5411    // Test basic rewrapping of a long line with a cursor
 5412    assert_rewrap(
 5413        indoc! {"
 5414            // ˇThis is a long comment that needs to be wrapped.
 5415        "},
 5416        indoc! {"
 5417            // ˇThis is a long comment that needs to
 5418            // be wrapped.
 5419        "},
 5420        cpp_language.clone(),
 5421        &mut cx,
 5422    );
 5423
 5424    // Test rewrapping a full selection
 5425    assert_rewrap(
 5426        indoc! {"
 5427            «// This selected long comment needs to be wrapped.ˇ»"
 5428        },
 5429        indoc! {"
 5430            «// This selected long comment needs to
 5431            // be wrapped.ˇ»"
 5432        },
 5433        cpp_language.clone(),
 5434        &mut cx,
 5435    );
 5436
 5437    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 5438    assert_rewrap(
 5439        indoc! {"
 5440            // ˇThis is the first line.
 5441            // Thisˇ is the second line.
 5442            // This is the thirdˇ line, all part of one paragraph.
 5443         "},
 5444        indoc! {"
 5445            // ˇThis is the first line. Thisˇ is the
 5446            // second line. This is the thirdˇ line,
 5447            // all part of one paragraph.
 5448         "},
 5449        cpp_language.clone(),
 5450        &mut cx,
 5451    );
 5452
 5453    // Test multiple cursors in different paragraphs trigger separate rewraps
 5454    assert_rewrap(
 5455        indoc! {"
 5456            // ˇThis is the first paragraph, first line.
 5457            // ˇThis is the first paragraph, second line.
 5458
 5459            // ˇThis is the second paragraph, first line.
 5460            // ˇThis is the second paragraph, second line.
 5461        "},
 5462        indoc! {"
 5463            // ˇThis is the first paragraph, first
 5464            // line. ˇThis is the first paragraph,
 5465            // second line.
 5466
 5467            // ˇThis is the second paragraph, first
 5468            // line. ˇThis is the second paragraph,
 5469            // second line.
 5470        "},
 5471        cpp_language.clone(),
 5472        &mut cx,
 5473    );
 5474
 5475    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 5476    assert_rewrap(
 5477        indoc! {"
 5478            «// A regular long long comment to be wrapped.
 5479            /// A documentation long comment to be wrapped.ˇ»
 5480          "},
 5481        indoc! {"
 5482            «// A regular long long comment to be
 5483            // wrapped.
 5484            /// A documentation long comment to be
 5485            /// wrapped.ˇ»
 5486          "},
 5487        rust_language.clone(),
 5488        &mut cx,
 5489    );
 5490
 5491    // Test that change in indentation level trigger seperate rewraps
 5492    assert_rewrap(
 5493        indoc! {"
 5494            fn foo() {
 5495                «// This is a long comment at the base indent.
 5496                    // This is a long comment at the next indent.ˇ»
 5497            }
 5498        "},
 5499        indoc! {"
 5500            fn foo() {
 5501                «// This is a long comment at the
 5502                // base indent.
 5503                    // This is a long comment at the
 5504                    // next indent.ˇ»
 5505            }
 5506        "},
 5507        rust_language.clone(),
 5508        &mut cx,
 5509    );
 5510
 5511    // Test that different comment prefix characters (e.g., '#') are handled correctly
 5512    assert_rewrap(
 5513        indoc! {"
 5514            # ˇThis is a long comment using a pound sign.
 5515        "},
 5516        indoc! {"
 5517            # ˇThis is a long comment using a pound
 5518            # sign.
 5519        "},
 5520        python_language.clone(),
 5521        &mut cx,
 5522    );
 5523
 5524    // Test rewrapping only affects comments, not code even when selected
 5525    assert_rewrap(
 5526        indoc! {"
 5527            «/// This doc comment is long and should be wrapped.
 5528            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 5529        "},
 5530        indoc! {"
 5531            «/// This doc comment is long and should
 5532            /// be wrapped.
 5533            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 5534        "},
 5535        rust_language.clone(),
 5536        &mut cx,
 5537    );
 5538
 5539    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 5540    assert_rewrap(
 5541        indoc! {"
 5542            # Header
 5543
 5544            A long long long line of markdown text to wrap.ˇ
 5545         "},
 5546        indoc! {"
 5547            # Header
 5548
 5549            A long long long line of markdown text
 5550            to wrap.ˇ
 5551         "},
 5552        markdown_language.clone(),
 5553        &mut cx,
 5554    );
 5555
 5556    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 5557    assert_rewrap(
 5558        indoc! {"
 5559            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 5560            2. This is a numbered list item that is very long and needs to be wrapped properly.
 5561            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 5562        "},
 5563        indoc! {"
 5564            «1. This is a numbered list item that is
 5565               very long and needs to be wrapped
 5566               properly.
 5567            2. This is a numbered list item that is
 5568               very long and needs to be wrapped
 5569               properly.
 5570            - This is an unordered list item that is
 5571              also very long and should not merge
 5572              with the numbered item.ˇ»
 5573        "},
 5574        markdown_language.clone(),
 5575        &mut cx,
 5576    );
 5577
 5578    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 5579    assert_rewrap(
 5580        indoc! {"
 5581            «1. This is a numbered list item that is
 5582            very long and needs to be wrapped
 5583            properly.
 5584            2. This is a numbered list item that is
 5585            very long and needs to be wrapped
 5586            properly.
 5587            - This is an unordered list item that is
 5588            also very long and should not merge with
 5589            the numbered item.ˇ»
 5590        "},
 5591        indoc! {"
 5592            «1. This is a numbered list item that is
 5593               very long and needs to be wrapped
 5594               properly.
 5595            2. This is a numbered list item that is
 5596               very long and needs to be wrapped
 5597               properly.
 5598            - This is an unordered list item that is
 5599              also very long and should not merge
 5600              with the numbered item.ˇ»
 5601        "},
 5602        markdown_language.clone(),
 5603        &mut cx,
 5604    );
 5605
 5606    // Test that rewrapping maintain indents even when they already exists.
 5607    assert_rewrap(
 5608        indoc! {"
 5609            «1. This is a numbered list
 5610               item that is very long and needs to be wrapped properly.
 5611            2. This is a numbered list
 5612               item that is very long and needs to be wrapped properly.
 5613            - This is an unordered list item that is also very long and
 5614              should not merge with the numbered item.ˇ»
 5615        "},
 5616        indoc! {"
 5617            «1. This is a numbered list item that is
 5618               very long and needs to be wrapped
 5619               properly.
 5620            2. This is a numbered list item that is
 5621               very long and needs to be wrapped
 5622               properly.
 5623            - This is an unordered list item that is
 5624              also very long and should not merge
 5625              with the numbered item.ˇ»
 5626        "},
 5627        markdown_language.clone(),
 5628        &mut cx,
 5629    );
 5630
 5631    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 5632    assert_rewrap(
 5633        indoc! {"
 5634            ˇThis is a very long line of plain text that will be wrapped.
 5635        "},
 5636        indoc! {"
 5637            ˇThis is a very long line of plain text
 5638            that will be wrapped.
 5639        "},
 5640        plaintext_language.clone(),
 5641        &mut cx,
 5642    );
 5643
 5644    // Test that non-commented code acts as a paragraph boundary within a selection
 5645    assert_rewrap(
 5646        indoc! {"
 5647               «// This is the first long comment block to be wrapped.
 5648               fn my_func(a: u32);
 5649               // This is the second long comment block to be wrapped.ˇ»
 5650           "},
 5651        indoc! {"
 5652               «// This is the first long comment block
 5653               // to be wrapped.
 5654               fn my_func(a: u32);
 5655               // This is the second long comment block
 5656               // to be wrapped.ˇ»
 5657           "},
 5658        rust_language.clone(),
 5659        &mut cx,
 5660    );
 5661
 5662    // Test rewrapping multiple selections, including ones with blank lines or tabs
 5663    assert_rewrap(
 5664        indoc! {"
 5665            «ˇThis is a very long line that will be wrapped.
 5666
 5667            This is another paragraph in the same selection.»
 5668
 5669            «\tThis is a very long indented line that will be wrapped.ˇ»
 5670         "},
 5671        indoc! {"
 5672            «ˇThis is a very long line that will be
 5673            wrapped.
 5674
 5675            This is another paragraph in the same
 5676            selection.»
 5677
 5678            «\tThis is a very long indented line
 5679            \tthat will be wrapped.ˇ»
 5680         "},
 5681        plaintext_language.clone(),
 5682        &mut cx,
 5683    );
 5684
 5685    // Test that an empty comment line acts as a paragraph boundary
 5686    assert_rewrap(
 5687        indoc! {"
 5688            // ˇThis is a long comment that will be wrapped.
 5689            //
 5690            // And this is another long comment that will also be wrapped.ˇ
 5691         "},
 5692        indoc! {"
 5693            // ˇThis is a long comment that will be
 5694            // wrapped.
 5695            //
 5696            // And this is another long comment that
 5697            // will also be wrapped.ˇ
 5698         "},
 5699        cpp_language,
 5700        &mut cx,
 5701    );
 5702
 5703    #[track_caller]
 5704    fn assert_rewrap(
 5705        unwrapped_text: &str,
 5706        wrapped_text: &str,
 5707        language: Arc<Language>,
 5708        cx: &mut EditorTestContext,
 5709    ) {
 5710        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 5711        cx.set_state(unwrapped_text);
 5712        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 5713        cx.assert_editor_state(wrapped_text);
 5714    }
 5715}
 5716
 5717#[gpui::test]
 5718async fn test_hard_wrap(cx: &mut TestAppContext) {
 5719    init_test(cx, |_| {});
 5720    let mut cx = EditorTestContext::new(cx).await;
 5721
 5722    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 5723    cx.update_editor(|editor, _, cx| {
 5724        editor.set_hard_wrap(Some(14), cx);
 5725    });
 5726
 5727    cx.set_state(indoc!(
 5728        "
 5729        one two three ˇ
 5730        "
 5731    ));
 5732    cx.simulate_input("four");
 5733    cx.run_until_parked();
 5734
 5735    cx.assert_editor_state(indoc!(
 5736        "
 5737        one two three
 5738        fourˇ
 5739        "
 5740    ));
 5741
 5742    cx.update_editor(|editor, window, cx| {
 5743        editor.newline(&Default::default(), window, cx);
 5744    });
 5745    cx.run_until_parked();
 5746    cx.assert_editor_state(indoc!(
 5747        "
 5748        one two three
 5749        four
 5750        ˇ
 5751        "
 5752    ));
 5753
 5754    cx.simulate_input("five");
 5755    cx.run_until_parked();
 5756    cx.assert_editor_state(indoc!(
 5757        "
 5758        one two three
 5759        four
 5760        fiveˇ
 5761        "
 5762    ));
 5763
 5764    cx.update_editor(|editor, window, cx| {
 5765        editor.newline(&Default::default(), window, cx);
 5766    });
 5767    cx.run_until_parked();
 5768    cx.simulate_input("# ");
 5769    cx.run_until_parked();
 5770    cx.assert_editor_state(indoc!(
 5771        "
 5772        one two three
 5773        four
 5774        five
 5775        # ˇ
 5776        "
 5777    ));
 5778
 5779    cx.update_editor(|editor, window, cx| {
 5780        editor.newline(&Default::default(), window, cx);
 5781    });
 5782    cx.run_until_parked();
 5783    cx.assert_editor_state(indoc!(
 5784        "
 5785        one two three
 5786        four
 5787        five
 5788        #\x20
 5789 5790        "
 5791    ));
 5792
 5793    cx.simulate_input(" 6");
 5794    cx.run_until_parked();
 5795    cx.assert_editor_state(indoc!(
 5796        "
 5797        one two three
 5798        four
 5799        five
 5800        #
 5801        # 6ˇ
 5802        "
 5803    ));
 5804}
 5805
 5806#[gpui::test]
 5807async fn test_clipboard(cx: &mut TestAppContext) {
 5808    init_test(cx, |_| {});
 5809
 5810    let mut cx = EditorTestContext::new(cx).await;
 5811
 5812    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 5813    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5814    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 5815
 5816    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 5817    cx.set_state("two ˇfour ˇsix ˇ");
 5818    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5819    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 5820
 5821    // Paste again but with only two cursors. Since the number of cursors doesn't
 5822    // match the number of slices in the clipboard, the entire clipboard text
 5823    // is pasted at each cursor.
 5824    cx.set_state("ˇtwo one✅ four three six five ˇ");
 5825    cx.update_editor(|e, window, cx| {
 5826        e.handle_input("( ", window, cx);
 5827        e.paste(&Paste, window, cx);
 5828        e.handle_input(") ", window, cx);
 5829    });
 5830    cx.assert_editor_state(
 5831        &([
 5832            "( one✅ ",
 5833            "three ",
 5834            "five ) ˇtwo one✅ four three six five ( one✅ ",
 5835            "three ",
 5836            "five ) ˇ",
 5837        ]
 5838        .join("\n")),
 5839    );
 5840
 5841    // Cut with three selections, one of which is full-line.
 5842    cx.set_state(indoc! {"
 5843        1«2ˇ»3
 5844        4ˇ567
 5845        «8ˇ»9"});
 5846    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5847    cx.assert_editor_state(indoc! {"
 5848        1ˇ3
 5849        ˇ9"});
 5850
 5851    // Paste with three selections, noticing how the copied selection that was full-line
 5852    // gets inserted before the second cursor.
 5853    cx.set_state(indoc! {"
 5854        1ˇ3
 5855 5856        «oˇ»ne"});
 5857    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5858    cx.assert_editor_state(indoc! {"
 5859        12ˇ3
 5860        4567
 5861 5862        8ˇne"});
 5863
 5864    // Copy with a single cursor only, which writes the whole line into the clipboard.
 5865    cx.set_state(indoc! {"
 5866        The quick brown
 5867        fox juˇmps over
 5868        the lazy dog"});
 5869    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5870    assert_eq!(
 5871        cx.read_from_clipboard()
 5872            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5873        Some("fox jumps over\n".to_string())
 5874    );
 5875
 5876    // Paste with three selections, noticing how the copied full-line selection is inserted
 5877    // before the empty selections but replaces the selection that is non-empty.
 5878    cx.set_state(indoc! {"
 5879        Tˇhe quick brown
 5880        «foˇ»x jumps over
 5881        tˇhe lazy dog"});
 5882    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5883    cx.assert_editor_state(indoc! {"
 5884        fox jumps over
 5885        Tˇhe quick brown
 5886        fox jumps over
 5887        ˇx jumps over
 5888        fox jumps over
 5889        tˇhe lazy dog"});
 5890}
 5891
 5892#[gpui::test]
 5893async fn test_copy_trim(cx: &mut TestAppContext) {
 5894    init_test(cx, |_| {});
 5895
 5896    let mut cx = EditorTestContext::new(cx).await;
 5897    cx.set_state(
 5898        r#"            «for selection in selections.iter() {
 5899            let mut start = selection.start;
 5900            let mut end = selection.end;
 5901            let is_entire_line = selection.is_empty();
 5902            if is_entire_line {
 5903                start = Point::new(start.row, 0);ˇ»
 5904                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5905            }
 5906        "#,
 5907    );
 5908    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5909    assert_eq!(
 5910        cx.read_from_clipboard()
 5911            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5912        Some(
 5913            "for selection in selections.iter() {
 5914            let mut start = selection.start;
 5915            let mut end = selection.end;
 5916            let is_entire_line = selection.is_empty();
 5917            if is_entire_line {
 5918                start = Point::new(start.row, 0);"
 5919                .to_string()
 5920        ),
 5921        "Regular copying preserves all indentation selected",
 5922    );
 5923    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5924    assert_eq!(
 5925        cx.read_from_clipboard()
 5926            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5927        Some(
 5928            "for selection in selections.iter() {
 5929let mut start = selection.start;
 5930let mut end = selection.end;
 5931let is_entire_line = selection.is_empty();
 5932if is_entire_line {
 5933    start = Point::new(start.row, 0);"
 5934                .to_string()
 5935        ),
 5936        "Copying with stripping should strip all leading whitespaces"
 5937    );
 5938
 5939    cx.set_state(
 5940        r#"       «     for selection in selections.iter() {
 5941            let mut start = selection.start;
 5942            let mut end = selection.end;
 5943            let is_entire_line = selection.is_empty();
 5944            if is_entire_line {
 5945                start = Point::new(start.row, 0);ˇ»
 5946                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5947            }
 5948        "#,
 5949    );
 5950    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5951    assert_eq!(
 5952        cx.read_from_clipboard()
 5953            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5954        Some(
 5955            "     for selection in selections.iter() {
 5956            let mut start = selection.start;
 5957            let mut end = selection.end;
 5958            let is_entire_line = selection.is_empty();
 5959            if is_entire_line {
 5960                start = Point::new(start.row, 0);"
 5961                .to_string()
 5962        ),
 5963        "Regular copying preserves all indentation selected",
 5964    );
 5965    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5966    assert_eq!(
 5967        cx.read_from_clipboard()
 5968            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5969        Some(
 5970            "for selection in selections.iter() {
 5971let mut start = selection.start;
 5972let mut end = selection.end;
 5973let is_entire_line = selection.is_empty();
 5974if is_entire_line {
 5975    start = Point::new(start.row, 0);"
 5976                .to_string()
 5977        ),
 5978        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 5979    );
 5980
 5981    cx.set_state(
 5982        r#"       «ˇ     for selection in selections.iter() {
 5983            let mut start = selection.start;
 5984            let mut end = selection.end;
 5985            let is_entire_line = selection.is_empty();
 5986            if is_entire_line {
 5987                start = Point::new(start.row, 0);»
 5988                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5989            }
 5990        "#,
 5991    );
 5992    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5993    assert_eq!(
 5994        cx.read_from_clipboard()
 5995            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5996        Some(
 5997            "     for selection in selections.iter() {
 5998            let mut start = selection.start;
 5999            let mut end = selection.end;
 6000            let is_entire_line = selection.is_empty();
 6001            if is_entire_line {
 6002                start = Point::new(start.row, 0);"
 6003                .to_string()
 6004        ),
 6005        "Regular copying for reverse selection works the same",
 6006    );
 6007    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6008    assert_eq!(
 6009        cx.read_from_clipboard()
 6010            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6011        Some(
 6012            "for selection in selections.iter() {
 6013let mut start = selection.start;
 6014let mut end = selection.end;
 6015let is_entire_line = selection.is_empty();
 6016if is_entire_line {
 6017    start = Point::new(start.row, 0);"
 6018                .to_string()
 6019        ),
 6020        "Copying with stripping for reverse selection works the same"
 6021    );
 6022
 6023    cx.set_state(
 6024        r#"            for selection «in selections.iter() {
 6025            let mut start = selection.start;
 6026            let mut end = selection.end;
 6027            let is_entire_line = selection.is_empty();
 6028            if is_entire_line {
 6029                start = Point::new(start.row, 0);ˇ»
 6030                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6031            }
 6032        "#,
 6033    );
 6034    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6035    assert_eq!(
 6036        cx.read_from_clipboard()
 6037            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6038        Some(
 6039            "in selections.iter() {
 6040            let mut start = selection.start;
 6041            let mut end = selection.end;
 6042            let is_entire_line = selection.is_empty();
 6043            if is_entire_line {
 6044                start = Point::new(start.row, 0);"
 6045                .to_string()
 6046        ),
 6047        "When selecting past the indent, the copying works as usual",
 6048    );
 6049    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6050    assert_eq!(
 6051        cx.read_from_clipboard()
 6052            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6053        Some(
 6054            "in selections.iter() {
 6055            let mut start = selection.start;
 6056            let mut end = selection.end;
 6057            let is_entire_line = selection.is_empty();
 6058            if is_entire_line {
 6059                start = Point::new(start.row, 0);"
 6060                .to_string()
 6061        ),
 6062        "When selecting past the indent, nothing is trimmed"
 6063    );
 6064
 6065    cx.set_state(
 6066        r#"            «for selection in selections.iter() {
 6067            let mut start = selection.start;
 6068
 6069            let mut end = selection.end;
 6070            let is_entire_line = selection.is_empty();
 6071            if is_entire_line {
 6072                start = Point::new(start.row, 0);
 6073ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6074            }
 6075        "#,
 6076    );
 6077    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6078    assert_eq!(
 6079        cx.read_from_clipboard()
 6080            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6081        Some(
 6082            "for selection in selections.iter() {
 6083let mut start = selection.start;
 6084
 6085let mut end = selection.end;
 6086let is_entire_line = selection.is_empty();
 6087if is_entire_line {
 6088    start = Point::new(start.row, 0);
 6089"
 6090            .to_string()
 6091        ),
 6092        "Copying with stripping should ignore empty lines"
 6093    );
 6094}
 6095
 6096#[gpui::test]
 6097async fn test_paste_multiline(cx: &mut TestAppContext) {
 6098    init_test(cx, |_| {});
 6099
 6100    let mut cx = EditorTestContext::new(cx).await;
 6101    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 6102
 6103    // Cut an indented block, without the leading whitespace.
 6104    cx.set_state(indoc! {"
 6105        const a: B = (
 6106            c(),
 6107            «d(
 6108                e,
 6109                f
 6110            )ˇ»
 6111        );
 6112    "});
 6113    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6114    cx.assert_editor_state(indoc! {"
 6115        const a: B = (
 6116            c(),
 6117            ˇ
 6118        );
 6119    "});
 6120
 6121    // Paste it at the same position.
 6122    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6123    cx.assert_editor_state(indoc! {"
 6124        const a: B = (
 6125            c(),
 6126            d(
 6127                e,
 6128                f
 6129 6130        );
 6131    "});
 6132
 6133    // Paste it at a line with a lower indent level.
 6134    cx.set_state(indoc! {"
 6135        ˇ
 6136        const a: B = (
 6137            c(),
 6138        );
 6139    "});
 6140    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6141    cx.assert_editor_state(indoc! {"
 6142        d(
 6143            e,
 6144            f
 6145 6146        const a: B = (
 6147            c(),
 6148        );
 6149    "});
 6150
 6151    // Cut an indented block, with the leading whitespace.
 6152    cx.set_state(indoc! {"
 6153        const a: B = (
 6154            c(),
 6155        «    d(
 6156                e,
 6157                f
 6158            )
 6159        ˇ»);
 6160    "});
 6161    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6162    cx.assert_editor_state(indoc! {"
 6163        const a: B = (
 6164            c(),
 6165        ˇ);
 6166    "});
 6167
 6168    // Paste it at the same position.
 6169    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6170    cx.assert_editor_state(indoc! {"
 6171        const a: B = (
 6172            c(),
 6173            d(
 6174                e,
 6175                f
 6176            )
 6177        ˇ);
 6178    "});
 6179
 6180    // Paste it at a line with a higher indent level.
 6181    cx.set_state(indoc! {"
 6182        const a: B = (
 6183            c(),
 6184            d(
 6185                e,
 6186 6187            )
 6188        );
 6189    "});
 6190    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6191    cx.assert_editor_state(indoc! {"
 6192        const a: B = (
 6193            c(),
 6194            d(
 6195                e,
 6196                f    d(
 6197                    e,
 6198                    f
 6199                )
 6200        ˇ
 6201            )
 6202        );
 6203    "});
 6204
 6205    // Copy an indented block, starting mid-line
 6206    cx.set_state(indoc! {"
 6207        const a: B = (
 6208            c(),
 6209            somethin«g(
 6210                e,
 6211                f
 6212            )ˇ»
 6213        );
 6214    "});
 6215    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6216
 6217    // Paste it on a line with a lower indent level
 6218    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 6219    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6220    cx.assert_editor_state(indoc! {"
 6221        const a: B = (
 6222            c(),
 6223            something(
 6224                e,
 6225                f
 6226            )
 6227        );
 6228        g(
 6229            e,
 6230            f
 6231"});
 6232}
 6233
 6234#[gpui::test]
 6235async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 6236    init_test(cx, |_| {});
 6237
 6238    cx.write_to_clipboard(ClipboardItem::new_string(
 6239        "    d(\n        e\n    );\n".into(),
 6240    ));
 6241
 6242    let mut cx = EditorTestContext::new(cx).await;
 6243    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 6244
 6245    cx.set_state(indoc! {"
 6246        fn a() {
 6247            b();
 6248            if c() {
 6249                ˇ
 6250            }
 6251        }
 6252    "});
 6253
 6254    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6255    cx.assert_editor_state(indoc! {"
 6256        fn a() {
 6257            b();
 6258            if c() {
 6259                d(
 6260                    e
 6261                );
 6262        ˇ
 6263            }
 6264        }
 6265    "});
 6266
 6267    cx.set_state(indoc! {"
 6268        fn a() {
 6269            b();
 6270            ˇ
 6271        }
 6272    "});
 6273
 6274    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6275    cx.assert_editor_state(indoc! {"
 6276        fn a() {
 6277            b();
 6278            d(
 6279                e
 6280            );
 6281        ˇ
 6282        }
 6283    "});
 6284}
 6285
 6286#[gpui::test]
 6287fn test_select_all(cx: &mut TestAppContext) {
 6288    init_test(cx, |_| {});
 6289
 6290    let editor = cx.add_window(|window, cx| {
 6291        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 6292        build_editor(buffer, window, cx)
 6293    });
 6294    _ = editor.update(cx, |editor, window, cx| {
 6295        editor.select_all(&SelectAll, window, cx);
 6296        assert_eq!(
 6297            editor.selections.display_ranges(cx),
 6298            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 6299        );
 6300    });
 6301}
 6302
 6303#[gpui::test]
 6304fn test_select_line(cx: &mut TestAppContext) {
 6305    init_test(cx, |_| {});
 6306
 6307    let editor = cx.add_window(|window, cx| {
 6308        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 6309        build_editor(buffer, window, cx)
 6310    });
 6311    _ = editor.update(cx, |editor, window, cx| {
 6312        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6313            s.select_display_ranges([
 6314                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6315                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6316                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6317                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 6318            ])
 6319        });
 6320        editor.select_line(&SelectLine, window, cx);
 6321        assert_eq!(
 6322            editor.selections.display_ranges(cx),
 6323            vec![
 6324                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 6325                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 6326            ]
 6327        );
 6328    });
 6329
 6330    _ = editor.update(cx, |editor, window, cx| {
 6331        editor.select_line(&SelectLine, window, cx);
 6332        assert_eq!(
 6333            editor.selections.display_ranges(cx),
 6334            vec![
 6335                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 6336                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 6337            ]
 6338        );
 6339    });
 6340
 6341    _ = editor.update(cx, |editor, window, cx| {
 6342        editor.select_line(&SelectLine, window, cx);
 6343        assert_eq!(
 6344            editor.selections.display_ranges(cx),
 6345            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 6346        );
 6347    });
 6348}
 6349
 6350#[gpui::test]
 6351async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 6352    init_test(cx, |_| {});
 6353    let mut cx = EditorTestContext::new(cx).await;
 6354
 6355    #[track_caller]
 6356    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 6357        cx.set_state(initial_state);
 6358        cx.update_editor(|e, window, cx| {
 6359            e.split_selection_into_lines(&SplitSelectionIntoLines, window, cx)
 6360        });
 6361        cx.assert_editor_state(expected_state);
 6362    }
 6363
 6364    // Selection starts and ends at the middle of lines, left-to-right
 6365    test(
 6366        &mut cx,
 6367        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 6368        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 6369    );
 6370    // Same thing, right-to-left
 6371    test(
 6372        &mut cx,
 6373        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 6374        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 6375    );
 6376
 6377    // Whole buffer, left-to-right, last line *doesn't* end with newline
 6378    test(
 6379        &mut cx,
 6380        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 6381        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 6382    );
 6383    // Same thing, right-to-left
 6384    test(
 6385        &mut cx,
 6386        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 6387        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 6388    );
 6389
 6390    // Whole buffer, left-to-right, last line ends with newline
 6391    test(
 6392        &mut cx,
 6393        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 6394        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 6395    );
 6396    // Same thing, right-to-left
 6397    test(
 6398        &mut cx,
 6399        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 6400        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 6401    );
 6402
 6403    // Starts at the end of a line, ends at the start of another
 6404    test(
 6405        &mut cx,
 6406        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 6407        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 6408    );
 6409}
 6410
 6411#[gpui::test]
 6412async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 6413    init_test(cx, |_| {});
 6414
 6415    let editor = cx.add_window(|window, cx| {
 6416        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 6417        build_editor(buffer, window, cx)
 6418    });
 6419
 6420    // setup
 6421    _ = editor.update(cx, |editor, window, cx| {
 6422        editor.fold_creases(
 6423            vec![
 6424                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 6425                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 6426                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 6427            ],
 6428            true,
 6429            window,
 6430            cx,
 6431        );
 6432        assert_eq!(
 6433            editor.display_text(cx),
 6434            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 6435        );
 6436    });
 6437
 6438    _ = editor.update(cx, |editor, window, cx| {
 6439        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6440            s.select_display_ranges([
 6441                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6442                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6443                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6444                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 6445            ])
 6446        });
 6447        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 6448        assert_eq!(
 6449            editor.display_text(cx),
 6450            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 6451        );
 6452    });
 6453    EditorTestContext::for_editor(editor, cx)
 6454        .await
 6455        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 6456
 6457    _ = editor.update(cx, |editor, window, cx| {
 6458        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6459            s.select_display_ranges([
 6460                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 6461            ])
 6462        });
 6463        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 6464        assert_eq!(
 6465            editor.display_text(cx),
 6466            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 6467        );
 6468        assert_eq!(
 6469            editor.selections.display_ranges(cx),
 6470            [
 6471                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 6472                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 6473                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 6474                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 6475                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 6476                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 6477                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 6478            ]
 6479        );
 6480    });
 6481    EditorTestContext::for_editor(editor, cx)
 6482        .await
 6483        .assert_editor_state(
 6484            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 6485        );
 6486}
 6487
 6488#[gpui::test]
 6489async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 6490    init_test(cx, |_| {});
 6491
 6492    let mut cx = EditorTestContext::new(cx).await;
 6493
 6494    cx.set_state(indoc!(
 6495        r#"abc
 6496           defˇghi
 6497
 6498           jk
 6499           nlmo
 6500           "#
 6501    ));
 6502
 6503    cx.update_editor(|editor, window, cx| {
 6504        editor.add_selection_above(&Default::default(), window, cx);
 6505    });
 6506
 6507    cx.assert_editor_state(indoc!(
 6508        r#"abcˇ
 6509           defˇghi
 6510
 6511           jk
 6512           nlmo
 6513           "#
 6514    ));
 6515
 6516    cx.update_editor(|editor, window, cx| {
 6517        editor.add_selection_above(&Default::default(), window, cx);
 6518    });
 6519
 6520    cx.assert_editor_state(indoc!(
 6521        r#"abcˇ
 6522            defˇghi
 6523
 6524            jk
 6525            nlmo
 6526            "#
 6527    ));
 6528
 6529    cx.update_editor(|editor, window, cx| {
 6530        editor.add_selection_below(&Default::default(), window, cx);
 6531    });
 6532
 6533    cx.assert_editor_state(indoc!(
 6534        r#"abc
 6535           defˇghi
 6536
 6537           jk
 6538           nlmo
 6539           "#
 6540    ));
 6541
 6542    cx.update_editor(|editor, window, cx| {
 6543        editor.undo_selection(&Default::default(), window, cx);
 6544    });
 6545
 6546    cx.assert_editor_state(indoc!(
 6547        r#"abcˇ
 6548           defˇghi
 6549
 6550           jk
 6551           nlmo
 6552           "#
 6553    ));
 6554
 6555    cx.update_editor(|editor, window, cx| {
 6556        editor.redo_selection(&Default::default(), window, cx);
 6557    });
 6558
 6559    cx.assert_editor_state(indoc!(
 6560        r#"abc
 6561           defˇghi
 6562
 6563           jk
 6564           nlmo
 6565           "#
 6566    ));
 6567
 6568    cx.update_editor(|editor, window, cx| {
 6569        editor.add_selection_below(&Default::default(), window, cx);
 6570    });
 6571
 6572    cx.assert_editor_state(indoc!(
 6573        r#"abc
 6574           defˇghi
 6575           ˇ
 6576           jk
 6577           nlmo
 6578           "#
 6579    ));
 6580
 6581    cx.update_editor(|editor, window, cx| {
 6582        editor.add_selection_below(&Default::default(), window, cx);
 6583    });
 6584
 6585    cx.assert_editor_state(indoc!(
 6586        r#"abc
 6587           defˇghi
 6588           ˇ
 6589           jkˇ
 6590           nlmo
 6591           "#
 6592    ));
 6593
 6594    cx.update_editor(|editor, window, cx| {
 6595        editor.add_selection_below(&Default::default(), window, cx);
 6596    });
 6597
 6598    cx.assert_editor_state(indoc!(
 6599        r#"abc
 6600           defˇghi
 6601           ˇ
 6602           jkˇ
 6603           nlmˇo
 6604           "#
 6605    ));
 6606
 6607    cx.update_editor(|editor, window, cx| {
 6608        editor.add_selection_below(&Default::default(), window, cx);
 6609    });
 6610
 6611    cx.assert_editor_state(indoc!(
 6612        r#"abc
 6613           defˇghi
 6614           ˇ
 6615           jkˇ
 6616           nlmˇo
 6617           ˇ"#
 6618    ));
 6619
 6620    // change selections
 6621    cx.set_state(indoc!(
 6622        r#"abc
 6623           def«ˇg»hi
 6624
 6625           jk
 6626           nlmo
 6627           "#
 6628    ));
 6629
 6630    cx.update_editor(|editor, window, cx| {
 6631        editor.add_selection_below(&Default::default(), window, cx);
 6632    });
 6633
 6634    cx.assert_editor_state(indoc!(
 6635        r#"abc
 6636           def«ˇg»hi
 6637
 6638           jk
 6639           nlm«ˇo»
 6640           "#
 6641    ));
 6642
 6643    cx.update_editor(|editor, window, cx| {
 6644        editor.add_selection_below(&Default::default(), window, cx);
 6645    });
 6646
 6647    cx.assert_editor_state(indoc!(
 6648        r#"abc
 6649           def«ˇg»hi
 6650
 6651           jk
 6652           nlm«ˇo»
 6653           "#
 6654    ));
 6655
 6656    cx.update_editor(|editor, window, cx| {
 6657        editor.add_selection_above(&Default::default(), window, cx);
 6658    });
 6659
 6660    cx.assert_editor_state(indoc!(
 6661        r#"abc
 6662           def«ˇg»hi
 6663
 6664           jk
 6665           nlmo
 6666           "#
 6667    ));
 6668
 6669    cx.update_editor(|editor, window, cx| {
 6670        editor.add_selection_above(&Default::default(), window, cx);
 6671    });
 6672
 6673    cx.assert_editor_state(indoc!(
 6674        r#"abc
 6675           def«ˇg»hi
 6676
 6677           jk
 6678           nlmo
 6679           "#
 6680    ));
 6681
 6682    // Change selections again
 6683    cx.set_state(indoc!(
 6684        r#"a«bc
 6685           defgˇ»hi
 6686
 6687           jk
 6688           nlmo
 6689           "#
 6690    ));
 6691
 6692    cx.update_editor(|editor, window, cx| {
 6693        editor.add_selection_below(&Default::default(), window, cx);
 6694    });
 6695
 6696    cx.assert_editor_state(indoc!(
 6697        r#"a«bcˇ»
 6698           d«efgˇ»hi
 6699
 6700           j«kˇ»
 6701           nlmo
 6702           "#
 6703    ));
 6704
 6705    cx.update_editor(|editor, window, cx| {
 6706        editor.add_selection_below(&Default::default(), window, cx);
 6707    });
 6708    cx.assert_editor_state(indoc!(
 6709        r#"a«bcˇ»
 6710           d«efgˇ»hi
 6711
 6712           j«kˇ»
 6713           n«lmoˇ»
 6714           "#
 6715    ));
 6716    cx.update_editor(|editor, window, cx| {
 6717        editor.add_selection_above(&Default::default(), window, cx);
 6718    });
 6719
 6720    cx.assert_editor_state(indoc!(
 6721        r#"a«bcˇ»
 6722           d«efgˇ»hi
 6723
 6724           j«kˇ»
 6725           nlmo
 6726           "#
 6727    ));
 6728
 6729    // Change selections again
 6730    cx.set_state(indoc!(
 6731        r#"abc
 6732           d«ˇefghi
 6733
 6734           jk
 6735           nlm»o
 6736           "#
 6737    ));
 6738
 6739    cx.update_editor(|editor, window, cx| {
 6740        editor.add_selection_above(&Default::default(), window, cx);
 6741    });
 6742
 6743    cx.assert_editor_state(indoc!(
 6744        r#"a«ˇbc»
 6745           d«ˇef»ghi
 6746
 6747           j«ˇk»
 6748           n«ˇlm»o
 6749           "#
 6750    ));
 6751
 6752    cx.update_editor(|editor, window, cx| {
 6753        editor.add_selection_below(&Default::default(), window, cx);
 6754    });
 6755
 6756    cx.assert_editor_state(indoc!(
 6757        r#"abc
 6758           d«ˇef»ghi
 6759
 6760           j«ˇk»
 6761           n«ˇlm»o
 6762           "#
 6763    ));
 6764}
 6765
 6766#[gpui::test]
 6767async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 6768    init_test(cx, |_| {});
 6769    let mut cx = EditorTestContext::new(cx).await;
 6770
 6771    cx.set_state(indoc!(
 6772        r#"line onˇe
 6773           liˇne two
 6774           line three
 6775           line four"#
 6776    ));
 6777
 6778    cx.update_editor(|editor, window, cx| {
 6779        editor.add_selection_below(&Default::default(), window, cx);
 6780    });
 6781
 6782    // test multiple cursors expand in the same direction
 6783    cx.assert_editor_state(indoc!(
 6784        r#"line onˇe
 6785           liˇne twˇo
 6786           liˇne three
 6787           line four"#
 6788    ));
 6789
 6790    cx.update_editor(|editor, window, cx| {
 6791        editor.add_selection_below(&Default::default(), window, cx);
 6792    });
 6793
 6794    cx.update_editor(|editor, window, cx| {
 6795        editor.add_selection_below(&Default::default(), window, cx);
 6796    });
 6797
 6798    // test multiple cursors expand below overflow
 6799    cx.assert_editor_state(indoc!(
 6800        r#"line onˇe
 6801           liˇne twˇo
 6802           liˇne thˇree
 6803           liˇne foˇur"#
 6804    ));
 6805
 6806    cx.update_editor(|editor, window, cx| {
 6807        editor.add_selection_above(&Default::default(), window, cx);
 6808    });
 6809
 6810    // test multiple cursors retrieves back correctly
 6811    cx.assert_editor_state(indoc!(
 6812        r#"line onˇe
 6813           liˇne twˇo
 6814           liˇne thˇree
 6815           line four"#
 6816    ));
 6817
 6818    cx.update_editor(|editor, window, cx| {
 6819        editor.add_selection_above(&Default::default(), window, cx);
 6820    });
 6821
 6822    cx.update_editor(|editor, window, cx| {
 6823        editor.add_selection_above(&Default::default(), window, cx);
 6824    });
 6825
 6826    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 6827    cx.assert_editor_state(indoc!(
 6828        r#"liˇne onˇe
 6829           liˇne two
 6830           line three
 6831           line four"#
 6832    ));
 6833
 6834    cx.update_editor(|editor, window, cx| {
 6835        editor.undo_selection(&Default::default(), window, cx);
 6836    });
 6837
 6838    // test undo
 6839    cx.assert_editor_state(indoc!(
 6840        r#"line onˇe
 6841           liˇne twˇo
 6842           line three
 6843           line four"#
 6844    ));
 6845
 6846    cx.update_editor(|editor, window, cx| {
 6847        editor.redo_selection(&Default::default(), window, cx);
 6848    });
 6849
 6850    // test redo
 6851    cx.assert_editor_state(indoc!(
 6852        r#"liˇne onˇe
 6853           liˇne two
 6854           line three
 6855           line four"#
 6856    ));
 6857
 6858    cx.set_state(indoc!(
 6859        r#"abcd
 6860           ef«ghˇ»
 6861           ijkl
 6862           «mˇ»nop"#
 6863    ));
 6864
 6865    cx.update_editor(|editor, window, cx| {
 6866        editor.add_selection_above(&Default::default(), window, cx);
 6867    });
 6868
 6869    // test multiple selections expand in the same direction
 6870    cx.assert_editor_state(indoc!(
 6871        r#"ab«cdˇ»
 6872           ef«ghˇ»
 6873           «iˇ»jkl
 6874           «mˇ»nop"#
 6875    ));
 6876
 6877    cx.update_editor(|editor, window, cx| {
 6878        editor.add_selection_above(&Default::default(), window, cx);
 6879    });
 6880
 6881    // test multiple selection upward overflow
 6882    cx.assert_editor_state(indoc!(
 6883        r#"ab«cdˇ»
 6884           «eˇ»f«ghˇ»
 6885           «iˇ»jkl
 6886           «mˇ»nop"#
 6887    ));
 6888
 6889    cx.update_editor(|editor, window, cx| {
 6890        editor.add_selection_below(&Default::default(), window, cx);
 6891    });
 6892
 6893    // test multiple selection retrieves back correctly
 6894    cx.assert_editor_state(indoc!(
 6895        r#"abcd
 6896           ef«ghˇ»
 6897           «iˇ»jkl
 6898           «mˇ»nop"#
 6899    ));
 6900
 6901    cx.update_editor(|editor, window, cx| {
 6902        editor.add_selection_below(&Default::default(), window, cx);
 6903    });
 6904
 6905    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 6906    cx.assert_editor_state(indoc!(
 6907        r#"abcd
 6908           ef«ghˇ»
 6909           ij«klˇ»
 6910           «mˇ»nop"#
 6911    ));
 6912
 6913    cx.update_editor(|editor, window, cx| {
 6914        editor.undo_selection(&Default::default(), window, cx);
 6915    });
 6916
 6917    // test undo
 6918    cx.assert_editor_state(indoc!(
 6919        r#"abcd
 6920           ef«ghˇ»
 6921           «iˇ»jkl
 6922           «mˇ»nop"#
 6923    ));
 6924
 6925    cx.update_editor(|editor, window, cx| {
 6926        editor.redo_selection(&Default::default(), window, cx);
 6927    });
 6928
 6929    // test redo
 6930    cx.assert_editor_state(indoc!(
 6931        r#"abcd
 6932           ef«ghˇ»
 6933           ij«klˇ»
 6934           «mˇ»nop"#
 6935    ));
 6936}
 6937
 6938#[gpui::test]
 6939async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 6940    init_test(cx, |_| {});
 6941    let mut cx = EditorTestContext::new(cx).await;
 6942
 6943    cx.set_state(indoc!(
 6944        r#"line onˇe
 6945           liˇne two
 6946           line three
 6947           line four"#
 6948    ));
 6949
 6950    cx.update_editor(|editor, window, cx| {
 6951        editor.add_selection_below(&Default::default(), window, cx);
 6952        editor.add_selection_below(&Default::default(), window, cx);
 6953        editor.add_selection_below(&Default::default(), window, cx);
 6954    });
 6955
 6956    // initial state with two multi cursor groups
 6957    cx.assert_editor_state(indoc!(
 6958        r#"line onˇe
 6959           liˇne twˇo
 6960           liˇne thˇree
 6961           liˇne foˇur"#
 6962    ));
 6963
 6964    // add single cursor in middle - simulate opt click
 6965    cx.update_editor(|editor, window, cx| {
 6966        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 6967        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 6968        editor.end_selection(window, cx);
 6969    });
 6970
 6971    cx.assert_editor_state(indoc!(
 6972        r#"line onˇe
 6973           liˇne twˇo
 6974           liˇneˇ thˇree
 6975           liˇne foˇur"#
 6976    ));
 6977
 6978    cx.update_editor(|editor, window, cx| {
 6979        editor.add_selection_above(&Default::default(), window, cx);
 6980    });
 6981
 6982    // test new added selection expands above and existing selection shrinks
 6983    cx.assert_editor_state(indoc!(
 6984        r#"line onˇe
 6985           liˇneˇ twˇo
 6986           liˇneˇ thˇree
 6987           line four"#
 6988    ));
 6989
 6990    cx.update_editor(|editor, window, cx| {
 6991        editor.add_selection_above(&Default::default(), window, cx);
 6992    });
 6993
 6994    // test new added selection expands above and existing selection shrinks
 6995    cx.assert_editor_state(indoc!(
 6996        r#"lineˇ onˇe
 6997           liˇneˇ twˇo
 6998           lineˇ three
 6999           line four"#
 7000    ));
 7001
 7002    // intial state with two selection groups
 7003    cx.set_state(indoc!(
 7004        r#"abcd
 7005           ef«ghˇ»
 7006           ijkl
 7007           «mˇ»nop"#
 7008    ));
 7009
 7010    cx.update_editor(|editor, window, cx| {
 7011        editor.add_selection_above(&Default::default(), window, cx);
 7012        editor.add_selection_above(&Default::default(), window, cx);
 7013    });
 7014
 7015    cx.assert_editor_state(indoc!(
 7016        r#"ab«cdˇ»
 7017           «eˇ»f«ghˇ»
 7018           «iˇ»jkl
 7019           «mˇ»nop"#
 7020    ));
 7021
 7022    // add single selection in middle - simulate opt drag
 7023    cx.update_editor(|editor, window, cx| {
 7024        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 7025        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 7026        editor.update_selection(
 7027            DisplayPoint::new(DisplayRow(2), 4),
 7028            0,
 7029            gpui::Point::<f32>::default(),
 7030            window,
 7031            cx,
 7032        );
 7033        editor.end_selection(window, cx);
 7034    });
 7035
 7036    cx.assert_editor_state(indoc!(
 7037        r#"ab«cdˇ»
 7038           «eˇ»f«ghˇ»
 7039           «iˇ»jk«lˇ»
 7040           «mˇ»nop"#
 7041    ));
 7042
 7043    cx.update_editor(|editor, window, cx| {
 7044        editor.add_selection_below(&Default::default(), window, cx);
 7045    });
 7046
 7047    // test new added selection expands below, others shrinks from above
 7048    cx.assert_editor_state(indoc!(
 7049        r#"abcd
 7050           ef«ghˇ»
 7051           «iˇ»jk«lˇ»
 7052           «mˇ»no«pˇ»"#
 7053    ));
 7054}
 7055
 7056#[gpui::test]
 7057async fn test_select_next(cx: &mut TestAppContext) {
 7058    init_test(cx, |_| {});
 7059
 7060    let mut cx = EditorTestContext::new(cx).await;
 7061    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7062
 7063    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7064        .unwrap();
 7065    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7066
 7067    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7068        .unwrap();
 7069    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 7070
 7071    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7072    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7073
 7074    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7075    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 7076
 7077    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7078        .unwrap();
 7079    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7080
 7081    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7082        .unwrap();
 7083    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7084
 7085    // Test selection direction should be preserved
 7086    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 7087
 7088    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7089        .unwrap();
 7090    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 7091}
 7092
 7093#[gpui::test]
 7094async fn test_select_all_matches(cx: &mut TestAppContext) {
 7095    init_test(cx, |_| {});
 7096
 7097    let mut cx = EditorTestContext::new(cx).await;
 7098
 7099    // Test caret-only selections
 7100    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7101    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7102        .unwrap();
 7103    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7104
 7105    // Test left-to-right selections
 7106    cx.set_state("abc\n«abcˇ»\nabc");
 7107    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7108        .unwrap();
 7109    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 7110
 7111    // Test right-to-left selections
 7112    cx.set_state("abc\n«ˇabc»\nabc");
 7113    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7114        .unwrap();
 7115    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 7116
 7117    // Test selecting whitespace with caret selection
 7118    cx.set_state("abc\nˇ   abc\nabc");
 7119    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7120        .unwrap();
 7121    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 7122
 7123    // Test selecting whitespace with left-to-right selection
 7124    cx.set_state("abc\n«ˇ  »abc\nabc");
 7125    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7126        .unwrap();
 7127    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 7128
 7129    // Test no matches with right-to-left selection
 7130    cx.set_state("abc\n«  ˇ»abc\nabc");
 7131    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7132        .unwrap();
 7133    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 7134
 7135    // Test with a single word and clip_at_line_ends=true (#29823)
 7136    cx.set_state("aˇbc");
 7137    cx.update_editor(|e, window, cx| {
 7138        e.set_clip_at_line_ends(true, cx);
 7139        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 7140        e.set_clip_at_line_ends(false, cx);
 7141    });
 7142    cx.assert_editor_state("«abcˇ»");
 7143}
 7144
 7145#[gpui::test]
 7146async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 7147    init_test(cx, |_| {});
 7148
 7149    let mut cx = EditorTestContext::new(cx).await;
 7150
 7151    let large_body_1 = "\nd".repeat(200);
 7152    let large_body_2 = "\ne".repeat(200);
 7153
 7154    cx.set_state(&format!(
 7155        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 7156    ));
 7157    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 7158        let scroll_position = editor.scroll_position(cx);
 7159        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 7160        scroll_position
 7161    });
 7162
 7163    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7164        .unwrap();
 7165    cx.assert_editor_state(&format!(
 7166        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 7167    ));
 7168    let scroll_position_after_selection =
 7169        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 7170    assert_eq!(
 7171        initial_scroll_position, scroll_position_after_selection,
 7172        "Scroll position should not change after selecting all matches"
 7173    );
 7174}
 7175
 7176#[gpui::test]
 7177async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 7178    init_test(cx, |_| {});
 7179
 7180    let mut cx = EditorLspTestContext::new_rust(
 7181        lsp::ServerCapabilities {
 7182            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 7183            ..Default::default()
 7184        },
 7185        cx,
 7186    )
 7187    .await;
 7188
 7189    cx.set_state(indoc! {"
 7190        line 1
 7191        line 2
 7192        linˇe 3
 7193        line 4
 7194        line 5
 7195    "});
 7196
 7197    // Make an edit
 7198    cx.update_editor(|editor, window, cx| {
 7199        editor.handle_input("X", window, cx);
 7200    });
 7201
 7202    // Move cursor to a different position
 7203    cx.update_editor(|editor, window, cx| {
 7204        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7205            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 7206        });
 7207    });
 7208
 7209    cx.assert_editor_state(indoc! {"
 7210        line 1
 7211        line 2
 7212        linXe 3
 7213        line 4
 7214        liˇne 5
 7215    "});
 7216
 7217    cx.lsp
 7218        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 7219            Ok(Some(vec![lsp::TextEdit::new(
 7220                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 7221                "PREFIX ".to_string(),
 7222            )]))
 7223        });
 7224
 7225    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 7226        .unwrap()
 7227        .await
 7228        .unwrap();
 7229
 7230    cx.assert_editor_state(indoc! {"
 7231        PREFIX line 1
 7232        line 2
 7233        linXe 3
 7234        line 4
 7235        liˇne 5
 7236    "});
 7237
 7238    // Undo formatting
 7239    cx.update_editor(|editor, window, cx| {
 7240        editor.undo(&Default::default(), window, cx);
 7241    });
 7242
 7243    // Verify cursor moved back to position after edit
 7244    cx.assert_editor_state(indoc! {"
 7245        line 1
 7246        line 2
 7247        linXˇe 3
 7248        line 4
 7249        line 5
 7250    "});
 7251}
 7252
 7253#[gpui::test]
 7254async fn test_undo_edit_prediction_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 7255    init_test(cx, |_| {});
 7256
 7257    let mut cx = EditorTestContext::new(cx).await;
 7258
 7259    let provider = cx.new(|_| FakeEditPredictionProvider::default());
 7260    cx.update_editor(|editor, window, cx| {
 7261        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 7262    });
 7263
 7264    cx.set_state(indoc! {"
 7265        line 1
 7266        line 2
 7267        linˇe 3
 7268        line 4
 7269        line 5
 7270        line 6
 7271        line 7
 7272        line 8
 7273        line 9
 7274        line 10
 7275    "});
 7276
 7277    let snapshot = cx.buffer_snapshot();
 7278    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 7279
 7280    cx.update(|_, cx| {
 7281        provider.update(cx, |provider, _| {
 7282            provider.set_edit_prediction(Some(edit_prediction::EditPrediction {
 7283                id: None,
 7284                edits: vec![(edit_position..edit_position, "X".into())],
 7285                edit_preview: None,
 7286            }))
 7287        })
 7288    });
 7289
 7290    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 7291    cx.update_editor(|editor, window, cx| {
 7292        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 7293    });
 7294
 7295    cx.assert_editor_state(indoc! {"
 7296        line 1
 7297        line 2
 7298        lineXˇ 3
 7299        line 4
 7300        line 5
 7301        line 6
 7302        line 7
 7303        line 8
 7304        line 9
 7305        line 10
 7306    "});
 7307
 7308    cx.update_editor(|editor, window, cx| {
 7309        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7310            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 7311        });
 7312    });
 7313
 7314    cx.assert_editor_state(indoc! {"
 7315        line 1
 7316        line 2
 7317        lineX 3
 7318        line 4
 7319        line 5
 7320        line 6
 7321        line 7
 7322        line 8
 7323        line 9
 7324        liˇne 10
 7325    "});
 7326
 7327    cx.update_editor(|editor, window, cx| {
 7328        editor.undo(&Default::default(), window, cx);
 7329    });
 7330
 7331    cx.assert_editor_state(indoc! {"
 7332        line 1
 7333        line 2
 7334        lineˇ 3
 7335        line 4
 7336        line 5
 7337        line 6
 7338        line 7
 7339        line 8
 7340        line 9
 7341        line 10
 7342    "});
 7343}
 7344
 7345#[gpui::test]
 7346async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 7347    init_test(cx, |_| {});
 7348
 7349    let mut cx = EditorTestContext::new(cx).await;
 7350    cx.set_state(
 7351        r#"let foo = 2;
 7352lˇet foo = 2;
 7353let fooˇ = 2;
 7354let foo = 2;
 7355let foo = ˇ2;"#,
 7356    );
 7357
 7358    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7359        .unwrap();
 7360    cx.assert_editor_state(
 7361        r#"let foo = 2;
 7362«letˇ» foo = 2;
 7363let «fooˇ» = 2;
 7364let foo = 2;
 7365let foo = «2ˇ»;"#,
 7366    );
 7367
 7368    // noop for multiple selections with different contents
 7369    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7370        .unwrap();
 7371    cx.assert_editor_state(
 7372        r#"let foo = 2;
 7373«letˇ» foo = 2;
 7374let «fooˇ» = 2;
 7375let foo = 2;
 7376let foo = «2ˇ»;"#,
 7377    );
 7378
 7379    // Test last selection direction should be preserved
 7380    cx.set_state(
 7381        r#"let foo = 2;
 7382let foo = 2;
 7383let «fooˇ» = 2;
 7384let «ˇfoo» = 2;
 7385let foo = 2;"#,
 7386    );
 7387
 7388    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7389        .unwrap();
 7390    cx.assert_editor_state(
 7391        r#"let foo = 2;
 7392let foo = 2;
 7393let «fooˇ» = 2;
 7394let «ˇfoo» = 2;
 7395let «ˇfoo» = 2;"#,
 7396    );
 7397}
 7398
 7399#[gpui::test]
 7400async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 7401    init_test(cx, |_| {});
 7402
 7403    let mut cx =
 7404        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 7405
 7406    cx.assert_editor_state(indoc! {"
 7407        ˇbbb
 7408        ccc
 7409
 7410        bbb
 7411        ccc
 7412        "});
 7413    cx.dispatch_action(SelectPrevious::default());
 7414    cx.assert_editor_state(indoc! {"
 7415                «bbbˇ»
 7416                ccc
 7417
 7418                bbb
 7419                ccc
 7420                "});
 7421    cx.dispatch_action(SelectPrevious::default());
 7422    cx.assert_editor_state(indoc! {"
 7423                «bbbˇ»
 7424                ccc
 7425
 7426                «bbbˇ»
 7427                ccc
 7428                "});
 7429}
 7430
 7431#[gpui::test]
 7432async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 7433    init_test(cx, |_| {});
 7434
 7435    let mut cx = EditorTestContext::new(cx).await;
 7436    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7437
 7438    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7439        .unwrap();
 7440    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7441
 7442    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7443        .unwrap();
 7444    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 7445
 7446    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7447    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7448
 7449    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7450    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 7451
 7452    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7453        .unwrap();
 7454    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 7455
 7456    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7457        .unwrap();
 7458    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7459}
 7460
 7461#[gpui::test]
 7462async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 7463    init_test(cx, |_| {});
 7464
 7465    let mut cx = EditorTestContext::new(cx).await;
 7466    cx.set_state("");
 7467
 7468    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7469        .unwrap();
 7470    cx.assert_editor_state("«aˇ»");
 7471    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7472        .unwrap();
 7473    cx.assert_editor_state("«aˇ»");
 7474}
 7475
 7476#[gpui::test]
 7477async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 7478    init_test(cx, |_| {});
 7479
 7480    let mut cx = EditorTestContext::new(cx).await;
 7481    cx.set_state(
 7482        r#"let foo = 2;
 7483lˇet foo = 2;
 7484let fooˇ = 2;
 7485let foo = 2;
 7486let foo = ˇ2;"#,
 7487    );
 7488
 7489    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7490        .unwrap();
 7491    cx.assert_editor_state(
 7492        r#"let foo = 2;
 7493«letˇ» foo = 2;
 7494let «fooˇ» = 2;
 7495let foo = 2;
 7496let foo = «2ˇ»;"#,
 7497    );
 7498
 7499    // noop for multiple selections with different contents
 7500    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7501        .unwrap();
 7502    cx.assert_editor_state(
 7503        r#"let foo = 2;
 7504«letˇ» foo = 2;
 7505let «fooˇ» = 2;
 7506let foo = 2;
 7507let foo = «2ˇ»;"#,
 7508    );
 7509}
 7510
 7511#[gpui::test]
 7512async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 7513    init_test(cx, |_| {});
 7514
 7515    let mut cx = EditorTestContext::new(cx).await;
 7516    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 7517
 7518    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7519        .unwrap();
 7520    // selection direction is preserved
 7521    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 7522
 7523    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7524        .unwrap();
 7525    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7526
 7527    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7528    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 7529
 7530    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7531    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7532
 7533    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7534        .unwrap();
 7535    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 7536
 7537    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7538        .unwrap();
 7539    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 7540}
 7541
 7542#[gpui::test]
 7543async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 7544    init_test(cx, |_| {});
 7545
 7546    let language = Arc::new(Language::new(
 7547        LanguageConfig::default(),
 7548        Some(tree_sitter_rust::LANGUAGE.into()),
 7549    ));
 7550
 7551    let text = r#"
 7552        use mod1::mod2::{mod3, mod4};
 7553
 7554        fn fn_1(param1: bool, param2: &str) {
 7555            let var1 = "text";
 7556        }
 7557    "#
 7558    .unindent();
 7559
 7560    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7561    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7562    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7563
 7564    editor
 7565        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7566        .await;
 7567
 7568    editor.update_in(cx, |editor, window, cx| {
 7569        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7570            s.select_display_ranges([
 7571                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 7572                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 7573                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 7574            ]);
 7575        });
 7576        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7577    });
 7578    editor.update(cx, |editor, cx| {
 7579        assert_text_with_selections(
 7580            editor,
 7581            indoc! {r#"
 7582                use mod1::mod2::{mod3, «mod4ˇ»};
 7583
 7584                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7585                    let var1 = "«ˇtext»";
 7586                }
 7587            "#},
 7588            cx,
 7589        );
 7590    });
 7591
 7592    editor.update_in(cx, |editor, window, cx| {
 7593        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7594    });
 7595    editor.update(cx, |editor, cx| {
 7596        assert_text_with_selections(
 7597            editor,
 7598            indoc! {r#"
 7599                use mod1::mod2::«{mod3, mod4}ˇ»;
 7600
 7601                «ˇfn fn_1(param1: bool, param2: &str) {
 7602                    let var1 = "text";
 7603 7604            "#},
 7605            cx,
 7606        );
 7607    });
 7608
 7609    editor.update_in(cx, |editor, window, cx| {
 7610        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7611    });
 7612    assert_eq!(
 7613        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7614        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7615    );
 7616
 7617    // Trying to expand the selected syntax node one more time has no effect.
 7618    editor.update_in(cx, |editor, window, cx| {
 7619        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7620    });
 7621    assert_eq!(
 7622        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7623        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7624    );
 7625
 7626    editor.update_in(cx, |editor, window, cx| {
 7627        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7628    });
 7629    editor.update(cx, |editor, cx| {
 7630        assert_text_with_selections(
 7631            editor,
 7632            indoc! {r#"
 7633                use mod1::mod2::«{mod3, mod4}ˇ»;
 7634
 7635                «ˇfn fn_1(param1: bool, param2: &str) {
 7636                    let var1 = "text";
 7637 7638            "#},
 7639            cx,
 7640        );
 7641    });
 7642
 7643    editor.update_in(cx, |editor, window, cx| {
 7644        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7645    });
 7646    editor.update(cx, |editor, cx| {
 7647        assert_text_with_selections(
 7648            editor,
 7649            indoc! {r#"
 7650                use mod1::mod2::{mod3, «mod4ˇ»};
 7651
 7652                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7653                    let var1 = "«ˇtext»";
 7654                }
 7655            "#},
 7656            cx,
 7657        );
 7658    });
 7659
 7660    editor.update_in(cx, |editor, window, cx| {
 7661        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7662    });
 7663    editor.update(cx, |editor, cx| {
 7664        assert_text_with_selections(
 7665            editor,
 7666            indoc! {r#"
 7667                use mod1::mod2::{mod3, mo«ˇ»d4};
 7668
 7669                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7670                    let var1 = "te«ˇ»xt";
 7671                }
 7672            "#},
 7673            cx,
 7674        );
 7675    });
 7676
 7677    // Trying to shrink the selected syntax node one more time has no effect.
 7678    editor.update_in(cx, |editor, window, cx| {
 7679        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7680    });
 7681    editor.update_in(cx, |editor, _, cx| {
 7682        assert_text_with_selections(
 7683            editor,
 7684            indoc! {r#"
 7685                use mod1::mod2::{mod3, mo«ˇ»d4};
 7686
 7687                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7688                    let var1 = "te«ˇ»xt";
 7689                }
 7690            "#},
 7691            cx,
 7692        );
 7693    });
 7694
 7695    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 7696    // a fold.
 7697    editor.update_in(cx, |editor, window, cx| {
 7698        editor.fold_creases(
 7699            vec![
 7700                Crease::simple(
 7701                    Point::new(0, 21)..Point::new(0, 24),
 7702                    FoldPlaceholder::test(),
 7703                ),
 7704                Crease::simple(
 7705                    Point::new(3, 20)..Point::new(3, 22),
 7706                    FoldPlaceholder::test(),
 7707                ),
 7708            ],
 7709            true,
 7710            window,
 7711            cx,
 7712        );
 7713        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7714    });
 7715    editor.update(cx, |editor, cx| {
 7716        assert_text_with_selections(
 7717            editor,
 7718            indoc! {r#"
 7719                use mod1::mod2::«{mod3, mod4}ˇ»;
 7720
 7721                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7722                    let var1 = "«ˇtext»";
 7723                }
 7724            "#},
 7725            cx,
 7726        );
 7727    });
 7728}
 7729
 7730#[gpui::test]
 7731async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 7732    init_test(cx, |_| {});
 7733
 7734    let language = Arc::new(Language::new(
 7735        LanguageConfig::default(),
 7736        Some(tree_sitter_rust::LANGUAGE.into()),
 7737    ));
 7738
 7739    let text = "let a = 2;";
 7740
 7741    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7742    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7743    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7744
 7745    editor
 7746        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7747        .await;
 7748
 7749    // Test case 1: Cursor at end of word
 7750    editor.update_in(cx, |editor, window, cx| {
 7751        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7752            s.select_display_ranges([
 7753                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 7754            ]);
 7755        });
 7756    });
 7757    editor.update(cx, |editor, cx| {
 7758        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 7759    });
 7760    editor.update_in(cx, |editor, window, cx| {
 7761        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7762    });
 7763    editor.update(cx, |editor, cx| {
 7764        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 7765    });
 7766    editor.update_in(cx, |editor, window, cx| {
 7767        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7768    });
 7769    editor.update(cx, |editor, cx| {
 7770        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7771    });
 7772
 7773    // Test case 2: Cursor at end of statement
 7774    editor.update_in(cx, |editor, window, cx| {
 7775        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7776            s.select_display_ranges([
 7777                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 7778            ]);
 7779        });
 7780    });
 7781    editor.update(cx, |editor, cx| {
 7782        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 7783    });
 7784    editor.update_in(cx, |editor, window, cx| {
 7785        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7786    });
 7787    editor.update(cx, |editor, cx| {
 7788        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7789    });
 7790}
 7791
 7792#[gpui::test]
 7793async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 7794    init_test(cx, |_| {});
 7795
 7796    let language = Arc::new(Language::new(
 7797        LanguageConfig::default(),
 7798        Some(tree_sitter_rust::LANGUAGE.into()),
 7799    ));
 7800
 7801    let text = r#"
 7802        use mod1::mod2::{mod3, mod4};
 7803
 7804        fn fn_1(param1: bool, param2: &str) {
 7805            let var1 = "hello world";
 7806        }
 7807    "#
 7808    .unindent();
 7809
 7810    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7811    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7812    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7813
 7814    editor
 7815        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7816        .await;
 7817
 7818    // Test 1: Cursor on a letter of a string word
 7819    editor.update_in(cx, |editor, window, cx| {
 7820        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7821            s.select_display_ranges([
 7822                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 7823            ]);
 7824        });
 7825    });
 7826    editor.update_in(cx, |editor, window, cx| {
 7827        assert_text_with_selections(
 7828            editor,
 7829            indoc! {r#"
 7830                use mod1::mod2::{mod3, mod4};
 7831
 7832                fn fn_1(param1: bool, param2: &str) {
 7833                    let var1 = "hˇello world";
 7834                }
 7835            "#},
 7836            cx,
 7837        );
 7838        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7839        assert_text_with_selections(
 7840            editor,
 7841            indoc! {r#"
 7842                use mod1::mod2::{mod3, mod4};
 7843
 7844                fn fn_1(param1: bool, param2: &str) {
 7845                    let var1 = "«ˇhello» world";
 7846                }
 7847            "#},
 7848            cx,
 7849        );
 7850    });
 7851
 7852    // Test 2: Partial selection within a word
 7853    editor.update_in(cx, |editor, window, cx| {
 7854        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7855            s.select_display_ranges([
 7856                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 7857            ]);
 7858        });
 7859    });
 7860    editor.update_in(cx, |editor, window, cx| {
 7861        assert_text_with_selections(
 7862            editor,
 7863            indoc! {r#"
 7864                use mod1::mod2::{mod3, mod4};
 7865
 7866                fn fn_1(param1: bool, param2: &str) {
 7867                    let var1 = "h«elˇ»lo world";
 7868                }
 7869            "#},
 7870            cx,
 7871        );
 7872        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7873        assert_text_with_selections(
 7874            editor,
 7875            indoc! {r#"
 7876                use mod1::mod2::{mod3, mod4};
 7877
 7878                fn fn_1(param1: bool, param2: &str) {
 7879                    let var1 = "«ˇhello» world";
 7880                }
 7881            "#},
 7882            cx,
 7883        );
 7884    });
 7885
 7886    // Test 3: Complete word already selected
 7887    editor.update_in(cx, |editor, window, cx| {
 7888        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7889            s.select_display_ranges([
 7890                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 7891            ]);
 7892        });
 7893    });
 7894    editor.update_in(cx, |editor, window, cx| {
 7895        assert_text_with_selections(
 7896            editor,
 7897            indoc! {r#"
 7898                use mod1::mod2::{mod3, mod4};
 7899
 7900                fn fn_1(param1: bool, param2: &str) {
 7901                    let var1 = "«helloˇ» world";
 7902                }
 7903            "#},
 7904            cx,
 7905        );
 7906        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7907        assert_text_with_selections(
 7908            editor,
 7909            indoc! {r#"
 7910                use mod1::mod2::{mod3, mod4};
 7911
 7912                fn fn_1(param1: bool, param2: &str) {
 7913                    let var1 = "«hello worldˇ»";
 7914                }
 7915            "#},
 7916            cx,
 7917        );
 7918    });
 7919
 7920    // Test 4: Selection spanning across words
 7921    editor.update_in(cx, |editor, window, cx| {
 7922        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7923            s.select_display_ranges([
 7924                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 7925            ]);
 7926        });
 7927    });
 7928    editor.update_in(cx, |editor, window, cx| {
 7929        assert_text_with_selections(
 7930            editor,
 7931            indoc! {r#"
 7932                use mod1::mod2::{mod3, mod4};
 7933
 7934                fn fn_1(param1: bool, param2: &str) {
 7935                    let var1 = "hel«lo woˇ»rld";
 7936                }
 7937            "#},
 7938            cx,
 7939        );
 7940        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7941        assert_text_with_selections(
 7942            editor,
 7943            indoc! {r#"
 7944                use mod1::mod2::{mod3, mod4};
 7945
 7946                fn fn_1(param1: bool, param2: &str) {
 7947                    let var1 = "«ˇhello world»";
 7948                }
 7949            "#},
 7950            cx,
 7951        );
 7952    });
 7953
 7954    // Test 5: Expansion beyond string
 7955    editor.update_in(cx, |editor, window, cx| {
 7956        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7957        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7958        assert_text_with_selections(
 7959            editor,
 7960            indoc! {r#"
 7961                use mod1::mod2::{mod3, mod4};
 7962
 7963                fn fn_1(param1: bool, param2: &str) {
 7964                    «ˇlet var1 = "hello world";»
 7965                }
 7966            "#},
 7967            cx,
 7968        );
 7969    });
 7970}
 7971
 7972#[gpui::test]
 7973async fn test_unwrap_syntax_node(cx: &mut gpui::TestAppContext) {
 7974    init_test(cx, |_| {});
 7975
 7976    let mut cx = EditorTestContext::new(cx).await;
 7977
 7978    let language = Arc::new(Language::new(
 7979        LanguageConfig::default(),
 7980        Some(tree_sitter_rust::LANGUAGE.into()),
 7981    ));
 7982
 7983    cx.update_buffer(|buffer, cx| {
 7984        buffer.set_language(Some(language), cx);
 7985    });
 7986
 7987    cx.set_state(
 7988        &r#"
 7989            use mod1::mod2::{«mod3ˇ», mod4};
 7990        "#
 7991        .unindent(),
 7992    );
 7993    cx.update_editor(|editor, window, cx| {
 7994        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 7995    });
 7996    cx.assert_editor_state(
 7997        &r#"
 7998            use mod1::mod2::«mod3ˇ»;
 7999        "#
 8000        .unindent(),
 8001    );
 8002}
 8003
 8004#[gpui::test]
 8005async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 8006    init_test(cx, |_| {});
 8007
 8008    let base_text = r#"
 8009        impl A {
 8010            // this is an uncommitted comment
 8011
 8012            fn b() {
 8013                c();
 8014            }
 8015
 8016            // this is another uncommitted comment
 8017
 8018            fn d() {
 8019                // e
 8020                // f
 8021            }
 8022        }
 8023
 8024        fn g() {
 8025            // h
 8026        }
 8027    "#
 8028    .unindent();
 8029
 8030    let text = r#"
 8031        ˇimpl A {
 8032
 8033            fn b() {
 8034                c();
 8035            }
 8036
 8037            fn d() {
 8038                // e
 8039                // f
 8040            }
 8041        }
 8042
 8043        fn g() {
 8044            // h
 8045        }
 8046    "#
 8047    .unindent();
 8048
 8049    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 8050    cx.set_state(&text);
 8051    cx.set_head_text(&base_text);
 8052    cx.update_editor(|editor, window, cx| {
 8053        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 8054    });
 8055
 8056    cx.assert_state_with_diff(
 8057        "
 8058        ˇimpl A {
 8059      -     // this is an uncommitted comment
 8060
 8061            fn b() {
 8062                c();
 8063            }
 8064
 8065      -     // this is another uncommitted comment
 8066      -
 8067            fn d() {
 8068                // e
 8069                // f
 8070            }
 8071        }
 8072
 8073        fn g() {
 8074            // h
 8075        }
 8076    "
 8077        .unindent(),
 8078    );
 8079
 8080    let expected_display_text = "
 8081        impl A {
 8082            // this is an uncommitted comment
 8083
 8084            fn b() {
 8085 8086            }
 8087
 8088            // this is another uncommitted comment
 8089
 8090            fn d() {
 8091 8092            }
 8093        }
 8094
 8095        fn g() {
 8096 8097        }
 8098        "
 8099    .unindent();
 8100
 8101    cx.update_editor(|editor, window, cx| {
 8102        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 8103        assert_eq!(editor.display_text(cx), expected_display_text);
 8104    });
 8105}
 8106
 8107#[gpui::test]
 8108async fn test_autoindent(cx: &mut TestAppContext) {
 8109    init_test(cx, |_| {});
 8110
 8111    let language = Arc::new(
 8112        Language::new(
 8113            LanguageConfig {
 8114                brackets: BracketPairConfig {
 8115                    pairs: vec![
 8116                        BracketPair {
 8117                            start: "{".to_string(),
 8118                            end: "}".to_string(),
 8119                            close: false,
 8120                            surround: false,
 8121                            newline: true,
 8122                        },
 8123                        BracketPair {
 8124                            start: "(".to_string(),
 8125                            end: ")".to_string(),
 8126                            close: false,
 8127                            surround: false,
 8128                            newline: true,
 8129                        },
 8130                    ],
 8131                    ..Default::default()
 8132                },
 8133                ..Default::default()
 8134            },
 8135            Some(tree_sitter_rust::LANGUAGE.into()),
 8136        )
 8137        .with_indents_query(
 8138            r#"
 8139                (_ "(" ")" @end) @indent
 8140                (_ "{" "}" @end) @indent
 8141            "#,
 8142        )
 8143        .unwrap(),
 8144    );
 8145
 8146    let text = "fn a() {}";
 8147
 8148    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8149    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8150    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8151    editor
 8152        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8153        .await;
 8154
 8155    editor.update_in(cx, |editor, window, cx| {
 8156        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8157            s.select_ranges([5..5, 8..8, 9..9])
 8158        });
 8159        editor.newline(&Newline, window, cx);
 8160        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 8161        assert_eq!(
 8162            editor.selections.ranges(cx),
 8163            &[
 8164                Point::new(1, 4)..Point::new(1, 4),
 8165                Point::new(3, 4)..Point::new(3, 4),
 8166                Point::new(5, 0)..Point::new(5, 0)
 8167            ]
 8168        );
 8169    });
 8170}
 8171
 8172#[gpui::test]
 8173async fn test_autoindent_selections(cx: &mut TestAppContext) {
 8174    init_test(cx, |_| {});
 8175
 8176    {
 8177        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 8178        cx.set_state(indoc! {"
 8179            impl A {
 8180
 8181                fn b() {}
 8182
 8183            «fn c() {
 8184
 8185            }ˇ»
 8186            }
 8187        "});
 8188
 8189        cx.update_editor(|editor, window, cx| {
 8190            editor.autoindent(&Default::default(), window, cx);
 8191        });
 8192
 8193        cx.assert_editor_state(indoc! {"
 8194            impl A {
 8195
 8196                fn b() {}
 8197
 8198                «fn c() {
 8199
 8200                }ˇ»
 8201            }
 8202        "});
 8203    }
 8204
 8205    {
 8206        let mut cx = EditorTestContext::new_multibuffer(
 8207            cx,
 8208            [indoc! { "
 8209                impl A {
 8210                «
 8211                // a
 8212                fn b(){}
 8213                »
 8214                «
 8215                    }
 8216                    fn c(){}
 8217                »
 8218            "}],
 8219        );
 8220
 8221        let buffer = cx.update_editor(|editor, _, cx| {
 8222            let buffer = editor.buffer().update(cx, |buffer, _| {
 8223                buffer.all_buffers().iter().next().unwrap().clone()
 8224            });
 8225            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 8226            buffer
 8227        });
 8228
 8229        cx.run_until_parked();
 8230        cx.update_editor(|editor, window, cx| {
 8231            editor.select_all(&Default::default(), window, cx);
 8232            editor.autoindent(&Default::default(), window, cx)
 8233        });
 8234        cx.run_until_parked();
 8235
 8236        cx.update(|_, cx| {
 8237            assert_eq!(
 8238                buffer.read(cx).text(),
 8239                indoc! { "
 8240                    impl A {
 8241
 8242                        // a
 8243                        fn b(){}
 8244
 8245
 8246                    }
 8247                    fn c(){}
 8248
 8249                " }
 8250            )
 8251        });
 8252    }
 8253}
 8254
 8255#[gpui::test]
 8256async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 8257    init_test(cx, |_| {});
 8258
 8259    let mut cx = EditorTestContext::new(cx).await;
 8260
 8261    let language = Arc::new(Language::new(
 8262        LanguageConfig {
 8263            brackets: BracketPairConfig {
 8264                pairs: vec![
 8265                    BracketPair {
 8266                        start: "{".to_string(),
 8267                        end: "}".to_string(),
 8268                        close: true,
 8269                        surround: true,
 8270                        newline: true,
 8271                    },
 8272                    BracketPair {
 8273                        start: "(".to_string(),
 8274                        end: ")".to_string(),
 8275                        close: true,
 8276                        surround: true,
 8277                        newline: true,
 8278                    },
 8279                    BracketPair {
 8280                        start: "/*".to_string(),
 8281                        end: " */".to_string(),
 8282                        close: true,
 8283                        surround: true,
 8284                        newline: true,
 8285                    },
 8286                    BracketPair {
 8287                        start: "[".to_string(),
 8288                        end: "]".to_string(),
 8289                        close: false,
 8290                        surround: false,
 8291                        newline: true,
 8292                    },
 8293                    BracketPair {
 8294                        start: "\"".to_string(),
 8295                        end: "\"".to_string(),
 8296                        close: true,
 8297                        surround: true,
 8298                        newline: false,
 8299                    },
 8300                    BracketPair {
 8301                        start: "<".to_string(),
 8302                        end: ">".to_string(),
 8303                        close: false,
 8304                        surround: true,
 8305                        newline: true,
 8306                    },
 8307                ],
 8308                ..Default::default()
 8309            },
 8310            autoclose_before: "})]".to_string(),
 8311            ..Default::default()
 8312        },
 8313        Some(tree_sitter_rust::LANGUAGE.into()),
 8314    ));
 8315
 8316    cx.language_registry().add(language.clone());
 8317    cx.update_buffer(|buffer, cx| {
 8318        buffer.set_language(Some(language), cx);
 8319    });
 8320
 8321    cx.set_state(
 8322        &r#"
 8323            🏀ˇ
 8324            εˇ
 8325            ❤️ˇ
 8326        "#
 8327        .unindent(),
 8328    );
 8329
 8330    // autoclose multiple nested brackets at multiple cursors
 8331    cx.update_editor(|editor, window, cx| {
 8332        editor.handle_input("{", window, cx);
 8333        editor.handle_input("{", window, cx);
 8334        editor.handle_input("{", window, cx);
 8335    });
 8336    cx.assert_editor_state(
 8337        &"
 8338            🏀{{{ˇ}}}
 8339            ε{{{ˇ}}}
 8340            ❤️{{{ˇ}}}
 8341        "
 8342        .unindent(),
 8343    );
 8344
 8345    // insert a different closing bracket
 8346    cx.update_editor(|editor, window, cx| {
 8347        editor.handle_input(")", window, cx);
 8348    });
 8349    cx.assert_editor_state(
 8350        &"
 8351            🏀{{{)ˇ}}}
 8352            ε{{{)ˇ}}}
 8353            ❤️{{{)ˇ}}}
 8354        "
 8355        .unindent(),
 8356    );
 8357
 8358    // skip over the auto-closed brackets when typing a closing bracket
 8359    cx.update_editor(|editor, window, cx| {
 8360        editor.move_right(&MoveRight, window, cx);
 8361        editor.handle_input("}", window, cx);
 8362        editor.handle_input("}", window, cx);
 8363        editor.handle_input("}", window, cx);
 8364    });
 8365    cx.assert_editor_state(
 8366        &"
 8367            🏀{{{)}}}}ˇ
 8368            ε{{{)}}}}ˇ
 8369            ❤️{{{)}}}}ˇ
 8370        "
 8371        .unindent(),
 8372    );
 8373
 8374    // autoclose multi-character pairs
 8375    cx.set_state(
 8376        &"
 8377            ˇ
 8378            ˇ
 8379        "
 8380        .unindent(),
 8381    );
 8382    cx.update_editor(|editor, window, cx| {
 8383        editor.handle_input("/", window, cx);
 8384        editor.handle_input("*", window, cx);
 8385    });
 8386    cx.assert_editor_state(
 8387        &"
 8388            /*ˇ */
 8389            /*ˇ */
 8390        "
 8391        .unindent(),
 8392    );
 8393
 8394    // one cursor autocloses a multi-character pair, one cursor
 8395    // does not autoclose.
 8396    cx.set_state(
 8397        &"
 8398 8399            ˇ
 8400        "
 8401        .unindent(),
 8402    );
 8403    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 8404    cx.assert_editor_state(
 8405        &"
 8406            /*ˇ */
 8407 8408        "
 8409        .unindent(),
 8410    );
 8411
 8412    // Don't autoclose if the next character isn't whitespace and isn't
 8413    // listed in the language's "autoclose_before" section.
 8414    cx.set_state("ˇa b");
 8415    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8416    cx.assert_editor_state("{ˇa b");
 8417
 8418    // Don't autoclose if `close` is false for the bracket pair
 8419    cx.set_state("ˇ");
 8420    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 8421    cx.assert_editor_state("");
 8422
 8423    // Surround with brackets if text is selected
 8424    cx.set_state("«aˇ» b");
 8425    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8426    cx.assert_editor_state("{«aˇ»} b");
 8427
 8428    // Autoclose when not immediately after a word character
 8429    cx.set_state("a ˇ");
 8430    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8431    cx.assert_editor_state("a \"ˇ\"");
 8432
 8433    // Autoclose pair where the start and end characters are the same
 8434    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8435    cx.assert_editor_state("a \"\"ˇ");
 8436
 8437    // Don't autoclose when immediately after a word character
 8438    cx.set_state("");
 8439    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8440    cx.assert_editor_state("a\"ˇ");
 8441
 8442    // Do autoclose when after a non-word character
 8443    cx.set_state("");
 8444    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8445    cx.assert_editor_state("{\"ˇ\"");
 8446
 8447    // Non identical pairs autoclose regardless of preceding character
 8448    cx.set_state("");
 8449    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8450    cx.assert_editor_state("a{ˇ}");
 8451
 8452    // Don't autoclose pair if autoclose is disabled
 8453    cx.set_state("ˇ");
 8454    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 8455    cx.assert_editor_state("");
 8456
 8457    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 8458    cx.set_state("«aˇ» b");
 8459    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 8460    cx.assert_editor_state("<«aˇ»> b");
 8461}
 8462
 8463#[gpui::test]
 8464async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 8465    init_test(cx, |settings| {
 8466        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 8467    });
 8468
 8469    let mut cx = EditorTestContext::new(cx).await;
 8470
 8471    let language = Arc::new(Language::new(
 8472        LanguageConfig {
 8473            brackets: BracketPairConfig {
 8474                pairs: vec![
 8475                    BracketPair {
 8476                        start: "{".to_string(),
 8477                        end: "}".to_string(),
 8478                        close: true,
 8479                        surround: true,
 8480                        newline: true,
 8481                    },
 8482                    BracketPair {
 8483                        start: "(".to_string(),
 8484                        end: ")".to_string(),
 8485                        close: true,
 8486                        surround: true,
 8487                        newline: true,
 8488                    },
 8489                    BracketPair {
 8490                        start: "[".to_string(),
 8491                        end: "]".to_string(),
 8492                        close: false,
 8493                        surround: false,
 8494                        newline: true,
 8495                    },
 8496                ],
 8497                ..Default::default()
 8498            },
 8499            autoclose_before: "})]".to_string(),
 8500            ..Default::default()
 8501        },
 8502        Some(tree_sitter_rust::LANGUAGE.into()),
 8503    ));
 8504
 8505    cx.language_registry().add(language.clone());
 8506    cx.update_buffer(|buffer, cx| {
 8507        buffer.set_language(Some(language), cx);
 8508    });
 8509
 8510    cx.set_state(
 8511        &"
 8512            ˇ
 8513            ˇ
 8514            ˇ
 8515        "
 8516        .unindent(),
 8517    );
 8518
 8519    // ensure only matching closing brackets are skipped over
 8520    cx.update_editor(|editor, window, cx| {
 8521        editor.handle_input("}", window, cx);
 8522        editor.move_left(&MoveLeft, window, cx);
 8523        editor.handle_input(")", window, cx);
 8524        editor.move_left(&MoveLeft, window, cx);
 8525    });
 8526    cx.assert_editor_state(
 8527        &"
 8528            ˇ)}
 8529            ˇ)}
 8530            ˇ)}
 8531        "
 8532        .unindent(),
 8533    );
 8534
 8535    // skip-over closing brackets at multiple cursors
 8536    cx.update_editor(|editor, window, cx| {
 8537        editor.handle_input(")", window, cx);
 8538        editor.handle_input("}", window, cx);
 8539    });
 8540    cx.assert_editor_state(
 8541        &"
 8542            )}ˇ
 8543            )}ˇ
 8544            )}ˇ
 8545        "
 8546        .unindent(),
 8547    );
 8548
 8549    // ignore non-close brackets
 8550    cx.update_editor(|editor, window, cx| {
 8551        editor.handle_input("]", window, cx);
 8552        editor.move_left(&MoveLeft, window, cx);
 8553        editor.handle_input("]", window, cx);
 8554    });
 8555    cx.assert_editor_state(
 8556        &"
 8557            )}]ˇ]
 8558            )}]ˇ]
 8559            )}]ˇ]
 8560        "
 8561        .unindent(),
 8562    );
 8563}
 8564
 8565#[gpui::test]
 8566async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
 8567    init_test(cx, |_| {});
 8568
 8569    let mut cx = EditorTestContext::new(cx).await;
 8570
 8571    let html_language = Arc::new(
 8572        Language::new(
 8573            LanguageConfig {
 8574                name: "HTML".into(),
 8575                brackets: BracketPairConfig {
 8576                    pairs: vec![
 8577                        BracketPair {
 8578                            start: "<".into(),
 8579                            end: ">".into(),
 8580                            close: true,
 8581                            ..Default::default()
 8582                        },
 8583                        BracketPair {
 8584                            start: "{".into(),
 8585                            end: "}".into(),
 8586                            close: true,
 8587                            ..Default::default()
 8588                        },
 8589                        BracketPair {
 8590                            start: "(".into(),
 8591                            end: ")".into(),
 8592                            close: true,
 8593                            ..Default::default()
 8594                        },
 8595                    ],
 8596                    ..Default::default()
 8597                },
 8598                autoclose_before: "})]>".into(),
 8599                ..Default::default()
 8600            },
 8601            Some(tree_sitter_html::LANGUAGE.into()),
 8602        )
 8603        .with_injection_query(
 8604            r#"
 8605            (script_element
 8606                (raw_text) @injection.content
 8607                (#set! injection.language "javascript"))
 8608            "#,
 8609        )
 8610        .unwrap(),
 8611    );
 8612
 8613    let javascript_language = Arc::new(Language::new(
 8614        LanguageConfig {
 8615            name: "JavaScript".into(),
 8616            brackets: BracketPairConfig {
 8617                pairs: vec![
 8618                    BracketPair {
 8619                        start: "/*".into(),
 8620                        end: " */".into(),
 8621                        close: true,
 8622                        ..Default::default()
 8623                    },
 8624                    BracketPair {
 8625                        start: "{".into(),
 8626                        end: "}".into(),
 8627                        close: true,
 8628                        ..Default::default()
 8629                    },
 8630                    BracketPair {
 8631                        start: "(".into(),
 8632                        end: ")".into(),
 8633                        close: true,
 8634                        ..Default::default()
 8635                    },
 8636                ],
 8637                ..Default::default()
 8638            },
 8639            autoclose_before: "})]>".into(),
 8640            ..Default::default()
 8641        },
 8642        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 8643    ));
 8644
 8645    cx.language_registry().add(html_language.clone());
 8646    cx.language_registry().add(javascript_language.clone());
 8647    cx.executor().run_until_parked();
 8648
 8649    cx.update_buffer(|buffer, cx| {
 8650        buffer.set_language(Some(html_language), cx);
 8651    });
 8652
 8653    cx.set_state(
 8654        &r#"
 8655            <body>ˇ
 8656                <script>
 8657                    var x = 1;ˇ
 8658                </script>
 8659            </body>ˇ
 8660        "#
 8661        .unindent(),
 8662    );
 8663
 8664    // Precondition: different languages are active at different locations.
 8665    cx.update_editor(|editor, window, cx| {
 8666        let snapshot = editor.snapshot(window, cx);
 8667        let cursors = editor.selections.ranges::<usize>(cx);
 8668        let languages = cursors
 8669            .iter()
 8670            .map(|c| snapshot.language_at(c.start).unwrap().name())
 8671            .collect::<Vec<_>>();
 8672        assert_eq!(
 8673            languages,
 8674            &["HTML".into(), "JavaScript".into(), "HTML".into()]
 8675        );
 8676    });
 8677
 8678    // Angle brackets autoclose in HTML, but not JavaScript.
 8679    cx.update_editor(|editor, window, cx| {
 8680        editor.handle_input("<", window, cx);
 8681        editor.handle_input("a", window, cx);
 8682    });
 8683    cx.assert_editor_state(
 8684        &r#"
 8685            <body><aˇ>
 8686                <script>
 8687                    var x = 1;<aˇ
 8688                </script>
 8689            </body><aˇ>
 8690        "#
 8691        .unindent(),
 8692    );
 8693
 8694    // Curly braces and parens autoclose in both HTML and JavaScript.
 8695    cx.update_editor(|editor, window, cx| {
 8696        editor.handle_input(" b=", window, cx);
 8697        editor.handle_input("{", window, cx);
 8698        editor.handle_input("c", window, cx);
 8699        editor.handle_input("(", window, cx);
 8700    });
 8701    cx.assert_editor_state(
 8702        &r#"
 8703            <body><a b={c(ˇ)}>
 8704                <script>
 8705                    var x = 1;<a b={c(ˇ)}
 8706                </script>
 8707            </body><a b={c(ˇ)}>
 8708        "#
 8709        .unindent(),
 8710    );
 8711
 8712    // Brackets that were already autoclosed are skipped.
 8713    cx.update_editor(|editor, window, cx| {
 8714        editor.handle_input(")", window, cx);
 8715        editor.handle_input("d", window, cx);
 8716        editor.handle_input("}", window, cx);
 8717    });
 8718    cx.assert_editor_state(
 8719        &r#"
 8720            <body><a b={c()d}ˇ>
 8721                <script>
 8722                    var x = 1;<a b={c()d}ˇ
 8723                </script>
 8724            </body><a b={c()d}ˇ>
 8725        "#
 8726        .unindent(),
 8727    );
 8728    cx.update_editor(|editor, window, cx| {
 8729        editor.handle_input(">", window, cx);
 8730    });
 8731    cx.assert_editor_state(
 8732        &r#"
 8733            <body><a b={c()d}>ˇ
 8734                <script>
 8735                    var x = 1;<a b={c()d}>ˇ
 8736                </script>
 8737            </body><a b={c()d}>ˇ
 8738        "#
 8739        .unindent(),
 8740    );
 8741
 8742    // Reset
 8743    cx.set_state(
 8744        &r#"
 8745            <body>ˇ
 8746                <script>
 8747                    var x = 1;ˇ
 8748                </script>
 8749            </body>ˇ
 8750        "#
 8751        .unindent(),
 8752    );
 8753
 8754    cx.update_editor(|editor, window, cx| {
 8755        editor.handle_input("<", window, cx);
 8756    });
 8757    cx.assert_editor_state(
 8758        &r#"
 8759            <body><ˇ>
 8760                <script>
 8761                    var x = 1;<ˇ
 8762                </script>
 8763            </body><ˇ>
 8764        "#
 8765        .unindent(),
 8766    );
 8767
 8768    // When backspacing, the closing angle brackets are removed.
 8769    cx.update_editor(|editor, window, cx| {
 8770        editor.backspace(&Backspace, window, cx);
 8771    });
 8772    cx.assert_editor_state(
 8773        &r#"
 8774            <body>ˇ
 8775                <script>
 8776                    var x = 1;ˇ
 8777                </script>
 8778            </body>ˇ
 8779        "#
 8780        .unindent(),
 8781    );
 8782
 8783    // Block comments autoclose in JavaScript, but not HTML.
 8784    cx.update_editor(|editor, window, cx| {
 8785        editor.handle_input("/", window, cx);
 8786        editor.handle_input("*", window, cx);
 8787    });
 8788    cx.assert_editor_state(
 8789        &r#"
 8790            <body>/*ˇ
 8791                <script>
 8792                    var x = 1;/*ˇ */
 8793                </script>
 8794            </body>/*ˇ
 8795        "#
 8796        .unindent(),
 8797    );
 8798}
 8799
 8800#[gpui::test]
 8801async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
 8802    init_test(cx, |_| {});
 8803
 8804    let mut cx = EditorTestContext::new(cx).await;
 8805
 8806    let rust_language = Arc::new(
 8807        Language::new(
 8808            LanguageConfig {
 8809                name: "Rust".into(),
 8810                brackets: serde_json::from_value(json!([
 8811                    { "start": "{", "end": "}", "close": true, "newline": true },
 8812                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
 8813                ]))
 8814                .unwrap(),
 8815                autoclose_before: "})]>".into(),
 8816                ..Default::default()
 8817            },
 8818            Some(tree_sitter_rust::LANGUAGE.into()),
 8819        )
 8820        .with_override_query("(string_literal) @string")
 8821        .unwrap(),
 8822    );
 8823
 8824    cx.language_registry().add(rust_language.clone());
 8825    cx.update_buffer(|buffer, cx| {
 8826        buffer.set_language(Some(rust_language), cx);
 8827    });
 8828
 8829    cx.set_state(
 8830        &r#"
 8831            let x = ˇ
 8832        "#
 8833        .unindent(),
 8834    );
 8835
 8836    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
 8837    cx.update_editor(|editor, window, cx| {
 8838        editor.handle_input("\"", window, cx);
 8839    });
 8840    cx.assert_editor_state(
 8841        &r#"
 8842            let x = "ˇ"
 8843        "#
 8844        .unindent(),
 8845    );
 8846
 8847    // Inserting another quotation mark. The cursor moves across the existing
 8848    // automatically-inserted quotation mark.
 8849    cx.update_editor(|editor, window, cx| {
 8850        editor.handle_input("\"", window, cx);
 8851    });
 8852    cx.assert_editor_state(
 8853        &r#"
 8854            let x = ""ˇ
 8855        "#
 8856        .unindent(),
 8857    );
 8858
 8859    // Reset
 8860    cx.set_state(
 8861        &r#"
 8862            let x = ˇ
 8863        "#
 8864        .unindent(),
 8865    );
 8866
 8867    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
 8868    cx.update_editor(|editor, window, cx| {
 8869        editor.handle_input("\"", window, cx);
 8870        editor.handle_input(" ", window, cx);
 8871        editor.move_left(&Default::default(), window, cx);
 8872        editor.handle_input("\\", window, cx);
 8873        editor.handle_input("\"", window, cx);
 8874    });
 8875    cx.assert_editor_state(
 8876        &r#"
 8877            let x = "\"ˇ "
 8878        "#
 8879        .unindent(),
 8880    );
 8881
 8882    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
 8883    // mark. Nothing is inserted.
 8884    cx.update_editor(|editor, window, cx| {
 8885        editor.move_right(&Default::default(), window, cx);
 8886        editor.handle_input("\"", window, cx);
 8887    });
 8888    cx.assert_editor_state(
 8889        &r#"
 8890            let x = "\" "ˇ
 8891        "#
 8892        .unindent(),
 8893    );
 8894}
 8895
 8896#[gpui::test]
 8897async fn test_surround_with_pair(cx: &mut TestAppContext) {
 8898    init_test(cx, |_| {});
 8899
 8900    let language = Arc::new(Language::new(
 8901        LanguageConfig {
 8902            brackets: BracketPairConfig {
 8903                pairs: vec![
 8904                    BracketPair {
 8905                        start: "{".to_string(),
 8906                        end: "}".to_string(),
 8907                        close: true,
 8908                        surround: true,
 8909                        newline: true,
 8910                    },
 8911                    BracketPair {
 8912                        start: "/* ".to_string(),
 8913                        end: "*/".to_string(),
 8914                        close: true,
 8915                        surround: true,
 8916                        ..Default::default()
 8917                    },
 8918                ],
 8919                ..Default::default()
 8920            },
 8921            ..Default::default()
 8922        },
 8923        Some(tree_sitter_rust::LANGUAGE.into()),
 8924    ));
 8925
 8926    let text = r#"
 8927        a
 8928        b
 8929        c
 8930    "#
 8931    .unindent();
 8932
 8933    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8934    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8935    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8936    editor
 8937        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8938        .await;
 8939
 8940    editor.update_in(cx, |editor, window, cx| {
 8941        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8942            s.select_display_ranges([
 8943                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8944                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8945                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
 8946            ])
 8947        });
 8948
 8949        editor.handle_input("{", window, cx);
 8950        editor.handle_input("{", window, cx);
 8951        editor.handle_input("{", window, cx);
 8952        assert_eq!(
 8953            editor.text(cx),
 8954            "
 8955                {{{a}}}
 8956                {{{b}}}
 8957                {{{c}}}
 8958            "
 8959            .unindent()
 8960        );
 8961        assert_eq!(
 8962            editor.selections.display_ranges(cx),
 8963            [
 8964                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
 8965                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
 8966                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
 8967            ]
 8968        );
 8969
 8970        editor.undo(&Undo, window, cx);
 8971        editor.undo(&Undo, window, cx);
 8972        editor.undo(&Undo, window, cx);
 8973        assert_eq!(
 8974            editor.text(cx),
 8975            "
 8976                a
 8977                b
 8978                c
 8979            "
 8980            .unindent()
 8981        );
 8982        assert_eq!(
 8983            editor.selections.display_ranges(cx),
 8984            [
 8985                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8986                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8987                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 8988            ]
 8989        );
 8990
 8991        // Ensure inserting the first character of a multi-byte bracket pair
 8992        // doesn't surround the selections with the bracket.
 8993        editor.handle_input("/", window, cx);
 8994        assert_eq!(
 8995            editor.text(cx),
 8996            "
 8997                /
 8998                /
 8999                /
 9000            "
 9001            .unindent()
 9002        );
 9003        assert_eq!(
 9004            editor.selections.display_ranges(cx),
 9005            [
 9006                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 9007                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 9008                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 9009            ]
 9010        );
 9011
 9012        editor.undo(&Undo, window, cx);
 9013        assert_eq!(
 9014            editor.text(cx),
 9015            "
 9016                a
 9017                b
 9018                c
 9019            "
 9020            .unindent()
 9021        );
 9022        assert_eq!(
 9023            editor.selections.display_ranges(cx),
 9024            [
 9025                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 9026                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 9027                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 9028            ]
 9029        );
 9030
 9031        // Ensure inserting the last character of a multi-byte bracket pair
 9032        // doesn't surround the selections with the bracket.
 9033        editor.handle_input("*", window, cx);
 9034        assert_eq!(
 9035            editor.text(cx),
 9036            "
 9037                *
 9038                *
 9039                *
 9040            "
 9041            .unindent()
 9042        );
 9043        assert_eq!(
 9044            editor.selections.display_ranges(cx),
 9045            [
 9046                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 9047                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 9048                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 9049            ]
 9050        );
 9051    });
 9052}
 9053
 9054#[gpui::test]
 9055async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
 9056    init_test(cx, |_| {});
 9057
 9058    let language = Arc::new(Language::new(
 9059        LanguageConfig {
 9060            brackets: BracketPairConfig {
 9061                pairs: vec![BracketPair {
 9062                    start: "{".to_string(),
 9063                    end: "}".to_string(),
 9064                    close: true,
 9065                    surround: true,
 9066                    newline: true,
 9067                }],
 9068                ..Default::default()
 9069            },
 9070            autoclose_before: "}".to_string(),
 9071            ..Default::default()
 9072        },
 9073        Some(tree_sitter_rust::LANGUAGE.into()),
 9074    ));
 9075
 9076    let text = r#"
 9077        a
 9078        b
 9079        c
 9080    "#
 9081    .unindent();
 9082
 9083    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9084    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9085    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9086    editor
 9087        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9088        .await;
 9089
 9090    editor.update_in(cx, |editor, window, cx| {
 9091        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9092            s.select_ranges([
 9093                Point::new(0, 1)..Point::new(0, 1),
 9094                Point::new(1, 1)..Point::new(1, 1),
 9095                Point::new(2, 1)..Point::new(2, 1),
 9096            ])
 9097        });
 9098
 9099        editor.handle_input("{", window, cx);
 9100        editor.handle_input("{", window, cx);
 9101        editor.handle_input("_", window, cx);
 9102        assert_eq!(
 9103            editor.text(cx),
 9104            "
 9105                a{{_}}
 9106                b{{_}}
 9107                c{{_}}
 9108            "
 9109            .unindent()
 9110        );
 9111        assert_eq!(
 9112            editor.selections.ranges::<Point>(cx),
 9113            [
 9114                Point::new(0, 4)..Point::new(0, 4),
 9115                Point::new(1, 4)..Point::new(1, 4),
 9116                Point::new(2, 4)..Point::new(2, 4)
 9117            ]
 9118        );
 9119
 9120        editor.backspace(&Default::default(), window, cx);
 9121        editor.backspace(&Default::default(), window, cx);
 9122        assert_eq!(
 9123            editor.text(cx),
 9124            "
 9125                a{}
 9126                b{}
 9127                c{}
 9128            "
 9129            .unindent()
 9130        );
 9131        assert_eq!(
 9132            editor.selections.ranges::<Point>(cx),
 9133            [
 9134                Point::new(0, 2)..Point::new(0, 2),
 9135                Point::new(1, 2)..Point::new(1, 2),
 9136                Point::new(2, 2)..Point::new(2, 2)
 9137            ]
 9138        );
 9139
 9140        editor.delete_to_previous_word_start(&Default::default(), window, cx);
 9141        assert_eq!(
 9142            editor.text(cx),
 9143            "
 9144                a
 9145                b
 9146                c
 9147            "
 9148            .unindent()
 9149        );
 9150        assert_eq!(
 9151            editor.selections.ranges::<Point>(cx),
 9152            [
 9153                Point::new(0, 1)..Point::new(0, 1),
 9154                Point::new(1, 1)..Point::new(1, 1),
 9155                Point::new(2, 1)..Point::new(2, 1)
 9156            ]
 9157        );
 9158    });
 9159}
 9160
 9161#[gpui::test]
 9162async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
 9163    init_test(cx, |settings| {
 9164        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 9165    });
 9166
 9167    let mut cx = EditorTestContext::new(cx).await;
 9168
 9169    let language = Arc::new(Language::new(
 9170        LanguageConfig {
 9171            brackets: BracketPairConfig {
 9172                pairs: vec![
 9173                    BracketPair {
 9174                        start: "{".to_string(),
 9175                        end: "}".to_string(),
 9176                        close: true,
 9177                        surround: true,
 9178                        newline: true,
 9179                    },
 9180                    BracketPair {
 9181                        start: "(".to_string(),
 9182                        end: ")".to_string(),
 9183                        close: true,
 9184                        surround: true,
 9185                        newline: true,
 9186                    },
 9187                    BracketPair {
 9188                        start: "[".to_string(),
 9189                        end: "]".to_string(),
 9190                        close: false,
 9191                        surround: true,
 9192                        newline: true,
 9193                    },
 9194                ],
 9195                ..Default::default()
 9196            },
 9197            autoclose_before: "})]".to_string(),
 9198            ..Default::default()
 9199        },
 9200        Some(tree_sitter_rust::LANGUAGE.into()),
 9201    ));
 9202
 9203    cx.language_registry().add(language.clone());
 9204    cx.update_buffer(|buffer, cx| {
 9205        buffer.set_language(Some(language), cx);
 9206    });
 9207
 9208    cx.set_state(
 9209        &"
 9210            {(ˇ)}
 9211            [[ˇ]]
 9212            {(ˇ)}
 9213        "
 9214        .unindent(),
 9215    );
 9216
 9217    cx.update_editor(|editor, window, cx| {
 9218        editor.backspace(&Default::default(), window, cx);
 9219        editor.backspace(&Default::default(), window, cx);
 9220    });
 9221
 9222    cx.assert_editor_state(
 9223        &"
 9224            ˇ
 9225            ˇ]]
 9226            ˇ
 9227        "
 9228        .unindent(),
 9229    );
 9230
 9231    cx.update_editor(|editor, window, cx| {
 9232        editor.handle_input("{", window, cx);
 9233        editor.handle_input("{", window, cx);
 9234        editor.move_right(&MoveRight, window, cx);
 9235        editor.move_right(&MoveRight, window, cx);
 9236        editor.move_left(&MoveLeft, window, cx);
 9237        editor.move_left(&MoveLeft, window, cx);
 9238        editor.backspace(&Default::default(), window, cx);
 9239    });
 9240
 9241    cx.assert_editor_state(
 9242        &"
 9243            {ˇ}
 9244            {ˇ}]]
 9245            {ˇ}
 9246        "
 9247        .unindent(),
 9248    );
 9249
 9250    cx.update_editor(|editor, window, cx| {
 9251        editor.backspace(&Default::default(), window, cx);
 9252    });
 9253
 9254    cx.assert_editor_state(
 9255        &"
 9256            ˇ
 9257            ˇ]]
 9258            ˇ
 9259        "
 9260        .unindent(),
 9261    );
 9262}
 9263
 9264#[gpui::test]
 9265async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
 9266    init_test(cx, |_| {});
 9267
 9268    let language = Arc::new(Language::new(
 9269        LanguageConfig::default(),
 9270        Some(tree_sitter_rust::LANGUAGE.into()),
 9271    ));
 9272
 9273    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
 9274    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9275    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9276    editor
 9277        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9278        .await;
 9279
 9280    editor.update_in(cx, |editor, window, cx| {
 9281        editor.set_auto_replace_emoji_shortcode(true);
 9282
 9283        editor.handle_input("Hello ", window, cx);
 9284        editor.handle_input(":wave", window, cx);
 9285        assert_eq!(editor.text(cx), "Hello :wave".unindent());
 9286
 9287        editor.handle_input(":", window, cx);
 9288        assert_eq!(editor.text(cx), "Hello 👋".unindent());
 9289
 9290        editor.handle_input(" :smile", window, cx);
 9291        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
 9292
 9293        editor.handle_input(":", window, cx);
 9294        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
 9295
 9296        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
 9297        editor.handle_input(":wave", window, cx);
 9298        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
 9299
 9300        editor.handle_input(":", window, cx);
 9301        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
 9302
 9303        editor.handle_input(":1", window, cx);
 9304        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
 9305
 9306        editor.handle_input(":", window, cx);
 9307        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
 9308
 9309        // Ensure shortcode does not get replaced when it is part of a word
 9310        editor.handle_input(" Test:wave", window, cx);
 9311        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
 9312
 9313        editor.handle_input(":", window, cx);
 9314        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
 9315
 9316        editor.set_auto_replace_emoji_shortcode(false);
 9317
 9318        // Ensure shortcode does not get replaced when auto replace is off
 9319        editor.handle_input(" :wave", window, cx);
 9320        assert_eq!(
 9321            editor.text(cx),
 9322            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
 9323        );
 9324
 9325        editor.handle_input(":", window, cx);
 9326        assert_eq!(
 9327            editor.text(cx),
 9328            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
 9329        );
 9330    });
 9331}
 9332
 9333#[gpui::test]
 9334async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
 9335    init_test(cx, |_| {});
 9336
 9337    let (text, insertion_ranges) = marked_text_ranges(
 9338        indoc! {"
 9339            ˇ
 9340        "},
 9341        false,
 9342    );
 9343
 9344    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
 9345    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9346
 9347    _ = editor.update_in(cx, |editor, window, cx| {
 9348        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
 9349
 9350        editor
 9351            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9352            .unwrap();
 9353
 9354        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
 9355            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
 9356            assert_eq!(editor.text(cx), expected_text);
 9357            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
 9358        }
 9359
 9360        assert(
 9361            editor,
 9362            cx,
 9363            indoc! {"
 9364            type «» =•
 9365            "},
 9366        );
 9367
 9368        assert!(editor.context_menu_visible(), "There should be a matches");
 9369    });
 9370}
 9371
 9372#[gpui::test]
 9373async fn test_snippets(cx: &mut TestAppContext) {
 9374    init_test(cx, |_| {});
 9375
 9376    let mut cx = EditorTestContext::new(cx).await;
 9377
 9378    cx.set_state(indoc! {"
 9379        a.ˇ b
 9380        a.ˇ b
 9381        a.ˇ b
 9382    "});
 9383
 9384    cx.update_editor(|editor, window, cx| {
 9385        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
 9386        let insertion_ranges = editor
 9387            .selections
 9388            .all(cx)
 9389            .iter()
 9390            .map(|s| s.range().clone())
 9391            .collect::<Vec<_>>();
 9392        editor
 9393            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9394            .unwrap();
 9395    });
 9396
 9397    cx.assert_editor_state(indoc! {"
 9398        a.f(«oneˇ», two, «threeˇ») b
 9399        a.f(«oneˇ», two, «threeˇ») b
 9400        a.f(«oneˇ», two, «threeˇ») b
 9401    "});
 9402
 9403    // Can't move earlier than the first tab stop
 9404    cx.update_editor(|editor, window, cx| {
 9405        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 9406    });
 9407    cx.assert_editor_state(indoc! {"
 9408        a.f(«oneˇ», two, «threeˇ») b
 9409        a.f(«oneˇ», two, «threeˇ») b
 9410        a.f(«oneˇ», two, «threeˇ») b
 9411    "});
 9412
 9413    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9414    cx.assert_editor_state(indoc! {"
 9415        a.f(one, «twoˇ», three) b
 9416        a.f(one, «twoˇ», three) b
 9417        a.f(one, «twoˇ», three) b
 9418    "});
 9419
 9420    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
 9421    cx.assert_editor_state(indoc! {"
 9422        a.f(«oneˇ», two, «threeˇ») b
 9423        a.f(«oneˇ», two, «threeˇ») b
 9424        a.f(«oneˇ», two, «threeˇ») b
 9425    "});
 9426
 9427    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9428    cx.assert_editor_state(indoc! {"
 9429        a.f(one, «twoˇ», three) b
 9430        a.f(one, «twoˇ», three) b
 9431        a.f(one, «twoˇ», three) b
 9432    "});
 9433    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9434    cx.assert_editor_state(indoc! {"
 9435        a.f(one, two, three)ˇ b
 9436        a.f(one, two, three)ˇ b
 9437        a.f(one, two, three)ˇ b
 9438    "});
 9439
 9440    // As soon as the last tab stop is reached, snippet state is gone
 9441    cx.update_editor(|editor, window, cx| {
 9442        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 9443    });
 9444    cx.assert_editor_state(indoc! {"
 9445        a.f(one, two, three)ˇ b
 9446        a.f(one, two, three)ˇ b
 9447        a.f(one, two, three)ˇ b
 9448    "});
 9449}
 9450
 9451#[gpui::test]
 9452async fn test_snippet_indentation(cx: &mut TestAppContext) {
 9453    init_test(cx, |_| {});
 9454
 9455    let mut cx = EditorTestContext::new(cx).await;
 9456
 9457    cx.update_editor(|editor, window, cx| {
 9458        let snippet = Snippet::parse(indoc! {"
 9459            /*
 9460             * Multiline comment with leading indentation
 9461             *
 9462             * $1
 9463             */
 9464            $0"})
 9465        .unwrap();
 9466        let insertion_ranges = editor
 9467            .selections
 9468            .all(cx)
 9469            .iter()
 9470            .map(|s| s.range().clone())
 9471            .collect::<Vec<_>>();
 9472        editor
 9473            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9474            .unwrap();
 9475    });
 9476
 9477    cx.assert_editor_state(indoc! {"
 9478        /*
 9479         * Multiline comment with leading indentation
 9480         *
 9481         * ˇ
 9482         */
 9483    "});
 9484
 9485    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9486    cx.assert_editor_state(indoc! {"
 9487        /*
 9488         * Multiline comment with leading indentation
 9489         *
 9490         *•
 9491         */
 9492        ˇ"});
 9493}
 9494
 9495#[gpui::test]
 9496async fn test_document_format_during_save(cx: &mut TestAppContext) {
 9497    init_test(cx, |_| {});
 9498
 9499    let fs = FakeFs::new(cx.executor());
 9500    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9501
 9502    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
 9503
 9504    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9505    language_registry.add(rust_lang());
 9506    let mut fake_servers = language_registry.register_fake_lsp(
 9507        "Rust",
 9508        FakeLspAdapter {
 9509            capabilities: lsp::ServerCapabilities {
 9510                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9511                ..Default::default()
 9512            },
 9513            ..Default::default()
 9514        },
 9515    );
 9516
 9517    let buffer = project
 9518        .update(cx, |project, cx| {
 9519            project.open_local_buffer(path!("/file.rs"), cx)
 9520        })
 9521        .await
 9522        .unwrap();
 9523
 9524    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9525    let (editor, cx) = cx.add_window_view(|window, cx| {
 9526        build_editor_with_project(project.clone(), buffer, window, cx)
 9527    });
 9528    editor.update_in(cx, |editor, window, cx| {
 9529        editor.set_text("one\ntwo\nthree\n", window, cx)
 9530    });
 9531    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9532
 9533    cx.executor().start_waiting();
 9534    let fake_server = fake_servers.next().await.unwrap();
 9535
 9536    {
 9537        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9538            move |params, _| async move {
 9539                assert_eq!(
 9540                    params.text_document.uri,
 9541                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9542                );
 9543                assert_eq!(params.options.tab_size, 4);
 9544                Ok(Some(vec![lsp::TextEdit::new(
 9545                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9546                    ", ".to_string(),
 9547                )]))
 9548            },
 9549        );
 9550        let save = editor
 9551            .update_in(cx, |editor, window, cx| {
 9552                editor.save(
 9553                    SaveOptions {
 9554                        format: true,
 9555                        autosave: false,
 9556                    },
 9557                    project.clone(),
 9558                    window,
 9559                    cx,
 9560                )
 9561            })
 9562            .unwrap();
 9563        cx.executor().start_waiting();
 9564        save.await;
 9565
 9566        assert_eq!(
 9567            editor.update(cx, |editor, cx| editor.text(cx)),
 9568            "one, two\nthree\n"
 9569        );
 9570        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9571    }
 9572
 9573    {
 9574        editor.update_in(cx, |editor, window, cx| {
 9575            editor.set_text("one\ntwo\nthree\n", window, cx)
 9576        });
 9577        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9578
 9579        // Ensure we can still save even if formatting hangs.
 9580        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9581            move |params, _| async move {
 9582                assert_eq!(
 9583                    params.text_document.uri,
 9584                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9585                );
 9586                futures::future::pending::<()>().await;
 9587                unreachable!()
 9588            },
 9589        );
 9590        let save = editor
 9591            .update_in(cx, |editor, window, cx| {
 9592                editor.save(
 9593                    SaveOptions {
 9594                        format: true,
 9595                        autosave: false,
 9596                    },
 9597                    project.clone(),
 9598                    window,
 9599                    cx,
 9600                )
 9601            })
 9602            .unwrap();
 9603        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9604        cx.executor().start_waiting();
 9605        save.await;
 9606        assert_eq!(
 9607            editor.update(cx, |editor, cx| editor.text(cx)),
 9608            "one\ntwo\nthree\n"
 9609        );
 9610    }
 9611
 9612    // Set rust language override and assert overridden tabsize is sent to language server
 9613    update_test_language_settings(cx, |settings| {
 9614        settings.languages.0.insert(
 9615            "Rust".into(),
 9616            LanguageSettingsContent {
 9617                tab_size: NonZeroU32::new(8),
 9618                ..Default::default()
 9619            },
 9620        );
 9621    });
 9622
 9623    {
 9624        editor.update_in(cx, |editor, window, cx| {
 9625            editor.set_text("somehting_new\n", window, cx)
 9626        });
 9627        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9628        let _formatting_request_signal = fake_server
 9629            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9630                assert_eq!(
 9631                    params.text_document.uri,
 9632                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9633                );
 9634                assert_eq!(params.options.tab_size, 8);
 9635                Ok(Some(vec![]))
 9636            });
 9637        let save = editor
 9638            .update_in(cx, |editor, window, cx| {
 9639                editor.save(
 9640                    SaveOptions {
 9641                        format: true,
 9642                        autosave: false,
 9643                    },
 9644                    project.clone(),
 9645                    window,
 9646                    cx,
 9647                )
 9648            })
 9649            .unwrap();
 9650        cx.executor().start_waiting();
 9651        save.await;
 9652    }
 9653}
 9654
 9655#[gpui::test]
 9656async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
 9657    init_test(cx, |settings| {
 9658        settings.defaults.ensure_final_newline_on_save = Some(false);
 9659    });
 9660
 9661    let fs = FakeFs::new(cx.executor());
 9662    fs.insert_file(path!("/file.txt"), "foo".into()).await;
 9663
 9664    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
 9665
 9666    let buffer = project
 9667        .update(cx, |project, cx| {
 9668            project.open_local_buffer(path!("/file.txt"), cx)
 9669        })
 9670        .await
 9671        .unwrap();
 9672
 9673    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9674    let (editor, cx) = cx.add_window_view(|window, cx| {
 9675        build_editor_with_project(project.clone(), buffer, window, cx)
 9676    });
 9677    editor.update_in(cx, |editor, window, cx| {
 9678        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 9679            s.select_ranges([0..0])
 9680        });
 9681    });
 9682    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9683
 9684    editor.update_in(cx, |editor, window, cx| {
 9685        editor.handle_input("\n", window, cx)
 9686    });
 9687    cx.run_until_parked();
 9688    save(&editor, &project, cx).await;
 9689    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
 9690
 9691    editor.update_in(cx, |editor, window, cx| {
 9692        editor.undo(&Default::default(), window, cx);
 9693    });
 9694    save(&editor, &project, cx).await;
 9695    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
 9696
 9697    editor.update_in(cx, |editor, window, cx| {
 9698        editor.redo(&Default::default(), window, cx);
 9699    });
 9700    cx.run_until_parked();
 9701    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
 9702
 9703    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
 9704        let save = editor
 9705            .update_in(cx, |editor, window, cx| {
 9706                editor.save(
 9707                    SaveOptions {
 9708                        format: true,
 9709                        autosave: false,
 9710                    },
 9711                    project.clone(),
 9712                    window,
 9713                    cx,
 9714                )
 9715            })
 9716            .unwrap();
 9717        cx.executor().start_waiting();
 9718        save.await;
 9719        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9720    }
 9721}
 9722
 9723#[gpui::test]
 9724async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
 9725    init_test(cx, |_| {});
 9726
 9727    let cols = 4;
 9728    let rows = 10;
 9729    let sample_text_1 = sample_text(rows, cols, 'a');
 9730    assert_eq!(
 9731        sample_text_1,
 9732        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
 9733    );
 9734    let sample_text_2 = sample_text(rows, cols, 'l');
 9735    assert_eq!(
 9736        sample_text_2,
 9737        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
 9738    );
 9739    let sample_text_3 = sample_text(rows, cols, 'v');
 9740    assert_eq!(
 9741        sample_text_3,
 9742        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
 9743    );
 9744
 9745    let fs = FakeFs::new(cx.executor());
 9746    fs.insert_tree(
 9747        path!("/a"),
 9748        json!({
 9749            "main.rs": sample_text_1,
 9750            "other.rs": sample_text_2,
 9751            "lib.rs": sample_text_3,
 9752        }),
 9753    )
 9754    .await;
 9755
 9756    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
 9757    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9758    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 9759
 9760    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9761    language_registry.add(rust_lang());
 9762    let mut fake_servers = language_registry.register_fake_lsp(
 9763        "Rust",
 9764        FakeLspAdapter {
 9765            capabilities: lsp::ServerCapabilities {
 9766                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9767                ..Default::default()
 9768            },
 9769            ..Default::default()
 9770        },
 9771    );
 9772
 9773    let worktree = project.update(cx, |project, cx| {
 9774        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
 9775        assert_eq!(worktrees.len(), 1);
 9776        worktrees.pop().unwrap()
 9777    });
 9778    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 9779
 9780    let buffer_1 = project
 9781        .update(cx, |project, cx| {
 9782            project.open_buffer((worktree_id, "main.rs"), cx)
 9783        })
 9784        .await
 9785        .unwrap();
 9786    let buffer_2 = project
 9787        .update(cx, |project, cx| {
 9788            project.open_buffer((worktree_id, "other.rs"), cx)
 9789        })
 9790        .await
 9791        .unwrap();
 9792    let buffer_3 = project
 9793        .update(cx, |project, cx| {
 9794            project.open_buffer((worktree_id, "lib.rs"), cx)
 9795        })
 9796        .await
 9797        .unwrap();
 9798
 9799    let multi_buffer = cx.new(|cx| {
 9800        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 9801        multi_buffer.push_excerpts(
 9802            buffer_1.clone(),
 9803            [
 9804                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9805                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9806                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9807            ],
 9808            cx,
 9809        );
 9810        multi_buffer.push_excerpts(
 9811            buffer_2.clone(),
 9812            [
 9813                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9814                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9815                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9816            ],
 9817            cx,
 9818        );
 9819        multi_buffer.push_excerpts(
 9820            buffer_3.clone(),
 9821            [
 9822                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9823                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9824                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9825            ],
 9826            cx,
 9827        );
 9828        multi_buffer
 9829    });
 9830    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
 9831        Editor::new(
 9832            EditorMode::full(),
 9833            multi_buffer,
 9834            Some(project.clone()),
 9835            window,
 9836            cx,
 9837        )
 9838    });
 9839
 9840    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 9841        editor.change_selections(
 9842            SelectionEffects::scroll(Autoscroll::Next),
 9843            window,
 9844            cx,
 9845            |s| s.select_ranges(Some(1..2)),
 9846        );
 9847        editor.insert("|one|two|three|", window, cx);
 9848    });
 9849    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 9850    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 9851        editor.change_selections(
 9852            SelectionEffects::scroll(Autoscroll::Next),
 9853            window,
 9854            cx,
 9855            |s| s.select_ranges(Some(60..70)),
 9856        );
 9857        editor.insert("|four|five|six|", window, cx);
 9858    });
 9859    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 9860
 9861    // First two buffers should be edited, but not the third one.
 9862    assert_eq!(
 9863        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 9864        "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}",
 9865    );
 9866    buffer_1.update(cx, |buffer, _| {
 9867        assert!(buffer.is_dirty());
 9868        assert_eq!(
 9869            buffer.text(),
 9870            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
 9871        )
 9872    });
 9873    buffer_2.update(cx, |buffer, _| {
 9874        assert!(buffer.is_dirty());
 9875        assert_eq!(
 9876            buffer.text(),
 9877            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
 9878        )
 9879    });
 9880    buffer_3.update(cx, |buffer, _| {
 9881        assert!(!buffer.is_dirty());
 9882        assert_eq!(buffer.text(), sample_text_3,)
 9883    });
 9884    cx.executor().run_until_parked();
 9885
 9886    cx.executor().start_waiting();
 9887    let save = multi_buffer_editor
 9888        .update_in(cx, |editor, window, cx| {
 9889            editor.save(
 9890                SaveOptions {
 9891                    format: true,
 9892                    autosave: false,
 9893                },
 9894                project.clone(),
 9895                window,
 9896                cx,
 9897            )
 9898        })
 9899        .unwrap();
 9900
 9901    let fake_server = fake_servers.next().await.unwrap();
 9902    fake_server
 9903        .server
 9904        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9905            Ok(Some(vec![lsp::TextEdit::new(
 9906                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9907                format!("[{} formatted]", params.text_document.uri),
 9908            )]))
 9909        })
 9910        .detach();
 9911    save.await;
 9912
 9913    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
 9914    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
 9915    assert_eq!(
 9916        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 9917        uri!(
 9918            "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}"
 9919        ),
 9920    );
 9921    buffer_1.update(cx, |buffer, _| {
 9922        assert!(!buffer.is_dirty());
 9923        assert_eq!(
 9924            buffer.text(),
 9925            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
 9926        )
 9927    });
 9928    buffer_2.update(cx, |buffer, _| {
 9929        assert!(!buffer.is_dirty());
 9930        assert_eq!(
 9931            buffer.text(),
 9932            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
 9933        )
 9934    });
 9935    buffer_3.update(cx, |buffer, _| {
 9936        assert!(!buffer.is_dirty());
 9937        assert_eq!(buffer.text(), sample_text_3,)
 9938    });
 9939}
 9940
 9941#[gpui::test]
 9942async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
 9943    init_test(cx, |_| {});
 9944
 9945    let fs = FakeFs::new(cx.executor());
 9946    fs.insert_tree(
 9947        path!("/dir"),
 9948        json!({
 9949            "file1.rs": "fn main() { println!(\"hello\"); }",
 9950            "file2.rs": "fn test() { println!(\"test\"); }",
 9951            "file3.rs": "fn other() { println!(\"other\"); }\n",
 9952        }),
 9953    )
 9954    .await;
 9955
 9956    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
 9957    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9958    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 9959
 9960    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9961    language_registry.add(rust_lang());
 9962
 9963    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
 9964    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 9965
 9966    // Open three buffers
 9967    let buffer_1 = project
 9968        .update(cx, |project, cx| {
 9969            project.open_buffer((worktree_id, "file1.rs"), cx)
 9970        })
 9971        .await
 9972        .unwrap();
 9973    let buffer_2 = project
 9974        .update(cx, |project, cx| {
 9975            project.open_buffer((worktree_id, "file2.rs"), cx)
 9976        })
 9977        .await
 9978        .unwrap();
 9979    let buffer_3 = project
 9980        .update(cx, |project, cx| {
 9981            project.open_buffer((worktree_id, "file3.rs"), cx)
 9982        })
 9983        .await
 9984        .unwrap();
 9985
 9986    // Create a multi-buffer with all three buffers
 9987    let multi_buffer = cx.new(|cx| {
 9988        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 9989        multi_buffer.push_excerpts(
 9990            buffer_1.clone(),
 9991            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9992            cx,
 9993        );
 9994        multi_buffer.push_excerpts(
 9995            buffer_2.clone(),
 9996            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9997            cx,
 9998        );
 9999        multi_buffer.push_excerpts(
10000            buffer_3.clone(),
10001            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
10002            cx,
10003        );
10004        multi_buffer
10005    });
10006
10007    let editor = cx.new_window_entity(|window, cx| {
10008        Editor::new(
10009            EditorMode::full(),
10010            multi_buffer,
10011            Some(project.clone()),
10012            window,
10013            cx,
10014        )
10015    });
10016
10017    // Edit only the first buffer
10018    editor.update_in(cx, |editor, window, cx| {
10019        editor.change_selections(
10020            SelectionEffects::scroll(Autoscroll::Next),
10021            window,
10022            cx,
10023            |s| s.select_ranges(Some(10..10)),
10024        );
10025        editor.insert("// edited", window, cx);
10026    });
10027
10028    // Verify that only buffer 1 is dirty
10029    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
10030    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10031    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10032
10033    // Get write counts after file creation (files were created with initial content)
10034    // We expect each file to have been written once during creation
10035    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
10036    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
10037    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
10038
10039    // Perform autosave
10040    let save_task = editor.update_in(cx, |editor, window, cx| {
10041        editor.save(
10042            SaveOptions {
10043                format: true,
10044                autosave: true,
10045            },
10046            project.clone(),
10047            window,
10048            cx,
10049        )
10050    });
10051    save_task.await.unwrap();
10052
10053    // Only the dirty buffer should have been saved
10054    assert_eq!(
10055        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
10056        1,
10057        "Buffer 1 was dirty, so it should have been written once during autosave"
10058    );
10059    assert_eq!(
10060        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
10061        0,
10062        "Buffer 2 was clean, so it should not have been written during autosave"
10063    );
10064    assert_eq!(
10065        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
10066        0,
10067        "Buffer 3 was clean, so it should not have been written during autosave"
10068    );
10069
10070    // Verify buffer states after autosave
10071    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10072    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10073    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10074
10075    // Now perform a manual save (format = true)
10076    let save_task = editor.update_in(cx, |editor, window, cx| {
10077        editor.save(
10078            SaveOptions {
10079                format: true,
10080                autosave: false,
10081            },
10082            project.clone(),
10083            window,
10084            cx,
10085        )
10086    });
10087    save_task.await.unwrap();
10088
10089    // During manual save, clean buffers don't get written to disk
10090    // They just get did_save called for language server notifications
10091    assert_eq!(
10092        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
10093        1,
10094        "Buffer 1 should only have been written once total (during autosave, not manual save)"
10095    );
10096    assert_eq!(
10097        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
10098        0,
10099        "Buffer 2 should not have been written at all"
10100    );
10101    assert_eq!(
10102        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
10103        0,
10104        "Buffer 3 should not have been written at all"
10105    );
10106}
10107
10108async fn setup_range_format_test(
10109    cx: &mut TestAppContext,
10110) -> (
10111    Entity<Project>,
10112    Entity<Editor>,
10113    &mut gpui::VisualTestContext,
10114    lsp::FakeLanguageServer,
10115) {
10116    init_test(cx, |_| {});
10117
10118    let fs = FakeFs::new(cx.executor());
10119    fs.insert_file(path!("/file.rs"), Default::default()).await;
10120
10121    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10122
10123    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10124    language_registry.add(rust_lang());
10125    let mut fake_servers = language_registry.register_fake_lsp(
10126        "Rust",
10127        FakeLspAdapter {
10128            capabilities: lsp::ServerCapabilities {
10129                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
10130                ..lsp::ServerCapabilities::default()
10131            },
10132            ..FakeLspAdapter::default()
10133        },
10134    );
10135
10136    let buffer = project
10137        .update(cx, |project, cx| {
10138            project.open_local_buffer(path!("/file.rs"), cx)
10139        })
10140        .await
10141        .unwrap();
10142
10143    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10144    let (editor, cx) = cx.add_window_view(|window, cx| {
10145        build_editor_with_project(project.clone(), buffer, window, cx)
10146    });
10147
10148    cx.executor().start_waiting();
10149    let fake_server = fake_servers.next().await.unwrap();
10150
10151    (project, editor, cx, fake_server)
10152}
10153
10154#[gpui::test]
10155async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
10156    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
10157
10158    editor.update_in(cx, |editor, window, cx| {
10159        editor.set_text("one\ntwo\nthree\n", window, cx)
10160    });
10161    assert!(cx.read(|cx| editor.is_dirty(cx)));
10162
10163    let save = editor
10164        .update_in(cx, |editor, window, cx| {
10165            editor.save(
10166                SaveOptions {
10167                    format: true,
10168                    autosave: false,
10169                },
10170                project.clone(),
10171                window,
10172                cx,
10173            )
10174        })
10175        .unwrap();
10176    fake_server
10177        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
10178            assert_eq!(
10179                params.text_document.uri,
10180                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10181            );
10182            assert_eq!(params.options.tab_size, 4);
10183            Ok(Some(vec![lsp::TextEdit::new(
10184                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10185                ", ".to_string(),
10186            )]))
10187        })
10188        .next()
10189        .await;
10190    cx.executor().start_waiting();
10191    save.await;
10192    assert_eq!(
10193        editor.update(cx, |editor, cx| editor.text(cx)),
10194        "one, two\nthree\n"
10195    );
10196    assert!(!cx.read(|cx| editor.is_dirty(cx)));
10197}
10198
10199#[gpui::test]
10200async fn test_range_format_on_save_timeout(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    // Test that save still works when formatting hangs
10209    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
10210        move |params, _| async move {
10211            assert_eq!(
10212                params.text_document.uri,
10213                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10214            );
10215            futures::future::pending::<()>().await;
10216            unreachable!()
10217        },
10218    );
10219    let save = editor
10220        .update_in(cx, |editor, window, cx| {
10221            editor.save(
10222                SaveOptions {
10223                    format: true,
10224                    autosave: false,
10225                },
10226                project.clone(),
10227                window,
10228                cx,
10229            )
10230        })
10231        .unwrap();
10232    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10233    cx.executor().start_waiting();
10234    save.await;
10235    assert_eq!(
10236        editor.update(cx, |editor, cx| editor.text(cx)),
10237        "one\ntwo\nthree\n"
10238    );
10239    assert!(!cx.read(|cx| editor.is_dirty(cx)));
10240}
10241
10242#[gpui::test]
10243async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
10244    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
10245
10246    // Buffer starts clean, no formatting should be requested
10247    let save = editor
10248        .update_in(cx, |editor, window, cx| {
10249            editor.save(
10250                SaveOptions {
10251                    format: false,
10252                    autosave: false,
10253                },
10254                project.clone(),
10255                window,
10256                cx,
10257            )
10258        })
10259        .unwrap();
10260    let _pending_format_request = fake_server
10261        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
10262            panic!("Should not be invoked");
10263        })
10264        .next();
10265    cx.executor().start_waiting();
10266    save.await;
10267    cx.run_until_parked();
10268}
10269
10270#[gpui::test]
10271async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
10272    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
10273
10274    // Set Rust language override and assert overridden tabsize is sent to language server
10275    update_test_language_settings(cx, |settings| {
10276        settings.languages.0.insert(
10277            "Rust".into(),
10278            LanguageSettingsContent {
10279                tab_size: NonZeroU32::new(8),
10280                ..Default::default()
10281            },
10282        );
10283    });
10284
10285    editor.update_in(cx, |editor, window, cx| {
10286        editor.set_text("something_new\n", window, cx)
10287    });
10288    assert!(cx.read(|cx| editor.is_dirty(cx)));
10289    let save = editor
10290        .update_in(cx, |editor, window, cx| {
10291            editor.save(
10292                SaveOptions {
10293                    format: true,
10294                    autosave: false,
10295                },
10296                project.clone(),
10297                window,
10298                cx,
10299            )
10300        })
10301        .unwrap();
10302    fake_server
10303        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
10304            assert_eq!(
10305                params.text_document.uri,
10306                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10307            );
10308            assert_eq!(params.options.tab_size, 8);
10309            Ok(Some(Vec::new()))
10310        })
10311        .next()
10312        .await;
10313    save.await;
10314}
10315
10316#[gpui::test]
10317async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
10318    init_test(cx, |settings| {
10319        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
10320            Formatter::LanguageServer { name: None },
10321        )))
10322    });
10323
10324    let fs = FakeFs::new(cx.executor());
10325    fs.insert_file(path!("/file.rs"), Default::default()).await;
10326
10327    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10328
10329    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10330    language_registry.add(Arc::new(Language::new(
10331        LanguageConfig {
10332            name: "Rust".into(),
10333            matcher: LanguageMatcher {
10334                path_suffixes: vec!["rs".to_string()],
10335                ..Default::default()
10336            },
10337            ..LanguageConfig::default()
10338        },
10339        Some(tree_sitter_rust::LANGUAGE.into()),
10340    )));
10341    update_test_language_settings(cx, |settings| {
10342        // Enable Prettier formatting for the same buffer, and ensure
10343        // LSP is called instead of Prettier.
10344        settings.defaults.prettier = Some(PrettierSettings {
10345            allowed: true,
10346            ..PrettierSettings::default()
10347        });
10348    });
10349    let mut fake_servers = language_registry.register_fake_lsp(
10350        "Rust",
10351        FakeLspAdapter {
10352            capabilities: lsp::ServerCapabilities {
10353                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10354                ..Default::default()
10355            },
10356            ..Default::default()
10357        },
10358    );
10359
10360    let buffer = project
10361        .update(cx, |project, cx| {
10362            project.open_local_buffer(path!("/file.rs"), cx)
10363        })
10364        .await
10365        .unwrap();
10366
10367    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10368    let (editor, cx) = cx.add_window_view(|window, cx| {
10369        build_editor_with_project(project.clone(), buffer, window, cx)
10370    });
10371    editor.update_in(cx, |editor, window, cx| {
10372        editor.set_text("one\ntwo\nthree\n", window, cx)
10373    });
10374
10375    cx.executor().start_waiting();
10376    let fake_server = fake_servers.next().await.unwrap();
10377
10378    let format = editor
10379        .update_in(cx, |editor, window, cx| {
10380            editor.perform_format(
10381                project.clone(),
10382                FormatTrigger::Manual,
10383                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10384                window,
10385                cx,
10386            )
10387        })
10388        .unwrap();
10389    fake_server
10390        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
10391            assert_eq!(
10392                params.text_document.uri,
10393                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10394            );
10395            assert_eq!(params.options.tab_size, 4);
10396            Ok(Some(vec![lsp::TextEdit::new(
10397                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10398                ", ".to_string(),
10399            )]))
10400        })
10401        .next()
10402        .await;
10403    cx.executor().start_waiting();
10404    format.await;
10405    assert_eq!(
10406        editor.update(cx, |editor, cx| editor.text(cx)),
10407        "one, two\nthree\n"
10408    );
10409
10410    editor.update_in(cx, |editor, window, cx| {
10411        editor.set_text("one\ntwo\nthree\n", window, cx)
10412    });
10413    // Ensure we don't lock if formatting hangs.
10414    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10415        move |params, _| async move {
10416            assert_eq!(
10417                params.text_document.uri,
10418                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10419            );
10420            futures::future::pending::<()>().await;
10421            unreachable!()
10422        },
10423    );
10424    let format = editor
10425        .update_in(cx, |editor, window, cx| {
10426            editor.perform_format(
10427                project,
10428                FormatTrigger::Manual,
10429                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10430                window,
10431                cx,
10432            )
10433        })
10434        .unwrap();
10435    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10436    cx.executor().start_waiting();
10437    format.await;
10438    assert_eq!(
10439        editor.update(cx, |editor, cx| editor.text(cx)),
10440        "one\ntwo\nthree\n"
10441    );
10442}
10443
10444#[gpui::test]
10445async fn test_multiple_formatters(cx: &mut TestAppContext) {
10446    init_test(cx, |settings| {
10447        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
10448        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
10449            Formatter::LanguageServer { name: None },
10450            Formatter::CodeActions(
10451                [
10452                    ("code-action-1".into(), true),
10453                    ("code-action-2".into(), true),
10454                ]
10455                .into_iter()
10456                .collect(),
10457            ),
10458        ])))
10459    });
10460
10461    let fs = FakeFs::new(cx.executor());
10462    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
10463        .await;
10464
10465    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10466    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10467    language_registry.add(rust_lang());
10468
10469    let mut fake_servers = language_registry.register_fake_lsp(
10470        "Rust",
10471        FakeLspAdapter {
10472            capabilities: lsp::ServerCapabilities {
10473                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10474                execute_command_provider: Some(lsp::ExecuteCommandOptions {
10475                    commands: vec!["the-command-for-code-action-1".into()],
10476                    ..Default::default()
10477                }),
10478                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10479                ..Default::default()
10480            },
10481            ..Default::default()
10482        },
10483    );
10484
10485    let buffer = project
10486        .update(cx, |project, cx| {
10487            project.open_local_buffer(path!("/file.rs"), cx)
10488        })
10489        .await
10490        .unwrap();
10491
10492    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10493    let (editor, cx) = cx.add_window_view(|window, cx| {
10494        build_editor_with_project(project.clone(), buffer, window, cx)
10495    });
10496
10497    cx.executor().start_waiting();
10498
10499    let fake_server = fake_servers.next().await.unwrap();
10500    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10501        move |_params, _| async move {
10502            Ok(Some(vec![lsp::TextEdit::new(
10503                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10504                "applied-formatting\n".to_string(),
10505            )]))
10506        },
10507    );
10508    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
10509        move |params, _| async move {
10510            assert_eq!(
10511                params.context.only,
10512                Some(vec!["code-action-1".into(), "code-action-2".into()])
10513            );
10514            let uri = lsp::Url::from_file_path(path!("/file.rs")).unwrap();
10515            Ok(Some(vec![
10516                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10517                    kind: Some("code-action-1".into()),
10518                    edit: Some(lsp::WorkspaceEdit::new(
10519                        [(
10520                            uri.clone(),
10521                            vec![lsp::TextEdit::new(
10522                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10523                                "applied-code-action-1-edit\n".to_string(),
10524                            )],
10525                        )]
10526                        .into_iter()
10527                        .collect(),
10528                    )),
10529                    command: Some(lsp::Command {
10530                        command: "the-command-for-code-action-1".into(),
10531                        ..Default::default()
10532                    }),
10533                    ..Default::default()
10534                }),
10535                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10536                    kind: Some("code-action-2".into()),
10537                    edit: Some(lsp::WorkspaceEdit::new(
10538                        [(
10539                            uri.clone(),
10540                            vec![lsp::TextEdit::new(
10541                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10542                                "applied-code-action-2-edit\n".to_string(),
10543                            )],
10544                        )]
10545                        .into_iter()
10546                        .collect(),
10547                    )),
10548                    ..Default::default()
10549                }),
10550            ]))
10551        },
10552    );
10553
10554    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
10555        move |params, _| async move { Ok(params) }
10556    });
10557
10558    let command_lock = Arc::new(futures::lock::Mutex::new(()));
10559    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
10560        let fake = fake_server.clone();
10561        let lock = command_lock.clone();
10562        move |params, _| {
10563            assert_eq!(params.command, "the-command-for-code-action-1");
10564            let fake = fake.clone();
10565            let lock = lock.clone();
10566            async move {
10567                lock.lock().await;
10568                fake.server
10569                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
10570                        label: None,
10571                        edit: lsp::WorkspaceEdit {
10572                            changes: Some(
10573                                [(
10574                                    lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
10575                                    vec![lsp::TextEdit {
10576                                        range: lsp::Range::new(
10577                                            lsp::Position::new(0, 0),
10578                                            lsp::Position::new(0, 0),
10579                                        ),
10580                                        new_text: "applied-code-action-1-command\n".into(),
10581                                    }],
10582                                )]
10583                                .into_iter()
10584                                .collect(),
10585                            ),
10586                            ..Default::default()
10587                        },
10588                    })
10589                    .await
10590                    .into_response()
10591                    .unwrap();
10592                Ok(Some(json!(null)))
10593            }
10594        }
10595    });
10596
10597    cx.executor().start_waiting();
10598    editor
10599        .update_in(cx, |editor, window, cx| {
10600            editor.perform_format(
10601                project.clone(),
10602                FormatTrigger::Manual,
10603                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10604                window,
10605                cx,
10606            )
10607        })
10608        .unwrap()
10609        .await;
10610    editor.update(cx, |editor, cx| {
10611        assert_eq!(
10612            editor.text(cx),
10613            r#"
10614                applied-code-action-2-edit
10615                applied-code-action-1-command
10616                applied-code-action-1-edit
10617                applied-formatting
10618                one
10619                two
10620                three
10621            "#
10622            .unindent()
10623        );
10624    });
10625
10626    editor.update_in(cx, |editor, window, cx| {
10627        editor.undo(&Default::default(), window, cx);
10628        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10629    });
10630
10631    // Perform a manual edit while waiting for an LSP command
10632    // that's being run as part of a formatting code action.
10633    let lock_guard = command_lock.lock().await;
10634    let format = editor
10635        .update_in(cx, |editor, window, cx| {
10636            editor.perform_format(
10637                project.clone(),
10638                FormatTrigger::Manual,
10639                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10640                window,
10641                cx,
10642            )
10643        })
10644        .unwrap();
10645    cx.run_until_parked();
10646    editor.update(cx, |editor, cx| {
10647        assert_eq!(
10648            editor.text(cx),
10649            r#"
10650                applied-code-action-1-edit
10651                applied-formatting
10652                one
10653                two
10654                three
10655            "#
10656            .unindent()
10657        );
10658
10659        editor.buffer.update(cx, |buffer, cx| {
10660            let ix = buffer.len(cx);
10661            buffer.edit([(ix..ix, "edited\n")], None, cx);
10662        });
10663    });
10664
10665    // Allow the LSP command to proceed. Because the buffer was edited,
10666    // the second code action will not be run.
10667    drop(lock_guard);
10668    format.await;
10669    editor.update_in(cx, |editor, window, cx| {
10670        assert_eq!(
10671            editor.text(cx),
10672            r#"
10673                applied-code-action-1-command
10674                applied-code-action-1-edit
10675                applied-formatting
10676                one
10677                two
10678                three
10679                edited
10680            "#
10681            .unindent()
10682        );
10683
10684        // The manual edit is undone first, because it is the last thing the user did
10685        // (even though the command completed afterwards).
10686        editor.undo(&Default::default(), window, cx);
10687        assert_eq!(
10688            editor.text(cx),
10689            r#"
10690                applied-code-action-1-command
10691                applied-code-action-1-edit
10692                applied-formatting
10693                one
10694                two
10695                three
10696            "#
10697            .unindent()
10698        );
10699
10700        // All the formatting (including the command, which completed after the manual edit)
10701        // is undone together.
10702        editor.undo(&Default::default(), window, cx);
10703        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10704    });
10705}
10706
10707#[gpui::test]
10708async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
10709    init_test(cx, |settings| {
10710        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
10711            Formatter::LanguageServer { name: None },
10712        ])))
10713    });
10714
10715    let fs = FakeFs::new(cx.executor());
10716    fs.insert_file(path!("/file.ts"), Default::default()).await;
10717
10718    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10719
10720    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10721    language_registry.add(Arc::new(Language::new(
10722        LanguageConfig {
10723            name: "TypeScript".into(),
10724            matcher: LanguageMatcher {
10725                path_suffixes: vec!["ts".to_string()],
10726                ..Default::default()
10727            },
10728            ..LanguageConfig::default()
10729        },
10730        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
10731    )));
10732    update_test_language_settings(cx, |settings| {
10733        settings.defaults.prettier = Some(PrettierSettings {
10734            allowed: true,
10735            ..PrettierSettings::default()
10736        });
10737    });
10738    let mut fake_servers = language_registry.register_fake_lsp(
10739        "TypeScript",
10740        FakeLspAdapter {
10741            capabilities: lsp::ServerCapabilities {
10742                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10743                ..Default::default()
10744            },
10745            ..Default::default()
10746        },
10747    );
10748
10749    let buffer = project
10750        .update(cx, |project, cx| {
10751            project.open_local_buffer(path!("/file.ts"), cx)
10752        })
10753        .await
10754        .unwrap();
10755
10756    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10757    let (editor, cx) = cx.add_window_view(|window, cx| {
10758        build_editor_with_project(project.clone(), buffer, window, cx)
10759    });
10760    editor.update_in(cx, |editor, window, cx| {
10761        editor.set_text(
10762            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
10763            window,
10764            cx,
10765        )
10766    });
10767
10768    cx.executor().start_waiting();
10769    let fake_server = fake_servers.next().await.unwrap();
10770
10771    let format = editor
10772        .update_in(cx, |editor, window, cx| {
10773            editor.perform_code_action_kind(
10774                project.clone(),
10775                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
10776                window,
10777                cx,
10778            )
10779        })
10780        .unwrap();
10781    fake_server
10782        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
10783            assert_eq!(
10784                params.text_document.uri,
10785                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
10786            );
10787            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
10788                lsp::CodeAction {
10789                    title: "Organize Imports".to_string(),
10790                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
10791                    edit: Some(lsp::WorkspaceEdit {
10792                        changes: Some(
10793                            [(
10794                                params.text_document.uri.clone(),
10795                                vec![lsp::TextEdit::new(
10796                                    lsp::Range::new(
10797                                        lsp::Position::new(1, 0),
10798                                        lsp::Position::new(2, 0),
10799                                    ),
10800                                    "".to_string(),
10801                                )],
10802                            )]
10803                            .into_iter()
10804                            .collect(),
10805                        ),
10806                        ..Default::default()
10807                    }),
10808                    ..Default::default()
10809                },
10810            )]))
10811        })
10812        .next()
10813        .await;
10814    cx.executor().start_waiting();
10815    format.await;
10816    assert_eq!(
10817        editor.update(cx, |editor, cx| editor.text(cx)),
10818        "import { a } from 'module';\n\nconst x = a;\n"
10819    );
10820
10821    editor.update_in(cx, |editor, window, cx| {
10822        editor.set_text(
10823            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
10824            window,
10825            cx,
10826        )
10827    });
10828    // Ensure we don't lock if code action hangs.
10829    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
10830        move |params, _| async move {
10831            assert_eq!(
10832                params.text_document.uri,
10833                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
10834            );
10835            futures::future::pending::<()>().await;
10836            unreachable!()
10837        },
10838    );
10839    let format = editor
10840        .update_in(cx, |editor, window, cx| {
10841            editor.perform_code_action_kind(
10842                project,
10843                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
10844                window,
10845                cx,
10846            )
10847        })
10848        .unwrap();
10849    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
10850    cx.executor().start_waiting();
10851    format.await;
10852    assert_eq!(
10853        editor.update(cx, |editor, cx| editor.text(cx)),
10854        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
10855    );
10856}
10857
10858#[gpui::test]
10859async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
10860    init_test(cx, |_| {});
10861
10862    let mut cx = EditorLspTestContext::new_rust(
10863        lsp::ServerCapabilities {
10864            document_formatting_provider: Some(lsp::OneOf::Left(true)),
10865            ..Default::default()
10866        },
10867        cx,
10868    )
10869    .await;
10870
10871    cx.set_state(indoc! {"
10872        one.twoˇ
10873    "});
10874
10875    // The format request takes a long time. When it completes, it inserts
10876    // a newline and an indent before the `.`
10877    cx.lsp
10878        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
10879            let executor = cx.background_executor().clone();
10880            async move {
10881                executor.timer(Duration::from_millis(100)).await;
10882                Ok(Some(vec![lsp::TextEdit {
10883                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
10884                    new_text: "\n    ".into(),
10885                }]))
10886            }
10887        });
10888
10889    // Submit a format request.
10890    let format_1 = cx
10891        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10892        .unwrap();
10893    cx.executor().run_until_parked();
10894
10895    // Submit a second format request.
10896    let format_2 = cx
10897        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10898        .unwrap();
10899    cx.executor().run_until_parked();
10900
10901    // Wait for both format requests to complete
10902    cx.executor().advance_clock(Duration::from_millis(200));
10903    cx.executor().start_waiting();
10904    format_1.await.unwrap();
10905    cx.executor().start_waiting();
10906    format_2.await.unwrap();
10907
10908    // The formatting edits only happens once.
10909    cx.assert_editor_state(indoc! {"
10910        one
10911            .twoˇ
10912    "});
10913}
10914
10915#[gpui::test]
10916async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
10917    init_test(cx, |settings| {
10918        settings.defaults.formatter = Some(SelectedFormatter::Auto)
10919    });
10920
10921    let mut cx = EditorLspTestContext::new_rust(
10922        lsp::ServerCapabilities {
10923            document_formatting_provider: Some(lsp::OneOf::Left(true)),
10924            ..Default::default()
10925        },
10926        cx,
10927    )
10928    .await;
10929
10930    // Set up a buffer white some trailing whitespace and no trailing newline.
10931    cx.set_state(
10932        &[
10933            "one ",   //
10934            "twoˇ",   //
10935            "three ", //
10936            "four",   //
10937        ]
10938        .join("\n"),
10939    );
10940
10941    // Submit a format request.
10942    let format = cx
10943        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10944        .unwrap();
10945
10946    // Record which buffer changes have been sent to the language server
10947    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
10948    cx.lsp
10949        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
10950            let buffer_changes = buffer_changes.clone();
10951            move |params, _| {
10952                buffer_changes.lock().extend(
10953                    params
10954                        .content_changes
10955                        .into_iter()
10956                        .map(|e| (e.range.unwrap(), e.text)),
10957                );
10958            }
10959        });
10960
10961    // Handle formatting requests to the language server.
10962    cx.lsp
10963        .set_request_handler::<lsp::request::Formatting, _, _>({
10964            let buffer_changes = buffer_changes.clone();
10965            move |_, _| {
10966                // When formatting is requested, trailing whitespace has already been stripped,
10967                // and the trailing newline has already been added.
10968                assert_eq!(
10969                    &buffer_changes.lock()[1..],
10970                    &[
10971                        (
10972                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
10973                            "".into()
10974                        ),
10975                        (
10976                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
10977                            "".into()
10978                        ),
10979                        (
10980                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
10981                            "\n".into()
10982                        ),
10983                    ]
10984                );
10985
10986                // Insert blank lines between each line of the buffer.
10987                async move {
10988                    Ok(Some(vec![
10989                        lsp::TextEdit {
10990                            range: lsp::Range::new(
10991                                lsp::Position::new(1, 0),
10992                                lsp::Position::new(1, 0),
10993                            ),
10994                            new_text: "\n".into(),
10995                        },
10996                        lsp::TextEdit {
10997                            range: lsp::Range::new(
10998                                lsp::Position::new(2, 0),
10999                                lsp::Position::new(2, 0),
11000                            ),
11001                            new_text: "\n".into(),
11002                        },
11003                    ]))
11004                }
11005            }
11006        });
11007
11008    // After formatting the buffer, the trailing whitespace is stripped,
11009    // a newline is appended, and the edits provided by the language server
11010    // have been applied.
11011    format.await.unwrap();
11012    cx.assert_editor_state(
11013        &[
11014            "one",   //
11015            "",      //
11016            "twoˇ",  //
11017            "",      //
11018            "three", //
11019            "four",  //
11020            "",      //
11021        ]
11022        .join("\n"),
11023    );
11024
11025    // Undoing the formatting undoes the trailing whitespace removal, the
11026    // trailing newline, and the LSP edits.
11027    cx.update_buffer(|buffer, cx| buffer.undo(cx));
11028    cx.assert_editor_state(
11029        &[
11030            "one ",   //
11031            "twoˇ",   //
11032            "three ", //
11033            "four",   //
11034        ]
11035        .join("\n"),
11036    );
11037}
11038
11039#[gpui::test]
11040async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
11041    cx: &mut TestAppContext,
11042) {
11043    init_test(cx, |_| {});
11044
11045    cx.update(|cx| {
11046        cx.update_global::<SettingsStore, _>(|settings, cx| {
11047            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11048                settings.auto_signature_help = Some(true);
11049            });
11050        });
11051    });
11052
11053    let mut cx = EditorLspTestContext::new_rust(
11054        lsp::ServerCapabilities {
11055            signature_help_provider: Some(lsp::SignatureHelpOptions {
11056                ..Default::default()
11057            }),
11058            ..Default::default()
11059        },
11060        cx,
11061    )
11062    .await;
11063
11064    let language = Language::new(
11065        LanguageConfig {
11066            name: "Rust".into(),
11067            brackets: BracketPairConfig {
11068                pairs: vec![
11069                    BracketPair {
11070                        start: "{".to_string(),
11071                        end: "}".to_string(),
11072                        close: true,
11073                        surround: true,
11074                        newline: true,
11075                    },
11076                    BracketPair {
11077                        start: "(".to_string(),
11078                        end: ")".to_string(),
11079                        close: true,
11080                        surround: true,
11081                        newline: true,
11082                    },
11083                    BracketPair {
11084                        start: "/*".to_string(),
11085                        end: " */".to_string(),
11086                        close: true,
11087                        surround: true,
11088                        newline: true,
11089                    },
11090                    BracketPair {
11091                        start: "[".to_string(),
11092                        end: "]".to_string(),
11093                        close: false,
11094                        surround: false,
11095                        newline: true,
11096                    },
11097                    BracketPair {
11098                        start: "\"".to_string(),
11099                        end: "\"".to_string(),
11100                        close: true,
11101                        surround: true,
11102                        newline: false,
11103                    },
11104                    BracketPair {
11105                        start: "<".to_string(),
11106                        end: ">".to_string(),
11107                        close: false,
11108                        surround: true,
11109                        newline: true,
11110                    },
11111                ],
11112                ..Default::default()
11113            },
11114            autoclose_before: "})]".to_string(),
11115            ..Default::default()
11116        },
11117        Some(tree_sitter_rust::LANGUAGE.into()),
11118    );
11119    let language = Arc::new(language);
11120
11121    cx.language_registry().add(language.clone());
11122    cx.update_buffer(|buffer, cx| {
11123        buffer.set_language(Some(language), cx);
11124    });
11125
11126    cx.set_state(
11127        &r#"
11128            fn main() {
11129                sampleˇ
11130            }
11131        "#
11132        .unindent(),
11133    );
11134
11135    cx.update_editor(|editor, window, cx| {
11136        editor.handle_input("(", window, cx);
11137    });
11138    cx.assert_editor_state(
11139        &"
11140            fn main() {
11141                sample(ˇ)
11142            }
11143        "
11144        .unindent(),
11145    );
11146
11147    let mocked_response = lsp::SignatureHelp {
11148        signatures: vec![lsp::SignatureInformation {
11149            label: "fn sample(param1: u8, param2: u8)".to_string(),
11150            documentation: None,
11151            parameters: Some(vec![
11152                lsp::ParameterInformation {
11153                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11154                    documentation: None,
11155                },
11156                lsp::ParameterInformation {
11157                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11158                    documentation: None,
11159                },
11160            ]),
11161            active_parameter: None,
11162        }],
11163        active_signature: Some(0),
11164        active_parameter: Some(0),
11165    };
11166    handle_signature_help_request(&mut cx, mocked_response).await;
11167
11168    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11169        .await;
11170
11171    cx.editor(|editor, _, _| {
11172        let signature_help_state = editor.signature_help_state.popover().cloned();
11173        let signature = signature_help_state.unwrap();
11174        assert_eq!(
11175            signature.signatures[signature.current_signature].label,
11176            "fn sample(param1: u8, param2: u8)"
11177        );
11178    });
11179}
11180
11181#[gpui::test]
11182async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
11183    init_test(cx, |_| {});
11184
11185    cx.update(|cx| {
11186        cx.update_global::<SettingsStore, _>(|settings, cx| {
11187            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11188                settings.auto_signature_help = Some(false);
11189                settings.show_signature_help_after_edits = Some(false);
11190            });
11191        });
11192    });
11193
11194    let mut cx = EditorLspTestContext::new_rust(
11195        lsp::ServerCapabilities {
11196            signature_help_provider: Some(lsp::SignatureHelpOptions {
11197                ..Default::default()
11198            }),
11199            ..Default::default()
11200        },
11201        cx,
11202    )
11203    .await;
11204
11205    let language = Language::new(
11206        LanguageConfig {
11207            name: "Rust".into(),
11208            brackets: BracketPairConfig {
11209                pairs: vec![
11210                    BracketPair {
11211                        start: "{".to_string(),
11212                        end: "}".to_string(),
11213                        close: true,
11214                        surround: true,
11215                        newline: true,
11216                    },
11217                    BracketPair {
11218                        start: "(".to_string(),
11219                        end: ")".to_string(),
11220                        close: true,
11221                        surround: true,
11222                        newline: true,
11223                    },
11224                    BracketPair {
11225                        start: "/*".to_string(),
11226                        end: " */".to_string(),
11227                        close: true,
11228                        surround: true,
11229                        newline: true,
11230                    },
11231                    BracketPair {
11232                        start: "[".to_string(),
11233                        end: "]".to_string(),
11234                        close: false,
11235                        surround: false,
11236                        newline: true,
11237                    },
11238                    BracketPair {
11239                        start: "\"".to_string(),
11240                        end: "\"".to_string(),
11241                        close: true,
11242                        surround: true,
11243                        newline: false,
11244                    },
11245                    BracketPair {
11246                        start: "<".to_string(),
11247                        end: ">".to_string(),
11248                        close: false,
11249                        surround: true,
11250                        newline: true,
11251                    },
11252                ],
11253                ..Default::default()
11254            },
11255            autoclose_before: "})]".to_string(),
11256            ..Default::default()
11257        },
11258        Some(tree_sitter_rust::LANGUAGE.into()),
11259    );
11260    let language = Arc::new(language);
11261
11262    cx.language_registry().add(language.clone());
11263    cx.update_buffer(|buffer, cx| {
11264        buffer.set_language(Some(language), cx);
11265    });
11266
11267    // Ensure that signature_help is not called when no signature help is enabled.
11268    cx.set_state(
11269        &r#"
11270            fn main() {
11271                sampleˇ
11272            }
11273        "#
11274        .unindent(),
11275    );
11276    cx.update_editor(|editor, window, cx| {
11277        editor.handle_input("(", window, cx);
11278    });
11279    cx.assert_editor_state(
11280        &"
11281            fn main() {
11282                sample(ˇ)
11283            }
11284        "
11285        .unindent(),
11286    );
11287    cx.editor(|editor, _, _| {
11288        assert!(editor.signature_help_state.task().is_none());
11289    });
11290
11291    let mocked_response = lsp::SignatureHelp {
11292        signatures: vec![lsp::SignatureInformation {
11293            label: "fn sample(param1: u8, param2: u8)".to_string(),
11294            documentation: None,
11295            parameters: Some(vec![
11296                lsp::ParameterInformation {
11297                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11298                    documentation: None,
11299                },
11300                lsp::ParameterInformation {
11301                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11302                    documentation: None,
11303                },
11304            ]),
11305            active_parameter: None,
11306        }],
11307        active_signature: Some(0),
11308        active_parameter: Some(0),
11309    };
11310
11311    // Ensure that signature_help is called when enabled afte edits
11312    cx.update(|_, cx| {
11313        cx.update_global::<SettingsStore, _>(|settings, cx| {
11314            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11315                settings.auto_signature_help = Some(false);
11316                settings.show_signature_help_after_edits = Some(true);
11317            });
11318        });
11319    });
11320    cx.set_state(
11321        &r#"
11322            fn main() {
11323                sampleˇ
11324            }
11325        "#
11326        .unindent(),
11327    );
11328    cx.update_editor(|editor, window, cx| {
11329        editor.handle_input("(", window, cx);
11330    });
11331    cx.assert_editor_state(
11332        &"
11333            fn main() {
11334                sample(ˇ)
11335            }
11336        "
11337        .unindent(),
11338    );
11339    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11340    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11341        .await;
11342    cx.update_editor(|editor, _, _| {
11343        let signature_help_state = editor.signature_help_state.popover().cloned();
11344        assert!(signature_help_state.is_some());
11345        let signature = signature_help_state.unwrap();
11346        assert_eq!(
11347            signature.signatures[signature.current_signature].label,
11348            "fn sample(param1: u8, param2: u8)"
11349        );
11350        editor.signature_help_state = SignatureHelpState::default();
11351    });
11352
11353    // Ensure that signature_help is called when auto signature help override is enabled
11354    cx.update(|_, cx| {
11355        cx.update_global::<SettingsStore, _>(|settings, cx| {
11356            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11357                settings.auto_signature_help = Some(true);
11358                settings.show_signature_help_after_edits = Some(false);
11359            });
11360        });
11361    });
11362    cx.set_state(
11363        &r#"
11364            fn main() {
11365                sampleˇ
11366            }
11367        "#
11368        .unindent(),
11369    );
11370    cx.update_editor(|editor, window, cx| {
11371        editor.handle_input("(", window, cx);
11372    });
11373    cx.assert_editor_state(
11374        &"
11375            fn main() {
11376                sample(ˇ)
11377            }
11378        "
11379        .unindent(),
11380    );
11381    handle_signature_help_request(&mut cx, mocked_response).await;
11382    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11383        .await;
11384    cx.editor(|editor, _, _| {
11385        let signature_help_state = editor.signature_help_state.popover().cloned();
11386        assert!(signature_help_state.is_some());
11387        let signature = signature_help_state.unwrap();
11388        assert_eq!(
11389            signature.signatures[signature.current_signature].label,
11390            "fn sample(param1: u8, param2: u8)"
11391        );
11392    });
11393}
11394
11395#[gpui::test]
11396async fn test_signature_help(cx: &mut TestAppContext) {
11397    init_test(cx, |_| {});
11398    cx.update(|cx| {
11399        cx.update_global::<SettingsStore, _>(|settings, cx| {
11400            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11401                settings.auto_signature_help = Some(true);
11402            });
11403        });
11404    });
11405
11406    let mut cx = EditorLspTestContext::new_rust(
11407        lsp::ServerCapabilities {
11408            signature_help_provider: Some(lsp::SignatureHelpOptions {
11409                ..Default::default()
11410            }),
11411            ..Default::default()
11412        },
11413        cx,
11414    )
11415    .await;
11416
11417    // A test that directly calls `show_signature_help`
11418    cx.update_editor(|editor, window, cx| {
11419        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11420    });
11421
11422    let mocked_response = lsp::SignatureHelp {
11423        signatures: vec![lsp::SignatureInformation {
11424            label: "fn sample(param1: u8, param2: u8)".to_string(),
11425            documentation: None,
11426            parameters: Some(vec![
11427                lsp::ParameterInformation {
11428                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11429                    documentation: None,
11430                },
11431                lsp::ParameterInformation {
11432                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11433                    documentation: None,
11434                },
11435            ]),
11436            active_parameter: None,
11437        }],
11438        active_signature: Some(0),
11439        active_parameter: Some(0),
11440    };
11441    handle_signature_help_request(&mut cx, mocked_response).await;
11442
11443    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11444        .await;
11445
11446    cx.editor(|editor, _, _| {
11447        let signature_help_state = editor.signature_help_state.popover().cloned();
11448        assert!(signature_help_state.is_some());
11449        let signature = signature_help_state.unwrap();
11450        assert_eq!(
11451            signature.signatures[signature.current_signature].label,
11452            "fn sample(param1: u8, param2: u8)"
11453        );
11454    });
11455
11456    // When exiting outside from inside the brackets, `signature_help` is closed.
11457    cx.set_state(indoc! {"
11458        fn main() {
11459            sample(ˇ);
11460        }
11461
11462        fn sample(param1: u8, param2: u8) {}
11463    "});
11464
11465    cx.update_editor(|editor, window, cx| {
11466        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11467            s.select_ranges([0..0])
11468        });
11469    });
11470
11471    let mocked_response = lsp::SignatureHelp {
11472        signatures: Vec::new(),
11473        active_signature: None,
11474        active_parameter: None,
11475    };
11476    handle_signature_help_request(&mut cx, mocked_response).await;
11477
11478    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11479        .await;
11480
11481    cx.editor(|editor, _, _| {
11482        assert!(!editor.signature_help_state.is_shown());
11483    });
11484
11485    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
11486    cx.set_state(indoc! {"
11487        fn main() {
11488            sample(ˇ);
11489        }
11490
11491        fn sample(param1: u8, param2: u8) {}
11492    "});
11493
11494    let mocked_response = lsp::SignatureHelp {
11495        signatures: vec![lsp::SignatureInformation {
11496            label: "fn sample(param1: u8, param2: u8)".to_string(),
11497            documentation: None,
11498            parameters: Some(vec![
11499                lsp::ParameterInformation {
11500                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11501                    documentation: None,
11502                },
11503                lsp::ParameterInformation {
11504                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11505                    documentation: None,
11506                },
11507            ]),
11508            active_parameter: None,
11509        }],
11510        active_signature: Some(0),
11511        active_parameter: Some(0),
11512    };
11513    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11514    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11515        .await;
11516    cx.editor(|editor, _, _| {
11517        assert!(editor.signature_help_state.is_shown());
11518    });
11519
11520    // Restore the popover with more parameter input
11521    cx.set_state(indoc! {"
11522        fn main() {
11523            sample(param1, param2ˇ);
11524        }
11525
11526        fn sample(param1: u8, param2: u8) {}
11527    "});
11528
11529    let mocked_response = lsp::SignatureHelp {
11530        signatures: vec![lsp::SignatureInformation {
11531            label: "fn sample(param1: u8, param2: u8)".to_string(),
11532            documentation: None,
11533            parameters: Some(vec![
11534                lsp::ParameterInformation {
11535                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11536                    documentation: None,
11537                },
11538                lsp::ParameterInformation {
11539                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11540                    documentation: None,
11541                },
11542            ]),
11543            active_parameter: None,
11544        }],
11545        active_signature: Some(0),
11546        active_parameter: Some(1),
11547    };
11548    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11549    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11550        .await;
11551
11552    // When selecting a range, the popover is gone.
11553    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
11554    cx.update_editor(|editor, window, cx| {
11555        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11556            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11557        })
11558    });
11559    cx.assert_editor_state(indoc! {"
11560        fn main() {
11561            sample(param1, «ˇparam2»);
11562        }
11563
11564        fn sample(param1: u8, param2: u8) {}
11565    "});
11566    cx.editor(|editor, _, _| {
11567        assert!(!editor.signature_help_state.is_shown());
11568    });
11569
11570    // When unselecting again, the popover is back if within the brackets.
11571    cx.update_editor(|editor, window, cx| {
11572        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11573            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11574        })
11575    });
11576    cx.assert_editor_state(indoc! {"
11577        fn main() {
11578            sample(param1, ˇparam2);
11579        }
11580
11581        fn sample(param1: u8, param2: u8) {}
11582    "});
11583    handle_signature_help_request(&mut cx, mocked_response).await;
11584    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11585        .await;
11586    cx.editor(|editor, _, _| {
11587        assert!(editor.signature_help_state.is_shown());
11588    });
11589
11590    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
11591    cx.update_editor(|editor, window, cx| {
11592        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11593            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
11594            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11595        })
11596    });
11597    cx.assert_editor_state(indoc! {"
11598        fn main() {
11599            sample(param1, ˇparam2);
11600        }
11601
11602        fn sample(param1: u8, param2: u8) {}
11603    "});
11604
11605    let mocked_response = lsp::SignatureHelp {
11606        signatures: vec![lsp::SignatureInformation {
11607            label: "fn sample(param1: u8, param2: u8)".to_string(),
11608            documentation: None,
11609            parameters: Some(vec![
11610                lsp::ParameterInformation {
11611                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11612                    documentation: None,
11613                },
11614                lsp::ParameterInformation {
11615                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11616                    documentation: None,
11617                },
11618            ]),
11619            active_parameter: None,
11620        }],
11621        active_signature: Some(0),
11622        active_parameter: Some(1),
11623    };
11624    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11625    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11626        .await;
11627    cx.update_editor(|editor, _, cx| {
11628        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
11629    });
11630    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11631        .await;
11632    cx.update_editor(|editor, window, cx| {
11633        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11634            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11635        })
11636    });
11637    cx.assert_editor_state(indoc! {"
11638        fn main() {
11639            sample(param1, «ˇparam2»);
11640        }
11641
11642        fn sample(param1: u8, param2: u8) {}
11643    "});
11644    cx.update_editor(|editor, window, cx| {
11645        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11646            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11647        })
11648    });
11649    cx.assert_editor_state(indoc! {"
11650        fn main() {
11651            sample(param1, ˇparam2);
11652        }
11653
11654        fn sample(param1: u8, param2: u8) {}
11655    "});
11656    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
11657        .await;
11658}
11659
11660#[gpui::test]
11661async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
11662    init_test(cx, |_| {});
11663
11664    let mut cx = EditorLspTestContext::new_rust(
11665        lsp::ServerCapabilities {
11666            signature_help_provider: Some(lsp::SignatureHelpOptions {
11667                ..Default::default()
11668            }),
11669            ..Default::default()
11670        },
11671        cx,
11672    )
11673    .await;
11674
11675    cx.set_state(indoc! {"
11676        fn main() {
11677            overloadedˇ
11678        }
11679    "});
11680
11681    cx.update_editor(|editor, window, cx| {
11682        editor.handle_input("(", window, cx);
11683        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11684    });
11685
11686    // Mock response with 3 signatures
11687    let mocked_response = lsp::SignatureHelp {
11688        signatures: vec![
11689            lsp::SignatureInformation {
11690                label: "fn overloaded(x: i32)".to_string(),
11691                documentation: None,
11692                parameters: Some(vec![lsp::ParameterInformation {
11693                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11694                    documentation: None,
11695                }]),
11696                active_parameter: None,
11697            },
11698            lsp::SignatureInformation {
11699                label: "fn overloaded(x: i32, y: i32)".to_string(),
11700                documentation: None,
11701                parameters: Some(vec![
11702                    lsp::ParameterInformation {
11703                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11704                        documentation: None,
11705                    },
11706                    lsp::ParameterInformation {
11707                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
11708                        documentation: None,
11709                    },
11710                ]),
11711                active_parameter: None,
11712            },
11713            lsp::SignatureInformation {
11714                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
11715                documentation: None,
11716                parameters: Some(vec![
11717                    lsp::ParameterInformation {
11718                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11719                        documentation: None,
11720                    },
11721                    lsp::ParameterInformation {
11722                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
11723                        documentation: None,
11724                    },
11725                    lsp::ParameterInformation {
11726                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
11727                        documentation: None,
11728                    },
11729                ]),
11730                active_parameter: None,
11731            },
11732        ],
11733        active_signature: Some(1),
11734        active_parameter: Some(0),
11735    };
11736    handle_signature_help_request(&mut cx, mocked_response).await;
11737
11738    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11739        .await;
11740
11741    // Verify we have multiple signatures and the right one is selected
11742    cx.editor(|editor, _, _| {
11743        let popover = editor.signature_help_state.popover().cloned().unwrap();
11744        assert_eq!(popover.signatures.len(), 3);
11745        // active_signature was 1, so that should be the current
11746        assert_eq!(popover.current_signature, 1);
11747        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
11748        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
11749        assert_eq!(
11750            popover.signatures[2].label,
11751            "fn overloaded(x: i32, y: i32, z: i32)"
11752        );
11753    });
11754
11755    // Test navigation functionality
11756    cx.update_editor(|editor, window, cx| {
11757        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
11758    });
11759
11760    cx.editor(|editor, _, _| {
11761        let popover = editor.signature_help_state.popover().cloned().unwrap();
11762        assert_eq!(popover.current_signature, 2);
11763    });
11764
11765    // Test wrap around
11766    cx.update_editor(|editor, window, cx| {
11767        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
11768    });
11769
11770    cx.editor(|editor, _, _| {
11771        let popover = editor.signature_help_state.popover().cloned().unwrap();
11772        assert_eq!(popover.current_signature, 0);
11773    });
11774
11775    // Test previous navigation
11776    cx.update_editor(|editor, window, cx| {
11777        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
11778    });
11779
11780    cx.editor(|editor, _, _| {
11781        let popover = editor.signature_help_state.popover().cloned().unwrap();
11782        assert_eq!(popover.current_signature, 2);
11783    });
11784}
11785
11786#[gpui::test]
11787async fn test_completion_mode(cx: &mut TestAppContext) {
11788    init_test(cx, |_| {});
11789    let mut cx = EditorLspTestContext::new_rust(
11790        lsp::ServerCapabilities {
11791            completion_provider: Some(lsp::CompletionOptions {
11792                resolve_provider: Some(true),
11793                ..Default::default()
11794            }),
11795            ..Default::default()
11796        },
11797        cx,
11798    )
11799    .await;
11800
11801    struct Run {
11802        run_description: &'static str,
11803        initial_state: String,
11804        buffer_marked_text: String,
11805        completion_label: &'static str,
11806        completion_text: &'static str,
11807        expected_with_insert_mode: String,
11808        expected_with_replace_mode: String,
11809        expected_with_replace_subsequence_mode: String,
11810        expected_with_replace_suffix_mode: String,
11811    }
11812
11813    let runs = [
11814        Run {
11815            run_description: "Start of word matches completion text",
11816            initial_state: "before ediˇ after".into(),
11817            buffer_marked_text: "before <edi|> after".into(),
11818            completion_label: "editor",
11819            completion_text: "editor",
11820            expected_with_insert_mode: "before editorˇ after".into(),
11821            expected_with_replace_mode: "before editorˇ after".into(),
11822            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11823            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11824        },
11825        Run {
11826            run_description: "Accept same text at the middle of the word",
11827            initial_state: "before ediˇtor after".into(),
11828            buffer_marked_text: "before <edi|tor> after".into(),
11829            completion_label: "editor",
11830            completion_text: "editor",
11831            expected_with_insert_mode: "before editorˇtor after".into(),
11832            expected_with_replace_mode: "before editorˇ after".into(),
11833            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11834            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11835        },
11836        Run {
11837            run_description: "End of word matches completion text -- cursor at end",
11838            initial_state: "before torˇ after".into(),
11839            buffer_marked_text: "before <tor|> after".into(),
11840            completion_label: "editor",
11841            completion_text: "editor",
11842            expected_with_insert_mode: "before editorˇ after".into(),
11843            expected_with_replace_mode: "before editorˇ after".into(),
11844            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11845            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11846        },
11847        Run {
11848            run_description: "End of word matches completion text -- cursor at start",
11849            initial_state: "before ˇtor after".into(),
11850            buffer_marked_text: "before <|tor> after".into(),
11851            completion_label: "editor",
11852            completion_text: "editor",
11853            expected_with_insert_mode: "before editorˇtor after".into(),
11854            expected_with_replace_mode: "before editorˇ after".into(),
11855            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11856            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11857        },
11858        Run {
11859            run_description: "Prepend text containing whitespace",
11860            initial_state: "pˇfield: bool".into(),
11861            buffer_marked_text: "<p|field>: bool".into(),
11862            completion_label: "pub ",
11863            completion_text: "pub ",
11864            expected_with_insert_mode: "pub ˇfield: bool".into(),
11865            expected_with_replace_mode: "pub ˇ: bool".into(),
11866            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
11867            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
11868        },
11869        Run {
11870            run_description: "Add element to start of list",
11871            initial_state: "[element_ˇelement_2]".into(),
11872            buffer_marked_text: "[<element_|element_2>]".into(),
11873            completion_label: "element_1",
11874            completion_text: "element_1",
11875            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
11876            expected_with_replace_mode: "[element_1ˇ]".into(),
11877            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
11878            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
11879        },
11880        Run {
11881            run_description: "Add element to start of list -- first and second elements are equal",
11882            initial_state: "[elˇelement]".into(),
11883            buffer_marked_text: "[<el|element>]".into(),
11884            completion_label: "element",
11885            completion_text: "element",
11886            expected_with_insert_mode: "[elementˇelement]".into(),
11887            expected_with_replace_mode: "[elementˇ]".into(),
11888            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
11889            expected_with_replace_suffix_mode: "[elementˇ]".into(),
11890        },
11891        Run {
11892            run_description: "Ends with matching suffix",
11893            initial_state: "SubˇError".into(),
11894            buffer_marked_text: "<Sub|Error>".into(),
11895            completion_label: "SubscriptionError",
11896            completion_text: "SubscriptionError",
11897            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
11898            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11899            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11900            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
11901        },
11902        Run {
11903            run_description: "Suffix is a subsequence -- contiguous",
11904            initial_state: "SubˇErr".into(),
11905            buffer_marked_text: "<Sub|Err>".into(),
11906            completion_label: "SubscriptionError",
11907            completion_text: "SubscriptionError",
11908            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
11909            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11910            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11911            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
11912        },
11913        Run {
11914            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
11915            initial_state: "Suˇscrirr".into(),
11916            buffer_marked_text: "<Su|scrirr>".into(),
11917            completion_label: "SubscriptionError",
11918            completion_text: "SubscriptionError",
11919            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
11920            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11921            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11922            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
11923        },
11924        Run {
11925            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
11926            initial_state: "foo(indˇix)".into(),
11927            buffer_marked_text: "foo(<ind|ix>)".into(),
11928            completion_label: "node_index",
11929            completion_text: "node_index",
11930            expected_with_insert_mode: "foo(node_indexˇix)".into(),
11931            expected_with_replace_mode: "foo(node_indexˇ)".into(),
11932            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
11933            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
11934        },
11935        Run {
11936            run_description: "Replace range ends before cursor - should extend to cursor",
11937            initial_state: "before editˇo after".into(),
11938            buffer_marked_text: "before <{ed}>it|o after".into(),
11939            completion_label: "editor",
11940            completion_text: "editor",
11941            expected_with_insert_mode: "before editorˇo after".into(),
11942            expected_with_replace_mode: "before editorˇo after".into(),
11943            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
11944            expected_with_replace_suffix_mode: "before editorˇo after".into(),
11945        },
11946        Run {
11947            run_description: "Uses label for suffix matching",
11948            initial_state: "before ediˇtor after".into(),
11949            buffer_marked_text: "before <edi|tor> after".into(),
11950            completion_label: "editor",
11951            completion_text: "editor()",
11952            expected_with_insert_mode: "before editor()ˇtor after".into(),
11953            expected_with_replace_mode: "before editor()ˇ after".into(),
11954            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
11955            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
11956        },
11957        Run {
11958            run_description: "Case insensitive subsequence and suffix matching",
11959            initial_state: "before EDiˇtoR after".into(),
11960            buffer_marked_text: "before <EDi|toR> after".into(),
11961            completion_label: "editor",
11962            completion_text: "editor",
11963            expected_with_insert_mode: "before editorˇtoR after".into(),
11964            expected_with_replace_mode: "before editorˇ after".into(),
11965            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11966            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11967        },
11968    ];
11969
11970    for run in runs {
11971        let run_variations = [
11972            (LspInsertMode::Insert, run.expected_with_insert_mode),
11973            (LspInsertMode::Replace, run.expected_with_replace_mode),
11974            (
11975                LspInsertMode::ReplaceSubsequence,
11976                run.expected_with_replace_subsequence_mode,
11977            ),
11978            (
11979                LspInsertMode::ReplaceSuffix,
11980                run.expected_with_replace_suffix_mode,
11981            ),
11982        ];
11983
11984        for (lsp_insert_mode, expected_text) in run_variations {
11985            eprintln!(
11986                "run = {:?}, mode = {lsp_insert_mode:.?}",
11987                run.run_description,
11988            );
11989
11990            update_test_language_settings(&mut cx, |settings| {
11991                settings.defaults.completions = Some(CompletionSettings {
11992                    lsp_insert_mode,
11993                    words: WordsCompletionMode::Disabled,
11994                    lsp: true,
11995                    lsp_fetch_timeout_ms: 0,
11996                });
11997            });
11998
11999            cx.set_state(&run.initial_state);
12000            cx.update_editor(|editor, window, cx| {
12001                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12002            });
12003
12004            let counter = Arc::new(AtomicUsize::new(0));
12005            handle_completion_request_with_insert_and_replace(
12006                &mut cx,
12007                &run.buffer_marked_text,
12008                vec![(run.completion_label, run.completion_text)],
12009                counter.clone(),
12010            )
12011            .await;
12012            cx.condition(|editor, _| editor.context_menu_visible())
12013                .await;
12014            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12015
12016            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12017                editor
12018                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
12019                    .unwrap()
12020            });
12021            cx.assert_editor_state(&expected_text);
12022            handle_resolve_completion_request(&mut cx, None).await;
12023            apply_additional_edits.await.unwrap();
12024        }
12025    }
12026}
12027
12028#[gpui::test]
12029async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
12030    init_test(cx, |_| {});
12031    let mut cx = EditorLspTestContext::new_rust(
12032        lsp::ServerCapabilities {
12033            completion_provider: Some(lsp::CompletionOptions {
12034                resolve_provider: Some(true),
12035                ..Default::default()
12036            }),
12037            ..Default::default()
12038        },
12039        cx,
12040    )
12041    .await;
12042
12043    let initial_state = "SubˇError";
12044    let buffer_marked_text = "<Sub|Error>";
12045    let completion_text = "SubscriptionError";
12046    let expected_with_insert_mode = "SubscriptionErrorˇError";
12047    let expected_with_replace_mode = "SubscriptionErrorˇ";
12048
12049    update_test_language_settings(&mut cx, |settings| {
12050        settings.defaults.completions = Some(CompletionSettings {
12051            words: WordsCompletionMode::Disabled,
12052            // set the opposite here to ensure that the action is overriding the default behavior
12053            lsp_insert_mode: LspInsertMode::Insert,
12054            lsp: true,
12055            lsp_fetch_timeout_ms: 0,
12056        });
12057    });
12058
12059    cx.set_state(initial_state);
12060    cx.update_editor(|editor, window, cx| {
12061        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12062    });
12063
12064    let counter = Arc::new(AtomicUsize::new(0));
12065    handle_completion_request_with_insert_and_replace(
12066        &mut cx,
12067        &buffer_marked_text,
12068        vec![(completion_text, completion_text)],
12069        counter.clone(),
12070    )
12071    .await;
12072    cx.condition(|editor, _| editor.context_menu_visible())
12073        .await;
12074    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12075
12076    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12077        editor
12078            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12079            .unwrap()
12080    });
12081    cx.assert_editor_state(&expected_with_replace_mode);
12082    handle_resolve_completion_request(&mut cx, None).await;
12083    apply_additional_edits.await.unwrap();
12084
12085    update_test_language_settings(&mut cx, |settings| {
12086        settings.defaults.completions = Some(CompletionSettings {
12087            words: WordsCompletionMode::Disabled,
12088            // set the opposite here to ensure that the action is overriding the default behavior
12089            lsp_insert_mode: LspInsertMode::Replace,
12090            lsp: true,
12091            lsp_fetch_timeout_ms: 0,
12092        });
12093    });
12094
12095    cx.set_state(initial_state);
12096    cx.update_editor(|editor, window, cx| {
12097        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12098    });
12099    handle_completion_request_with_insert_and_replace(
12100        &mut cx,
12101        &buffer_marked_text,
12102        vec![(completion_text, completion_text)],
12103        counter.clone(),
12104    )
12105    .await;
12106    cx.condition(|editor, _| editor.context_menu_visible())
12107        .await;
12108    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12109
12110    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12111        editor
12112            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
12113            .unwrap()
12114    });
12115    cx.assert_editor_state(&expected_with_insert_mode);
12116    handle_resolve_completion_request(&mut cx, None).await;
12117    apply_additional_edits.await.unwrap();
12118}
12119
12120#[gpui::test]
12121async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
12122    init_test(cx, |_| {});
12123    let mut cx = EditorLspTestContext::new_rust(
12124        lsp::ServerCapabilities {
12125            completion_provider: Some(lsp::CompletionOptions {
12126                resolve_provider: Some(true),
12127                ..Default::default()
12128            }),
12129            ..Default::default()
12130        },
12131        cx,
12132    )
12133    .await;
12134
12135    // scenario: surrounding text matches completion text
12136    let completion_text = "to_offset";
12137    let initial_state = indoc! {"
12138        1. buf.to_offˇsuffix
12139        2. buf.to_offˇsuf
12140        3. buf.to_offˇfix
12141        4. buf.to_offˇ
12142        5. into_offˇensive
12143        6. ˇsuffix
12144        7. let ˇ //
12145        8. aaˇzz
12146        9. buf.to_off«zzzzzˇ»suffix
12147        10. buf.«ˇzzzzz»suffix
12148        11. to_off«ˇzzzzz»
12149
12150        buf.to_offˇsuffix  // newest cursor
12151    "};
12152    let completion_marked_buffer = indoc! {"
12153        1. buf.to_offsuffix
12154        2. buf.to_offsuf
12155        3. buf.to_offfix
12156        4. buf.to_off
12157        5. into_offensive
12158        6. suffix
12159        7. let  //
12160        8. aazz
12161        9. buf.to_offzzzzzsuffix
12162        10. buf.zzzzzsuffix
12163        11. to_offzzzzz
12164
12165        buf.<to_off|suffix>  // newest cursor
12166    "};
12167    let expected = indoc! {"
12168        1. buf.to_offsetˇ
12169        2. buf.to_offsetˇsuf
12170        3. buf.to_offsetˇfix
12171        4. buf.to_offsetˇ
12172        5. into_offsetˇensive
12173        6. to_offsetˇsuffix
12174        7. let to_offsetˇ //
12175        8. aato_offsetˇzz
12176        9. buf.to_offsetˇ
12177        10. buf.to_offsetˇsuffix
12178        11. to_offsetˇ
12179
12180        buf.to_offsetˇ  // newest cursor
12181    "};
12182    cx.set_state(initial_state);
12183    cx.update_editor(|editor, window, cx| {
12184        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12185    });
12186    handle_completion_request_with_insert_and_replace(
12187        &mut cx,
12188        completion_marked_buffer,
12189        vec![(completion_text, completion_text)],
12190        Arc::new(AtomicUsize::new(0)),
12191    )
12192    .await;
12193    cx.condition(|editor, _| editor.context_menu_visible())
12194        .await;
12195    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12196        editor
12197            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12198            .unwrap()
12199    });
12200    cx.assert_editor_state(expected);
12201    handle_resolve_completion_request(&mut cx, None).await;
12202    apply_additional_edits.await.unwrap();
12203
12204    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
12205    let completion_text = "foo_and_bar";
12206    let initial_state = indoc! {"
12207        1. ooanbˇ
12208        2. zooanbˇ
12209        3. ooanbˇz
12210        4. zooanbˇz
12211        5. ooanˇ
12212        6. oanbˇ
12213
12214        ooanbˇ
12215    "};
12216    let completion_marked_buffer = indoc! {"
12217        1. ooanb
12218        2. zooanb
12219        3. ooanbz
12220        4. zooanbz
12221        5. ooan
12222        6. oanb
12223
12224        <ooanb|>
12225    "};
12226    let expected = indoc! {"
12227        1. foo_and_barˇ
12228        2. zfoo_and_barˇ
12229        3. foo_and_barˇz
12230        4. zfoo_and_barˇz
12231        5. ooanfoo_and_barˇ
12232        6. oanbfoo_and_barˇ
12233
12234        foo_and_barˇ
12235    "};
12236    cx.set_state(initial_state);
12237    cx.update_editor(|editor, window, cx| {
12238        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12239    });
12240    handle_completion_request_with_insert_and_replace(
12241        &mut cx,
12242        completion_marked_buffer,
12243        vec![(completion_text, completion_text)],
12244        Arc::new(AtomicUsize::new(0)),
12245    )
12246    .await;
12247    cx.condition(|editor, _| editor.context_menu_visible())
12248        .await;
12249    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12250        editor
12251            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12252            .unwrap()
12253    });
12254    cx.assert_editor_state(expected);
12255    handle_resolve_completion_request(&mut cx, None).await;
12256    apply_additional_edits.await.unwrap();
12257
12258    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
12259    // (expects the same as if it was inserted at the end)
12260    let completion_text = "foo_and_bar";
12261    let initial_state = indoc! {"
12262        1. ooˇanb
12263        2. zooˇanb
12264        3. ooˇanbz
12265        4. zooˇanbz
12266
12267        ooˇanb
12268    "};
12269    let completion_marked_buffer = indoc! {"
12270        1. ooanb
12271        2. zooanb
12272        3. ooanbz
12273        4. zooanbz
12274
12275        <oo|anb>
12276    "};
12277    let expected = indoc! {"
12278        1. foo_and_barˇ
12279        2. zfoo_and_barˇ
12280        3. foo_and_barˇz
12281        4. zfoo_and_barˇz
12282
12283        foo_and_barˇ
12284    "};
12285    cx.set_state(initial_state);
12286    cx.update_editor(|editor, window, cx| {
12287        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12288    });
12289    handle_completion_request_with_insert_and_replace(
12290        &mut cx,
12291        completion_marked_buffer,
12292        vec![(completion_text, completion_text)],
12293        Arc::new(AtomicUsize::new(0)),
12294    )
12295    .await;
12296    cx.condition(|editor, _| editor.context_menu_visible())
12297        .await;
12298    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12299        editor
12300            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12301            .unwrap()
12302    });
12303    cx.assert_editor_state(expected);
12304    handle_resolve_completion_request(&mut cx, None).await;
12305    apply_additional_edits.await.unwrap();
12306}
12307
12308// This used to crash
12309#[gpui::test]
12310async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
12311    init_test(cx, |_| {});
12312
12313    let buffer_text = indoc! {"
12314        fn main() {
12315            10.satu;
12316
12317            //
12318            // separate cursors so they open in different excerpts (manually reproducible)
12319            //
12320
12321            10.satu20;
12322        }
12323    "};
12324    let multibuffer_text_with_selections = indoc! {"
12325        fn main() {
12326            10.satuˇ;
12327
12328            //
12329
12330            //
12331
12332            10.satuˇ20;
12333        }
12334    "};
12335    let expected_multibuffer = indoc! {"
12336        fn main() {
12337            10.saturating_sub()ˇ;
12338
12339            //
12340
12341            //
12342
12343            10.saturating_sub()ˇ;
12344        }
12345    "};
12346
12347    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
12348    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
12349
12350    let fs = FakeFs::new(cx.executor());
12351    fs.insert_tree(
12352        path!("/a"),
12353        json!({
12354            "main.rs": buffer_text,
12355        }),
12356    )
12357    .await;
12358
12359    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12360    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12361    language_registry.add(rust_lang());
12362    let mut fake_servers = language_registry.register_fake_lsp(
12363        "Rust",
12364        FakeLspAdapter {
12365            capabilities: lsp::ServerCapabilities {
12366                completion_provider: Some(lsp::CompletionOptions {
12367                    resolve_provider: None,
12368                    ..lsp::CompletionOptions::default()
12369                }),
12370                ..lsp::ServerCapabilities::default()
12371            },
12372            ..FakeLspAdapter::default()
12373        },
12374    );
12375    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12376    let cx = &mut VisualTestContext::from_window(*workspace, cx);
12377    let buffer = project
12378        .update(cx, |project, cx| {
12379            project.open_local_buffer(path!("/a/main.rs"), cx)
12380        })
12381        .await
12382        .unwrap();
12383
12384    let multi_buffer = cx.new(|cx| {
12385        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
12386        multi_buffer.push_excerpts(
12387            buffer.clone(),
12388            [ExcerptRange::new(0..first_excerpt_end)],
12389            cx,
12390        );
12391        multi_buffer.push_excerpts(
12392            buffer.clone(),
12393            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
12394            cx,
12395        );
12396        multi_buffer
12397    });
12398
12399    let editor = workspace
12400        .update(cx, |_, window, cx| {
12401            cx.new(|cx| {
12402                Editor::new(
12403                    EditorMode::Full {
12404                        scale_ui_elements_with_buffer_font_size: false,
12405                        show_active_line_background: false,
12406                        sized_by_content: false,
12407                    },
12408                    multi_buffer.clone(),
12409                    Some(project.clone()),
12410                    window,
12411                    cx,
12412                )
12413            })
12414        })
12415        .unwrap();
12416
12417    let pane = workspace
12418        .update(cx, |workspace, _, _| workspace.active_pane().clone())
12419        .unwrap();
12420    pane.update_in(cx, |pane, window, cx| {
12421        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
12422    });
12423
12424    let fake_server = fake_servers.next().await.unwrap();
12425
12426    editor.update_in(cx, |editor, window, cx| {
12427        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12428            s.select_ranges([
12429                Point::new(1, 11)..Point::new(1, 11),
12430                Point::new(7, 11)..Point::new(7, 11),
12431            ])
12432        });
12433
12434        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
12435    });
12436
12437    editor.update_in(cx, |editor, window, cx| {
12438        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12439    });
12440
12441    fake_server
12442        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
12443            let completion_item = lsp::CompletionItem {
12444                label: "saturating_sub()".into(),
12445                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
12446                    lsp::InsertReplaceEdit {
12447                        new_text: "saturating_sub()".to_owned(),
12448                        insert: lsp::Range::new(
12449                            lsp::Position::new(7, 7),
12450                            lsp::Position::new(7, 11),
12451                        ),
12452                        replace: lsp::Range::new(
12453                            lsp::Position::new(7, 7),
12454                            lsp::Position::new(7, 13),
12455                        ),
12456                    },
12457                )),
12458                ..lsp::CompletionItem::default()
12459            };
12460
12461            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
12462        })
12463        .next()
12464        .await
12465        .unwrap();
12466
12467    cx.condition(&editor, |editor, _| editor.context_menu_visible())
12468        .await;
12469
12470    editor
12471        .update_in(cx, |editor, window, cx| {
12472            editor
12473                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12474                .unwrap()
12475        })
12476        .await
12477        .unwrap();
12478
12479    editor.update(cx, |editor, cx| {
12480        assert_text_with_selections(editor, expected_multibuffer, cx);
12481    })
12482}
12483
12484#[gpui::test]
12485async fn test_completion(cx: &mut TestAppContext) {
12486    init_test(cx, |_| {});
12487
12488    let mut cx = EditorLspTestContext::new_rust(
12489        lsp::ServerCapabilities {
12490            completion_provider: Some(lsp::CompletionOptions {
12491                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12492                resolve_provider: Some(true),
12493                ..Default::default()
12494            }),
12495            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12496            ..Default::default()
12497        },
12498        cx,
12499    )
12500    .await;
12501    let counter = Arc::new(AtomicUsize::new(0));
12502
12503    cx.set_state(indoc! {"
12504        oneˇ
12505        two
12506        three
12507    "});
12508    cx.simulate_keystroke(".");
12509    handle_completion_request(
12510        indoc! {"
12511            one.|<>
12512            two
12513            three
12514        "},
12515        vec!["first_completion", "second_completion"],
12516        true,
12517        counter.clone(),
12518        &mut cx,
12519    )
12520    .await;
12521    cx.condition(|editor, _| editor.context_menu_visible())
12522        .await;
12523    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12524
12525    let _handler = handle_signature_help_request(
12526        &mut cx,
12527        lsp::SignatureHelp {
12528            signatures: vec![lsp::SignatureInformation {
12529                label: "test signature".to_string(),
12530                documentation: None,
12531                parameters: Some(vec![lsp::ParameterInformation {
12532                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
12533                    documentation: None,
12534                }]),
12535                active_parameter: None,
12536            }],
12537            active_signature: None,
12538            active_parameter: None,
12539        },
12540    );
12541    cx.update_editor(|editor, window, cx| {
12542        assert!(
12543            !editor.signature_help_state.is_shown(),
12544            "No signature help was called for"
12545        );
12546        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12547    });
12548    cx.run_until_parked();
12549    cx.update_editor(|editor, _, _| {
12550        assert!(
12551            !editor.signature_help_state.is_shown(),
12552            "No signature help should be shown when completions menu is open"
12553        );
12554    });
12555
12556    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12557        editor.context_menu_next(&Default::default(), window, cx);
12558        editor
12559            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12560            .unwrap()
12561    });
12562    cx.assert_editor_state(indoc! {"
12563        one.second_completionˇ
12564        two
12565        three
12566    "});
12567
12568    handle_resolve_completion_request(
12569        &mut cx,
12570        Some(vec![
12571            (
12572                //This overlaps with the primary completion edit which is
12573                //misbehavior from the LSP spec, test that we filter it out
12574                indoc! {"
12575                    one.second_ˇcompletion
12576                    two
12577                    threeˇ
12578                "},
12579                "overlapping additional edit",
12580            ),
12581            (
12582                indoc! {"
12583                    one.second_completion
12584                    two
12585                    threeˇ
12586                "},
12587                "\nadditional edit",
12588            ),
12589        ]),
12590    )
12591    .await;
12592    apply_additional_edits.await.unwrap();
12593    cx.assert_editor_state(indoc! {"
12594        one.second_completionˇ
12595        two
12596        three
12597        additional edit
12598    "});
12599
12600    cx.set_state(indoc! {"
12601        one.second_completion
12602        twoˇ
12603        threeˇ
12604        additional edit
12605    "});
12606    cx.simulate_keystroke(" ");
12607    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12608    cx.simulate_keystroke("s");
12609    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12610
12611    cx.assert_editor_state(indoc! {"
12612        one.second_completion
12613        two sˇ
12614        three sˇ
12615        additional edit
12616    "});
12617    handle_completion_request(
12618        indoc! {"
12619            one.second_completion
12620            two s
12621            three <s|>
12622            additional edit
12623        "},
12624        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
12625        true,
12626        counter.clone(),
12627        &mut cx,
12628    )
12629    .await;
12630    cx.condition(|editor, _| editor.context_menu_visible())
12631        .await;
12632    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12633
12634    cx.simulate_keystroke("i");
12635
12636    handle_completion_request(
12637        indoc! {"
12638            one.second_completion
12639            two si
12640            three <si|>
12641            additional edit
12642        "},
12643        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
12644        true,
12645        counter.clone(),
12646        &mut cx,
12647    )
12648    .await;
12649    cx.condition(|editor, _| editor.context_menu_visible())
12650        .await;
12651    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12652
12653    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12654        editor
12655            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12656            .unwrap()
12657    });
12658    cx.assert_editor_state(indoc! {"
12659        one.second_completion
12660        two sixth_completionˇ
12661        three sixth_completionˇ
12662        additional edit
12663    "});
12664
12665    apply_additional_edits.await.unwrap();
12666
12667    update_test_language_settings(&mut cx, |settings| {
12668        settings.defaults.show_completions_on_input = Some(false);
12669    });
12670    cx.set_state("editorˇ");
12671    cx.simulate_keystroke(".");
12672    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12673    cx.simulate_keystrokes("c l o");
12674    cx.assert_editor_state("editor.cloˇ");
12675    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12676    cx.update_editor(|editor, window, cx| {
12677        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12678    });
12679    handle_completion_request(
12680        "editor.<clo|>",
12681        vec!["close", "clobber"],
12682        true,
12683        counter.clone(),
12684        &mut cx,
12685    )
12686    .await;
12687    cx.condition(|editor, _| editor.context_menu_visible())
12688        .await;
12689    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
12690
12691    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12692        editor
12693            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12694            .unwrap()
12695    });
12696    cx.assert_editor_state("editor.clobberˇ");
12697    handle_resolve_completion_request(&mut cx, None).await;
12698    apply_additional_edits.await.unwrap();
12699}
12700
12701#[gpui::test]
12702async fn test_completion_reuse(cx: &mut TestAppContext) {
12703    init_test(cx, |_| {});
12704
12705    let mut cx = EditorLspTestContext::new_rust(
12706        lsp::ServerCapabilities {
12707            completion_provider: Some(lsp::CompletionOptions {
12708                trigger_characters: Some(vec![".".to_string()]),
12709                ..Default::default()
12710            }),
12711            ..Default::default()
12712        },
12713        cx,
12714    )
12715    .await;
12716
12717    let counter = Arc::new(AtomicUsize::new(0));
12718    cx.set_state("objˇ");
12719    cx.simulate_keystroke(".");
12720
12721    // Initial completion request returns complete results
12722    let is_incomplete = false;
12723    handle_completion_request(
12724        "obj.|<>",
12725        vec!["a", "ab", "abc"],
12726        is_incomplete,
12727        counter.clone(),
12728        &mut cx,
12729    )
12730    .await;
12731    cx.run_until_parked();
12732    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12733    cx.assert_editor_state("obj.ˇ");
12734    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12735
12736    // Type "a" - filters existing completions
12737    cx.simulate_keystroke("a");
12738    cx.run_until_parked();
12739    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12740    cx.assert_editor_state("obj.aˇ");
12741    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12742
12743    // Type "b" - filters existing completions
12744    cx.simulate_keystroke("b");
12745    cx.run_until_parked();
12746    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12747    cx.assert_editor_state("obj.abˇ");
12748    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12749
12750    // Type "c" - filters existing completions
12751    cx.simulate_keystroke("c");
12752    cx.run_until_parked();
12753    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12754    cx.assert_editor_state("obj.abcˇ");
12755    check_displayed_completions(vec!["abc"], &mut cx);
12756
12757    // Backspace to delete "c" - filters existing completions
12758    cx.update_editor(|editor, window, cx| {
12759        editor.backspace(&Backspace, window, cx);
12760    });
12761    cx.run_until_parked();
12762    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12763    cx.assert_editor_state("obj.abˇ");
12764    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12765
12766    // Moving cursor to the left dismisses menu.
12767    cx.update_editor(|editor, window, cx| {
12768        editor.move_left(&MoveLeft, window, cx);
12769    });
12770    cx.run_until_parked();
12771    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12772    cx.assert_editor_state("obj.aˇb");
12773    cx.update_editor(|editor, _, _| {
12774        assert_eq!(editor.context_menu_visible(), false);
12775    });
12776
12777    // Type "b" - new request
12778    cx.simulate_keystroke("b");
12779    let is_incomplete = false;
12780    handle_completion_request(
12781        "obj.<ab|>a",
12782        vec!["ab", "abc"],
12783        is_incomplete,
12784        counter.clone(),
12785        &mut cx,
12786    )
12787    .await;
12788    cx.run_until_parked();
12789    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12790    cx.assert_editor_state("obj.abˇb");
12791    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12792
12793    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
12794    cx.update_editor(|editor, window, cx| {
12795        editor.backspace(&Backspace, window, cx);
12796    });
12797    let is_incomplete = false;
12798    handle_completion_request(
12799        "obj.<a|>b",
12800        vec!["a", "ab", "abc"],
12801        is_incomplete,
12802        counter.clone(),
12803        &mut cx,
12804    )
12805    .await;
12806    cx.run_until_parked();
12807    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12808    cx.assert_editor_state("obj.aˇb");
12809    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12810
12811    // Backspace to delete "a" - dismisses menu.
12812    cx.update_editor(|editor, window, cx| {
12813        editor.backspace(&Backspace, window, cx);
12814    });
12815    cx.run_until_parked();
12816    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12817    cx.assert_editor_state("obj.ˇb");
12818    cx.update_editor(|editor, _, _| {
12819        assert_eq!(editor.context_menu_visible(), false);
12820    });
12821}
12822
12823#[gpui::test]
12824async fn test_word_completion(cx: &mut TestAppContext) {
12825    let lsp_fetch_timeout_ms = 10;
12826    init_test(cx, |language_settings| {
12827        language_settings.defaults.completions = Some(CompletionSettings {
12828            words: WordsCompletionMode::Fallback,
12829            lsp: true,
12830            lsp_fetch_timeout_ms: 10,
12831            lsp_insert_mode: LspInsertMode::Insert,
12832        });
12833    });
12834
12835    let mut cx = EditorLspTestContext::new_rust(
12836        lsp::ServerCapabilities {
12837            completion_provider: Some(lsp::CompletionOptions {
12838                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12839                ..lsp::CompletionOptions::default()
12840            }),
12841            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12842            ..lsp::ServerCapabilities::default()
12843        },
12844        cx,
12845    )
12846    .await;
12847
12848    let throttle_completions = Arc::new(AtomicBool::new(false));
12849
12850    let lsp_throttle_completions = throttle_completions.clone();
12851    let _completion_requests_handler =
12852        cx.lsp
12853            .server
12854            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
12855                let lsp_throttle_completions = lsp_throttle_completions.clone();
12856                let cx = cx.clone();
12857                async move {
12858                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
12859                        cx.background_executor()
12860                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
12861                            .await;
12862                    }
12863                    Ok(Some(lsp::CompletionResponse::Array(vec![
12864                        lsp::CompletionItem {
12865                            label: "first".into(),
12866                            ..lsp::CompletionItem::default()
12867                        },
12868                        lsp::CompletionItem {
12869                            label: "last".into(),
12870                            ..lsp::CompletionItem::default()
12871                        },
12872                    ])))
12873                }
12874            });
12875
12876    cx.set_state(indoc! {"
12877        oneˇ
12878        two
12879        three
12880    "});
12881    cx.simulate_keystroke(".");
12882    cx.executor().run_until_parked();
12883    cx.condition(|editor, _| editor.context_menu_visible())
12884        .await;
12885    cx.update_editor(|editor, window, cx| {
12886        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12887        {
12888            assert_eq!(
12889                completion_menu_entries(&menu),
12890                &["first", "last"],
12891                "When LSP server is fast to reply, no fallback word completions are used"
12892            );
12893        } else {
12894            panic!("expected completion menu to be open");
12895        }
12896        editor.cancel(&Cancel, window, cx);
12897    });
12898    cx.executor().run_until_parked();
12899    cx.condition(|editor, _| !editor.context_menu_visible())
12900        .await;
12901
12902    throttle_completions.store(true, atomic::Ordering::Release);
12903    cx.simulate_keystroke(".");
12904    cx.executor()
12905        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
12906    cx.executor().run_until_parked();
12907    cx.condition(|editor, _| editor.context_menu_visible())
12908        .await;
12909    cx.update_editor(|editor, _, _| {
12910        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12911        {
12912            assert_eq!(completion_menu_entries(&menu), &["one", "three", "two"],
12913                "When LSP server is slow, document words can be shown instead, if configured accordingly");
12914        } else {
12915            panic!("expected completion menu to be open");
12916        }
12917    });
12918}
12919
12920#[gpui::test]
12921async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
12922    init_test(cx, |language_settings| {
12923        language_settings.defaults.completions = Some(CompletionSettings {
12924            words: WordsCompletionMode::Enabled,
12925            lsp: true,
12926            lsp_fetch_timeout_ms: 0,
12927            lsp_insert_mode: LspInsertMode::Insert,
12928        });
12929    });
12930
12931    let mut cx = EditorLspTestContext::new_rust(
12932        lsp::ServerCapabilities {
12933            completion_provider: Some(lsp::CompletionOptions {
12934                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12935                ..lsp::CompletionOptions::default()
12936            }),
12937            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12938            ..lsp::ServerCapabilities::default()
12939        },
12940        cx,
12941    )
12942    .await;
12943
12944    let _completion_requests_handler =
12945        cx.lsp
12946            .server
12947            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12948                Ok(Some(lsp::CompletionResponse::Array(vec![
12949                    lsp::CompletionItem {
12950                        label: "first".into(),
12951                        ..lsp::CompletionItem::default()
12952                    },
12953                    lsp::CompletionItem {
12954                        label: "last".into(),
12955                        ..lsp::CompletionItem::default()
12956                    },
12957                ])))
12958            });
12959
12960    cx.set_state(indoc! {"ˇ
12961        first
12962        last
12963        second
12964    "});
12965    cx.simulate_keystroke(".");
12966    cx.executor().run_until_parked();
12967    cx.condition(|editor, _| editor.context_menu_visible())
12968        .await;
12969    cx.update_editor(|editor, _, _| {
12970        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12971        {
12972            assert_eq!(
12973                completion_menu_entries(&menu),
12974                &["first", "last", "second"],
12975                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
12976            );
12977        } else {
12978            panic!("expected completion menu to be open");
12979        }
12980    });
12981}
12982
12983#[gpui::test]
12984async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
12985    init_test(cx, |language_settings| {
12986        language_settings.defaults.completions = Some(CompletionSettings {
12987            words: WordsCompletionMode::Disabled,
12988            lsp: true,
12989            lsp_fetch_timeout_ms: 0,
12990            lsp_insert_mode: LspInsertMode::Insert,
12991        });
12992    });
12993
12994    let mut cx = EditorLspTestContext::new_rust(
12995        lsp::ServerCapabilities {
12996            completion_provider: Some(lsp::CompletionOptions {
12997                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12998                ..lsp::CompletionOptions::default()
12999            }),
13000            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13001            ..lsp::ServerCapabilities::default()
13002        },
13003        cx,
13004    )
13005    .await;
13006
13007    let _completion_requests_handler =
13008        cx.lsp
13009            .server
13010            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
13011                panic!("LSP completions should not be queried when dealing with word completions")
13012            });
13013
13014    cx.set_state(indoc! {"ˇ
13015        first
13016        last
13017        second
13018    "});
13019    cx.update_editor(|editor, window, cx| {
13020        editor.show_word_completions(&ShowWordCompletions, window, cx);
13021    });
13022    cx.executor().run_until_parked();
13023    cx.condition(|editor, _| editor.context_menu_visible())
13024        .await;
13025    cx.update_editor(|editor, _, _| {
13026        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13027        {
13028            assert_eq!(
13029                completion_menu_entries(&menu),
13030                &["first", "last", "second"],
13031                "`ShowWordCompletions` action should show word completions"
13032            );
13033        } else {
13034            panic!("expected completion menu to be open");
13035        }
13036    });
13037
13038    cx.simulate_keystroke("l");
13039    cx.executor().run_until_parked();
13040    cx.condition(|editor, _| editor.context_menu_visible())
13041        .await;
13042    cx.update_editor(|editor, _, _| {
13043        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13044        {
13045            assert_eq!(
13046                completion_menu_entries(&menu),
13047                &["last"],
13048                "After showing word completions, further editing should filter them and not query the LSP"
13049            );
13050        } else {
13051            panic!("expected completion menu to be open");
13052        }
13053    });
13054}
13055
13056#[gpui::test]
13057async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
13058    init_test(cx, |language_settings| {
13059        language_settings.defaults.completions = Some(CompletionSettings {
13060            words: WordsCompletionMode::Fallback,
13061            lsp: false,
13062            lsp_fetch_timeout_ms: 0,
13063            lsp_insert_mode: LspInsertMode::Insert,
13064        });
13065    });
13066
13067    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
13068
13069    cx.set_state(indoc! {"ˇ
13070        0_usize
13071        let
13072        33
13073        4.5f32
13074    "});
13075    cx.update_editor(|editor, window, cx| {
13076        editor.show_completions(&ShowCompletions::default(), window, cx);
13077    });
13078    cx.executor().run_until_parked();
13079    cx.condition(|editor, _| editor.context_menu_visible())
13080        .await;
13081    cx.update_editor(|editor, window, cx| {
13082        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13083        {
13084            assert_eq!(
13085                completion_menu_entries(&menu),
13086                &["let"],
13087                "With no digits in the completion query, no digits should be in the word completions"
13088            );
13089        } else {
13090            panic!("expected completion menu to be open");
13091        }
13092        editor.cancel(&Cancel, window, cx);
13093    });
13094
13095    cx.set_state(indoc! {"13096        0_usize
13097        let
13098        3
13099        33.35f32
13100    "});
13101    cx.update_editor(|editor, window, cx| {
13102        editor.show_completions(&ShowCompletions::default(), window, cx);
13103    });
13104    cx.executor().run_until_parked();
13105    cx.condition(|editor, _| editor.context_menu_visible())
13106        .await;
13107    cx.update_editor(|editor, _, _| {
13108        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13109        {
13110            assert_eq!(completion_menu_entries(&menu), &["33", "35f32"], "The digit is in the completion query, \
13111                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
13112        } else {
13113            panic!("expected completion menu to be open");
13114        }
13115    });
13116}
13117
13118fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
13119    let position = || lsp::Position {
13120        line: params.text_document_position.position.line,
13121        character: params.text_document_position.position.character,
13122    };
13123    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13124        range: lsp::Range {
13125            start: position(),
13126            end: position(),
13127        },
13128        new_text: text.to_string(),
13129    }))
13130}
13131
13132#[gpui::test]
13133async fn test_multiline_completion(cx: &mut TestAppContext) {
13134    init_test(cx, |_| {});
13135
13136    let fs = FakeFs::new(cx.executor());
13137    fs.insert_tree(
13138        path!("/a"),
13139        json!({
13140            "main.ts": "a",
13141        }),
13142    )
13143    .await;
13144
13145    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13146    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13147    let typescript_language = Arc::new(Language::new(
13148        LanguageConfig {
13149            name: "TypeScript".into(),
13150            matcher: LanguageMatcher {
13151                path_suffixes: vec!["ts".to_string()],
13152                ..LanguageMatcher::default()
13153            },
13154            line_comments: vec!["// ".into()],
13155            ..LanguageConfig::default()
13156        },
13157        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
13158    ));
13159    language_registry.add(typescript_language.clone());
13160    let mut fake_servers = language_registry.register_fake_lsp(
13161        "TypeScript",
13162        FakeLspAdapter {
13163            capabilities: lsp::ServerCapabilities {
13164                completion_provider: Some(lsp::CompletionOptions {
13165                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13166                    ..lsp::CompletionOptions::default()
13167                }),
13168                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13169                ..lsp::ServerCapabilities::default()
13170            },
13171            // Emulate vtsls label generation
13172            label_for_completion: Some(Box::new(|item, _| {
13173                let text = if let Some(description) = item
13174                    .label_details
13175                    .as_ref()
13176                    .and_then(|label_details| label_details.description.as_ref())
13177                {
13178                    format!("{} {}", item.label, description)
13179                } else if let Some(detail) = &item.detail {
13180                    format!("{} {}", item.label, detail)
13181                } else {
13182                    item.label.clone()
13183                };
13184                let len = text.len();
13185                Some(language::CodeLabel {
13186                    text,
13187                    runs: Vec::new(),
13188                    filter_range: 0..len,
13189                })
13190            })),
13191            ..FakeLspAdapter::default()
13192        },
13193    );
13194    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13195    let cx = &mut VisualTestContext::from_window(*workspace, cx);
13196    let worktree_id = workspace
13197        .update(cx, |workspace, _window, cx| {
13198            workspace.project().update(cx, |project, cx| {
13199                project.worktrees(cx).next().unwrap().read(cx).id()
13200            })
13201        })
13202        .unwrap();
13203    let _buffer = project
13204        .update(cx, |project, cx| {
13205            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
13206        })
13207        .await
13208        .unwrap();
13209    let editor = workspace
13210        .update(cx, |workspace, window, cx| {
13211            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
13212        })
13213        .unwrap()
13214        .await
13215        .unwrap()
13216        .downcast::<Editor>()
13217        .unwrap();
13218    let fake_server = fake_servers.next().await.unwrap();
13219
13220    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
13221    let multiline_label_2 = "a\nb\nc\n";
13222    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
13223    let multiline_description = "d\ne\nf\n";
13224    let multiline_detail_2 = "g\nh\ni\n";
13225
13226    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
13227        move |params, _| async move {
13228            Ok(Some(lsp::CompletionResponse::Array(vec![
13229                lsp::CompletionItem {
13230                    label: multiline_label.to_string(),
13231                    text_edit: gen_text_edit(&params, "new_text_1"),
13232                    ..lsp::CompletionItem::default()
13233                },
13234                lsp::CompletionItem {
13235                    label: "single line label 1".to_string(),
13236                    detail: Some(multiline_detail.to_string()),
13237                    text_edit: gen_text_edit(&params, "new_text_2"),
13238                    ..lsp::CompletionItem::default()
13239                },
13240                lsp::CompletionItem {
13241                    label: "single line label 2".to_string(),
13242                    label_details: Some(lsp::CompletionItemLabelDetails {
13243                        description: Some(multiline_description.to_string()),
13244                        detail: None,
13245                    }),
13246                    text_edit: gen_text_edit(&params, "new_text_2"),
13247                    ..lsp::CompletionItem::default()
13248                },
13249                lsp::CompletionItem {
13250                    label: multiline_label_2.to_string(),
13251                    detail: Some(multiline_detail_2.to_string()),
13252                    text_edit: gen_text_edit(&params, "new_text_3"),
13253                    ..lsp::CompletionItem::default()
13254                },
13255                lsp::CompletionItem {
13256                    label: "Label with many     spaces and \t but without newlines".to_string(),
13257                    detail: Some(
13258                        "Details with many     spaces and \t but without newlines".to_string(),
13259                    ),
13260                    text_edit: gen_text_edit(&params, "new_text_4"),
13261                    ..lsp::CompletionItem::default()
13262                },
13263            ])))
13264        },
13265    );
13266
13267    editor.update_in(cx, |editor, window, cx| {
13268        cx.focus_self(window);
13269        editor.move_to_end(&MoveToEnd, window, cx);
13270        editor.handle_input(".", window, cx);
13271    });
13272    cx.run_until_parked();
13273    completion_handle.next().await.unwrap();
13274
13275    editor.update(cx, |editor, _| {
13276        assert!(editor.context_menu_visible());
13277        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13278        {
13279            let completion_labels = menu
13280                .completions
13281                .borrow()
13282                .iter()
13283                .map(|c| c.label.text.clone())
13284                .collect::<Vec<_>>();
13285            assert_eq!(
13286                completion_labels,
13287                &[
13288                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
13289                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
13290                    "single line label 2 d e f ",
13291                    "a b c g h i ",
13292                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
13293                ],
13294                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
13295            );
13296
13297            for completion in menu
13298                .completions
13299                .borrow()
13300                .iter() {
13301                    assert_eq!(
13302                        completion.label.filter_range,
13303                        0..completion.label.text.len(),
13304                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
13305                    );
13306                }
13307        } else {
13308            panic!("expected completion menu to be open");
13309        }
13310    });
13311}
13312
13313#[gpui::test]
13314async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
13315    init_test(cx, |_| {});
13316    let mut cx = EditorLspTestContext::new_rust(
13317        lsp::ServerCapabilities {
13318            completion_provider: Some(lsp::CompletionOptions {
13319                trigger_characters: Some(vec![".".to_string()]),
13320                ..Default::default()
13321            }),
13322            ..Default::default()
13323        },
13324        cx,
13325    )
13326    .await;
13327    cx.lsp
13328        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13329            Ok(Some(lsp::CompletionResponse::Array(vec![
13330                lsp::CompletionItem {
13331                    label: "first".into(),
13332                    ..Default::default()
13333                },
13334                lsp::CompletionItem {
13335                    label: "last".into(),
13336                    ..Default::default()
13337                },
13338            ])))
13339        });
13340    cx.set_state("variableˇ");
13341    cx.simulate_keystroke(".");
13342    cx.executor().run_until_parked();
13343
13344    cx.update_editor(|editor, _, _| {
13345        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13346        {
13347            assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
13348        } else {
13349            panic!("expected completion menu to be open");
13350        }
13351    });
13352
13353    cx.update_editor(|editor, window, cx| {
13354        editor.move_page_down(&MovePageDown::default(), window, cx);
13355        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13356        {
13357            assert!(
13358                menu.selected_item == 1,
13359                "expected PageDown to select the last item from the context menu"
13360            );
13361        } else {
13362            panic!("expected completion menu to stay open after PageDown");
13363        }
13364    });
13365
13366    cx.update_editor(|editor, window, cx| {
13367        editor.move_page_up(&MovePageUp::default(), window, cx);
13368        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13369        {
13370            assert!(
13371                menu.selected_item == 0,
13372                "expected PageUp to select the first item from the context menu"
13373            );
13374        } else {
13375            panic!("expected completion menu to stay open after PageUp");
13376        }
13377    });
13378}
13379
13380#[gpui::test]
13381async fn test_as_is_completions(cx: &mut TestAppContext) {
13382    init_test(cx, |_| {});
13383    let mut cx = EditorLspTestContext::new_rust(
13384        lsp::ServerCapabilities {
13385            completion_provider: Some(lsp::CompletionOptions {
13386                ..Default::default()
13387            }),
13388            ..Default::default()
13389        },
13390        cx,
13391    )
13392    .await;
13393    cx.lsp
13394        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13395            Ok(Some(lsp::CompletionResponse::Array(vec![
13396                lsp::CompletionItem {
13397                    label: "unsafe".into(),
13398                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13399                        range: lsp::Range {
13400                            start: lsp::Position {
13401                                line: 1,
13402                                character: 2,
13403                            },
13404                            end: lsp::Position {
13405                                line: 1,
13406                                character: 3,
13407                            },
13408                        },
13409                        new_text: "unsafe".to_string(),
13410                    })),
13411                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
13412                    ..Default::default()
13413                },
13414            ])))
13415        });
13416    cx.set_state("fn a() {}\n");
13417    cx.executor().run_until_parked();
13418    cx.update_editor(|editor, window, cx| {
13419        editor.show_completions(
13420            &ShowCompletions {
13421                trigger: Some("\n".into()),
13422            },
13423            window,
13424            cx,
13425        );
13426    });
13427    cx.executor().run_until_parked();
13428
13429    cx.update_editor(|editor, window, cx| {
13430        editor.confirm_completion(&Default::default(), window, cx)
13431    });
13432    cx.executor().run_until_parked();
13433    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
13434}
13435
13436#[gpui::test]
13437async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
13438    init_test(cx, |_| {});
13439    let language =
13440        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
13441    let mut cx = EditorLspTestContext::new(
13442        language,
13443        lsp::ServerCapabilities {
13444            completion_provider: Some(lsp::CompletionOptions {
13445                ..lsp::CompletionOptions::default()
13446            }),
13447            ..lsp::ServerCapabilities::default()
13448        },
13449        cx,
13450    )
13451    .await;
13452
13453    cx.set_state(
13454        "#ifndef BAR_H
13455#define BAR_H
13456
13457#include <stdbool.h>
13458
13459int fn_branch(bool do_branch1, bool do_branch2);
13460
13461#endif // BAR_H
13462ˇ",
13463    );
13464    cx.executor().run_until_parked();
13465    cx.update_editor(|editor, window, cx| {
13466        editor.handle_input("#", window, cx);
13467    });
13468    cx.executor().run_until_parked();
13469    cx.update_editor(|editor, window, cx| {
13470        editor.handle_input("i", window, cx);
13471    });
13472    cx.executor().run_until_parked();
13473    cx.update_editor(|editor, window, cx| {
13474        editor.handle_input("n", window, cx);
13475    });
13476    cx.executor().run_until_parked();
13477    cx.assert_editor_state(
13478        "#ifndef BAR_H
13479#define BAR_H
13480
13481#include <stdbool.h>
13482
13483int fn_branch(bool do_branch1, bool do_branch2);
13484
13485#endif // BAR_H
13486#inˇ",
13487    );
13488
13489    cx.lsp
13490        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13491            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
13492                is_incomplete: false,
13493                item_defaults: None,
13494                items: vec![lsp::CompletionItem {
13495                    kind: Some(lsp::CompletionItemKind::SNIPPET),
13496                    label_details: Some(lsp::CompletionItemLabelDetails {
13497                        detail: Some("header".to_string()),
13498                        description: None,
13499                    }),
13500                    label: " include".to_string(),
13501                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13502                        range: lsp::Range {
13503                            start: lsp::Position {
13504                                line: 8,
13505                                character: 1,
13506                            },
13507                            end: lsp::Position {
13508                                line: 8,
13509                                character: 1,
13510                            },
13511                        },
13512                        new_text: "include \"$0\"".to_string(),
13513                    })),
13514                    sort_text: Some("40b67681include".to_string()),
13515                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
13516                    filter_text: Some("include".to_string()),
13517                    insert_text: Some("include \"$0\"".to_string()),
13518                    ..lsp::CompletionItem::default()
13519                }],
13520            })))
13521        });
13522    cx.update_editor(|editor, window, cx| {
13523        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13524    });
13525    cx.executor().run_until_parked();
13526    cx.update_editor(|editor, window, cx| {
13527        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
13528    });
13529    cx.executor().run_until_parked();
13530    cx.assert_editor_state(
13531        "#ifndef BAR_H
13532#define BAR_H
13533
13534#include <stdbool.h>
13535
13536int fn_branch(bool do_branch1, bool do_branch2);
13537
13538#endif // BAR_H
13539#include \"ˇ\"",
13540    );
13541
13542    cx.lsp
13543        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13544            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
13545                is_incomplete: true,
13546                item_defaults: None,
13547                items: vec![lsp::CompletionItem {
13548                    kind: Some(lsp::CompletionItemKind::FILE),
13549                    label: "AGL/".to_string(),
13550                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13551                        range: lsp::Range {
13552                            start: lsp::Position {
13553                                line: 8,
13554                                character: 10,
13555                            },
13556                            end: lsp::Position {
13557                                line: 8,
13558                                character: 11,
13559                            },
13560                        },
13561                        new_text: "AGL/".to_string(),
13562                    })),
13563                    sort_text: Some("40b67681AGL/".to_string()),
13564                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
13565                    filter_text: Some("AGL/".to_string()),
13566                    insert_text: Some("AGL/".to_string()),
13567                    ..lsp::CompletionItem::default()
13568                }],
13569            })))
13570        });
13571    cx.update_editor(|editor, window, cx| {
13572        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13573    });
13574    cx.executor().run_until_parked();
13575    cx.update_editor(|editor, window, cx| {
13576        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
13577    });
13578    cx.executor().run_until_parked();
13579    cx.assert_editor_state(
13580        r##"#ifndef BAR_H
13581#define BAR_H
13582
13583#include <stdbool.h>
13584
13585int fn_branch(bool do_branch1, bool do_branch2);
13586
13587#endif // BAR_H
13588#include "AGL/ˇ"##,
13589    );
13590
13591    cx.update_editor(|editor, window, cx| {
13592        editor.handle_input("\"", window, cx);
13593    });
13594    cx.executor().run_until_parked();
13595    cx.assert_editor_state(
13596        r##"#ifndef BAR_H
13597#define BAR_H
13598
13599#include <stdbool.h>
13600
13601int fn_branch(bool do_branch1, bool do_branch2);
13602
13603#endif // BAR_H
13604#include "AGL/"ˇ"##,
13605    );
13606}
13607
13608#[gpui::test]
13609async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
13610    init_test(cx, |_| {});
13611
13612    let mut cx = EditorLspTestContext::new_rust(
13613        lsp::ServerCapabilities {
13614            completion_provider: Some(lsp::CompletionOptions {
13615                trigger_characters: Some(vec![".".to_string()]),
13616                resolve_provider: Some(true),
13617                ..Default::default()
13618            }),
13619            ..Default::default()
13620        },
13621        cx,
13622    )
13623    .await;
13624
13625    cx.set_state("fn main() { let a = 2ˇ; }");
13626    cx.simulate_keystroke(".");
13627    let completion_item = lsp::CompletionItem {
13628        label: "Some".into(),
13629        kind: Some(lsp::CompletionItemKind::SNIPPET),
13630        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
13631        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
13632            kind: lsp::MarkupKind::Markdown,
13633            value: "```rust\nSome(2)\n```".to_string(),
13634        })),
13635        deprecated: Some(false),
13636        sort_text: Some("Some".to_string()),
13637        filter_text: Some("Some".to_string()),
13638        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
13639        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13640            range: lsp::Range {
13641                start: lsp::Position {
13642                    line: 0,
13643                    character: 22,
13644                },
13645                end: lsp::Position {
13646                    line: 0,
13647                    character: 22,
13648                },
13649            },
13650            new_text: "Some(2)".to_string(),
13651        })),
13652        additional_text_edits: Some(vec![lsp::TextEdit {
13653            range: lsp::Range {
13654                start: lsp::Position {
13655                    line: 0,
13656                    character: 20,
13657                },
13658                end: lsp::Position {
13659                    line: 0,
13660                    character: 22,
13661                },
13662            },
13663            new_text: "".to_string(),
13664        }]),
13665        ..Default::default()
13666    };
13667
13668    let closure_completion_item = completion_item.clone();
13669    let counter = Arc::new(AtomicUsize::new(0));
13670    let counter_clone = counter.clone();
13671    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
13672        let task_completion_item = closure_completion_item.clone();
13673        counter_clone.fetch_add(1, atomic::Ordering::Release);
13674        async move {
13675            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
13676                is_incomplete: true,
13677                item_defaults: None,
13678                items: vec![task_completion_item],
13679            })))
13680        }
13681    });
13682
13683    cx.condition(|editor, _| editor.context_menu_visible())
13684        .await;
13685    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
13686    assert!(request.next().await.is_some());
13687    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13688
13689    cx.simulate_keystrokes("S o m");
13690    cx.condition(|editor, _| editor.context_menu_visible())
13691        .await;
13692    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
13693    assert!(request.next().await.is_some());
13694    assert!(request.next().await.is_some());
13695    assert!(request.next().await.is_some());
13696    request.close();
13697    assert!(request.next().await.is_none());
13698    assert_eq!(
13699        counter.load(atomic::Ordering::Acquire),
13700        4,
13701        "With the completions menu open, only one LSP request should happen per input"
13702    );
13703}
13704
13705#[gpui::test]
13706async fn test_toggle_comment(cx: &mut TestAppContext) {
13707    init_test(cx, |_| {});
13708    let mut cx = EditorTestContext::new(cx).await;
13709    let language = Arc::new(Language::new(
13710        LanguageConfig {
13711            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
13712            ..Default::default()
13713        },
13714        Some(tree_sitter_rust::LANGUAGE.into()),
13715    ));
13716    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
13717
13718    // If multiple selections intersect a line, the line is only toggled once.
13719    cx.set_state(indoc! {"
13720        fn a() {
13721            «//b();
13722            ˇ»// «c();
13723            //ˇ»  d();
13724        }
13725    "});
13726
13727    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13728
13729    cx.assert_editor_state(indoc! {"
13730        fn a() {
13731            «b();
13732            c();
13733            ˇ» d();
13734        }
13735    "});
13736
13737    // The comment prefix is inserted at the same column for every line in a
13738    // selection.
13739    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13740
13741    cx.assert_editor_state(indoc! {"
13742        fn a() {
13743            // «b();
13744            // c();
13745            ˇ»//  d();
13746        }
13747    "});
13748
13749    // If a selection ends at the beginning of a line, that line is not toggled.
13750    cx.set_selections_state(indoc! {"
13751        fn a() {
13752            // b();
13753            «// c();
13754        ˇ»    //  d();
13755        }
13756    "});
13757
13758    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13759
13760    cx.assert_editor_state(indoc! {"
13761        fn a() {
13762            // b();
13763            «c();
13764        ˇ»    //  d();
13765        }
13766    "});
13767
13768    // If a selection span a single line and is empty, the line is toggled.
13769    cx.set_state(indoc! {"
13770        fn a() {
13771            a();
13772            b();
13773        ˇ
13774        }
13775    "});
13776
13777    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13778
13779    cx.assert_editor_state(indoc! {"
13780        fn a() {
13781            a();
13782            b();
13783        //•ˇ
13784        }
13785    "});
13786
13787    // If a selection span multiple lines, empty lines are not toggled.
13788    cx.set_state(indoc! {"
13789        fn a() {
13790            «a();
13791
13792            c();ˇ»
13793        }
13794    "});
13795
13796    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13797
13798    cx.assert_editor_state(indoc! {"
13799        fn a() {
13800            // «a();
13801
13802            // c();ˇ»
13803        }
13804    "});
13805
13806    // If a selection includes multiple comment prefixes, all lines are uncommented.
13807    cx.set_state(indoc! {"
13808        fn a() {
13809            «// a();
13810            /// b();
13811            //! c();ˇ»
13812        }
13813    "});
13814
13815    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13816
13817    cx.assert_editor_state(indoc! {"
13818        fn a() {
13819            «a();
13820            b();
13821            c();ˇ»
13822        }
13823    "});
13824}
13825
13826#[gpui::test]
13827async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
13828    init_test(cx, |_| {});
13829    let mut cx = EditorTestContext::new(cx).await;
13830    let language = Arc::new(Language::new(
13831        LanguageConfig {
13832            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
13833            ..Default::default()
13834        },
13835        Some(tree_sitter_rust::LANGUAGE.into()),
13836    ));
13837    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
13838
13839    let toggle_comments = &ToggleComments {
13840        advance_downwards: false,
13841        ignore_indent: true,
13842    };
13843
13844    // If multiple selections intersect a line, the line is only toggled once.
13845    cx.set_state(indoc! {"
13846        fn a() {
13847        //    «b();
13848        //    c();
13849        //    ˇ» d();
13850        }
13851    "});
13852
13853    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13854
13855    cx.assert_editor_state(indoc! {"
13856        fn a() {
13857            «b();
13858            c();
13859            ˇ» d();
13860        }
13861    "});
13862
13863    // The comment prefix is inserted at the beginning of each line
13864    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13865
13866    cx.assert_editor_state(indoc! {"
13867        fn a() {
13868        //    «b();
13869        //    c();
13870        //    ˇ» d();
13871        }
13872    "});
13873
13874    // If a selection ends at the beginning of a line, that line is not toggled.
13875    cx.set_selections_state(indoc! {"
13876        fn a() {
13877        //    b();
13878        //    «c();
13879        ˇ»//     d();
13880        }
13881    "});
13882
13883    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13884
13885    cx.assert_editor_state(indoc! {"
13886        fn a() {
13887        //    b();
13888            «c();
13889        ˇ»//     d();
13890        }
13891    "});
13892
13893    // If a selection span a single line and is empty, the line is toggled.
13894    cx.set_state(indoc! {"
13895        fn a() {
13896            a();
13897            b();
13898        ˇ
13899        }
13900    "});
13901
13902    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13903
13904    cx.assert_editor_state(indoc! {"
13905        fn a() {
13906            a();
13907            b();
13908        //ˇ
13909        }
13910    "});
13911
13912    // If a selection span multiple lines, empty lines are not toggled.
13913    cx.set_state(indoc! {"
13914        fn a() {
13915            «a();
13916
13917            c();ˇ»
13918        }
13919    "});
13920
13921    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13922
13923    cx.assert_editor_state(indoc! {"
13924        fn a() {
13925        //    «a();
13926
13927        //    c();ˇ»
13928        }
13929    "});
13930
13931    // If a selection includes multiple comment prefixes, all lines are uncommented.
13932    cx.set_state(indoc! {"
13933        fn a() {
13934        //    «a();
13935        ///    b();
13936        //!    c();ˇ»
13937        }
13938    "});
13939
13940    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13941
13942    cx.assert_editor_state(indoc! {"
13943        fn a() {
13944            «a();
13945            b();
13946            c();ˇ»
13947        }
13948    "});
13949}
13950
13951#[gpui::test]
13952async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
13953    init_test(cx, |_| {});
13954
13955    let language = Arc::new(Language::new(
13956        LanguageConfig {
13957            line_comments: vec!["// ".into()],
13958            ..Default::default()
13959        },
13960        Some(tree_sitter_rust::LANGUAGE.into()),
13961    ));
13962
13963    let mut cx = EditorTestContext::new(cx).await;
13964
13965    cx.language_registry().add(language.clone());
13966    cx.update_buffer(|buffer, cx| {
13967        buffer.set_language(Some(language), cx);
13968    });
13969
13970    let toggle_comments = &ToggleComments {
13971        advance_downwards: true,
13972        ignore_indent: false,
13973    };
13974
13975    // Single cursor on one line -> advance
13976    // Cursor moves horizontally 3 characters as well on non-blank line
13977    cx.set_state(indoc!(
13978        "fn a() {
13979             ˇdog();
13980             cat();
13981        }"
13982    ));
13983    cx.update_editor(|editor, window, cx| {
13984        editor.toggle_comments(toggle_comments, window, cx);
13985    });
13986    cx.assert_editor_state(indoc!(
13987        "fn a() {
13988             // dog();
13989             catˇ();
13990        }"
13991    ));
13992
13993    // Single selection on one line -> don't advance
13994    cx.set_state(indoc!(
13995        "fn a() {
13996             «dog()ˇ»;
13997             cat();
13998        }"
13999    ));
14000    cx.update_editor(|editor, window, cx| {
14001        editor.toggle_comments(toggle_comments, window, cx);
14002    });
14003    cx.assert_editor_state(indoc!(
14004        "fn a() {
14005             // «dog()ˇ»;
14006             cat();
14007        }"
14008    ));
14009
14010    // Multiple cursors on one line -> advance
14011    cx.set_state(indoc!(
14012        "fn a() {
14013             ˇdˇog();
14014             cat();
14015        }"
14016    ));
14017    cx.update_editor(|editor, window, cx| {
14018        editor.toggle_comments(toggle_comments, window, cx);
14019    });
14020    cx.assert_editor_state(indoc!(
14021        "fn a() {
14022             // dog();
14023             catˇ(ˇ);
14024        }"
14025    ));
14026
14027    // Multiple cursors on one line, with selection -> don't advance
14028    cx.set_state(indoc!(
14029        "fn a() {
14030             ˇdˇog«()ˇ»;
14031             cat();
14032        }"
14033    ));
14034    cx.update_editor(|editor, window, cx| {
14035        editor.toggle_comments(toggle_comments, window, cx);
14036    });
14037    cx.assert_editor_state(indoc!(
14038        "fn a() {
14039             // ˇdˇog«()ˇ»;
14040             cat();
14041        }"
14042    ));
14043
14044    // Single cursor on one line -> advance
14045    // Cursor moves to column 0 on blank line
14046    cx.set_state(indoc!(
14047        "fn a() {
14048             ˇdog();
14049
14050             cat();
14051        }"
14052    ));
14053    cx.update_editor(|editor, window, cx| {
14054        editor.toggle_comments(toggle_comments, window, cx);
14055    });
14056    cx.assert_editor_state(indoc!(
14057        "fn a() {
14058             // dog();
14059        ˇ
14060             cat();
14061        }"
14062    ));
14063
14064    // Single cursor on one line -> advance
14065    // Cursor starts and ends at column 0
14066    cx.set_state(indoc!(
14067        "fn a() {
14068         ˇ    dog();
14069             cat();
14070        }"
14071    ));
14072    cx.update_editor(|editor, window, cx| {
14073        editor.toggle_comments(toggle_comments, window, cx);
14074    });
14075    cx.assert_editor_state(indoc!(
14076        "fn a() {
14077             // dog();
14078         ˇ    cat();
14079        }"
14080    ));
14081}
14082
14083#[gpui::test]
14084async fn test_toggle_block_comment(cx: &mut TestAppContext) {
14085    init_test(cx, |_| {});
14086
14087    let mut cx = EditorTestContext::new(cx).await;
14088
14089    let html_language = Arc::new(
14090        Language::new(
14091            LanguageConfig {
14092                name: "HTML".into(),
14093                block_comment: Some(BlockCommentConfig {
14094                    start: "<!-- ".into(),
14095                    prefix: "".into(),
14096                    end: " -->".into(),
14097                    tab_size: 0,
14098                }),
14099                ..Default::default()
14100            },
14101            Some(tree_sitter_html::LANGUAGE.into()),
14102        )
14103        .with_injection_query(
14104            r#"
14105            (script_element
14106                (raw_text) @injection.content
14107                (#set! injection.language "javascript"))
14108            "#,
14109        )
14110        .unwrap(),
14111    );
14112
14113    let javascript_language = Arc::new(Language::new(
14114        LanguageConfig {
14115            name: "JavaScript".into(),
14116            line_comments: vec!["// ".into()],
14117            ..Default::default()
14118        },
14119        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
14120    ));
14121
14122    cx.language_registry().add(html_language.clone());
14123    cx.language_registry().add(javascript_language.clone());
14124    cx.update_buffer(|buffer, cx| {
14125        buffer.set_language(Some(html_language), cx);
14126    });
14127
14128    // Toggle comments for empty selections
14129    cx.set_state(
14130        &r#"
14131            <p>A</p>ˇ
14132            <p>B</p>ˇ
14133            <p>C</p>ˇ
14134        "#
14135        .unindent(),
14136    );
14137    cx.update_editor(|editor, window, cx| {
14138        editor.toggle_comments(&ToggleComments::default(), window, cx)
14139    });
14140    cx.assert_editor_state(
14141        &r#"
14142            <!-- <p>A</p>ˇ -->
14143            <!-- <p>B</p>ˇ -->
14144            <!-- <p>C</p>ˇ -->
14145        "#
14146        .unindent(),
14147    );
14148    cx.update_editor(|editor, window, cx| {
14149        editor.toggle_comments(&ToggleComments::default(), window, cx)
14150    });
14151    cx.assert_editor_state(
14152        &r#"
14153            <p>A</p>ˇ
14154            <p>B</p>ˇ
14155            <p>C</p>ˇ
14156        "#
14157        .unindent(),
14158    );
14159
14160    // Toggle comments for mixture of empty and non-empty selections, where
14161    // multiple selections occupy a given line.
14162    cx.set_state(
14163        &r#"
14164            <p>A«</p>
14165            <p>ˇ»B</p>ˇ
14166            <p>C«</p>
14167            <p>ˇ»D</p>ˇ
14168        "#
14169        .unindent(),
14170    );
14171
14172    cx.update_editor(|editor, window, cx| {
14173        editor.toggle_comments(&ToggleComments::default(), window, cx)
14174    });
14175    cx.assert_editor_state(
14176        &r#"
14177            <!-- <p>A«</p>
14178            <p>ˇ»B</p>ˇ -->
14179            <!-- <p>C«</p>
14180            <p>ˇ»D</p>ˇ -->
14181        "#
14182        .unindent(),
14183    );
14184    cx.update_editor(|editor, window, cx| {
14185        editor.toggle_comments(&ToggleComments::default(), window, cx)
14186    });
14187    cx.assert_editor_state(
14188        &r#"
14189            <p>A«</p>
14190            <p>ˇ»B</p>ˇ
14191            <p>C«</p>
14192            <p>ˇ»D</p>ˇ
14193        "#
14194        .unindent(),
14195    );
14196
14197    // Toggle comments when different languages are active for different
14198    // selections.
14199    cx.set_state(
14200        &r#"
14201            ˇ<script>
14202                ˇvar x = new Y();
14203            ˇ</script>
14204        "#
14205        .unindent(),
14206    );
14207    cx.executor().run_until_parked();
14208    cx.update_editor(|editor, window, cx| {
14209        editor.toggle_comments(&ToggleComments::default(), window, cx)
14210    });
14211    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
14212    // Uncommenting and commenting from this position brings in even more wrong artifacts.
14213    cx.assert_editor_state(
14214        &r#"
14215            <!-- ˇ<script> -->
14216                // ˇvar x = new Y();
14217            <!-- ˇ</script> -->
14218        "#
14219        .unindent(),
14220    );
14221}
14222
14223#[gpui::test]
14224fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
14225    init_test(cx, |_| {});
14226
14227    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
14228    let multibuffer = cx.new(|cx| {
14229        let mut multibuffer = MultiBuffer::new(ReadWrite);
14230        multibuffer.push_excerpts(
14231            buffer.clone(),
14232            [
14233                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
14234                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
14235            ],
14236            cx,
14237        );
14238        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
14239        multibuffer
14240    });
14241
14242    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
14243    editor.update_in(cx, |editor, window, cx| {
14244        assert_eq!(editor.text(cx), "aaaa\nbbbb");
14245        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14246            s.select_ranges([
14247                Point::new(0, 0)..Point::new(0, 0),
14248                Point::new(1, 0)..Point::new(1, 0),
14249            ])
14250        });
14251
14252        editor.handle_input("X", window, cx);
14253        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
14254        assert_eq!(
14255            editor.selections.ranges(cx),
14256            [
14257                Point::new(0, 1)..Point::new(0, 1),
14258                Point::new(1, 1)..Point::new(1, 1),
14259            ]
14260        );
14261
14262        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
14263        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14264            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
14265        });
14266        editor.backspace(&Default::default(), window, cx);
14267        assert_eq!(editor.text(cx), "Xa\nbbb");
14268        assert_eq!(
14269            editor.selections.ranges(cx),
14270            [Point::new(1, 0)..Point::new(1, 0)]
14271        );
14272
14273        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14274            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
14275        });
14276        editor.backspace(&Default::default(), window, cx);
14277        assert_eq!(editor.text(cx), "X\nbb");
14278        assert_eq!(
14279            editor.selections.ranges(cx),
14280            [Point::new(0, 1)..Point::new(0, 1)]
14281        );
14282    });
14283}
14284
14285#[gpui::test]
14286fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
14287    init_test(cx, |_| {});
14288
14289    let markers = vec![('[', ']').into(), ('(', ')').into()];
14290    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
14291        indoc! {"
14292            [aaaa
14293            (bbbb]
14294            cccc)",
14295        },
14296        markers.clone(),
14297    );
14298    let excerpt_ranges = markers.into_iter().map(|marker| {
14299        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
14300        ExcerptRange::new(context.clone())
14301    });
14302    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
14303    let multibuffer = cx.new(|cx| {
14304        let mut multibuffer = MultiBuffer::new(ReadWrite);
14305        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
14306        multibuffer
14307    });
14308
14309    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
14310    editor.update_in(cx, |editor, window, cx| {
14311        let (expected_text, selection_ranges) = marked_text_ranges(
14312            indoc! {"
14313                aaaa
14314                bˇbbb
14315                bˇbbˇb
14316                cccc"
14317            },
14318            true,
14319        );
14320        assert_eq!(editor.text(cx), expected_text);
14321        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14322            s.select_ranges(selection_ranges)
14323        });
14324
14325        editor.handle_input("X", window, cx);
14326
14327        let (expected_text, expected_selections) = marked_text_ranges(
14328            indoc! {"
14329                aaaa
14330                bXˇbbXb
14331                bXˇbbXˇb
14332                cccc"
14333            },
14334            false,
14335        );
14336        assert_eq!(editor.text(cx), expected_text);
14337        assert_eq!(editor.selections.ranges(cx), expected_selections);
14338
14339        editor.newline(&Newline, window, cx);
14340        let (expected_text, expected_selections) = marked_text_ranges(
14341            indoc! {"
14342                aaaa
14343                bX
14344                ˇbbX
14345                b
14346                bX
14347                ˇbbX
14348                ˇb
14349                cccc"
14350            },
14351            false,
14352        );
14353        assert_eq!(editor.text(cx), expected_text);
14354        assert_eq!(editor.selections.ranges(cx), expected_selections);
14355    });
14356}
14357
14358#[gpui::test]
14359fn test_refresh_selections(cx: &mut TestAppContext) {
14360    init_test(cx, |_| {});
14361
14362    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
14363    let mut excerpt1_id = None;
14364    let multibuffer = cx.new(|cx| {
14365        let mut multibuffer = MultiBuffer::new(ReadWrite);
14366        excerpt1_id = multibuffer
14367            .push_excerpts(
14368                buffer.clone(),
14369                [
14370                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
14371                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
14372                ],
14373                cx,
14374            )
14375            .into_iter()
14376            .next();
14377        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
14378        multibuffer
14379    });
14380
14381    let editor = cx.add_window(|window, cx| {
14382        let mut editor = build_editor(multibuffer.clone(), window, cx);
14383        let snapshot = editor.snapshot(window, cx);
14384        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14385            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
14386        });
14387        editor.begin_selection(
14388            Point::new(2, 1).to_display_point(&snapshot),
14389            true,
14390            1,
14391            window,
14392            cx,
14393        );
14394        assert_eq!(
14395            editor.selections.ranges(cx),
14396            [
14397                Point::new(1, 3)..Point::new(1, 3),
14398                Point::new(2, 1)..Point::new(2, 1),
14399            ]
14400        );
14401        editor
14402    });
14403
14404    // Refreshing selections is a no-op when excerpts haven't changed.
14405    _ = editor.update(cx, |editor, window, cx| {
14406        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14407        assert_eq!(
14408            editor.selections.ranges(cx),
14409            [
14410                Point::new(1, 3)..Point::new(1, 3),
14411                Point::new(2, 1)..Point::new(2, 1),
14412            ]
14413        );
14414    });
14415
14416    multibuffer.update(cx, |multibuffer, cx| {
14417        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
14418    });
14419    _ = editor.update(cx, |editor, window, cx| {
14420        // Removing an excerpt causes the first selection to become degenerate.
14421        assert_eq!(
14422            editor.selections.ranges(cx),
14423            [
14424                Point::new(0, 0)..Point::new(0, 0),
14425                Point::new(0, 1)..Point::new(0, 1)
14426            ]
14427        );
14428
14429        // Refreshing selections will relocate the first selection to the original buffer
14430        // location.
14431        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14432        assert_eq!(
14433            editor.selections.ranges(cx),
14434            [
14435                Point::new(0, 1)..Point::new(0, 1),
14436                Point::new(0, 3)..Point::new(0, 3)
14437            ]
14438        );
14439        assert!(editor.selections.pending_anchor().is_some());
14440    });
14441}
14442
14443#[gpui::test]
14444fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
14445    init_test(cx, |_| {});
14446
14447    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
14448    let mut excerpt1_id = None;
14449    let multibuffer = cx.new(|cx| {
14450        let mut multibuffer = MultiBuffer::new(ReadWrite);
14451        excerpt1_id = multibuffer
14452            .push_excerpts(
14453                buffer.clone(),
14454                [
14455                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
14456                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
14457                ],
14458                cx,
14459            )
14460            .into_iter()
14461            .next();
14462        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
14463        multibuffer
14464    });
14465
14466    let editor = cx.add_window(|window, cx| {
14467        let mut editor = build_editor(multibuffer.clone(), window, cx);
14468        let snapshot = editor.snapshot(window, cx);
14469        editor.begin_selection(
14470            Point::new(1, 3).to_display_point(&snapshot),
14471            false,
14472            1,
14473            window,
14474            cx,
14475        );
14476        assert_eq!(
14477            editor.selections.ranges(cx),
14478            [Point::new(1, 3)..Point::new(1, 3)]
14479        );
14480        editor
14481    });
14482
14483    multibuffer.update(cx, |multibuffer, cx| {
14484        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
14485    });
14486    _ = editor.update(cx, |editor, window, cx| {
14487        assert_eq!(
14488            editor.selections.ranges(cx),
14489            [Point::new(0, 0)..Point::new(0, 0)]
14490        );
14491
14492        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
14493        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14494        assert_eq!(
14495            editor.selections.ranges(cx),
14496            [Point::new(0, 3)..Point::new(0, 3)]
14497        );
14498        assert!(editor.selections.pending_anchor().is_some());
14499    });
14500}
14501
14502#[gpui::test]
14503async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
14504    init_test(cx, |_| {});
14505
14506    let language = Arc::new(
14507        Language::new(
14508            LanguageConfig {
14509                brackets: BracketPairConfig {
14510                    pairs: vec![
14511                        BracketPair {
14512                            start: "{".to_string(),
14513                            end: "}".to_string(),
14514                            close: true,
14515                            surround: true,
14516                            newline: true,
14517                        },
14518                        BracketPair {
14519                            start: "/* ".to_string(),
14520                            end: " */".to_string(),
14521                            close: true,
14522                            surround: true,
14523                            newline: true,
14524                        },
14525                    ],
14526                    ..Default::default()
14527                },
14528                ..Default::default()
14529            },
14530            Some(tree_sitter_rust::LANGUAGE.into()),
14531        )
14532        .with_indents_query("")
14533        .unwrap(),
14534    );
14535
14536    let text = concat!(
14537        "{   }\n",     //
14538        "  x\n",       //
14539        "  /*   */\n", //
14540        "x\n",         //
14541        "{{} }\n",     //
14542    );
14543
14544    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
14545    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
14546    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
14547    editor
14548        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
14549        .await;
14550
14551    editor.update_in(cx, |editor, window, cx| {
14552        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14553            s.select_display_ranges([
14554                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
14555                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
14556                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
14557            ])
14558        });
14559        editor.newline(&Newline, window, cx);
14560
14561        assert_eq!(
14562            editor.buffer().read(cx).read(cx).text(),
14563            concat!(
14564                "{ \n",    // Suppress rustfmt
14565                "\n",      //
14566                "}\n",     //
14567                "  x\n",   //
14568                "  /* \n", //
14569                "  \n",    //
14570                "  */\n",  //
14571                "x\n",     //
14572                "{{} \n",  //
14573                "}\n",     //
14574            )
14575        );
14576    });
14577}
14578
14579#[gpui::test]
14580fn test_highlighted_ranges(cx: &mut TestAppContext) {
14581    init_test(cx, |_| {});
14582
14583    let editor = cx.add_window(|window, cx| {
14584        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
14585        build_editor(buffer.clone(), window, cx)
14586    });
14587
14588    _ = editor.update(cx, |editor, window, cx| {
14589        struct Type1;
14590        struct Type2;
14591
14592        let buffer = editor.buffer.read(cx).snapshot(cx);
14593
14594        let anchor_range =
14595            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
14596
14597        editor.highlight_background::<Type1>(
14598            &[
14599                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
14600                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
14601                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
14602                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
14603            ],
14604            |_| Hsla::red(),
14605            cx,
14606        );
14607        editor.highlight_background::<Type2>(
14608            &[
14609                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
14610                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
14611                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
14612                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
14613            ],
14614            |_| Hsla::green(),
14615            cx,
14616        );
14617
14618        let snapshot = editor.snapshot(window, cx);
14619        let mut highlighted_ranges = editor.background_highlights_in_range(
14620            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
14621            &snapshot,
14622            cx.theme(),
14623        );
14624        // Enforce a consistent ordering based on color without relying on the ordering of the
14625        // highlight's `TypeId` which is non-executor.
14626        highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
14627        assert_eq!(
14628            highlighted_ranges,
14629            &[
14630                (
14631                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
14632                    Hsla::red(),
14633                ),
14634                (
14635                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
14636                    Hsla::red(),
14637                ),
14638                (
14639                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
14640                    Hsla::green(),
14641                ),
14642                (
14643                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
14644                    Hsla::green(),
14645                ),
14646            ]
14647        );
14648        assert_eq!(
14649            editor.background_highlights_in_range(
14650                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
14651                &snapshot,
14652                cx.theme(),
14653            ),
14654            &[(
14655                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
14656                Hsla::red(),
14657            )]
14658        );
14659    });
14660}
14661
14662#[gpui::test]
14663async fn test_following(cx: &mut TestAppContext) {
14664    init_test(cx, |_| {});
14665
14666    let fs = FakeFs::new(cx.executor());
14667    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
14668
14669    let buffer = project.update(cx, |project, cx| {
14670        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
14671        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
14672    });
14673    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
14674    let follower = cx.update(|cx| {
14675        cx.open_window(
14676            WindowOptions {
14677                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
14678                    gpui::Point::new(px(0.), px(0.)),
14679                    gpui::Point::new(px(10.), px(80.)),
14680                ))),
14681                ..Default::default()
14682            },
14683            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
14684        )
14685        .unwrap()
14686    });
14687
14688    let is_still_following = Rc::new(RefCell::new(true));
14689    let follower_edit_event_count = Rc::new(RefCell::new(0));
14690    let pending_update = Rc::new(RefCell::new(None));
14691    let leader_entity = leader.root(cx).unwrap();
14692    let follower_entity = follower.root(cx).unwrap();
14693    _ = follower.update(cx, {
14694        let update = pending_update.clone();
14695        let is_still_following = is_still_following.clone();
14696        let follower_edit_event_count = follower_edit_event_count.clone();
14697        |_, window, cx| {
14698            cx.subscribe_in(
14699                &leader_entity,
14700                window,
14701                move |_, leader, event, window, cx| {
14702                    leader.read(cx).add_event_to_update_proto(
14703                        event,
14704                        &mut update.borrow_mut(),
14705                        window,
14706                        cx,
14707                    );
14708                },
14709            )
14710            .detach();
14711
14712            cx.subscribe_in(
14713                &follower_entity,
14714                window,
14715                move |_, _, event: &EditorEvent, _window, _cx| {
14716                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
14717                        *is_still_following.borrow_mut() = false;
14718                    }
14719
14720                    if let EditorEvent::BufferEdited = event {
14721                        *follower_edit_event_count.borrow_mut() += 1;
14722                    }
14723                },
14724            )
14725            .detach();
14726        }
14727    });
14728
14729    // Update the selections only
14730    _ = leader.update(cx, |leader, window, cx| {
14731        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14732            s.select_ranges([1..1])
14733        });
14734    });
14735    follower
14736        .update(cx, |follower, window, cx| {
14737            follower.apply_update_proto(
14738                &project,
14739                pending_update.borrow_mut().take().unwrap(),
14740                window,
14741                cx,
14742            )
14743        })
14744        .unwrap()
14745        .await
14746        .unwrap();
14747    _ = follower.update(cx, |follower, _, cx| {
14748        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
14749    });
14750    assert!(*is_still_following.borrow());
14751    assert_eq!(*follower_edit_event_count.borrow(), 0);
14752
14753    // Update the scroll position only
14754    _ = leader.update(cx, |leader, window, cx| {
14755        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
14756    });
14757    follower
14758        .update(cx, |follower, window, cx| {
14759            follower.apply_update_proto(
14760                &project,
14761                pending_update.borrow_mut().take().unwrap(),
14762                window,
14763                cx,
14764            )
14765        })
14766        .unwrap()
14767        .await
14768        .unwrap();
14769    assert_eq!(
14770        follower
14771            .update(cx, |follower, _, cx| follower.scroll_position(cx))
14772            .unwrap(),
14773        gpui::Point::new(1.5, 3.5)
14774    );
14775    assert!(*is_still_following.borrow());
14776    assert_eq!(*follower_edit_event_count.borrow(), 0);
14777
14778    // Update the selections and scroll position. The follower's scroll position is updated
14779    // via autoscroll, not via the leader's exact scroll position.
14780    _ = leader.update(cx, |leader, window, cx| {
14781        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14782            s.select_ranges([0..0])
14783        });
14784        leader.request_autoscroll(Autoscroll::newest(), cx);
14785        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
14786    });
14787    follower
14788        .update(cx, |follower, window, cx| {
14789            follower.apply_update_proto(
14790                &project,
14791                pending_update.borrow_mut().take().unwrap(),
14792                window,
14793                cx,
14794            )
14795        })
14796        .unwrap()
14797        .await
14798        .unwrap();
14799    _ = follower.update(cx, |follower, _, cx| {
14800        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
14801        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
14802    });
14803    assert!(*is_still_following.borrow());
14804
14805    // Creating a pending selection that precedes another selection
14806    _ = leader.update(cx, |leader, window, cx| {
14807        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14808            s.select_ranges([1..1])
14809        });
14810        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
14811    });
14812    follower
14813        .update(cx, |follower, window, cx| {
14814            follower.apply_update_proto(
14815                &project,
14816                pending_update.borrow_mut().take().unwrap(),
14817                window,
14818                cx,
14819            )
14820        })
14821        .unwrap()
14822        .await
14823        .unwrap();
14824    _ = follower.update(cx, |follower, _, cx| {
14825        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
14826    });
14827    assert!(*is_still_following.borrow());
14828
14829    // Extend the pending selection so that it surrounds another selection
14830    _ = leader.update(cx, |leader, window, cx| {
14831        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
14832    });
14833    follower
14834        .update(cx, |follower, window, cx| {
14835            follower.apply_update_proto(
14836                &project,
14837                pending_update.borrow_mut().take().unwrap(),
14838                window,
14839                cx,
14840            )
14841        })
14842        .unwrap()
14843        .await
14844        .unwrap();
14845    _ = follower.update(cx, |follower, _, cx| {
14846        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
14847    });
14848
14849    // Scrolling locally breaks the follow
14850    _ = follower.update(cx, |follower, window, cx| {
14851        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
14852        follower.set_scroll_anchor(
14853            ScrollAnchor {
14854                anchor: top_anchor,
14855                offset: gpui::Point::new(0.0, 0.5),
14856            },
14857            window,
14858            cx,
14859        );
14860    });
14861    assert!(!(*is_still_following.borrow()));
14862}
14863
14864#[gpui::test]
14865async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
14866    init_test(cx, |_| {});
14867
14868    let fs = FakeFs::new(cx.executor());
14869    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
14870    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14871    let pane = workspace
14872        .update(cx, |workspace, _, _| workspace.active_pane().clone())
14873        .unwrap();
14874
14875    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14876
14877    let leader = pane.update_in(cx, |_, window, cx| {
14878        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
14879        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
14880    });
14881
14882    // Start following the editor when it has no excerpts.
14883    let mut state_message =
14884        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
14885    let workspace_entity = workspace.root(cx).unwrap();
14886    let follower_1 = cx
14887        .update_window(*workspace.deref(), |_, window, cx| {
14888            Editor::from_state_proto(
14889                workspace_entity,
14890                ViewId {
14891                    creator: CollaboratorId::PeerId(PeerId::default()),
14892                    id: 0,
14893                },
14894                &mut state_message,
14895                window,
14896                cx,
14897            )
14898        })
14899        .unwrap()
14900        .unwrap()
14901        .await
14902        .unwrap();
14903
14904    let update_message = Rc::new(RefCell::new(None));
14905    follower_1.update_in(cx, {
14906        let update = update_message.clone();
14907        |_, window, cx| {
14908            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
14909                leader.read(cx).add_event_to_update_proto(
14910                    event,
14911                    &mut update.borrow_mut(),
14912                    window,
14913                    cx,
14914                );
14915            })
14916            .detach();
14917        }
14918    });
14919
14920    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
14921        (
14922            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
14923            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
14924        )
14925    });
14926
14927    // Insert some excerpts.
14928    leader.update(cx, |leader, cx| {
14929        leader.buffer.update(cx, |multibuffer, cx| {
14930            multibuffer.set_excerpts_for_path(
14931                PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
14932                buffer_1.clone(),
14933                vec![
14934                    Point::row_range(0..3),
14935                    Point::row_range(1..6),
14936                    Point::row_range(12..15),
14937                ],
14938                0,
14939                cx,
14940            );
14941            multibuffer.set_excerpts_for_path(
14942                PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
14943                buffer_2.clone(),
14944                vec![Point::row_range(0..6), Point::row_range(8..12)],
14945                0,
14946                cx,
14947            );
14948        });
14949    });
14950
14951    // Apply the update of adding the excerpts.
14952    follower_1
14953        .update_in(cx, |follower, window, cx| {
14954            follower.apply_update_proto(
14955                &project,
14956                update_message.borrow().clone().unwrap(),
14957                window,
14958                cx,
14959            )
14960        })
14961        .await
14962        .unwrap();
14963    assert_eq!(
14964        follower_1.update(cx, |editor, cx| editor.text(cx)),
14965        leader.update(cx, |editor, cx| editor.text(cx))
14966    );
14967    update_message.borrow_mut().take();
14968
14969    // Start following separately after it already has excerpts.
14970    let mut state_message =
14971        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
14972    let workspace_entity = workspace.root(cx).unwrap();
14973    let follower_2 = cx
14974        .update_window(*workspace.deref(), |_, window, cx| {
14975            Editor::from_state_proto(
14976                workspace_entity,
14977                ViewId {
14978                    creator: CollaboratorId::PeerId(PeerId::default()),
14979                    id: 0,
14980                },
14981                &mut state_message,
14982                window,
14983                cx,
14984            )
14985        })
14986        .unwrap()
14987        .unwrap()
14988        .await
14989        .unwrap();
14990    assert_eq!(
14991        follower_2.update(cx, |editor, cx| editor.text(cx)),
14992        leader.update(cx, |editor, cx| editor.text(cx))
14993    );
14994
14995    // Remove some excerpts.
14996    leader.update(cx, |leader, cx| {
14997        leader.buffer.update(cx, |multibuffer, cx| {
14998            let excerpt_ids = multibuffer.excerpt_ids();
14999            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
15000            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
15001        });
15002    });
15003
15004    // Apply the update of removing the excerpts.
15005    follower_1
15006        .update_in(cx, |follower, window, cx| {
15007            follower.apply_update_proto(
15008                &project,
15009                update_message.borrow().clone().unwrap(),
15010                window,
15011                cx,
15012            )
15013        })
15014        .await
15015        .unwrap();
15016    follower_2
15017        .update_in(cx, |follower, window, cx| {
15018            follower.apply_update_proto(
15019                &project,
15020                update_message.borrow().clone().unwrap(),
15021                window,
15022                cx,
15023            )
15024        })
15025        .await
15026        .unwrap();
15027    update_message.borrow_mut().take();
15028    assert_eq!(
15029        follower_1.update(cx, |editor, cx| editor.text(cx)),
15030        leader.update(cx, |editor, cx| editor.text(cx))
15031    );
15032}
15033
15034#[gpui::test]
15035async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
15036    init_test(cx, |_| {});
15037
15038    let mut cx = EditorTestContext::new(cx).await;
15039    let lsp_store =
15040        cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
15041
15042    cx.set_state(indoc! {"
15043        ˇfn func(abc def: i32) -> u32 {
15044        }
15045    "});
15046
15047    cx.update(|_, cx| {
15048        lsp_store.update(cx, |lsp_store, cx| {
15049            lsp_store
15050                .update_diagnostics(
15051                    LanguageServerId(0),
15052                    lsp::PublishDiagnosticsParams {
15053                        uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
15054                        version: None,
15055                        diagnostics: vec![
15056                            lsp::Diagnostic {
15057                                range: lsp::Range::new(
15058                                    lsp::Position::new(0, 11),
15059                                    lsp::Position::new(0, 12),
15060                                ),
15061                                severity: Some(lsp::DiagnosticSeverity::ERROR),
15062                                ..Default::default()
15063                            },
15064                            lsp::Diagnostic {
15065                                range: lsp::Range::new(
15066                                    lsp::Position::new(0, 12),
15067                                    lsp::Position::new(0, 15),
15068                                ),
15069                                severity: Some(lsp::DiagnosticSeverity::ERROR),
15070                                ..Default::default()
15071                            },
15072                            lsp::Diagnostic {
15073                                range: lsp::Range::new(
15074                                    lsp::Position::new(0, 25),
15075                                    lsp::Position::new(0, 28),
15076                                ),
15077                                severity: Some(lsp::DiagnosticSeverity::ERROR),
15078                                ..Default::default()
15079                            },
15080                        ],
15081                    },
15082                    None,
15083                    DiagnosticSourceKind::Pushed,
15084                    &[],
15085                    cx,
15086                )
15087                .unwrap()
15088        });
15089    });
15090
15091    executor.run_until_parked();
15092
15093    cx.update_editor(|editor, window, cx| {
15094        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
15095    });
15096
15097    cx.assert_editor_state(indoc! {"
15098        fn func(abc def: i32) -> ˇu32 {
15099        }
15100    "});
15101
15102    cx.update_editor(|editor, window, cx| {
15103        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
15104    });
15105
15106    cx.assert_editor_state(indoc! {"
15107        fn func(abc ˇdef: i32) -> u32 {
15108        }
15109    "});
15110
15111    cx.update_editor(|editor, window, cx| {
15112        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
15113    });
15114
15115    cx.assert_editor_state(indoc! {"
15116        fn func(abcˇ def: i32) -> u32 {
15117        }
15118    "});
15119
15120    cx.update_editor(|editor, window, cx| {
15121        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
15122    });
15123
15124    cx.assert_editor_state(indoc! {"
15125        fn func(abc def: i32) -> ˇu32 {
15126        }
15127    "});
15128}
15129
15130#[gpui::test]
15131async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
15132    init_test(cx, |_| {});
15133
15134    let mut cx = EditorTestContext::new(cx).await;
15135
15136    let diff_base = r#"
15137        use some::mod;
15138
15139        const A: u32 = 42;
15140
15141        fn main() {
15142            println!("hello");
15143
15144            println!("world");
15145        }
15146        "#
15147    .unindent();
15148
15149    // Edits are modified, removed, modified, added
15150    cx.set_state(
15151        &r#"
15152        use some::modified;
15153
15154        ˇ
15155        fn main() {
15156            println!("hello there");
15157
15158            println!("around the");
15159            println!("world");
15160        }
15161        "#
15162        .unindent(),
15163    );
15164
15165    cx.set_head_text(&diff_base);
15166    executor.run_until_parked();
15167
15168    cx.update_editor(|editor, window, cx| {
15169        //Wrap around the bottom of the buffer
15170        for _ in 0..3 {
15171            editor.go_to_next_hunk(&GoToHunk, window, cx);
15172        }
15173    });
15174
15175    cx.assert_editor_state(
15176        &r#"
15177        ˇuse some::modified;
15178
15179
15180        fn main() {
15181            println!("hello there");
15182
15183            println!("around the");
15184            println!("world");
15185        }
15186        "#
15187        .unindent(),
15188    );
15189
15190    cx.update_editor(|editor, window, cx| {
15191        //Wrap around the top of the buffer
15192        for _ in 0..2 {
15193            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
15194        }
15195    });
15196
15197    cx.assert_editor_state(
15198        &r#"
15199        use some::modified;
15200
15201
15202        fn main() {
15203        ˇ    println!("hello there");
15204
15205            println!("around the");
15206            println!("world");
15207        }
15208        "#
15209        .unindent(),
15210    );
15211
15212    cx.update_editor(|editor, window, cx| {
15213        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
15214    });
15215
15216    cx.assert_editor_state(
15217        &r#"
15218        use some::modified;
15219
15220        ˇ
15221        fn main() {
15222            println!("hello there");
15223
15224            println!("around the");
15225            println!("world");
15226        }
15227        "#
15228        .unindent(),
15229    );
15230
15231    cx.update_editor(|editor, window, cx| {
15232        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
15233    });
15234
15235    cx.assert_editor_state(
15236        &r#"
15237        ˇuse some::modified;
15238
15239
15240        fn main() {
15241            println!("hello there");
15242
15243            println!("around the");
15244            println!("world");
15245        }
15246        "#
15247        .unindent(),
15248    );
15249
15250    cx.update_editor(|editor, window, cx| {
15251        for _ in 0..2 {
15252            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
15253        }
15254    });
15255
15256    cx.assert_editor_state(
15257        &r#"
15258        use some::modified;
15259
15260
15261        fn main() {
15262        ˇ    println!("hello there");
15263
15264            println!("around the");
15265            println!("world");
15266        }
15267        "#
15268        .unindent(),
15269    );
15270
15271    cx.update_editor(|editor, window, cx| {
15272        editor.fold(&Fold, window, cx);
15273    });
15274
15275    cx.update_editor(|editor, window, cx| {
15276        editor.go_to_next_hunk(&GoToHunk, window, cx);
15277    });
15278
15279    cx.assert_editor_state(
15280        &r#"
15281        ˇuse some::modified;
15282
15283
15284        fn main() {
15285            println!("hello there");
15286
15287            println!("around the");
15288            println!("world");
15289        }
15290        "#
15291        .unindent(),
15292    );
15293}
15294
15295#[test]
15296fn test_split_words() {
15297    fn split(text: &str) -> Vec<&str> {
15298        split_words(text).collect()
15299    }
15300
15301    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
15302    assert_eq!(split("hello_world"), &["hello_", "world"]);
15303    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
15304    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
15305    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
15306    assert_eq!(split("helloworld"), &["helloworld"]);
15307
15308    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
15309}
15310
15311#[gpui::test]
15312async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
15313    init_test(cx, |_| {});
15314
15315    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
15316    let mut assert = |before, after| {
15317        let _state_context = cx.set_state(before);
15318        cx.run_until_parked();
15319        cx.update_editor(|editor, window, cx| {
15320            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
15321        });
15322        cx.run_until_parked();
15323        cx.assert_editor_state(after);
15324    };
15325
15326    // Outside bracket jumps to outside of matching bracket
15327    assert("console.logˇ(var);", "console.log(var)ˇ;");
15328    assert("console.log(var)ˇ;", "console.logˇ(var);");
15329
15330    // Inside bracket jumps to inside of matching bracket
15331    assert("console.log(ˇvar);", "console.log(varˇ);");
15332    assert("console.log(varˇ);", "console.log(ˇvar);");
15333
15334    // When outside a bracket and inside, favor jumping to the inside bracket
15335    assert(
15336        "console.log('foo', [1, 2, 3]ˇ);",
15337        "console.log(ˇ'foo', [1, 2, 3]);",
15338    );
15339    assert(
15340        "console.log(ˇ'foo', [1, 2, 3]);",
15341        "console.log('foo', [1, 2, 3]ˇ);",
15342    );
15343
15344    // Bias forward if two options are equally likely
15345    assert(
15346        "let result = curried_fun()ˇ();",
15347        "let result = curried_fun()()ˇ;",
15348    );
15349
15350    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
15351    assert(
15352        indoc! {"
15353            function test() {
15354                console.log('test')ˇ
15355            }"},
15356        indoc! {"
15357            function test() {
15358                console.logˇ('test')
15359            }"},
15360    );
15361}
15362
15363#[gpui::test]
15364async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
15365    init_test(cx, |_| {});
15366
15367    let fs = FakeFs::new(cx.executor());
15368    fs.insert_tree(
15369        path!("/a"),
15370        json!({
15371            "main.rs": "fn main() { let a = 5; }",
15372            "other.rs": "// Test file",
15373        }),
15374    )
15375    .await;
15376    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15377
15378    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15379    language_registry.add(Arc::new(Language::new(
15380        LanguageConfig {
15381            name: "Rust".into(),
15382            matcher: LanguageMatcher {
15383                path_suffixes: vec!["rs".to_string()],
15384                ..Default::default()
15385            },
15386            brackets: BracketPairConfig {
15387                pairs: vec![BracketPair {
15388                    start: "{".to_string(),
15389                    end: "}".to_string(),
15390                    close: true,
15391                    surround: true,
15392                    newline: true,
15393                }],
15394                disabled_scopes_by_bracket_ix: Vec::new(),
15395            },
15396            ..Default::default()
15397        },
15398        Some(tree_sitter_rust::LANGUAGE.into()),
15399    )));
15400    let mut fake_servers = language_registry.register_fake_lsp(
15401        "Rust",
15402        FakeLspAdapter {
15403            capabilities: lsp::ServerCapabilities {
15404                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
15405                    first_trigger_character: "{".to_string(),
15406                    more_trigger_character: None,
15407                }),
15408                ..Default::default()
15409            },
15410            ..Default::default()
15411        },
15412    );
15413
15414    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15415
15416    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15417
15418    let worktree_id = workspace
15419        .update(cx, |workspace, _, cx| {
15420            workspace.project().update(cx, |project, cx| {
15421                project.worktrees(cx).next().unwrap().read(cx).id()
15422            })
15423        })
15424        .unwrap();
15425
15426    let buffer = project
15427        .update(cx, |project, cx| {
15428            project.open_local_buffer(path!("/a/main.rs"), cx)
15429        })
15430        .await
15431        .unwrap();
15432    let editor_handle = workspace
15433        .update(cx, |workspace, window, cx| {
15434            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
15435        })
15436        .unwrap()
15437        .await
15438        .unwrap()
15439        .downcast::<Editor>()
15440        .unwrap();
15441
15442    cx.executor().start_waiting();
15443    let fake_server = fake_servers.next().await.unwrap();
15444
15445    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
15446        |params, _| async move {
15447            assert_eq!(
15448                params.text_document_position.text_document.uri,
15449                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
15450            );
15451            assert_eq!(
15452                params.text_document_position.position,
15453                lsp::Position::new(0, 21),
15454            );
15455
15456            Ok(Some(vec![lsp::TextEdit {
15457                new_text: "]".to_string(),
15458                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15459            }]))
15460        },
15461    );
15462
15463    editor_handle.update_in(cx, |editor, window, cx| {
15464        window.focus(&editor.focus_handle(cx));
15465        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15466            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
15467        });
15468        editor.handle_input("{", window, cx);
15469    });
15470
15471    cx.executor().run_until_parked();
15472
15473    buffer.update(cx, |buffer, _| {
15474        assert_eq!(
15475            buffer.text(),
15476            "fn main() { let a = {5}; }",
15477            "No extra braces from on type formatting should appear in the buffer"
15478        )
15479    });
15480}
15481
15482#[gpui::test(iterations = 20, seeds(31))]
15483async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
15484    init_test(cx, |_| {});
15485
15486    let mut cx = EditorLspTestContext::new_rust(
15487        lsp::ServerCapabilities {
15488            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
15489                first_trigger_character: ".".to_string(),
15490                more_trigger_character: None,
15491            }),
15492            ..Default::default()
15493        },
15494        cx,
15495    )
15496    .await;
15497
15498    cx.update_buffer(|buffer, _| {
15499        // This causes autoindent to be async.
15500        buffer.set_sync_parse_timeout(Duration::ZERO)
15501    });
15502
15503    cx.set_state("fn c() {\n    d()ˇ\n}\n");
15504    cx.simulate_keystroke("\n");
15505    cx.run_until_parked();
15506
15507    let buffer_cloned =
15508        cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap().clone());
15509    let mut request =
15510        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
15511            let buffer_cloned = buffer_cloned.clone();
15512            async move {
15513                buffer_cloned.update(&mut cx, |buffer, _| {
15514                    assert_eq!(
15515                        buffer.text(),
15516                        "fn c() {\n    d()\n        .\n}\n",
15517                        "OnTypeFormatting should triggered after autoindent applied"
15518                    )
15519                })?;
15520
15521                Ok(Some(vec![]))
15522            }
15523        });
15524
15525    cx.simulate_keystroke(".");
15526    cx.run_until_parked();
15527
15528    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
15529    assert!(request.next().await.is_some());
15530    request.close();
15531    assert!(request.next().await.is_none());
15532}
15533
15534#[gpui::test]
15535async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
15536    init_test(cx, |_| {});
15537
15538    let fs = FakeFs::new(cx.executor());
15539    fs.insert_tree(
15540        path!("/a"),
15541        json!({
15542            "main.rs": "fn main() { let a = 5; }",
15543            "other.rs": "// Test file",
15544        }),
15545    )
15546    .await;
15547
15548    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15549
15550    let server_restarts = Arc::new(AtomicUsize::new(0));
15551    let closure_restarts = Arc::clone(&server_restarts);
15552    let language_server_name = "test language server";
15553    let language_name: LanguageName = "Rust".into();
15554
15555    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15556    language_registry.add(Arc::new(Language::new(
15557        LanguageConfig {
15558            name: language_name.clone(),
15559            matcher: LanguageMatcher {
15560                path_suffixes: vec!["rs".to_string()],
15561                ..Default::default()
15562            },
15563            ..Default::default()
15564        },
15565        Some(tree_sitter_rust::LANGUAGE.into()),
15566    )));
15567    let mut fake_servers = language_registry.register_fake_lsp(
15568        "Rust",
15569        FakeLspAdapter {
15570            name: language_server_name,
15571            initialization_options: Some(json!({
15572                "testOptionValue": true
15573            })),
15574            initializer: Some(Box::new(move |fake_server| {
15575                let task_restarts = Arc::clone(&closure_restarts);
15576                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
15577                    task_restarts.fetch_add(1, atomic::Ordering::Release);
15578                    futures::future::ready(Ok(()))
15579                });
15580            })),
15581            ..Default::default()
15582        },
15583    );
15584
15585    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15586    let _buffer = project
15587        .update(cx, |project, cx| {
15588            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
15589        })
15590        .await
15591        .unwrap();
15592    let _fake_server = fake_servers.next().await.unwrap();
15593    update_test_language_settings(cx, |language_settings| {
15594        language_settings.languages.0.insert(
15595            language_name.clone(),
15596            LanguageSettingsContent {
15597                tab_size: NonZeroU32::new(8),
15598                ..Default::default()
15599            },
15600        );
15601    });
15602    cx.executor().run_until_parked();
15603    assert_eq!(
15604        server_restarts.load(atomic::Ordering::Acquire),
15605        0,
15606        "Should not restart LSP server on an unrelated change"
15607    );
15608
15609    update_test_project_settings(cx, |project_settings| {
15610        project_settings.lsp.insert(
15611            "Some other server name".into(),
15612            LspSettings {
15613                binary: None,
15614                settings: None,
15615                initialization_options: Some(json!({
15616                    "some other init value": false
15617                })),
15618                enable_lsp_tasks: false,
15619            },
15620        );
15621    });
15622    cx.executor().run_until_parked();
15623    assert_eq!(
15624        server_restarts.load(atomic::Ordering::Acquire),
15625        0,
15626        "Should not restart LSP server on an unrelated LSP settings change"
15627    );
15628
15629    update_test_project_settings(cx, |project_settings| {
15630        project_settings.lsp.insert(
15631            language_server_name.into(),
15632            LspSettings {
15633                binary: None,
15634                settings: None,
15635                initialization_options: Some(json!({
15636                    "anotherInitValue": false
15637                })),
15638                enable_lsp_tasks: false,
15639            },
15640        );
15641    });
15642    cx.executor().run_until_parked();
15643    assert_eq!(
15644        server_restarts.load(atomic::Ordering::Acquire),
15645        1,
15646        "Should restart LSP server on a related LSP settings change"
15647    );
15648
15649    update_test_project_settings(cx, |project_settings| {
15650        project_settings.lsp.insert(
15651            language_server_name.into(),
15652            LspSettings {
15653                binary: None,
15654                settings: None,
15655                initialization_options: Some(json!({
15656                    "anotherInitValue": false
15657                })),
15658                enable_lsp_tasks: false,
15659            },
15660        );
15661    });
15662    cx.executor().run_until_parked();
15663    assert_eq!(
15664        server_restarts.load(atomic::Ordering::Acquire),
15665        1,
15666        "Should not restart LSP server on a related LSP settings change that is the same"
15667    );
15668
15669    update_test_project_settings(cx, |project_settings| {
15670        project_settings.lsp.insert(
15671            language_server_name.into(),
15672            LspSettings {
15673                binary: None,
15674                settings: None,
15675                initialization_options: None,
15676                enable_lsp_tasks: false,
15677            },
15678        );
15679    });
15680    cx.executor().run_until_parked();
15681    assert_eq!(
15682        server_restarts.load(atomic::Ordering::Acquire),
15683        2,
15684        "Should restart LSP server on another related LSP settings change"
15685    );
15686}
15687
15688#[gpui::test]
15689async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
15690    init_test(cx, |_| {});
15691
15692    let mut cx = EditorLspTestContext::new_rust(
15693        lsp::ServerCapabilities {
15694            completion_provider: Some(lsp::CompletionOptions {
15695                trigger_characters: Some(vec![".".to_string()]),
15696                resolve_provider: Some(true),
15697                ..Default::default()
15698            }),
15699            ..Default::default()
15700        },
15701        cx,
15702    )
15703    .await;
15704
15705    cx.set_state("fn main() { let a = 2ˇ; }");
15706    cx.simulate_keystroke(".");
15707    let completion_item = lsp::CompletionItem {
15708        label: "some".into(),
15709        kind: Some(lsp::CompletionItemKind::SNIPPET),
15710        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15711        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15712            kind: lsp::MarkupKind::Markdown,
15713            value: "```rust\nSome(2)\n```".to_string(),
15714        })),
15715        deprecated: Some(false),
15716        sort_text: Some("fffffff2".to_string()),
15717        filter_text: Some("some".to_string()),
15718        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15719        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15720            range: lsp::Range {
15721                start: lsp::Position {
15722                    line: 0,
15723                    character: 22,
15724                },
15725                end: lsp::Position {
15726                    line: 0,
15727                    character: 22,
15728                },
15729            },
15730            new_text: "Some(2)".to_string(),
15731        })),
15732        additional_text_edits: Some(vec![lsp::TextEdit {
15733            range: lsp::Range {
15734                start: lsp::Position {
15735                    line: 0,
15736                    character: 20,
15737                },
15738                end: lsp::Position {
15739                    line: 0,
15740                    character: 22,
15741                },
15742            },
15743            new_text: "".to_string(),
15744        }]),
15745        ..Default::default()
15746    };
15747
15748    let closure_completion_item = completion_item.clone();
15749    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15750        let task_completion_item = closure_completion_item.clone();
15751        async move {
15752            Ok(Some(lsp::CompletionResponse::Array(vec![
15753                task_completion_item,
15754            ])))
15755        }
15756    });
15757
15758    request.next().await;
15759
15760    cx.condition(|editor, _| editor.context_menu_visible())
15761        .await;
15762    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15763        editor
15764            .confirm_completion(&ConfirmCompletion::default(), window, cx)
15765            .unwrap()
15766    });
15767    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
15768
15769    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
15770        let task_completion_item = completion_item.clone();
15771        async move { Ok(task_completion_item) }
15772    })
15773    .next()
15774    .await
15775    .unwrap();
15776    apply_additional_edits.await.unwrap();
15777    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
15778}
15779
15780#[gpui::test]
15781async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
15782    init_test(cx, |_| {});
15783
15784    let mut cx = EditorLspTestContext::new_rust(
15785        lsp::ServerCapabilities {
15786            completion_provider: Some(lsp::CompletionOptions {
15787                trigger_characters: Some(vec![".".to_string()]),
15788                resolve_provider: Some(true),
15789                ..Default::default()
15790            }),
15791            ..Default::default()
15792        },
15793        cx,
15794    )
15795    .await;
15796
15797    cx.set_state("fn main() { let a = 2ˇ; }");
15798    cx.simulate_keystroke(".");
15799
15800    let item1 = lsp::CompletionItem {
15801        label: "method id()".to_string(),
15802        filter_text: Some("id".to_string()),
15803        detail: None,
15804        documentation: None,
15805        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15806            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15807            new_text: ".id".to_string(),
15808        })),
15809        ..lsp::CompletionItem::default()
15810    };
15811
15812    let item2 = lsp::CompletionItem {
15813        label: "other".to_string(),
15814        filter_text: Some("other".to_string()),
15815        detail: None,
15816        documentation: None,
15817        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15818            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15819            new_text: ".other".to_string(),
15820        })),
15821        ..lsp::CompletionItem::default()
15822    };
15823
15824    let item1 = item1.clone();
15825    cx.set_request_handler::<lsp::request::Completion, _, _>({
15826        let item1 = item1.clone();
15827        move |_, _, _| {
15828            let item1 = item1.clone();
15829            let item2 = item2.clone();
15830            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
15831        }
15832    })
15833    .next()
15834    .await;
15835
15836    cx.condition(|editor, _| editor.context_menu_visible())
15837        .await;
15838    cx.update_editor(|editor, _, _| {
15839        let context_menu = editor.context_menu.borrow_mut();
15840        let context_menu = context_menu
15841            .as_ref()
15842            .expect("Should have the context menu deployed");
15843        match context_menu {
15844            CodeContextMenu::Completions(completions_menu) => {
15845                let completions = completions_menu.completions.borrow_mut();
15846                assert_eq!(
15847                    completions
15848                        .iter()
15849                        .map(|completion| &completion.label.text)
15850                        .collect::<Vec<_>>(),
15851                    vec!["method id()", "other"]
15852                )
15853            }
15854            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15855        }
15856    });
15857
15858    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
15859        let item1 = item1.clone();
15860        move |_, item_to_resolve, _| {
15861            let item1 = item1.clone();
15862            async move {
15863                if item1 == item_to_resolve {
15864                    Ok(lsp::CompletionItem {
15865                        label: "method id()".to_string(),
15866                        filter_text: Some("id".to_string()),
15867                        detail: Some("Now resolved!".to_string()),
15868                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
15869                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15870                            range: lsp::Range::new(
15871                                lsp::Position::new(0, 22),
15872                                lsp::Position::new(0, 22),
15873                            ),
15874                            new_text: ".id".to_string(),
15875                        })),
15876                        ..lsp::CompletionItem::default()
15877                    })
15878                } else {
15879                    Ok(item_to_resolve)
15880                }
15881            }
15882        }
15883    })
15884    .next()
15885    .await
15886    .unwrap();
15887    cx.run_until_parked();
15888
15889    cx.update_editor(|editor, window, cx| {
15890        editor.context_menu_next(&Default::default(), window, cx);
15891    });
15892
15893    cx.update_editor(|editor, _, _| {
15894        let context_menu = editor.context_menu.borrow_mut();
15895        let context_menu = context_menu
15896            .as_ref()
15897            .expect("Should have the context menu deployed");
15898        match context_menu {
15899            CodeContextMenu::Completions(completions_menu) => {
15900                let completions = completions_menu.completions.borrow_mut();
15901                assert_eq!(
15902                    completions
15903                        .iter()
15904                        .map(|completion| &completion.label.text)
15905                        .collect::<Vec<_>>(),
15906                    vec!["method id() Now resolved!", "other"],
15907                    "Should update first completion label, but not second as the filter text did not match."
15908                );
15909            }
15910            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15911        }
15912    });
15913}
15914
15915#[gpui::test]
15916async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
15917    init_test(cx, |_| {});
15918    let mut cx = EditorLspTestContext::new_rust(
15919        lsp::ServerCapabilities {
15920            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
15921            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
15922            completion_provider: Some(lsp::CompletionOptions {
15923                resolve_provider: Some(true),
15924                ..Default::default()
15925            }),
15926            ..Default::default()
15927        },
15928        cx,
15929    )
15930    .await;
15931    cx.set_state(indoc! {"
15932        struct TestStruct {
15933            field: i32
15934        }
15935
15936        fn mainˇ() {
15937            let unused_var = 42;
15938            let test_struct = TestStruct { field: 42 };
15939        }
15940    "});
15941    let symbol_range = cx.lsp_range(indoc! {"
15942        struct TestStruct {
15943            field: i32
15944        }
15945
15946        «fn main»() {
15947            let unused_var = 42;
15948            let test_struct = TestStruct { field: 42 };
15949        }
15950    "});
15951    let mut hover_requests =
15952        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
15953            Ok(Some(lsp::Hover {
15954                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
15955                    kind: lsp::MarkupKind::Markdown,
15956                    value: "Function documentation".to_string(),
15957                }),
15958                range: Some(symbol_range),
15959            }))
15960        });
15961
15962    // Case 1: Test that code action menu hide hover popover
15963    cx.dispatch_action(Hover);
15964    hover_requests.next().await;
15965    cx.condition(|editor, _| editor.hover_state.visible()).await;
15966    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
15967        move |_, _, _| async move {
15968            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
15969                lsp::CodeAction {
15970                    title: "Remove unused variable".to_string(),
15971                    kind: Some(CodeActionKind::QUICKFIX),
15972                    edit: Some(lsp::WorkspaceEdit {
15973                        changes: Some(
15974                            [(
15975                                lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
15976                                vec![lsp::TextEdit {
15977                                    range: lsp::Range::new(
15978                                        lsp::Position::new(5, 4),
15979                                        lsp::Position::new(5, 27),
15980                                    ),
15981                                    new_text: "".to_string(),
15982                                }],
15983                            )]
15984                            .into_iter()
15985                            .collect(),
15986                        ),
15987                        ..Default::default()
15988                    }),
15989                    ..Default::default()
15990                },
15991            )]))
15992        },
15993    );
15994    cx.update_editor(|editor, window, cx| {
15995        editor.toggle_code_actions(
15996            &ToggleCodeActions {
15997                deployed_from: None,
15998                quick_launch: false,
15999            },
16000            window,
16001            cx,
16002        );
16003    });
16004    code_action_requests.next().await;
16005    cx.run_until_parked();
16006    cx.condition(|editor, _| editor.context_menu_visible())
16007        .await;
16008    cx.update_editor(|editor, _, _| {
16009        assert!(
16010            !editor.hover_state.visible(),
16011            "Hover popover should be hidden when code action menu is shown"
16012        );
16013        // Hide code actions
16014        editor.context_menu.take();
16015    });
16016
16017    // Case 2: Test that code completions hide hover popover
16018    cx.dispatch_action(Hover);
16019    hover_requests.next().await;
16020    cx.condition(|editor, _| editor.hover_state.visible()).await;
16021    let counter = Arc::new(AtomicUsize::new(0));
16022    let mut completion_requests =
16023        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16024            let counter = counter.clone();
16025            async move {
16026                counter.fetch_add(1, atomic::Ordering::Release);
16027                Ok(Some(lsp::CompletionResponse::Array(vec![
16028                    lsp::CompletionItem {
16029                        label: "main".into(),
16030                        kind: Some(lsp::CompletionItemKind::FUNCTION),
16031                        detail: Some("() -> ()".to_string()),
16032                        ..Default::default()
16033                    },
16034                    lsp::CompletionItem {
16035                        label: "TestStruct".into(),
16036                        kind: Some(lsp::CompletionItemKind::STRUCT),
16037                        detail: Some("struct TestStruct".to_string()),
16038                        ..Default::default()
16039                    },
16040                ])))
16041            }
16042        });
16043    cx.update_editor(|editor, window, cx| {
16044        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
16045    });
16046    completion_requests.next().await;
16047    cx.condition(|editor, _| editor.context_menu_visible())
16048        .await;
16049    cx.update_editor(|editor, _, _| {
16050        assert!(
16051            !editor.hover_state.visible(),
16052            "Hover popover should be hidden when completion menu is shown"
16053        );
16054    });
16055}
16056
16057#[gpui::test]
16058async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
16059    init_test(cx, |_| {});
16060
16061    let mut cx = EditorLspTestContext::new_rust(
16062        lsp::ServerCapabilities {
16063            completion_provider: Some(lsp::CompletionOptions {
16064                trigger_characters: Some(vec![".".to_string()]),
16065                resolve_provider: Some(true),
16066                ..Default::default()
16067            }),
16068            ..Default::default()
16069        },
16070        cx,
16071    )
16072    .await;
16073
16074    cx.set_state("fn main() { let a = 2ˇ; }");
16075    cx.simulate_keystroke(".");
16076
16077    let unresolved_item_1 = lsp::CompletionItem {
16078        label: "id".to_string(),
16079        filter_text: Some("id".to_string()),
16080        detail: None,
16081        documentation: None,
16082        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16083            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16084            new_text: ".id".to_string(),
16085        })),
16086        ..lsp::CompletionItem::default()
16087    };
16088    let resolved_item_1 = lsp::CompletionItem {
16089        additional_text_edits: Some(vec![lsp::TextEdit {
16090            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
16091            new_text: "!!".to_string(),
16092        }]),
16093        ..unresolved_item_1.clone()
16094    };
16095    let unresolved_item_2 = lsp::CompletionItem {
16096        label: "other".to_string(),
16097        filter_text: Some("other".to_string()),
16098        detail: None,
16099        documentation: None,
16100        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16101            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16102            new_text: ".other".to_string(),
16103        })),
16104        ..lsp::CompletionItem::default()
16105    };
16106    let resolved_item_2 = lsp::CompletionItem {
16107        additional_text_edits: Some(vec![lsp::TextEdit {
16108            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
16109            new_text: "??".to_string(),
16110        }]),
16111        ..unresolved_item_2.clone()
16112    };
16113
16114    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
16115    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
16116    cx.lsp
16117        .server
16118        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
16119            let unresolved_item_1 = unresolved_item_1.clone();
16120            let resolved_item_1 = resolved_item_1.clone();
16121            let unresolved_item_2 = unresolved_item_2.clone();
16122            let resolved_item_2 = resolved_item_2.clone();
16123            let resolve_requests_1 = resolve_requests_1.clone();
16124            let resolve_requests_2 = resolve_requests_2.clone();
16125            move |unresolved_request, _| {
16126                let unresolved_item_1 = unresolved_item_1.clone();
16127                let resolved_item_1 = resolved_item_1.clone();
16128                let unresolved_item_2 = unresolved_item_2.clone();
16129                let resolved_item_2 = resolved_item_2.clone();
16130                let resolve_requests_1 = resolve_requests_1.clone();
16131                let resolve_requests_2 = resolve_requests_2.clone();
16132                async move {
16133                    if unresolved_request == unresolved_item_1 {
16134                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
16135                        Ok(resolved_item_1.clone())
16136                    } else if unresolved_request == unresolved_item_2 {
16137                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
16138                        Ok(resolved_item_2.clone())
16139                    } else {
16140                        panic!("Unexpected completion item {unresolved_request:?}")
16141                    }
16142                }
16143            }
16144        })
16145        .detach();
16146
16147    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16148        let unresolved_item_1 = unresolved_item_1.clone();
16149        let unresolved_item_2 = unresolved_item_2.clone();
16150        async move {
16151            Ok(Some(lsp::CompletionResponse::Array(vec![
16152                unresolved_item_1,
16153                unresolved_item_2,
16154            ])))
16155        }
16156    })
16157    .next()
16158    .await;
16159
16160    cx.condition(|editor, _| editor.context_menu_visible())
16161        .await;
16162    cx.update_editor(|editor, _, _| {
16163        let context_menu = editor.context_menu.borrow_mut();
16164        let context_menu = context_menu
16165            .as_ref()
16166            .expect("Should have the context menu deployed");
16167        match context_menu {
16168            CodeContextMenu::Completions(completions_menu) => {
16169                let completions = completions_menu.completions.borrow_mut();
16170                assert_eq!(
16171                    completions
16172                        .iter()
16173                        .map(|completion| &completion.label.text)
16174                        .collect::<Vec<_>>(),
16175                    vec!["id", "other"]
16176                )
16177            }
16178            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
16179        }
16180    });
16181    cx.run_until_parked();
16182
16183    cx.update_editor(|editor, window, cx| {
16184        editor.context_menu_next(&ContextMenuNext, window, cx);
16185    });
16186    cx.run_until_parked();
16187    cx.update_editor(|editor, window, cx| {
16188        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
16189    });
16190    cx.run_until_parked();
16191    cx.update_editor(|editor, window, cx| {
16192        editor.context_menu_next(&ContextMenuNext, window, cx);
16193    });
16194    cx.run_until_parked();
16195    cx.update_editor(|editor, window, cx| {
16196        editor
16197            .compose_completion(&ComposeCompletion::default(), window, cx)
16198            .expect("No task returned")
16199    })
16200    .await
16201    .expect("Completion failed");
16202    cx.run_until_parked();
16203
16204    cx.update_editor(|editor, _, cx| {
16205        assert_eq!(
16206            resolve_requests_1.load(atomic::Ordering::Acquire),
16207            1,
16208            "Should always resolve once despite multiple selections"
16209        );
16210        assert_eq!(
16211            resolve_requests_2.load(atomic::Ordering::Acquire),
16212            1,
16213            "Should always resolve once after multiple selections and applying the completion"
16214        );
16215        assert_eq!(
16216            editor.text(cx),
16217            "fn main() { let a = ??.other; }",
16218            "Should use resolved data when applying the completion"
16219        );
16220    });
16221}
16222
16223#[gpui::test]
16224async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
16225    init_test(cx, |_| {});
16226
16227    let item_0 = lsp::CompletionItem {
16228        label: "abs".into(),
16229        insert_text: Some("abs".into()),
16230        data: Some(json!({ "very": "special"})),
16231        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
16232        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
16233            lsp::InsertReplaceEdit {
16234                new_text: "abs".to_string(),
16235                insert: lsp::Range::default(),
16236                replace: lsp::Range::default(),
16237            },
16238        )),
16239        ..lsp::CompletionItem::default()
16240    };
16241    let items = iter::once(item_0.clone())
16242        .chain((11..51).map(|i| lsp::CompletionItem {
16243            label: format!("item_{}", i),
16244            insert_text: Some(format!("item_{}", i)),
16245            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
16246            ..lsp::CompletionItem::default()
16247        }))
16248        .collect::<Vec<_>>();
16249
16250    let default_commit_characters = vec!["?".to_string()];
16251    let default_data = json!({ "default": "data"});
16252    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
16253    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
16254    let default_edit_range = lsp::Range {
16255        start: lsp::Position {
16256            line: 0,
16257            character: 5,
16258        },
16259        end: lsp::Position {
16260            line: 0,
16261            character: 5,
16262        },
16263    };
16264
16265    let mut cx = EditorLspTestContext::new_rust(
16266        lsp::ServerCapabilities {
16267            completion_provider: Some(lsp::CompletionOptions {
16268                trigger_characters: Some(vec![".".to_string()]),
16269                resolve_provider: Some(true),
16270                ..Default::default()
16271            }),
16272            ..Default::default()
16273        },
16274        cx,
16275    )
16276    .await;
16277
16278    cx.set_state("fn main() { let a = 2ˇ; }");
16279    cx.simulate_keystroke(".");
16280
16281    let completion_data = default_data.clone();
16282    let completion_characters = default_commit_characters.clone();
16283    let completion_items = items.clone();
16284    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16285        let default_data = completion_data.clone();
16286        let default_commit_characters = completion_characters.clone();
16287        let items = completion_items.clone();
16288        async move {
16289            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16290                items,
16291                item_defaults: Some(lsp::CompletionListItemDefaults {
16292                    data: Some(default_data.clone()),
16293                    commit_characters: Some(default_commit_characters.clone()),
16294                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
16295                        default_edit_range,
16296                    )),
16297                    insert_text_format: Some(default_insert_text_format),
16298                    insert_text_mode: Some(default_insert_text_mode),
16299                }),
16300                ..lsp::CompletionList::default()
16301            })))
16302        }
16303    })
16304    .next()
16305    .await;
16306
16307    let resolved_items = Arc::new(Mutex::new(Vec::new()));
16308    cx.lsp
16309        .server
16310        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
16311            let closure_resolved_items = resolved_items.clone();
16312            move |item_to_resolve, _| {
16313                let closure_resolved_items = closure_resolved_items.clone();
16314                async move {
16315                    closure_resolved_items.lock().push(item_to_resolve.clone());
16316                    Ok(item_to_resolve)
16317                }
16318            }
16319        })
16320        .detach();
16321
16322    cx.condition(|editor, _| editor.context_menu_visible())
16323        .await;
16324    cx.run_until_parked();
16325    cx.update_editor(|editor, _, _| {
16326        let menu = editor.context_menu.borrow_mut();
16327        match menu.as_ref().expect("should have the completions menu") {
16328            CodeContextMenu::Completions(completions_menu) => {
16329                assert_eq!(
16330                    completions_menu
16331                        .entries
16332                        .borrow()
16333                        .iter()
16334                        .map(|mat| mat.string.clone())
16335                        .collect::<Vec<String>>(),
16336                    items
16337                        .iter()
16338                        .map(|completion| completion.label.clone())
16339                        .collect::<Vec<String>>()
16340                );
16341            }
16342            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
16343        }
16344    });
16345    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
16346    // with 4 from the end.
16347    assert_eq!(
16348        *resolved_items.lock(),
16349        [&items[0..16], &items[items.len() - 4..items.len()]]
16350            .concat()
16351            .iter()
16352            .cloned()
16353            .map(|mut item| {
16354                if item.data.is_none() {
16355                    item.data = Some(default_data.clone());
16356                }
16357                item
16358            })
16359            .collect::<Vec<lsp::CompletionItem>>(),
16360        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
16361    );
16362    resolved_items.lock().clear();
16363
16364    cx.update_editor(|editor, window, cx| {
16365        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
16366    });
16367    cx.run_until_parked();
16368    // Completions that have already been resolved are skipped.
16369    assert_eq!(
16370        *resolved_items.lock(),
16371        items[items.len() - 17..items.len() - 4]
16372            .iter()
16373            .cloned()
16374            .map(|mut item| {
16375                if item.data.is_none() {
16376                    item.data = Some(default_data.clone());
16377                }
16378                item
16379            })
16380            .collect::<Vec<lsp::CompletionItem>>()
16381    );
16382    resolved_items.lock().clear();
16383}
16384
16385#[gpui::test]
16386async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
16387    init_test(cx, |_| {});
16388
16389    let mut cx = EditorLspTestContext::new(
16390        Language::new(
16391            LanguageConfig {
16392                matcher: LanguageMatcher {
16393                    path_suffixes: vec!["jsx".into()],
16394                    ..Default::default()
16395                },
16396                overrides: [(
16397                    "element".into(),
16398                    LanguageConfigOverride {
16399                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
16400                        ..Default::default()
16401                    },
16402                )]
16403                .into_iter()
16404                .collect(),
16405                ..Default::default()
16406            },
16407            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
16408        )
16409        .with_override_query("(jsx_self_closing_element) @element")
16410        .unwrap(),
16411        lsp::ServerCapabilities {
16412            completion_provider: Some(lsp::CompletionOptions {
16413                trigger_characters: Some(vec![":".to_string()]),
16414                ..Default::default()
16415            }),
16416            ..Default::default()
16417        },
16418        cx,
16419    )
16420    .await;
16421
16422    cx.lsp
16423        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16424            Ok(Some(lsp::CompletionResponse::Array(vec![
16425                lsp::CompletionItem {
16426                    label: "bg-blue".into(),
16427                    ..Default::default()
16428                },
16429                lsp::CompletionItem {
16430                    label: "bg-red".into(),
16431                    ..Default::default()
16432                },
16433                lsp::CompletionItem {
16434                    label: "bg-yellow".into(),
16435                    ..Default::default()
16436                },
16437            ])))
16438        });
16439
16440    cx.set_state(r#"<p class="bgˇ" />"#);
16441
16442    // Trigger completion when typing a dash, because the dash is an extra
16443    // word character in the 'element' scope, which contains the cursor.
16444    cx.simulate_keystroke("-");
16445    cx.executor().run_until_parked();
16446    cx.update_editor(|editor, _, _| {
16447        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16448        {
16449            assert_eq!(
16450                completion_menu_entries(&menu),
16451                &["bg-blue", "bg-red", "bg-yellow"]
16452            );
16453        } else {
16454            panic!("expected completion menu to be open");
16455        }
16456    });
16457
16458    cx.simulate_keystroke("l");
16459    cx.executor().run_until_parked();
16460    cx.update_editor(|editor, _, _| {
16461        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16462        {
16463            assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
16464        } else {
16465            panic!("expected completion menu to be open");
16466        }
16467    });
16468
16469    // When filtering completions, consider the character after the '-' to
16470    // be the start of a subword.
16471    cx.set_state(r#"<p class="yelˇ" />"#);
16472    cx.simulate_keystroke("l");
16473    cx.executor().run_until_parked();
16474    cx.update_editor(|editor, _, _| {
16475        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16476        {
16477            assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
16478        } else {
16479            panic!("expected completion menu to be open");
16480        }
16481    });
16482}
16483
16484fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
16485    let entries = menu.entries.borrow();
16486    entries.iter().map(|mat| mat.string.clone()).collect()
16487}
16488
16489#[gpui::test]
16490async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
16491    init_test(cx, |settings| {
16492        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
16493            Formatter::Prettier,
16494        )))
16495    });
16496
16497    let fs = FakeFs::new(cx.executor());
16498    fs.insert_file(path!("/file.ts"), Default::default()).await;
16499
16500    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
16501    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16502
16503    language_registry.add(Arc::new(Language::new(
16504        LanguageConfig {
16505            name: "TypeScript".into(),
16506            matcher: LanguageMatcher {
16507                path_suffixes: vec!["ts".to_string()],
16508                ..Default::default()
16509            },
16510            ..Default::default()
16511        },
16512        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
16513    )));
16514    update_test_language_settings(cx, |settings| {
16515        settings.defaults.prettier = Some(PrettierSettings {
16516            allowed: true,
16517            ..PrettierSettings::default()
16518        });
16519    });
16520
16521    let test_plugin = "test_plugin";
16522    let _ = language_registry.register_fake_lsp(
16523        "TypeScript",
16524        FakeLspAdapter {
16525            prettier_plugins: vec![test_plugin],
16526            ..Default::default()
16527        },
16528    );
16529
16530    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
16531    let buffer = project
16532        .update(cx, |project, cx| {
16533            project.open_local_buffer(path!("/file.ts"), cx)
16534        })
16535        .await
16536        .unwrap();
16537
16538    let buffer_text = "one\ntwo\nthree\n";
16539    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16540    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16541    editor.update_in(cx, |editor, window, cx| {
16542        editor.set_text(buffer_text, window, cx)
16543    });
16544
16545    editor
16546        .update_in(cx, |editor, window, cx| {
16547            editor.perform_format(
16548                project.clone(),
16549                FormatTrigger::Manual,
16550                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
16551                window,
16552                cx,
16553            )
16554        })
16555        .unwrap()
16556        .await;
16557    assert_eq!(
16558        editor.update(cx, |editor, cx| editor.text(cx)),
16559        buffer_text.to_string() + prettier_format_suffix,
16560        "Test prettier formatting was not applied to the original buffer text",
16561    );
16562
16563    update_test_language_settings(cx, |settings| {
16564        settings.defaults.formatter = Some(SelectedFormatter::Auto)
16565    });
16566    let format = editor.update_in(cx, |editor, window, cx| {
16567        editor.perform_format(
16568            project.clone(),
16569            FormatTrigger::Manual,
16570            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
16571            window,
16572            cx,
16573        )
16574    });
16575    format.await.unwrap();
16576    assert_eq!(
16577        editor.update(cx, |editor, cx| editor.text(cx)),
16578        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
16579        "Autoformatting (via test prettier) was not applied to the original buffer text",
16580    );
16581}
16582
16583#[gpui::test]
16584async fn test_addition_reverts(cx: &mut TestAppContext) {
16585    init_test(cx, |_| {});
16586    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16587    let base_text = indoc! {r#"
16588        struct Row;
16589        struct Row1;
16590        struct Row2;
16591
16592        struct Row4;
16593        struct Row5;
16594        struct Row6;
16595
16596        struct Row8;
16597        struct Row9;
16598        struct Row10;"#};
16599
16600    // When addition hunks are not adjacent to carets, no hunk revert is performed
16601    assert_hunk_revert(
16602        indoc! {r#"struct Row;
16603                   struct Row1;
16604                   struct Row1.1;
16605                   struct Row1.2;
16606                   struct Row2;ˇ
16607
16608                   struct Row4;
16609                   struct Row5;
16610                   struct Row6;
16611
16612                   struct Row8;
16613                   ˇstruct Row9;
16614                   struct Row9.1;
16615                   struct Row9.2;
16616                   struct Row9.3;
16617                   struct Row10;"#},
16618        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
16619        indoc! {r#"struct Row;
16620                   struct Row1;
16621                   struct Row1.1;
16622                   struct Row1.2;
16623                   struct Row2;ˇ
16624
16625                   struct Row4;
16626                   struct Row5;
16627                   struct Row6;
16628
16629                   struct Row8;
16630                   ˇstruct Row9;
16631                   struct Row9.1;
16632                   struct Row9.2;
16633                   struct Row9.3;
16634                   struct Row10;"#},
16635        base_text,
16636        &mut cx,
16637    );
16638    // Same for selections
16639    assert_hunk_revert(
16640        indoc! {r#"struct Row;
16641                   struct Row1;
16642                   struct Row2;
16643                   struct Row2.1;
16644                   struct Row2.2;
16645                   «ˇ
16646                   struct Row4;
16647                   struct» Row5;
16648                   «struct Row6;
16649                   ˇ»
16650                   struct Row9.1;
16651                   struct Row9.2;
16652                   struct Row9.3;
16653                   struct Row8;
16654                   struct Row9;
16655                   struct Row10;"#},
16656        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
16657        indoc! {r#"struct Row;
16658                   struct Row1;
16659                   struct Row2;
16660                   struct Row2.1;
16661                   struct Row2.2;
16662                   «ˇ
16663                   struct Row4;
16664                   struct» Row5;
16665                   «struct Row6;
16666                   ˇ»
16667                   struct Row9.1;
16668                   struct Row9.2;
16669                   struct Row9.3;
16670                   struct Row8;
16671                   struct Row9;
16672                   struct Row10;"#},
16673        base_text,
16674        &mut cx,
16675    );
16676
16677    // When carets and selections intersect the addition hunks, those are reverted.
16678    // Adjacent carets got merged.
16679    assert_hunk_revert(
16680        indoc! {r#"struct Row;
16681                   ˇ// something on the top
16682                   struct Row1;
16683                   struct Row2;
16684                   struct Roˇw3.1;
16685                   struct Row2.2;
16686                   struct Row2.3;ˇ
16687
16688                   struct Row4;
16689                   struct ˇRow5.1;
16690                   struct Row5.2;
16691                   struct «Rowˇ»5.3;
16692                   struct Row5;
16693                   struct Row6;
16694                   ˇ
16695                   struct Row9.1;
16696                   struct «Rowˇ»9.2;
16697                   struct «ˇRow»9.3;
16698                   struct Row8;
16699                   struct Row9;
16700                   «ˇ// something on bottom»
16701                   struct Row10;"#},
16702        vec![
16703            DiffHunkStatusKind::Added,
16704            DiffHunkStatusKind::Added,
16705            DiffHunkStatusKind::Added,
16706            DiffHunkStatusKind::Added,
16707            DiffHunkStatusKind::Added,
16708        ],
16709        indoc! {r#"struct Row;
16710                   ˇstruct Row1;
16711                   struct Row2;
16712                   ˇ
16713                   struct Row4;
16714                   ˇstruct Row5;
16715                   struct Row6;
16716                   ˇ
16717                   ˇstruct Row8;
16718                   struct Row9;
16719                   ˇstruct Row10;"#},
16720        base_text,
16721        &mut cx,
16722    );
16723}
16724
16725#[gpui::test]
16726async fn test_modification_reverts(cx: &mut TestAppContext) {
16727    init_test(cx, |_| {});
16728    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16729    let base_text = indoc! {r#"
16730        struct Row;
16731        struct Row1;
16732        struct Row2;
16733
16734        struct Row4;
16735        struct Row5;
16736        struct Row6;
16737
16738        struct Row8;
16739        struct Row9;
16740        struct Row10;"#};
16741
16742    // Modification hunks behave the same as the addition ones.
16743    assert_hunk_revert(
16744        indoc! {r#"struct Row;
16745                   struct Row1;
16746                   struct Row33;
16747                   ˇ
16748                   struct Row4;
16749                   struct Row5;
16750                   struct Row6;
16751                   ˇ
16752                   struct Row99;
16753                   struct Row9;
16754                   struct Row10;"#},
16755        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
16756        indoc! {r#"struct Row;
16757                   struct Row1;
16758                   struct Row33;
16759                   ˇ
16760                   struct Row4;
16761                   struct Row5;
16762                   struct Row6;
16763                   ˇ
16764                   struct Row99;
16765                   struct Row9;
16766                   struct Row10;"#},
16767        base_text,
16768        &mut cx,
16769    );
16770    assert_hunk_revert(
16771        indoc! {r#"struct Row;
16772                   struct Row1;
16773                   struct Row33;
16774                   «ˇ
16775                   struct Row4;
16776                   struct» Row5;
16777                   «struct Row6;
16778                   ˇ»
16779                   struct Row99;
16780                   struct Row9;
16781                   struct Row10;"#},
16782        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
16783        indoc! {r#"struct Row;
16784                   struct Row1;
16785                   struct Row33;
16786                   «ˇ
16787                   struct Row4;
16788                   struct» Row5;
16789                   «struct Row6;
16790                   ˇ»
16791                   struct Row99;
16792                   struct Row9;
16793                   struct Row10;"#},
16794        base_text,
16795        &mut cx,
16796    );
16797
16798    assert_hunk_revert(
16799        indoc! {r#"ˇstruct Row1.1;
16800                   struct Row1;
16801                   «ˇstr»uct Row22;
16802
16803                   struct ˇRow44;
16804                   struct Row5;
16805                   struct «Rˇ»ow66;ˇ
16806
16807                   «struˇ»ct Row88;
16808                   struct Row9;
16809                   struct Row1011;ˇ"#},
16810        vec![
16811            DiffHunkStatusKind::Modified,
16812            DiffHunkStatusKind::Modified,
16813            DiffHunkStatusKind::Modified,
16814            DiffHunkStatusKind::Modified,
16815            DiffHunkStatusKind::Modified,
16816            DiffHunkStatusKind::Modified,
16817        ],
16818        indoc! {r#"struct Row;
16819                   ˇstruct Row1;
16820                   struct Row2;
16821                   ˇ
16822                   struct Row4;
16823                   ˇstruct Row5;
16824                   struct Row6;
16825                   ˇ
16826                   struct Row8;
16827                   ˇstruct Row9;
16828                   struct Row10;ˇ"#},
16829        base_text,
16830        &mut cx,
16831    );
16832}
16833
16834#[gpui::test]
16835async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
16836    init_test(cx, |_| {});
16837    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16838    let base_text = indoc! {r#"
16839        one
16840
16841        two
16842        three
16843        "#};
16844
16845    cx.set_head_text(base_text);
16846    cx.set_state("\nˇ\n");
16847    cx.executor().run_until_parked();
16848    cx.update_editor(|editor, _window, cx| {
16849        editor.expand_selected_diff_hunks(cx);
16850    });
16851    cx.executor().run_until_parked();
16852    cx.update_editor(|editor, window, cx| {
16853        editor.backspace(&Default::default(), window, cx);
16854    });
16855    cx.run_until_parked();
16856    cx.assert_state_with_diff(
16857        indoc! {r#"
16858
16859        - two
16860        - threeˇ
16861        +
16862        "#}
16863        .to_string(),
16864    );
16865}
16866
16867#[gpui::test]
16868async fn test_deletion_reverts(cx: &mut TestAppContext) {
16869    init_test(cx, |_| {});
16870    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16871    let base_text = indoc! {r#"struct Row;
16872struct Row1;
16873struct Row2;
16874
16875struct Row4;
16876struct Row5;
16877struct Row6;
16878
16879struct Row8;
16880struct Row9;
16881struct Row10;"#};
16882
16883    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
16884    assert_hunk_revert(
16885        indoc! {r#"struct Row;
16886                   struct Row2;
16887
16888                   ˇstruct Row4;
16889                   struct Row5;
16890                   struct Row6;
16891                   ˇ
16892                   struct Row8;
16893                   struct Row10;"#},
16894        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16895        indoc! {r#"struct Row;
16896                   struct Row2;
16897
16898                   ˇstruct Row4;
16899                   struct Row5;
16900                   struct Row6;
16901                   ˇ
16902                   struct Row8;
16903                   struct Row10;"#},
16904        base_text,
16905        &mut cx,
16906    );
16907    assert_hunk_revert(
16908        indoc! {r#"struct Row;
16909                   struct Row2;
16910
16911                   «ˇstruct Row4;
16912                   struct» Row5;
16913                   «struct Row6;
16914                   ˇ»
16915                   struct Row8;
16916                   struct Row10;"#},
16917        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16918        indoc! {r#"struct Row;
16919                   struct Row2;
16920
16921                   «ˇstruct Row4;
16922                   struct» Row5;
16923                   «struct Row6;
16924                   ˇ»
16925                   struct Row8;
16926                   struct Row10;"#},
16927        base_text,
16928        &mut cx,
16929    );
16930
16931    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
16932    assert_hunk_revert(
16933        indoc! {r#"struct Row;
16934                   ˇstruct Row2;
16935
16936                   struct Row4;
16937                   struct Row5;
16938                   struct Row6;
16939
16940                   struct Row8;ˇ
16941                   struct Row10;"#},
16942        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16943        indoc! {r#"struct Row;
16944                   struct Row1;
16945                   ˇstruct Row2;
16946
16947                   struct Row4;
16948                   struct Row5;
16949                   struct Row6;
16950
16951                   struct Row8;ˇ
16952                   struct Row9;
16953                   struct Row10;"#},
16954        base_text,
16955        &mut cx,
16956    );
16957    assert_hunk_revert(
16958        indoc! {r#"struct Row;
16959                   struct Row2«ˇ;
16960                   struct Row4;
16961                   struct» Row5;
16962                   «struct Row6;
16963
16964                   struct Row8;ˇ»
16965                   struct Row10;"#},
16966        vec![
16967            DiffHunkStatusKind::Deleted,
16968            DiffHunkStatusKind::Deleted,
16969            DiffHunkStatusKind::Deleted,
16970        ],
16971        indoc! {r#"struct Row;
16972                   struct Row1;
16973                   struct Row2«ˇ;
16974
16975                   struct Row4;
16976                   struct» Row5;
16977                   «struct Row6;
16978
16979                   struct Row8;ˇ»
16980                   struct Row9;
16981                   struct Row10;"#},
16982        base_text,
16983        &mut cx,
16984    );
16985}
16986
16987#[gpui::test]
16988async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
16989    init_test(cx, |_| {});
16990
16991    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
16992    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
16993    let base_text_3 =
16994        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
16995
16996    let text_1 = edit_first_char_of_every_line(base_text_1);
16997    let text_2 = edit_first_char_of_every_line(base_text_2);
16998    let text_3 = edit_first_char_of_every_line(base_text_3);
16999
17000    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
17001    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
17002    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
17003
17004    let multibuffer = cx.new(|cx| {
17005        let mut multibuffer = MultiBuffer::new(ReadWrite);
17006        multibuffer.push_excerpts(
17007            buffer_1.clone(),
17008            [
17009                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17010                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17011                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17012            ],
17013            cx,
17014        );
17015        multibuffer.push_excerpts(
17016            buffer_2.clone(),
17017            [
17018                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17019                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17020                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17021            ],
17022            cx,
17023        );
17024        multibuffer.push_excerpts(
17025            buffer_3.clone(),
17026            [
17027                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17028                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17029                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17030            ],
17031            cx,
17032        );
17033        multibuffer
17034    });
17035
17036    let fs = FakeFs::new(cx.executor());
17037    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
17038    let (editor, cx) = cx
17039        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
17040    editor.update_in(cx, |editor, _window, cx| {
17041        for (buffer, diff_base) in [
17042            (buffer_1.clone(), base_text_1),
17043            (buffer_2.clone(), base_text_2),
17044            (buffer_3.clone(), base_text_3),
17045        ] {
17046            let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
17047            editor
17048                .buffer
17049                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
17050        }
17051    });
17052    cx.executor().run_until_parked();
17053
17054    editor.update_in(cx, |editor, window, cx| {
17055        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}");
17056        editor.select_all(&SelectAll, window, cx);
17057        editor.git_restore(&Default::default(), window, cx);
17058    });
17059    cx.executor().run_until_parked();
17060
17061    // When all ranges are selected, all buffer hunks are reverted.
17062    editor.update(cx, |editor, cx| {
17063        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");
17064    });
17065    buffer_1.update(cx, |buffer, _| {
17066        assert_eq!(buffer.text(), base_text_1);
17067    });
17068    buffer_2.update(cx, |buffer, _| {
17069        assert_eq!(buffer.text(), base_text_2);
17070    });
17071    buffer_3.update(cx, |buffer, _| {
17072        assert_eq!(buffer.text(), base_text_3);
17073    });
17074
17075    editor.update_in(cx, |editor, window, cx| {
17076        editor.undo(&Default::default(), window, cx);
17077    });
17078
17079    editor.update_in(cx, |editor, window, cx| {
17080        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17081            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
17082        });
17083        editor.git_restore(&Default::default(), window, cx);
17084    });
17085
17086    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
17087    // but not affect buffer_2 and its related excerpts.
17088    editor.update(cx, |editor, cx| {
17089        assert_eq!(
17090            editor.text(cx),
17091            "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}"
17092        );
17093    });
17094    buffer_1.update(cx, |buffer, _| {
17095        assert_eq!(buffer.text(), base_text_1);
17096    });
17097    buffer_2.update(cx, |buffer, _| {
17098        assert_eq!(
17099            buffer.text(),
17100            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
17101        );
17102    });
17103    buffer_3.update(cx, |buffer, _| {
17104        assert_eq!(
17105            buffer.text(),
17106            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
17107        );
17108    });
17109
17110    fn edit_first_char_of_every_line(text: &str) -> String {
17111        text.split('\n')
17112            .map(|line| format!("X{}", &line[1..]))
17113            .collect::<Vec<_>>()
17114            .join("\n")
17115    }
17116}
17117
17118#[gpui::test]
17119async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
17120    init_test(cx, |_| {});
17121
17122    let cols = 4;
17123    let rows = 10;
17124    let sample_text_1 = sample_text(rows, cols, 'a');
17125    assert_eq!(
17126        sample_text_1,
17127        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
17128    );
17129    let sample_text_2 = sample_text(rows, cols, 'l');
17130    assert_eq!(
17131        sample_text_2,
17132        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
17133    );
17134    let sample_text_3 = sample_text(rows, cols, 'v');
17135    assert_eq!(
17136        sample_text_3,
17137        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
17138    );
17139
17140    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
17141    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
17142    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
17143
17144    let multi_buffer = cx.new(|cx| {
17145        let mut multibuffer = MultiBuffer::new(ReadWrite);
17146        multibuffer.push_excerpts(
17147            buffer_1.clone(),
17148            [
17149                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17150                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17151                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17152            ],
17153            cx,
17154        );
17155        multibuffer.push_excerpts(
17156            buffer_2.clone(),
17157            [
17158                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17159                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17160                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17161            ],
17162            cx,
17163        );
17164        multibuffer.push_excerpts(
17165            buffer_3.clone(),
17166            [
17167                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17168                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17169                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17170            ],
17171            cx,
17172        );
17173        multibuffer
17174    });
17175
17176    let fs = FakeFs::new(cx.executor());
17177    fs.insert_tree(
17178        "/a",
17179        json!({
17180            "main.rs": sample_text_1,
17181            "other.rs": sample_text_2,
17182            "lib.rs": sample_text_3,
17183        }),
17184    )
17185    .await;
17186    let project = Project::test(fs, ["/a".as_ref()], cx).await;
17187    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17188    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17189    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
17190        Editor::new(
17191            EditorMode::full(),
17192            multi_buffer,
17193            Some(project.clone()),
17194            window,
17195            cx,
17196        )
17197    });
17198    let multibuffer_item_id = workspace
17199        .update(cx, |workspace, window, cx| {
17200            assert!(
17201                workspace.active_item(cx).is_none(),
17202                "active item should be None before the first item is added"
17203            );
17204            workspace.add_item_to_active_pane(
17205                Box::new(multi_buffer_editor.clone()),
17206                None,
17207                true,
17208                window,
17209                cx,
17210            );
17211            let active_item = workspace
17212                .active_item(cx)
17213                .expect("should have an active item after adding the multi buffer");
17214            assert!(
17215                !active_item.is_singleton(cx),
17216                "A multi buffer was expected to active after adding"
17217            );
17218            active_item.item_id()
17219        })
17220        .unwrap();
17221    cx.executor().run_until_parked();
17222
17223    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17224        editor.change_selections(
17225            SelectionEffects::scroll(Autoscroll::Next),
17226            window,
17227            cx,
17228            |s| s.select_ranges(Some(1..2)),
17229        );
17230        editor.open_excerpts(&OpenExcerpts, window, cx);
17231    });
17232    cx.executor().run_until_parked();
17233    let first_item_id = workspace
17234        .update(cx, |workspace, window, cx| {
17235            let active_item = workspace
17236                .active_item(cx)
17237                .expect("should have an active item after navigating into the 1st buffer");
17238            let first_item_id = active_item.item_id();
17239            assert_ne!(
17240                first_item_id, multibuffer_item_id,
17241                "Should navigate into the 1st buffer and activate it"
17242            );
17243            assert!(
17244                active_item.is_singleton(cx),
17245                "New active item should be a singleton buffer"
17246            );
17247            assert_eq!(
17248                active_item
17249                    .act_as::<Editor>(cx)
17250                    .expect("should have navigated into an editor for the 1st buffer")
17251                    .read(cx)
17252                    .text(cx),
17253                sample_text_1
17254            );
17255
17256            workspace
17257                .go_back(workspace.active_pane().downgrade(), window, cx)
17258                .detach_and_log_err(cx);
17259
17260            first_item_id
17261        })
17262        .unwrap();
17263    cx.executor().run_until_parked();
17264    workspace
17265        .update(cx, |workspace, _, cx| {
17266            let active_item = workspace
17267                .active_item(cx)
17268                .expect("should have an active item after navigating back");
17269            assert_eq!(
17270                active_item.item_id(),
17271                multibuffer_item_id,
17272                "Should navigate back to the multi buffer"
17273            );
17274            assert!(!active_item.is_singleton(cx));
17275        })
17276        .unwrap();
17277
17278    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17279        editor.change_selections(
17280            SelectionEffects::scroll(Autoscroll::Next),
17281            window,
17282            cx,
17283            |s| s.select_ranges(Some(39..40)),
17284        );
17285        editor.open_excerpts(&OpenExcerpts, window, cx);
17286    });
17287    cx.executor().run_until_parked();
17288    let second_item_id = workspace
17289        .update(cx, |workspace, window, cx| {
17290            let active_item = workspace
17291                .active_item(cx)
17292                .expect("should have an active item after navigating into the 2nd buffer");
17293            let second_item_id = active_item.item_id();
17294            assert_ne!(
17295                second_item_id, multibuffer_item_id,
17296                "Should navigate away from the multibuffer"
17297            );
17298            assert_ne!(
17299                second_item_id, first_item_id,
17300                "Should navigate into the 2nd buffer and activate it"
17301            );
17302            assert!(
17303                active_item.is_singleton(cx),
17304                "New active item should be a singleton buffer"
17305            );
17306            assert_eq!(
17307                active_item
17308                    .act_as::<Editor>(cx)
17309                    .expect("should have navigated into an editor")
17310                    .read(cx)
17311                    .text(cx),
17312                sample_text_2
17313            );
17314
17315            workspace
17316                .go_back(workspace.active_pane().downgrade(), window, cx)
17317                .detach_and_log_err(cx);
17318
17319            second_item_id
17320        })
17321        .unwrap();
17322    cx.executor().run_until_parked();
17323    workspace
17324        .update(cx, |workspace, _, cx| {
17325            let active_item = workspace
17326                .active_item(cx)
17327                .expect("should have an active item after navigating back from the 2nd buffer");
17328            assert_eq!(
17329                active_item.item_id(),
17330                multibuffer_item_id,
17331                "Should navigate back from the 2nd buffer to the multi buffer"
17332            );
17333            assert!(!active_item.is_singleton(cx));
17334        })
17335        .unwrap();
17336
17337    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17338        editor.change_selections(
17339            SelectionEffects::scroll(Autoscroll::Next),
17340            window,
17341            cx,
17342            |s| s.select_ranges(Some(70..70)),
17343        );
17344        editor.open_excerpts(&OpenExcerpts, window, cx);
17345    });
17346    cx.executor().run_until_parked();
17347    workspace
17348        .update(cx, |workspace, window, cx| {
17349            let active_item = workspace
17350                .active_item(cx)
17351                .expect("should have an active item after navigating into the 3rd buffer");
17352            let third_item_id = active_item.item_id();
17353            assert_ne!(
17354                third_item_id, multibuffer_item_id,
17355                "Should navigate into the 3rd buffer and activate it"
17356            );
17357            assert_ne!(third_item_id, first_item_id);
17358            assert_ne!(third_item_id, second_item_id);
17359            assert!(
17360                active_item.is_singleton(cx),
17361                "New active item should be a singleton buffer"
17362            );
17363            assert_eq!(
17364                active_item
17365                    .act_as::<Editor>(cx)
17366                    .expect("should have navigated into an editor")
17367                    .read(cx)
17368                    .text(cx),
17369                sample_text_3
17370            );
17371
17372            workspace
17373                .go_back(workspace.active_pane().downgrade(), window, cx)
17374                .detach_and_log_err(cx);
17375        })
17376        .unwrap();
17377    cx.executor().run_until_parked();
17378    workspace
17379        .update(cx, |workspace, _, cx| {
17380            let active_item = workspace
17381                .active_item(cx)
17382                .expect("should have an active item after navigating back from the 3rd buffer");
17383            assert_eq!(
17384                active_item.item_id(),
17385                multibuffer_item_id,
17386                "Should navigate back from the 3rd buffer to the multi buffer"
17387            );
17388            assert!(!active_item.is_singleton(cx));
17389        })
17390        .unwrap();
17391}
17392
17393#[gpui::test]
17394async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17395    init_test(cx, |_| {});
17396
17397    let mut cx = EditorTestContext::new(cx).await;
17398
17399    let diff_base = r#"
17400        use some::mod;
17401
17402        const A: u32 = 42;
17403
17404        fn main() {
17405            println!("hello");
17406
17407            println!("world");
17408        }
17409        "#
17410    .unindent();
17411
17412    cx.set_state(
17413        &r#"
17414        use some::modified;
17415
17416        ˇ
17417        fn main() {
17418            println!("hello there");
17419
17420            println!("around the");
17421            println!("world");
17422        }
17423        "#
17424        .unindent(),
17425    );
17426
17427    cx.set_head_text(&diff_base);
17428    executor.run_until_parked();
17429
17430    cx.update_editor(|editor, window, cx| {
17431        editor.go_to_next_hunk(&GoToHunk, window, cx);
17432        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17433    });
17434    executor.run_until_parked();
17435    cx.assert_state_with_diff(
17436        r#"
17437          use some::modified;
17438
17439
17440          fn main() {
17441        -     println!("hello");
17442        + ˇ    println!("hello there");
17443
17444              println!("around the");
17445              println!("world");
17446          }
17447        "#
17448        .unindent(),
17449    );
17450
17451    cx.update_editor(|editor, window, cx| {
17452        for _ in 0..2 {
17453            editor.go_to_next_hunk(&GoToHunk, window, cx);
17454            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17455        }
17456    });
17457    executor.run_until_parked();
17458    cx.assert_state_with_diff(
17459        r#"
17460        - use some::mod;
17461        + ˇuse some::modified;
17462
17463
17464          fn main() {
17465        -     println!("hello");
17466        +     println!("hello there");
17467
17468        +     println!("around the");
17469              println!("world");
17470          }
17471        "#
17472        .unindent(),
17473    );
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::mod;
17483        + use some::modified;
17484
17485        - const A: u32 = 42;
17486          ˇ
17487          fn main() {
17488        -     println!("hello");
17489        +     println!("hello there");
17490
17491        +     println!("around the");
17492              println!("world");
17493          }
17494        "#
17495        .unindent(),
17496    );
17497
17498    cx.update_editor(|editor, window, cx| {
17499        editor.cancel(&Cancel, window, cx);
17500    });
17501
17502    cx.assert_state_with_diff(
17503        r#"
17504          use some::modified;
17505
17506          ˇ
17507          fn main() {
17508              println!("hello there");
17509
17510              println!("around the");
17511              println!("world");
17512          }
17513        "#
17514        .unindent(),
17515    );
17516}
17517
17518#[gpui::test]
17519async fn test_diff_base_change_with_expanded_diff_hunks(
17520    executor: BackgroundExecutor,
17521    cx: &mut TestAppContext,
17522) {
17523    init_test(cx, |_| {});
17524
17525    let mut cx = EditorTestContext::new(cx).await;
17526
17527    let diff_base = r#"
17528        use some::mod1;
17529        use some::mod2;
17530
17531        const A: u32 = 42;
17532        const B: u32 = 42;
17533        const C: u32 = 42;
17534
17535        fn main() {
17536            println!("hello");
17537
17538            println!("world");
17539        }
17540        "#
17541    .unindent();
17542
17543    cx.set_state(
17544        &r#"
17545        use some::mod2;
17546
17547        const A: u32 = 42;
17548        const C: u32 = 42;
17549
17550        fn main(ˇ) {
17551            //println!("hello");
17552
17553            println!("world");
17554            //
17555            //
17556        }
17557        "#
17558        .unindent(),
17559    );
17560
17561    cx.set_head_text(&diff_base);
17562    executor.run_until_parked();
17563
17564    cx.update_editor(|editor, window, cx| {
17565        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17566    });
17567    executor.run_until_parked();
17568    cx.assert_state_with_diff(
17569        r#"
17570        - use some::mod1;
17571          use some::mod2;
17572
17573          const A: u32 = 42;
17574        - const B: u32 = 42;
17575          const C: u32 = 42;
17576
17577          fn main(ˇ) {
17578        -     println!("hello");
17579        +     //println!("hello");
17580
17581              println!("world");
17582        +     //
17583        +     //
17584          }
17585        "#
17586        .unindent(),
17587    );
17588
17589    cx.set_head_text("new diff base!");
17590    executor.run_until_parked();
17591    cx.assert_state_with_diff(
17592        r#"
17593        - new diff base!
17594        + use some::mod2;
17595        +
17596        + const A: u32 = 42;
17597        + const C: u32 = 42;
17598        +
17599        + fn main(ˇ) {
17600        +     //println!("hello");
17601        +
17602        +     println!("world");
17603        +     //
17604        +     //
17605        + }
17606        "#
17607        .unindent(),
17608    );
17609}
17610
17611#[gpui::test]
17612async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
17613    init_test(cx, |_| {});
17614
17615    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
17616    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
17617    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
17618    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
17619    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
17620    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
17621
17622    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
17623    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
17624    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
17625
17626    let multi_buffer = cx.new(|cx| {
17627        let mut multibuffer = MultiBuffer::new(ReadWrite);
17628        multibuffer.push_excerpts(
17629            buffer_1.clone(),
17630            [
17631                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17632                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17633                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17634            ],
17635            cx,
17636        );
17637        multibuffer.push_excerpts(
17638            buffer_2.clone(),
17639            [
17640                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17641                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17642                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17643            ],
17644            cx,
17645        );
17646        multibuffer.push_excerpts(
17647            buffer_3.clone(),
17648            [
17649                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17650                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17651                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17652            ],
17653            cx,
17654        );
17655        multibuffer
17656    });
17657
17658    let editor =
17659        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
17660    editor
17661        .update(cx, |editor, _window, cx| {
17662            for (buffer, diff_base) in [
17663                (buffer_1.clone(), file_1_old),
17664                (buffer_2.clone(), file_2_old),
17665                (buffer_3.clone(), file_3_old),
17666            ] {
17667                let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
17668                editor
17669                    .buffer
17670                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
17671            }
17672        })
17673        .unwrap();
17674
17675    let mut cx = EditorTestContext::for_editor(editor, cx).await;
17676    cx.run_until_parked();
17677
17678    cx.assert_editor_state(
17679        &"
17680            ˇaaa
17681            ccc
17682            ddd
17683
17684            ggg
17685            hhh
17686
17687
17688            lll
17689            mmm
17690            NNN
17691
17692            qqq
17693            rrr
17694
17695            uuu
17696            111
17697            222
17698            333
17699
17700            666
17701            777
17702
17703            000
17704            !!!"
17705        .unindent(),
17706    );
17707
17708    cx.update_editor(|editor, window, cx| {
17709        editor.select_all(&SelectAll, window, cx);
17710        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17711    });
17712    cx.executor().run_until_parked();
17713
17714    cx.assert_state_with_diff(
17715        "
17716            «aaa
17717          - bbb
17718            ccc
17719            ddd
17720
17721            ggg
17722            hhh
17723
17724
17725            lll
17726            mmm
17727          - nnn
17728          + NNN
17729
17730            qqq
17731            rrr
17732
17733            uuu
17734            111
17735            222
17736            333
17737
17738          + 666
17739            777
17740
17741            000
17742            !!!ˇ»"
17743            .unindent(),
17744    );
17745}
17746
17747#[gpui::test]
17748async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
17749    init_test(cx, |_| {});
17750
17751    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
17752    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
17753
17754    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
17755    let multi_buffer = cx.new(|cx| {
17756        let mut multibuffer = MultiBuffer::new(ReadWrite);
17757        multibuffer.push_excerpts(
17758            buffer.clone(),
17759            [
17760                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
17761                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
17762                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
17763            ],
17764            cx,
17765        );
17766        multibuffer
17767    });
17768
17769    let editor =
17770        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
17771    editor
17772        .update(cx, |editor, _window, cx| {
17773            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
17774            editor
17775                .buffer
17776                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
17777        })
17778        .unwrap();
17779
17780    let mut cx = EditorTestContext::for_editor(editor, cx).await;
17781    cx.run_until_parked();
17782
17783    cx.update_editor(|editor, window, cx| {
17784        editor.expand_all_diff_hunks(&Default::default(), window, cx)
17785    });
17786    cx.executor().run_until_parked();
17787
17788    // When the start of a hunk coincides with the start of its excerpt,
17789    // the hunk is expanded. When the start of a a hunk is earlier than
17790    // the start of its excerpt, the hunk is not expanded.
17791    cx.assert_state_with_diff(
17792        "
17793            ˇaaa
17794          - bbb
17795          + BBB
17796
17797          - ddd
17798          - eee
17799          + DDD
17800          + EEE
17801            fff
17802
17803            iii
17804        "
17805        .unindent(),
17806    );
17807}
17808
17809#[gpui::test]
17810async fn test_edits_around_expanded_insertion_hunks(
17811    executor: BackgroundExecutor,
17812    cx: &mut TestAppContext,
17813) {
17814    init_test(cx, |_| {});
17815
17816    let mut cx = EditorTestContext::new(cx).await;
17817
17818    let diff_base = r#"
17819        use some::mod1;
17820        use some::mod2;
17821
17822        const A: u32 = 42;
17823
17824        fn main() {
17825            println!("hello");
17826
17827            println!("world");
17828        }
17829        "#
17830    .unindent();
17831    executor.run_until_parked();
17832    cx.set_state(
17833        &r#"
17834        use some::mod1;
17835        use some::mod2;
17836
17837        const A: u32 = 42;
17838        const B: u32 = 42;
17839        const C: u32 = 42;
17840        ˇ
17841
17842        fn main() {
17843            println!("hello");
17844
17845            println!("world");
17846        }
17847        "#
17848        .unindent(),
17849    );
17850
17851    cx.set_head_text(&diff_base);
17852    executor.run_until_parked();
17853
17854    cx.update_editor(|editor, window, cx| {
17855        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17856    });
17857    executor.run_until_parked();
17858
17859    cx.assert_state_with_diff(
17860        r#"
17861        use some::mod1;
17862        use some::mod2;
17863
17864        const A: u32 = 42;
17865      + const B: u32 = 42;
17866      + const C: u32 = 42;
17867      + ˇ
17868
17869        fn main() {
17870            println!("hello");
17871
17872            println!("world");
17873        }
17874      "#
17875        .unindent(),
17876    );
17877
17878    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
17879    executor.run_until_parked();
17880
17881    cx.assert_state_with_diff(
17882        r#"
17883        use some::mod1;
17884        use some::mod2;
17885
17886        const A: u32 = 42;
17887      + const B: u32 = 42;
17888      + const C: u32 = 42;
17889      + const D: u32 = 42;
17890      + ˇ
17891
17892        fn main() {
17893            println!("hello");
17894
17895            println!("world");
17896        }
17897      "#
17898        .unindent(),
17899    );
17900
17901    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
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      + const D: u32 = 42;
17913      + const E: u32 = 42;
17914      + ˇ
17915
17916        fn main() {
17917            println!("hello");
17918
17919            println!("world");
17920        }
17921      "#
17922        .unindent(),
17923    );
17924
17925    cx.update_editor(|editor, window, cx| {
17926        editor.delete_line(&DeleteLine, window, cx);
17927    });
17928    executor.run_until_parked();
17929
17930    cx.assert_state_with_diff(
17931        r#"
17932        use some::mod1;
17933        use some::mod2;
17934
17935        const A: u32 = 42;
17936      + const B: u32 = 42;
17937      + const C: u32 = 42;
17938      + const D: u32 = 42;
17939      + const E: u32 = 42;
17940        ˇ
17941        fn main() {
17942            println!("hello");
17943
17944            println!("world");
17945        }
17946      "#
17947        .unindent(),
17948    );
17949
17950    cx.update_editor(|editor, window, cx| {
17951        editor.move_up(&MoveUp, window, cx);
17952        editor.delete_line(&DeleteLine, window, cx);
17953        editor.move_up(&MoveUp, window, cx);
17954        editor.delete_line(&DeleteLine, window, cx);
17955        editor.move_up(&MoveUp, window, cx);
17956        editor.delete_line(&DeleteLine, window, cx);
17957    });
17958    executor.run_until_parked();
17959    cx.assert_state_with_diff(
17960        r#"
17961        use some::mod1;
17962        use some::mod2;
17963
17964        const A: u32 = 42;
17965      + const B: u32 = 42;
17966        ˇ
17967        fn main() {
17968            println!("hello");
17969
17970            println!("world");
17971        }
17972      "#
17973        .unindent(),
17974    );
17975
17976    cx.update_editor(|editor, window, cx| {
17977        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
17978        editor.delete_line(&DeleteLine, window, cx);
17979    });
17980    executor.run_until_parked();
17981    cx.assert_state_with_diff(
17982        r#"
17983        ˇ
17984        fn main() {
17985            println!("hello");
17986
17987            println!("world");
17988        }
17989      "#
17990        .unindent(),
17991    );
17992}
17993
17994#[gpui::test]
17995async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
17996    init_test(cx, |_| {});
17997
17998    let mut cx = EditorTestContext::new(cx).await;
17999    cx.set_head_text(indoc! { "
18000        one
18001        two
18002        three
18003        four
18004        five
18005        "
18006    });
18007    cx.set_state(indoc! { "
18008        one
18009        ˇthree
18010        five
18011    "});
18012    cx.run_until_parked();
18013    cx.update_editor(|editor, window, cx| {
18014        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
18015    });
18016    cx.assert_state_with_diff(
18017        indoc! { "
18018        one
18019      - two
18020        ˇthree
18021      - four
18022        five
18023    "}
18024        .to_string(),
18025    );
18026    cx.update_editor(|editor, window, cx| {
18027        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
18028    });
18029
18030    cx.assert_state_with_diff(
18031        indoc! { "
18032        one
18033        ˇthree
18034        five
18035    "}
18036        .to_string(),
18037    );
18038
18039    cx.set_state(indoc! { "
18040        one
18041        ˇTWO
18042        three
18043        four
18044        five
18045    "});
18046    cx.run_until_parked();
18047    cx.update_editor(|editor, window, cx| {
18048        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
18049    });
18050
18051    cx.assert_state_with_diff(
18052        indoc! { "
18053            one
18054          - two
18055          + ˇTWO
18056            three
18057            four
18058            five
18059        "}
18060        .to_string(),
18061    );
18062    cx.update_editor(|editor, window, cx| {
18063        editor.move_up(&Default::default(), window, cx);
18064        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
18065    });
18066    cx.assert_state_with_diff(
18067        indoc! { "
18068            one
18069            ˇTWO
18070            three
18071            four
18072            five
18073        "}
18074        .to_string(),
18075    );
18076}
18077
18078#[gpui::test]
18079async fn test_edits_around_expanded_deletion_hunks(
18080    executor: BackgroundExecutor,
18081    cx: &mut TestAppContext,
18082) {
18083    init_test(cx, |_| {});
18084
18085    let mut cx = EditorTestContext::new(cx).await;
18086
18087    let diff_base = r#"
18088        use some::mod1;
18089        use some::mod2;
18090
18091        const A: u32 = 42;
18092        const B: u32 = 42;
18093        const C: u32 = 42;
18094
18095
18096        fn main() {
18097            println!("hello");
18098
18099            println!("world");
18100        }
18101    "#
18102    .unindent();
18103    executor.run_until_parked();
18104    cx.set_state(
18105        &r#"
18106        use some::mod1;
18107        use some::mod2;
18108
18109        ˇconst B: u32 = 42;
18110        const C: u32 = 42;
18111
18112
18113        fn main() {
18114            println!("hello");
18115
18116            println!("world");
18117        }
18118        "#
18119        .unindent(),
18120    );
18121
18122    cx.set_head_text(&diff_base);
18123    executor.run_until_parked();
18124
18125    cx.update_editor(|editor, window, cx| {
18126        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18127    });
18128    executor.run_until_parked();
18129
18130    cx.assert_state_with_diff(
18131        r#"
18132        use some::mod1;
18133        use some::mod2;
18134
18135      - const A: u32 = 42;
18136        ˇconst B: u32 = 42;
18137        const C: u32 = 42;
18138
18139
18140        fn main() {
18141            println!("hello");
18142
18143            println!("world");
18144        }
18145      "#
18146        .unindent(),
18147    );
18148
18149    cx.update_editor(|editor, window, cx| {
18150        editor.delete_line(&DeleteLine, window, cx);
18151    });
18152    executor.run_until_parked();
18153    cx.assert_state_with_diff(
18154        r#"
18155        use some::mod1;
18156        use some::mod2;
18157
18158      - const A: u32 = 42;
18159      - const B: u32 = 42;
18160        ˇconst C: u32 = 42;
18161
18162
18163        fn main() {
18164            println!("hello");
18165
18166            println!("world");
18167        }
18168      "#
18169        .unindent(),
18170    );
18171
18172    cx.update_editor(|editor, window, cx| {
18173        editor.delete_line(&DeleteLine, window, cx);
18174    });
18175    executor.run_until_parked();
18176    cx.assert_state_with_diff(
18177        r#"
18178        use some::mod1;
18179        use some::mod2;
18180
18181      - const A: u32 = 42;
18182      - const B: u32 = 42;
18183      - const C: u32 = 42;
18184        ˇ
18185
18186        fn main() {
18187            println!("hello");
18188
18189            println!("world");
18190        }
18191      "#
18192        .unindent(),
18193    );
18194
18195    cx.update_editor(|editor, window, cx| {
18196        editor.handle_input("replacement", window, cx);
18197    });
18198    executor.run_until_parked();
18199    cx.assert_state_with_diff(
18200        r#"
18201        use some::mod1;
18202        use some::mod2;
18203
18204      - const A: u32 = 42;
18205      - const B: u32 = 42;
18206      - const C: u32 = 42;
18207      -
18208      + replacementˇ
18209
18210        fn main() {
18211            println!("hello");
18212
18213            println!("world");
18214        }
18215      "#
18216        .unindent(),
18217    );
18218}
18219
18220#[gpui::test]
18221async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18222    init_test(cx, |_| {});
18223
18224    let mut cx = EditorTestContext::new(cx).await;
18225
18226    let base_text = r#"
18227        one
18228        two
18229        three
18230        four
18231        five
18232    "#
18233    .unindent();
18234    executor.run_until_parked();
18235    cx.set_state(
18236        &r#"
18237        one
18238        two
18239        fˇour
18240        five
18241        "#
18242        .unindent(),
18243    );
18244
18245    cx.set_head_text(&base_text);
18246    executor.run_until_parked();
18247
18248    cx.update_editor(|editor, window, cx| {
18249        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18250    });
18251    executor.run_until_parked();
18252
18253    cx.assert_state_with_diff(
18254        r#"
18255          one
18256          two
18257        - three
18258          fˇour
18259          five
18260        "#
18261        .unindent(),
18262    );
18263
18264    cx.update_editor(|editor, window, cx| {
18265        editor.backspace(&Backspace, window, cx);
18266        editor.backspace(&Backspace, window, cx);
18267    });
18268    executor.run_until_parked();
18269    cx.assert_state_with_diff(
18270        r#"
18271          one
18272          two
18273        - threeˇ
18274        - four
18275        + our
18276          five
18277        "#
18278        .unindent(),
18279    );
18280}
18281
18282#[gpui::test]
18283async fn test_edit_after_expanded_modification_hunk(
18284    executor: BackgroundExecutor,
18285    cx: &mut TestAppContext,
18286) {
18287    init_test(cx, |_| {});
18288
18289    let mut cx = EditorTestContext::new(cx).await;
18290
18291    let diff_base = r#"
18292        use some::mod1;
18293        use some::mod2;
18294
18295        const A: u32 = 42;
18296        const B: u32 = 42;
18297        const C: u32 = 42;
18298        const D: u32 = 42;
18299
18300
18301        fn main() {
18302            println!("hello");
18303
18304            println!("world");
18305        }"#
18306    .unindent();
18307
18308    cx.set_state(
18309        &r#"
18310        use some::mod1;
18311        use some::mod2;
18312
18313        const A: u32 = 42;
18314        const B: u32 = 42;
18315        const C: u32 = 43ˇ
18316        const D: u32 = 42;
18317
18318
18319        fn main() {
18320            println!("hello");
18321
18322            println!("world");
18323        }"#
18324        .unindent(),
18325    );
18326
18327    cx.set_head_text(&diff_base);
18328    executor.run_until_parked();
18329    cx.update_editor(|editor, window, cx| {
18330        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18331    });
18332    executor.run_until_parked();
18333
18334    cx.assert_state_with_diff(
18335        r#"
18336        use some::mod1;
18337        use some::mod2;
18338
18339        const A: u32 = 42;
18340        const B: u32 = 42;
18341      - const C: u32 = 42;
18342      + const C: u32 = 43ˇ
18343        const D: u32 = 42;
18344
18345
18346        fn main() {
18347            println!("hello");
18348
18349            println!("world");
18350        }"#
18351        .unindent(),
18352    );
18353
18354    cx.update_editor(|editor, window, cx| {
18355        editor.handle_input("\nnew_line\n", window, cx);
18356    });
18357    executor.run_until_parked();
18358
18359    cx.assert_state_with_diff(
18360        r#"
18361        use some::mod1;
18362        use some::mod2;
18363
18364        const A: u32 = 42;
18365        const B: u32 = 42;
18366      - const C: u32 = 42;
18367      + const C: u32 = 43
18368      + new_line
18369      + ˇ
18370        const D: u32 = 42;
18371
18372
18373        fn main() {
18374            println!("hello");
18375
18376            println!("world");
18377        }"#
18378        .unindent(),
18379    );
18380}
18381
18382#[gpui::test]
18383async fn test_stage_and_unstage_added_file_hunk(
18384    executor: BackgroundExecutor,
18385    cx: &mut TestAppContext,
18386) {
18387    init_test(cx, |_| {});
18388
18389    let mut cx = EditorTestContext::new(cx).await;
18390    cx.update_editor(|editor, _, cx| {
18391        editor.set_expand_all_diff_hunks(cx);
18392    });
18393
18394    let working_copy = r#"
18395            ˇfn main() {
18396                println!("hello, world!");
18397            }
18398        "#
18399    .unindent();
18400
18401    cx.set_state(&working_copy);
18402    executor.run_until_parked();
18403
18404    cx.assert_state_with_diff(
18405        r#"
18406            + ˇfn main() {
18407            +     println!("hello, world!");
18408            + }
18409        "#
18410        .unindent(),
18411    );
18412    cx.assert_index_text(None);
18413
18414    cx.update_editor(|editor, window, cx| {
18415        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18416    });
18417    executor.run_until_parked();
18418    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
18419    cx.assert_state_with_diff(
18420        r#"
18421            + ˇfn main() {
18422            +     println!("hello, world!");
18423            + }
18424        "#
18425        .unindent(),
18426    );
18427
18428    cx.update_editor(|editor, window, cx| {
18429        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18430    });
18431    executor.run_until_parked();
18432    cx.assert_index_text(None);
18433}
18434
18435async fn setup_indent_guides_editor(
18436    text: &str,
18437    cx: &mut TestAppContext,
18438) -> (BufferId, EditorTestContext) {
18439    init_test(cx, |_| {});
18440
18441    let mut cx = EditorTestContext::new(cx).await;
18442
18443    let buffer_id = cx.update_editor(|editor, window, cx| {
18444        editor.set_text(text, window, cx);
18445        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
18446
18447        buffer_ids[0]
18448    });
18449
18450    (buffer_id, cx)
18451}
18452
18453fn assert_indent_guides(
18454    range: Range<u32>,
18455    expected: Vec<IndentGuide>,
18456    active_indices: Option<Vec<usize>>,
18457    cx: &mut EditorTestContext,
18458) {
18459    let indent_guides = cx.update_editor(|editor, window, cx| {
18460        let snapshot = editor.snapshot(window, cx).display_snapshot;
18461        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
18462            editor,
18463            MultiBufferRow(range.start)..MultiBufferRow(range.end),
18464            true,
18465            &snapshot,
18466            cx,
18467        );
18468
18469        indent_guides.sort_by(|a, b| {
18470            a.depth.cmp(&b.depth).then(
18471                a.start_row
18472                    .cmp(&b.start_row)
18473                    .then(a.end_row.cmp(&b.end_row)),
18474            )
18475        });
18476        indent_guides
18477    });
18478
18479    if let Some(expected) = active_indices {
18480        let active_indices = cx.update_editor(|editor, window, cx| {
18481            let snapshot = editor.snapshot(window, cx).display_snapshot;
18482            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
18483        });
18484
18485        assert_eq!(
18486            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
18487            expected,
18488            "Active indent guide indices do not match"
18489        );
18490    }
18491
18492    assert_eq!(indent_guides, expected, "Indent guides do not match");
18493}
18494
18495fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
18496    IndentGuide {
18497        buffer_id,
18498        start_row: MultiBufferRow(start_row),
18499        end_row: MultiBufferRow(end_row),
18500        depth,
18501        tab_size: 4,
18502        settings: IndentGuideSettings {
18503            enabled: true,
18504            line_width: 1,
18505            active_line_width: 1,
18506            ..Default::default()
18507        },
18508    }
18509}
18510
18511#[gpui::test]
18512async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
18513    let (buffer_id, mut cx) = setup_indent_guides_editor(
18514        &"
18515        fn main() {
18516            let a = 1;
18517        }"
18518        .unindent(),
18519        cx,
18520    )
18521    .await;
18522
18523    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
18524}
18525
18526#[gpui::test]
18527async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
18528    let (buffer_id, mut cx) = setup_indent_guides_editor(
18529        &"
18530        fn main() {
18531            let a = 1;
18532            let b = 2;
18533        }"
18534        .unindent(),
18535        cx,
18536    )
18537    .await;
18538
18539    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
18540}
18541
18542#[gpui::test]
18543async fn test_indent_guide_nested(cx: &mut TestAppContext) {
18544    let (buffer_id, mut cx) = setup_indent_guides_editor(
18545        &"
18546        fn main() {
18547            let a = 1;
18548            if a == 3 {
18549                let b = 2;
18550            } else {
18551                let c = 3;
18552            }
18553        }"
18554        .unindent(),
18555        cx,
18556    )
18557    .await;
18558
18559    assert_indent_guides(
18560        0..8,
18561        vec![
18562            indent_guide(buffer_id, 1, 6, 0),
18563            indent_guide(buffer_id, 3, 3, 1),
18564            indent_guide(buffer_id, 5, 5, 1),
18565        ],
18566        None,
18567        &mut cx,
18568    );
18569}
18570
18571#[gpui::test]
18572async fn test_indent_guide_tab(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            let c = 3;
18579        }"
18580        .unindent(),
18581        cx,
18582    )
18583    .await;
18584
18585    assert_indent_guides(
18586        0..5,
18587        vec![
18588            indent_guide(buffer_id, 1, 3, 0),
18589            indent_guide(buffer_id, 2, 2, 1),
18590        ],
18591        None,
18592        &mut cx,
18593    );
18594}
18595
18596#[gpui::test]
18597async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
18598    let (buffer_id, mut cx) = setup_indent_guides_editor(
18599        &"
18600        fn main() {
18601            let a = 1;
18602
18603            let c = 3;
18604        }"
18605        .unindent(),
18606        cx,
18607    )
18608    .await;
18609
18610    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
18611}
18612
18613#[gpui::test]
18614async fn test_indent_guide_complex(cx: &mut TestAppContext) {
18615    let (buffer_id, mut cx) = setup_indent_guides_editor(
18616        &"
18617        fn main() {
18618            let a = 1;
18619
18620            let c = 3;
18621
18622            if a == 3 {
18623                let b = 2;
18624            } else {
18625                let c = 3;
18626            }
18627        }"
18628        .unindent(),
18629        cx,
18630    )
18631    .await;
18632
18633    assert_indent_guides(
18634        0..11,
18635        vec![
18636            indent_guide(buffer_id, 1, 9, 0),
18637            indent_guide(buffer_id, 6, 6, 1),
18638            indent_guide(buffer_id, 8, 8, 1),
18639        ],
18640        None,
18641        &mut cx,
18642    );
18643}
18644
18645#[gpui::test]
18646async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
18647    let (buffer_id, mut cx) = setup_indent_guides_editor(
18648        &"
18649        fn main() {
18650            let a = 1;
18651
18652            let c = 3;
18653
18654            if a == 3 {
18655                let b = 2;
18656            } else {
18657                let c = 3;
18658            }
18659        }"
18660        .unindent(),
18661        cx,
18662    )
18663    .await;
18664
18665    assert_indent_guides(
18666        1..11,
18667        vec![
18668            indent_guide(buffer_id, 1, 9, 0),
18669            indent_guide(buffer_id, 6, 6, 1),
18670            indent_guide(buffer_id, 8, 8, 1),
18671        ],
18672        None,
18673        &mut cx,
18674    );
18675}
18676
18677#[gpui::test]
18678async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
18679    let (buffer_id, mut cx) = setup_indent_guides_editor(
18680        &"
18681        fn main() {
18682            let a = 1;
18683
18684            let c = 3;
18685
18686            if a == 3 {
18687                let b = 2;
18688            } else {
18689                let c = 3;
18690            }
18691        }"
18692        .unindent(),
18693        cx,
18694    )
18695    .await;
18696
18697    assert_indent_guides(
18698        1..10,
18699        vec![
18700            indent_guide(buffer_id, 1, 9, 0),
18701            indent_guide(buffer_id, 6, 6, 1),
18702            indent_guide(buffer_id, 8, 8, 1),
18703        ],
18704        None,
18705        &mut cx,
18706    );
18707}
18708
18709#[gpui::test]
18710async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
18711    let (buffer_id, mut cx) = setup_indent_guides_editor(
18712        &"
18713        fn main() {
18714            if a {
18715                b(
18716                    c,
18717                    d,
18718                )
18719            } else {
18720                e(
18721                    f
18722                )
18723            }
18724        }"
18725        .unindent(),
18726        cx,
18727    )
18728    .await;
18729
18730    assert_indent_guides(
18731        0..11,
18732        vec![
18733            indent_guide(buffer_id, 1, 10, 0),
18734            indent_guide(buffer_id, 2, 5, 1),
18735            indent_guide(buffer_id, 7, 9, 1),
18736            indent_guide(buffer_id, 3, 4, 2),
18737            indent_guide(buffer_id, 8, 8, 2),
18738        ],
18739        None,
18740        &mut cx,
18741    );
18742
18743    cx.update_editor(|editor, window, cx| {
18744        editor.fold_at(MultiBufferRow(2), window, cx);
18745        assert_eq!(
18746            editor.display_text(cx),
18747            "
18748            fn main() {
18749                if a {
18750                    b(⋯
18751                    )
18752                } else {
18753                    e(
18754                        f
18755                    )
18756                }
18757            }"
18758            .unindent()
18759        );
18760    });
18761
18762    assert_indent_guides(
18763        0..11,
18764        vec![
18765            indent_guide(buffer_id, 1, 10, 0),
18766            indent_guide(buffer_id, 2, 5, 1),
18767            indent_guide(buffer_id, 7, 9, 1),
18768            indent_guide(buffer_id, 8, 8, 2),
18769        ],
18770        None,
18771        &mut cx,
18772    );
18773}
18774
18775#[gpui::test]
18776async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
18777    let (buffer_id, mut cx) = setup_indent_guides_editor(
18778        &"
18779        block1
18780            block2
18781                block3
18782                    block4
18783            block2
18784        block1
18785        block1"
18786            .unindent(),
18787        cx,
18788    )
18789    .await;
18790
18791    assert_indent_guides(
18792        1..10,
18793        vec![
18794            indent_guide(buffer_id, 1, 4, 0),
18795            indent_guide(buffer_id, 2, 3, 1),
18796            indent_guide(buffer_id, 3, 3, 2),
18797        ],
18798        None,
18799        &mut cx,
18800    );
18801}
18802
18803#[gpui::test]
18804async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
18805    let (buffer_id, mut cx) = setup_indent_guides_editor(
18806        &"
18807        block1
18808            block2
18809                block3
18810
18811        block1
18812        block1"
18813            .unindent(),
18814        cx,
18815    )
18816    .await;
18817
18818    assert_indent_guides(
18819        0..6,
18820        vec![
18821            indent_guide(buffer_id, 1, 2, 0),
18822            indent_guide(buffer_id, 2, 2, 1),
18823        ],
18824        None,
18825        &mut cx,
18826    );
18827}
18828
18829#[gpui::test]
18830async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
18831    let (buffer_id, mut cx) = setup_indent_guides_editor(
18832        &"
18833        function component() {
18834        \treturn (
18835        \t\t\t
18836        \t\t<div>
18837        \t\t\t<abc></abc>
18838        \t\t</div>
18839        \t)
18840        }"
18841        .unindent(),
18842        cx,
18843    )
18844    .await;
18845
18846    assert_indent_guides(
18847        0..8,
18848        vec![
18849            indent_guide(buffer_id, 1, 6, 0),
18850            indent_guide(buffer_id, 2, 5, 1),
18851            indent_guide(buffer_id, 4, 4, 2),
18852        ],
18853        None,
18854        &mut cx,
18855    );
18856}
18857
18858#[gpui::test]
18859async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
18860    let (buffer_id, mut cx) = setup_indent_guides_editor(
18861        &"
18862        function component() {
18863        \treturn (
18864        \t
18865        \t\t<div>
18866        \t\t\t<abc></abc>
18867        \t\t</div>
18868        \t)
18869        }"
18870        .unindent(),
18871        cx,
18872    )
18873    .await;
18874
18875    assert_indent_guides(
18876        0..8,
18877        vec![
18878            indent_guide(buffer_id, 1, 6, 0),
18879            indent_guide(buffer_id, 2, 5, 1),
18880            indent_guide(buffer_id, 4, 4, 2),
18881        ],
18882        None,
18883        &mut cx,
18884    );
18885}
18886
18887#[gpui::test]
18888async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
18889    let (buffer_id, mut cx) = setup_indent_guides_editor(
18890        &"
18891        block1
18892
18893
18894
18895            block2
18896        "
18897        .unindent(),
18898        cx,
18899    )
18900    .await;
18901
18902    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
18903}
18904
18905#[gpui::test]
18906async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
18907    let (buffer_id, mut cx) = setup_indent_guides_editor(
18908        &"
18909        def a:
18910        \tb = 3
18911        \tif True:
18912        \t\tc = 4
18913        \t\td = 5
18914        \tprint(b)
18915        "
18916        .unindent(),
18917        cx,
18918    )
18919    .await;
18920
18921    assert_indent_guides(
18922        0..6,
18923        vec![
18924            indent_guide(buffer_id, 1, 5, 0),
18925            indent_guide(buffer_id, 3, 4, 1),
18926        ],
18927        None,
18928        &mut cx,
18929    );
18930}
18931
18932#[gpui::test]
18933async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
18934    let (buffer_id, mut cx) = setup_indent_guides_editor(
18935        &"
18936    fn main() {
18937        let a = 1;
18938    }"
18939        .unindent(),
18940        cx,
18941    )
18942    .await;
18943
18944    cx.update_editor(|editor, window, cx| {
18945        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18946            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18947        });
18948    });
18949
18950    assert_indent_guides(
18951        0..3,
18952        vec![indent_guide(buffer_id, 1, 1, 0)],
18953        Some(vec![0]),
18954        &mut cx,
18955    );
18956}
18957
18958#[gpui::test]
18959async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
18960    let (buffer_id, mut cx) = setup_indent_guides_editor(
18961        &"
18962    fn main() {
18963        if 1 == 2 {
18964            let a = 1;
18965        }
18966    }"
18967        .unindent(),
18968        cx,
18969    )
18970    .await;
18971
18972    cx.update_editor(|editor, window, cx| {
18973        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18974            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18975        });
18976    });
18977
18978    assert_indent_guides(
18979        0..4,
18980        vec![
18981            indent_guide(buffer_id, 1, 3, 0),
18982            indent_guide(buffer_id, 2, 2, 1),
18983        ],
18984        Some(vec![1]),
18985        &mut cx,
18986    );
18987
18988    cx.update_editor(|editor, window, cx| {
18989        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18990            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
18991        });
18992    });
18993
18994    assert_indent_guides(
18995        0..4,
18996        vec![
18997            indent_guide(buffer_id, 1, 3, 0),
18998            indent_guide(buffer_id, 2, 2, 1),
18999        ],
19000        Some(vec![1]),
19001        &mut cx,
19002    );
19003
19004    cx.update_editor(|editor, window, cx| {
19005        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19006            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
19007        });
19008    });
19009
19010    assert_indent_guides(
19011        0..4,
19012        vec![
19013            indent_guide(buffer_id, 1, 3, 0),
19014            indent_guide(buffer_id, 2, 2, 1),
19015        ],
19016        Some(vec![0]),
19017        &mut cx,
19018    );
19019}
19020
19021#[gpui::test]
19022async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
19023    let (buffer_id, mut cx) = setup_indent_guides_editor(
19024        &"
19025    fn main() {
19026        let a = 1;
19027
19028        let b = 2;
19029    }"
19030        .unindent(),
19031        cx,
19032    )
19033    .await;
19034
19035    cx.update_editor(|editor, window, cx| {
19036        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19037            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
19038        });
19039    });
19040
19041    assert_indent_guides(
19042        0..5,
19043        vec![indent_guide(buffer_id, 1, 3, 0)],
19044        Some(vec![0]),
19045        &mut cx,
19046    );
19047}
19048
19049#[gpui::test]
19050async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
19051    let (buffer_id, mut cx) = setup_indent_guides_editor(
19052        &"
19053    def m:
19054        a = 1
19055        pass"
19056            .unindent(),
19057        cx,
19058    )
19059    .await;
19060
19061    cx.update_editor(|editor, window, cx| {
19062        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19063            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
19064        });
19065    });
19066
19067    assert_indent_guides(
19068        0..3,
19069        vec![indent_guide(buffer_id, 1, 2, 0)],
19070        Some(vec![0]),
19071        &mut cx,
19072    );
19073}
19074
19075#[gpui::test]
19076async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
19077    init_test(cx, |_| {});
19078    let mut cx = EditorTestContext::new(cx).await;
19079    let text = indoc! {
19080        "
19081        impl A {
19082            fn b() {
19083                0;
19084                3;
19085                5;
19086                6;
19087                7;
19088            }
19089        }
19090        "
19091    };
19092    let base_text = indoc! {
19093        "
19094        impl A {
19095            fn b() {
19096                0;
19097                1;
19098                2;
19099                3;
19100                4;
19101            }
19102            fn c() {
19103                5;
19104                6;
19105                7;
19106            }
19107        }
19108        "
19109    };
19110
19111    cx.update_editor(|editor, window, cx| {
19112        editor.set_text(text, window, cx);
19113
19114        editor.buffer().update(cx, |multibuffer, cx| {
19115            let buffer = multibuffer.as_singleton().unwrap();
19116            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
19117
19118            multibuffer.set_all_diff_hunks_expanded(cx);
19119            multibuffer.add_diff(diff, cx);
19120
19121            buffer.read(cx).remote_id()
19122        })
19123    });
19124    cx.run_until_parked();
19125
19126    cx.assert_state_with_diff(
19127        indoc! { "
19128          impl A {
19129              fn b() {
19130                  0;
19131        -         1;
19132        -         2;
19133                  3;
19134        -         4;
19135        -     }
19136        -     fn c() {
19137                  5;
19138                  6;
19139                  7;
19140              }
19141          }
19142          ˇ"
19143        }
19144        .to_string(),
19145    );
19146
19147    let mut actual_guides = cx.update_editor(|editor, window, cx| {
19148        editor
19149            .snapshot(window, cx)
19150            .buffer_snapshot
19151            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
19152            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
19153            .collect::<Vec<_>>()
19154    });
19155    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
19156    assert_eq!(
19157        actual_guides,
19158        vec![
19159            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
19160            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
19161            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
19162        ]
19163    );
19164}
19165
19166#[gpui::test]
19167async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19168    init_test(cx, |_| {});
19169    let mut cx = EditorTestContext::new(cx).await;
19170
19171    let diff_base = r#"
19172        a
19173        b
19174        c
19175        "#
19176    .unindent();
19177
19178    cx.set_state(
19179        &r#"
19180        ˇA
19181        b
19182        C
19183        "#
19184        .unindent(),
19185    );
19186    cx.set_head_text(&diff_base);
19187    cx.update_editor(|editor, window, cx| {
19188        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19189    });
19190    executor.run_until_parked();
19191
19192    let both_hunks_expanded = r#"
19193        - a
19194        + ˇA
19195          b
19196        - c
19197        + C
19198        "#
19199    .unindent();
19200
19201    cx.assert_state_with_diff(both_hunks_expanded.clone());
19202
19203    let hunk_ranges = cx.update_editor(|editor, window, cx| {
19204        let snapshot = editor.snapshot(window, cx);
19205        let hunks = editor
19206            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19207            .collect::<Vec<_>>();
19208        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
19209        let buffer_id = hunks[0].buffer_id;
19210        hunks
19211            .into_iter()
19212            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
19213            .collect::<Vec<_>>()
19214    });
19215    assert_eq!(hunk_ranges.len(), 2);
19216
19217    cx.update_editor(|editor, _, cx| {
19218        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19219    });
19220    executor.run_until_parked();
19221
19222    let second_hunk_expanded = r#"
19223          ˇA
19224          b
19225        - c
19226        + C
19227        "#
19228    .unindent();
19229
19230    cx.assert_state_with_diff(second_hunk_expanded);
19231
19232    cx.update_editor(|editor, _, cx| {
19233        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19234    });
19235    executor.run_until_parked();
19236
19237    cx.assert_state_with_diff(both_hunks_expanded.clone());
19238
19239    cx.update_editor(|editor, _, cx| {
19240        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
19241    });
19242    executor.run_until_parked();
19243
19244    let first_hunk_expanded = r#"
19245        - a
19246        + ˇA
19247          b
19248          C
19249        "#
19250    .unindent();
19251
19252    cx.assert_state_with_diff(first_hunk_expanded);
19253
19254    cx.update_editor(|editor, _, cx| {
19255        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
19256    });
19257    executor.run_until_parked();
19258
19259    cx.assert_state_with_diff(both_hunks_expanded);
19260
19261    cx.set_state(
19262        &r#"
19263        ˇA
19264        b
19265        "#
19266        .unindent(),
19267    );
19268    cx.run_until_parked();
19269
19270    // TODO this cursor position seems bad
19271    cx.assert_state_with_diff(
19272        r#"
19273        - ˇa
19274        + A
19275          b
19276        "#
19277        .unindent(),
19278    );
19279
19280    cx.update_editor(|editor, window, cx| {
19281        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19282    });
19283
19284    cx.assert_state_with_diff(
19285        r#"
19286            - ˇa
19287            + A
19288              b
19289            - c
19290            "#
19291        .unindent(),
19292    );
19293
19294    let hunk_ranges = cx.update_editor(|editor, window, cx| {
19295        let snapshot = editor.snapshot(window, cx);
19296        let hunks = editor
19297            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19298            .collect::<Vec<_>>();
19299        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
19300        let buffer_id = hunks[0].buffer_id;
19301        hunks
19302            .into_iter()
19303            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
19304            .collect::<Vec<_>>()
19305    });
19306    assert_eq!(hunk_ranges.len(), 2);
19307
19308    cx.update_editor(|editor, _, cx| {
19309        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
19310    });
19311    executor.run_until_parked();
19312
19313    cx.assert_state_with_diff(
19314        r#"
19315        - ˇa
19316        + A
19317          b
19318        "#
19319        .unindent(),
19320    );
19321}
19322
19323#[gpui::test]
19324async fn test_toggle_deletion_hunk_at_start_of_file(
19325    executor: BackgroundExecutor,
19326    cx: &mut TestAppContext,
19327) {
19328    init_test(cx, |_| {});
19329    let mut cx = EditorTestContext::new(cx).await;
19330
19331    let diff_base = r#"
19332        a
19333        b
19334        c
19335        "#
19336    .unindent();
19337
19338    cx.set_state(
19339        &r#"
19340        ˇb
19341        c
19342        "#
19343        .unindent(),
19344    );
19345    cx.set_head_text(&diff_base);
19346    cx.update_editor(|editor, window, cx| {
19347        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19348    });
19349    executor.run_until_parked();
19350
19351    let hunk_expanded = r#"
19352        - a
19353          ˇb
19354          c
19355        "#
19356    .unindent();
19357
19358    cx.assert_state_with_diff(hunk_expanded.clone());
19359
19360    let hunk_ranges = cx.update_editor(|editor, window, cx| {
19361        let snapshot = editor.snapshot(window, cx);
19362        let hunks = editor
19363            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19364            .collect::<Vec<_>>();
19365        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
19366        let buffer_id = hunks[0].buffer_id;
19367        hunks
19368            .into_iter()
19369            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
19370            .collect::<Vec<_>>()
19371    });
19372    assert_eq!(hunk_ranges.len(), 1);
19373
19374    cx.update_editor(|editor, _, cx| {
19375        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19376    });
19377    executor.run_until_parked();
19378
19379    let hunk_collapsed = r#"
19380          ˇb
19381          c
19382        "#
19383    .unindent();
19384
19385    cx.assert_state_with_diff(hunk_collapsed);
19386
19387    cx.update_editor(|editor, _, cx| {
19388        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19389    });
19390    executor.run_until_parked();
19391
19392    cx.assert_state_with_diff(hunk_expanded.clone());
19393}
19394
19395#[gpui::test]
19396async fn test_display_diff_hunks(cx: &mut TestAppContext) {
19397    init_test(cx, |_| {});
19398
19399    let fs = FakeFs::new(cx.executor());
19400    fs.insert_tree(
19401        path!("/test"),
19402        json!({
19403            ".git": {},
19404            "file-1": "ONE\n",
19405            "file-2": "TWO\n",
19406            "file-3": "THREE\n",
19407        }),
19408    )
19409    .await;
19410
19411    fs.set_head_for_repo(
19412        path!("/test/.git").as_ref(),
19413        &[
19414            ("file-1".into(), "one\n".into()),
19415            ("file-2".into(), "two\n".into()),
19416            ("file-3".into(), "three\n".into()),
19417        ],
19418        "deadbeef",
19419    );
19420
19421    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
19422    let mut buffers = vec![];
19423    for i in 1..=3 {
19424        let buffer = project
19425            .update(cx, |project, cx| {
19426                let path = format!(path!("/test/file-{}"), i);
19427                project.open_local_buffer(path, cx)
19428            })
19429            .await
19430            .unwrap();
19431        buffers.push(buffer);
19432    }
19433
19434    let multibuffer = cx.new(|cx| {
19435        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
19436        multibuffer.set_all_diff_hunks_expanded(cx);
19437        for buffer in &buffers {
19438            let snapshot = buffer.read(cx).snapshot();
19439            multibuffer.set_excerpts_for_path(
19440                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
19441                buffer.clone(),
19442                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
19443                DEFAULT_MULTIBUFFER_CONTEXT,
19444                cx,
19445            );
19446        }
19447        multibuffer
19448    });
19449
19450    let editor = cx.add_window(|window, cx| {
19451        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
19452    });
19453    cx.run_until_parked();
19454
19455    let snapshot = editor
19456        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19457        .unwrap();
19458    let hunks = snapshot
19459        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
19460        .map(|hunk| match hunk {
19461            DisplayDiffHunk::Unfolded {
19462                display_row_range, ..
19463            } => display_row_range,
19464            DisplayDiffHunk::Folded { .. } => unreachable!(),
19465        })
19466        .collect::<Vec<_>>();
19467    assert_eq!(
19468        hunks,
19469        [
19470            DisplayRow(2)..DisplayRow(4),
19471            DisplayRow(7)..DisplayRow(9),
19472            DisplayRow(12)..DisplayRow(14),
19473        ]
19474    );
19475}
19476
19477#[gpui::test]
19478async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
19479    init_test(cx, |_| {});
19480
19481    let mut cx = EditorTestContext::new(cx).await;
19482    cx.set_head_text(indoc! { "
19483        one
19484        two
19485        three
19486        four
19487        five
19488        "
19489    });
19490    cx.set_index_text(indoc! { "
19491        one
19492        two
19493        three
19494        four
19495        five
19496        "
19497    });
19498    cx.set_state(indoc! {"
19499        one
19500        TWO
19501        ˇTHREE
19502        FOUR
19503        five
19504    "});
19505    cx.run_until_parked();
19506    cx.update_editor(|editor, window, cx| {
19507        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19508    });
19509    cx.run_until_parked();
19510    cx.assert_index_text(Some(indoc! {"
19511        one
19512        TWO
19513        THREE
19514        FOUR
19515        five
19516    "}));
19517    cx.set_state(indoc! { "
19518        one
19519        TWO
19520        ˇTHREE-HUNDRED
19521        FOUR
19522        five
19523    "});
19524    cx.run_until_parked();
19525    cx.update_editor(|editor, window, cx| {
19526        let snapshot = editor.snapshot(window, cx);
19527        let hunks = editor
19528            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19529            .collect::<Vec<_>>();
19530        assert_eq!(hunks.len(), 1);
19531        assert_eq!(
19532            hunks[0].status(),
19533            DiffHunkStatus {
19534                kind: DiffHunkStatusKind::Modified,
19535                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
19536            }
19537        );
19538
19539        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19540    });
19541    cx.run_until_parked();
19542    cx.assert_index_text(Some(indoc! {"
19543        one
19544        TWO
19545        THREE-HUNDRED
19546        FOUR
19547        five
19548    "}));
19549}
19550
19551#[gpui::test]
19552fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
19553    init_test(cx, |_| {});
19554
19555    let editor = cx.add_window(|window, cx| {
19556        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
19557        build_editor(buffer, window, cx)
19558    });
19559
19560    let render_args = Arc::new(Mutex::new(None));
19561    let snapshot = editor
19562        .update(cx, |editor, window, cx| {
19563            let snapshot = editor.buffer().read(cx).snapshot(cx);
19564            let range =
19565                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
19566
19567            struct RenderArgs {
19568                row: MultiBufferRow,
19569                folded: bool,
19570                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
19571            }
19572
19573            let crease = Crease::inline(
19574                range,
19575                FoldPlaceholder::test(),
19576                {
19577                    let toggle_callback = render_args.clone();
19578                    move |row, folded, callback, _window, _cx| {
19579                        *toggle_callback.lock() = Some(RenderArgs {
19580                            row,
19581                            folded,
19582                            callback,
19583                        });
19584                        div()
19585                    }
19586                },
19587                |_row, _folded, _window, _cx| div(),
19588            );
19589
19590            editor.insert_creases(Some(crease), cx);
19591            let snapshot = editor.snapshot(window, cx);
19592            let _div = snapshot.render_crease_toggle(
19593                MultiBufferRow(1),
19594                false,
19595                cx.entity().clone(),
19596                window,
19597                cx,
19598            );
19599            snapshot
19600        })
19601        .unwrap();
19602
19603    let render_args = render_args.lock().take().unwrap();
19604    assert_eq!(render_args.row, MultiBufferRow(1));
19605    assert!(!render_args.folded);
19606    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
19607
19608    cx.update_window(*editor, |_, window, cx| {
19609        (render_args.callback)(true, window, cx)
19610    })
19611    .unwrap();
19612    let snapshot = editor
19613        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19614        .unwrap();
19615    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
19616
19617    cx.update_window(*editor, |_, window, cx| {
19618        (render_args.callback)(false, window, cx)
19619    })
19620    .unwrap();
19621    let snapshot = editor
19622        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19623        .unwrap();
19624    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
19625}
19626
19627#[gpui::test]
19628async fn test_input_text(cx: &mut TestAppContext) {
19629    init_test(cx, |_| {});
19630    let mut cx = EditorTestContext::new(cx).await;
19631
19632    cx.set_state(
19633        &r#"ˇone
19634        two
19635
19636        three
19637        fourˇ
19638        five
19639
19640        siˇx"#
19641            .unindent(),
19642    );
19643
19644    cx.dispatch_action(HandleInput(String::new()));
19645    cx.assert_editor_state(
19646        &r#"ˇone
19647        two
19648
19649        three
19650        fourˇ
19651        five
19652
19653        siˇx"#
19654            .unindent(),
19655    );
19656
19657    cx.dispatch_action(HandleInput("AAAA".to_string()));
19658    cx.assert_editor_state(
19659        &r#"AAAAˇone
19660        two
19661
19662        three
19663        fourAAAAˇ
19664        five
19665
19666        siAAAAˇx"#
19667            .unindent(),
19668    );
19669}
19670
19671#[gpui::test]
19672async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
19673    init_test(cx, |_| {});
19674
19675    let mut cx = EditorTestContext::new(cx).await;
19676    cx.set_state(
19677        r#"let foo = 1;
19678let foo = 2;
19679let foo = 3;
19680let fooˇ = 4;
19681let foo = 5;
19682let foo = 6;
19683let foo = 7;
19684let foo = 8;
19685let foo = 9;
19686let foo = 10;
19687let foo = 11;
19688let foo = 12;
19689let foo = 13;
19690let foo = 14;
19691let foo = 15;"#,
19692    );
19693
19694    cx.update_editor(|e, window, cx| {
19695        assert_eq!(
19696            e.next_scroll_position,
19697            NextScrollCursorCenterTopBottom::Center,
19698            "Default next scroll direction is center",
19699        );
19700
19701        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19702        assert_eq!(
19703            e.next_scroll_position,
19704            NextScrollCursorCenterTopBottom::Top,
19705            "After center, next scroll direction should be top",
19706        );
19707
19708        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19709        assert_eq!(
19710            e.next_scroll_position,
19711            NextScrollCursorCenterTopBottom::Bottom,
19712            "After top, next scroll direction should be bottom",
19713        );
19714
19715        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19716        assert_eq!(
19717            e.next_scroll_position,
19718            NextScrollCursorCenterTopBottom::Center,
19719            "After bottom, scrolling should start over",
19720        );
19721
19722        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19723        assert_eq!(
19724            e.next_scroll_position,
19725            NextScrollCursorCenterTopBottom::Top,
19726            "Scrolling continues if retriggered fast enough"
19727        );
19728    });
19729
19730    cx.executor()
19731        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
19732    cx.executor().run_until_parked();
19733    cx.update_editor(|e, _, _| {
19734        assert_eq!(
19735            e.next_scroll_position,
19736            NextScrollCursorCenterTopBottom::Center,
19737            "If scrolling is not triggered fast enough, it should reset"
19738        );
19739    });
19740}
19741
19742#[gpui::test]
19743async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
19744    init_test(cx, |_| {});
19745    let mut cx = EditorLspTestContext::new_rust(
19746        lsp::ServerCapabilities {
19747            definition_provider: Some(lsp::OneOf::Left(true)),
19748            references_provider: Some(lsp::OneOf::Left(true)),
19749            ..lsp::ServerCapabilities::default()
19750        },
19751        cx,
19752    )
19753    .await;
19754
19755    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
19756        let go_to_definition = cx
19757            .lsp
19758            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
19759                move |params, _| async move {
19760                    if empty_go_to_definition {
19761                        Ok(None)
19762                    } else {
19763                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
19764                            uri: params.text_document_position_params.text_document.uri,
19765                            range: lsp::Range::new(
19766                                lsp::Position::new(4, 3),
19767                                lsp::Position::new(4, 6),
19768                            ),
19769                        })))
19770                    }
19771                },
19772            );
19773        let references = cx
19774            .lsp
19775            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
19776                Ok(Some(vec![lsp::Location {
19777                    uri: params.text_document_position.text_document.uri,
19778                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
19779                }]))
19780            });
19781        (go_to_definition, references)
19782    };
19783
19784    cx.set_state(
19785        &r#"fn one() {
19786            let mut a = ˇtwo();
19787        }
19788
19789        fn two() {}"#
19790            .unindent(),
19791    );
19792    set_up_lsp_handlers(false, &mut cx);
19793    let navigated = cx
19794        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19795        .await
19796        .expect("Failed to navigate to definition");
19797    assert_eq!(
19798        navigated,
19799        Navigated::Yes,
19800        "Should have navigated to definition from the GetDefinition response"
19801    );
19802    cx.assert_editor_state(
19803        &r#"fn one() {
19804            let mut a = two();
19805        }
19806
19807        fn «twoˇ»() {}"#
19808            .unindent(),
19809    );
19810
19811    let editors = cx.update_workspace(|workspace, _, cx| {
19812        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19813    });
19814    cx.update_editor(|_, _, test_editor_cx| {
19815        assert_eq!(
19816            editors.len(),
19817            1,
19818            "Initially, only one, test, editor should be open in the workspace"
19819        );
19820        assert_eq!(
19821            test_editor_cx.entity(),
19822            editors.last().expect("Asserted len is 1").clone()
19823        );
19824    });
19825
19826    set_up_lsp_handlers(true, &mut cx);
19827    let navigated = cx
19828        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19829        .await
19830        .expect("Failed to navigate to lookup references");
19831    assert_eq!(
19832        navigated,
19833        Navigated::Yes,
19834        "Should have navigated to references as a fallback after empty GoToDefinition response"
19835    );
19836    // We should not change the selections in the existing file,
19837    // if opening another milti buffer with the references
19838    cx.assert_editor_state(
19839        &r#"fn one() {
19840            let mut a = two();
19841        }
19842
19843        fn «twoˇ»() {}"#
19844            .unindent(),
19845    );
19846    let editors = cx.update_workspace(|workspace, _, cx| {
19847        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19848    });
19849    cx.update_editor(|_, _, test_editor_cx| {
19850        assert_eq!(
19851            editors.len(),
19852            2,
19853            "After falling back to references search, we open a new editor with the results"
19854        );
19855        let references_fallback_text = editors
19856            .into_iter()
19857            .find(|new_editor| *new_editor != test_editor_cx.entity())
19858            .expect("Should have one non-test editor now")
19859            .read(test_editor_cx)
19860            .text(test_editor_cx);
19861        assert_eq!(
19862            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
19863            "Should use the range from the references response and not the GoToDefinition one"
19864        );
19865    });
19866}
19867
19868#[gpui::test]
19869async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
19870    init_test(cx, |_| {});
19871    cx.update(|cx| {
19872        let mut editor_settings = EditorSettings::get_global(cx).clone();
19873        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
19874        EditorSettings::override_global(editor_settings, cx);
19875    });
19876    let mut cx = EditorLspTestContext::new_rust(
19877        lsp::ServerCapabilities {
19878            definition_provider: Some(lsp::OneOf::Left(true)),
19879            references_provider: Some(lsp::OneOf::Left(true)),
19880            ..lsp::ServerCapabilities::default()
19881        },
19882        cx,
19883    )
19884    .await;
19885    let original_state = r#"fn one() {
19886        let mut a = ˇtwo();
19887    }
19888
19889    fn two() {}"#
19890        .unindent();
19891    cx.set_state(&original_state);
19892
19893    let mut go_to_definition = cx
19894        .lsp
19895        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
19896            move |_, _| async move { Ok(None) },
19897        );
19898    let _references = cx
19899        .lsp
19900        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
19901            panic!("Should not call for references with no go to definition fallback")
19902        });
19903
19904    let navigated = cx
19905        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19906        .await
19907        .expect("Failed to navigate to lookup references");
19908    go_to_definition
19909        .next()
19910        .await
19911        .expect("Should have called the go_to_definition handler");
19912
19913    assert_eq!(
19914        navigated,
19915        Navigated::No,
19916        "Should have navigated to references as a fallback after empty GoToDefinition response"
19917    );
19918    cx.assert_editor_state(&original_state);
19919    let editors = cx.update_workspace(|workspace, _, cx| {
19920        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19921    });
19922    cx.update_editor(|_, _, _| {
19923        assert_eq!(
19924            editors.len(),
19925            1,
19926            "After unsuccessful fallback, no other editor should have been opened"
19927        );
19928    });
19929}
19930
19931#[gpui::test]
19932async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
19933    init_test(cx, |_| {});
19934
19935    let language = Arc::new(Language::new(
19936        LanguageConfig::default(),
19937        Some(tree_sitter_rust::LANGUAGE.into()),
19938    ));
19939
19940    let text = r#"
19941        #[cfg(test)]
19942        mod tests() {
19943            #[test]
19944            fn runnable_1() {
19945                let a = 1;
19946            }
19947
19948            #[test]
19949            fn runnable_2() {
19950                let a = 1;
19951                let b = 2;
19952            }
19953        }
19954    "#
19955    .unindent();
19956
19957    let fs = FakeFs::new(cx.executor());
19958    fs.insert_file("/file.rs", Default::default()).await;
19959
19960    let project = Project::test(fs, ["/a".as_ref()], cx).await;
19961    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19962    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19963    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
19964    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
19965
19966    let editor = cx.new_window_entity(|window, cx| {
19967        Editor::new(
19968            EditorMode::full(),
19969            multi_buffer,
19970            Some(project.clone()),
19971            window,
19972            cx,
19973        )
19974    });
19975
19976    editor.update_in(cx, |editor, window, cx| {
19977        let snapshot = editor.buffer().read(cx).snapshot(cx);
19978        editor.tasks.insert(
19979            (buffer.read(cx).remote_id(), 3),
19980            RunnableTasks {
19981                templates: vec![],
19982                offset: snapshot.anchor_before(43),
19983                column: 0,
19984                extra_variables: HashMap::default(),
19985                context_range: BufferOffset(43)..BufferOffset(85),
19986            },
19987        );
19988        editor.tasks.insert(
19989            (buffer.read(cx).remote_id(), 8),
19990            RunnableTasks {
19991                templates: vec![],
19992                offset: snapshot.anchor_before(86),
19993                column: 0,
19994                extra_variables: HashMap::default(),
19995                context_range: BufferOffset(86)..BufferOffset(191),
19996            },
19997        );
19998
19999        // Test finding task when cursor is inside function body
20000        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20001            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
20002        });
20003        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
20004        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
20005
20006        // Test finding task when cursor is on function name
20007        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20008            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
20009        });
20010        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
20011        assert_eq!(row, 8, "Should find task when cursor is on function name");
20012    });
20013}
20014
20015#[gpui::test]
20016async fn test_folding_buffers(cx: &mut TestAppContext) {
20017    init_test(cx, |_| {});
20018
20019    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
20020    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
20021    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
20022
20023    let fs = FakeFs::new(cx.executor());
20024    fs.insert_tree(
20025        path!("/a"),
20026        json!({
20027            "first.rs": sample_text_1,
20028            "second.rs": sample_text_2,
20029            "third.rs": sample_text_3,
20030        }),
20031    )
20032    .await;
20033    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20034    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20035    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20036    let worktree = project.update(cx, |project, cx| {
20037        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
20038        assert_eq!(worktrees.len(), 1);
20039        worktrees.pop().unwrap()
20040    });
20041    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
20042
20043    let buffer_1 = project
20044        .update(cx, |project, cx| {
20045            project.open_buffer((worktree_id, "first.rs"), cx)
20046        })
20047        .await
20048        .unwrap();
20049    let buffer_2 = project
20050        .update(cx, |project, cx| {
20051            project.open_buffer((worktree_id, "second.rs"), cx)
20052        })
20053        .await
20054        .unwrap();
20055    let buffer_3 = project
20056        .update(cx, |project, cx| {
20057            project.open_buffer((worktree_id, "third.rs"), cx)
20058        })
20059        .await
20060        .unwrap();
20061
20062    let multi_buffer = cx.new(|cx| {
20063        let mut multi_buffer = MultiBuffer::new(ReadWrite);
20064        multi_buffer.push_excerpts(
20065            buffer_1.clone(),
20066            [
20067                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20068                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20069                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20070            ],
20071            cx,
20072        );
20073        multi_buffer.push_excerpts(
20074            buffer_2.clone(),
20075            [
20076                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20077                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20078                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20079            ],
20080            cx,
20081        );
20082        multi_buffer.push_excerpts(
20083            buffer_3.clone(),
20084            [
20085                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20086                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20087                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20088            ],
20089            cx,
20090        );
20091        multi_buffer
20092    });
20093    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20094        Editor::new(
20095            EditorMode::full(),
20096            multi_buffer.clone(),
20097            Some(project.clone()),
20098            window,
20099            cx,
20100        )
20101    });
20102
20103    assert_eq!(
20104        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20105        "\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",
20106    );
20107
20108    multi_buffer_editor.update(cx, |editor, cx| {
20109        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
20110    });
20111    assert_eq!(
20112        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20113        "\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",
20114        "After folding the first buffer, its text should not be displayed"
20115    );
20116
20117    multi_buffer_editor.update(cx, |editor, cx| {
20118        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
20119    });
20120    assert_eq!(
20121        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20122        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
20123        "After folding the second buffer, its text should not be displayed"
20124    );
20125
20126    multi_buffer_editor.update(cx, |editor, cx| {
20127        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
20128    });
20129    assert_eq!(
20130        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20131        "\n\n\n\n\n",
20132        "After folding the third buffer, its text should not be displayed"
20133    );
20134
20135    // Emulate selection inside the fold logic, that should work
20136    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20137        editor
20138            .snapshot(window, cx)
20139            .next_line_boundary(Point::new(0, 4));
20140    });
20141
20142    multi_buffer_editor.update(cx, |editor, cx| {
20143        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
20144    });
20145    assert_eq!(
20146        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20147        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
20148        "After unfolding the second buffer, its text should be displayed"
20149    );
20150
20151    // Typing inside of buffer 1 causes that buffer to be unfolded.
20152    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20153        assert_eq!(
20154            multi_buffer
20155                .read(cx)
20156                .snapshot(cx)
20157                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
20158                .collect::<String>(),
20159            "bbbb"
20160        );
20161        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
20162            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
20163        });
20164        editor.handle_input("B", window, cx);
20165    });
20166
20167    assert_eq!(
20168        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20169        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
20170        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
20171    );
20172
20173    multi_buffer_editor.update(cx, |editor, cx| {
20174        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
20175    });
20176    assert_eq!(
20177        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20178        "\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",
20179        "After unfolding the all buffers, all original text should be displayed"
20180    );
20181}
20182
20183#[gpui::test]
20184async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
20185    init_test(cx, |_| {});
20186
20187    let sample_text_1 = "1111\n2222\n3333".to_string();
20188    let sample_text_2 = "4444\n5555\n6666".to_string();
20189    let sample_text_3 = "7777\n8888\n9999".to_string();
20190
20191    let fs = FakeFs::new(cx.executor());
20192    fs.insert_tree(
20193        path!("/a"),
20194        json!({
20195            "first.rs": sample_text_1,
20196            "second.rs": sample_text_2,
20197            "third.rs": sample_text_3,
20198        }),
20199    )
20200    .await;
20201    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20202    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20203    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20204    let worktree = project.update(cx, |project, cx| {
20205        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
20206        assert_eq!(worktrees.len(), 1);
20207        worktrees.pop().unwrap()
20208    });
20209    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
20210
20211    let buffer_1 = project
20212        .update(cx, |project, cx| {
20213            project.open_buffer((worktree_id, "first.rs"), cx)
20214        })
20215        .await
20216        .unwrap();
20217    let buffer_2 = project
20218        .update(cx, |project, cx| {
20219            project.open_buffer((worktree_id, "second.rs"), cx)
20220        })
20221        .await
20222        .unwrap();
20223    let buffer_3 = project
20224        .update(cx, |project, cx| {
20225            project.open_buffer((worktree_id, "third.rs"), cx)
20226        })
20227        .await
20228        .unwrap();
20229
20230    let multi_buffer = cx.new(|cx| {
20231        let mut multi_buffer = MultiBuffer::new(ReadWrite);
20232        multi_buffer.push_excerpts(
20233            buffer_1.clone(),
20234            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
20235            cx,
20236        );
20237        multi_buffer.push_excerpts(
20238            buffer_2.clone(),
20239            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
20240            cx,
20241        );
20242        multi_buffer.push_excerpts(
20243            buffer_3.clone(),
20244            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
20245            cx,
20246        );
20247        multi_buffer
20248    });
20249
20250    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20251        Editor::new(
20252            EditorMode::full(),
20253            multi_buffer,
20254            Some(project.clone()),
20255            window,
20256            cx,
20257        )
20258    });
20259
20260    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
20261    assert_eq!(
20262        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20263        full_text,
20264    );
20265
20266    multi_buffer_editor.update(cx, |editor, cx| {
20267        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
20268    });
20269    assert_eq!(
20270        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20271        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
20272        "After folding the first buffer, its text should not be displayed"
20273    );
20274
20275    multi_buffer_editor.update(cx, |editor, cx| {
20276        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
20277    });
20278
20279    assert_eq!(
20280        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20281        "\n\n\n\n\n\n7777\n8888\n9999",
20282        "After folding the second buffer, its text should not be displayed"
20283    );
20284
20285    multi_buffer_editor.update(cx, |editor, cx| {
20286        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
20287    });
20288    assert_eq!(
20289        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20290        "\n\n\n\n\n",
20291        "After folding the third buffer, its text should not be displayed"
20292    );
20293
20294    multi_buffer_editor.update(cx, |editor, cx| {
20295        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
20296    });
20297    assert_eq!(
20298        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20299        "\n\n\n\n4444\n5555\n6666\n\n",
20300        "After unfolding the second buffer, its text should be displayed"
20301    );
20302
20303    multi_buffer_editor.update(cx, |editor, cx| {
20304        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
20305    });
20306    assert_eq!(
20307        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20308        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
20309        "After unfolding the first buffer, its text should be displayed"
20310    );
20311
20312    multi_buffer_editor.update(cx, |editor, cx| {
20313        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
20314    });
20315    assert_eq!(
20316        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20317        full_text,
20318        "After unfolding all buffers, all original text should be displayed"
20319    );
20320}
20321
20322#[gpui::test]
20323async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
20324    init_test(cx, |_| {});
20325
20326    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
20327
20328    let fs = FakeFs::new(cx.executor());
20329    fs.insert_tree(
20330        path!("/a"),
20331        json!({
20332            "main.rs": sample_text,
20333        }),
20334    )
20335    .await;
20336    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20337    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20338    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20339    let worktree = project.update(cx, |project, cx| {
20340        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
20341        assert_eq!(worktrees.len(), 1);
20342        worktrees.pop().unwrap()
20343    });
20344    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
20345
20346    let buffer_1 = project
20347        .update(cx, |project, cx| {
20348            project.open_buffer((worktree_id, "main.rs"), cx)
20349        })
20350        .await
20351        .unwrap();
20352
20353    let multi_buffer = cx.new(|cx| {
20354        let mut multi_buffer = MultiBuffer::new(ReadWrite);
20355        multi_buffer.push_excerpts(
20356            buffer_1.clone(),
20357            [ExcerptRange::new(
20358                Point::new(0, 0)
20359                    ..Point::new(
20360                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
20361                        0,
20362                    ),
20363            )],
20364            cx,
20365        );
20366        multi_buffer
20367    });
20368    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20369        Editor::new(
20370            EditorMode::full(),
20371            multi_buffer,
20372            Some(project.clone()),
20373            window,
20374            cx,
20375        )
20376    });
20377
20378    let selection_range = Point::new(1, 0)..Point::new(2, 0);
20379    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20380        enum TestHighlight {}
20381        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
20382        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
20383        editor.highlight_text::<TestHighlight>(
20384            vec![highlight_range.clone()],
20385            HighlightStyle::color(Hsla::green()),
20386            cx,
20387        );
20388        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20389            s.select_ranges(Some(highlight_range))
20390        });
20391    });
20392
20393    let full_text = format!("\n\n{sample_text}");
20394    assert_eq!(
20395        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20396        full_text,
20397    );
20398}
20399
20400#[gpui::test]
20401async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
20402    init_test(cx, |_| {});
20403    cx.update(|cx| {
20404        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
20405            "keymaps/default-linux.json",
20406            cx,
20407        )
20408        .unwrap();
20409        cx.bind_keys(default_key_bindings);
20410    });
20411
20412    let (editor, cx) = cx.add_window_view(|window, cx| {
20413        let multi_buffer = MultiBuffer::build_multi(
20414            [
20415                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
20416                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
20417                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
20418                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
20419            ],
20420            cx,
20421        );
20422        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
20423
20424        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
20425        // fold all but the second buffer, so that we test navigating between two
20426        // adjacent folded buffers, as well as folded buffers at the start and
20427        // end the multibuffer
20428        editor.fold_buffer(buffer_ids[0], cx);
20429        editor.fold_buffer(buffer_ids[2], cx);
20430        editor.fold_buffer(buffer_ids[3], cx);
20431
20432        editor
20433    });
20434    cx.simulate_resize(size(px(1000.), px(1000.)));
20435
20436    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
20437    cx.assert_excerpts_with_selections(indoc! {"
20438        [EXCERPT]
20439        ˇ[FOLDED]
20440        [EXCERPT]
20441        a1
20442        b1
20443        [EXCERPT]
20444        [FOLDED]
20445        [EXCERPT]
20446        [FOLDED]
20447        "
20448    });
20449    cx.simulate_keystroke("down");
20450    cx.assert_excerpts_with_selections(indoc! {"
20451        [EXCERPT]
20452        [FOLDED]
20453        [EXCERPT]
20454        ˇa1
20455        b1
20456        [EXCERPT]
20457        [FOLDED]
20458        [EXCERPT]
20459        [FOLDED]
20460        "
20461    });
20462    cx.simulate_keystroke("down");
20463    cx.assert_excerpts_with_selections(indoc! {"
20464        [EXCERPT]
20465        [FOLDED]
20466        [EXCERPT]
20467        a1
20468        ˇb1
20469        [EXCERPT]
20470        [FOLDED]
20471        [EXCERPT]
20472        [FOLDED]
20473        "
20474    });
20475    cx.simulate_keystroke("down");
20476    cx.assert_excerpts_with_selections(indoc! {"
20477        [EXCERPT]
20478        [FOLDED]
20479        [EXCERPT]
20480        a1
20481        b1
20482        ˇ[EXCERPT]
20483        [FOLDED]
20484        [EXCERPT]
20485        [FOLDED]
20486        "
20487    });
20488    cx.simulate_keystroke("down");
20489    cx.assert_excerpts_with_selections(indoc! {"
20490        [EXCERPT]
20491        [FOLDED]
20492        [EXCERPT]
20493        a1
20494        b1
20495        [EXCERPT]
20496        ˇ[FOLDED]
20497        [EXCERPT]
20498        [FOLDED]
20499        "
20500    });
20501    for _ in 0..5 {
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    }
20516
20517    cx.simulate_keystroke("up");
20518    cx.assert_excerpts_with_selections(indoc! {"
20519        [EXCERPT]
20520        [FOLDED]
20521        [EXCERPT]
20522        a1
20523        b1
20524        [EXCERPT]
20525        ˇ[FOLDED]
20526        [EXCERPT]
20527        [FOLDED]
20528        "
20529    });
20530    cx.simulate_keystroke("up");
20531    cx.assert_excerpts_with_selections(indoc! {"
20532        [EXCERPT]
20533        [FOLDED]
20534        [EXCERPT]
20535        a1
20536        b1
20537        ˇ[EXCERPT]
20538        [FOLDED]
20539        [EXCERPT]
20540        [FOLDED]
20541        "
20542    });
20543    cx.simulate_keystroke("up");
20544    cx.assert_excerpts_with_selections(indoc! {"
20545        [EXCERPT]
20546        [FOLDED]
20547        [EXCERPT]
20548        a1
20549        ˇb1
20550        [EXCERPT]
20551        [FOLDED]
20552        [EXCERPT]
20553        [FOLDED]
20554        "
20555    });
20556    cx.simulate_keystroke("up");
20557    cx.assert_excerpts_with_selections(indoc! {"
20558        [EXCERPT]
20559        [FOLDED]
20560        [EXCERPT]
20561        ˇa1
20562        b1
20563        [EXCERPT]
20564        [FOLDED]
20565        [EXCERPT]
20566        [FOLDED]
20567        "
20568    });
20569    for _ in 0..5 {
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    }
20584}
20585
20586#[gpui::test]
20587async fn test_edit_prediction_text(cx: &mut TestAppContext) {
20588    init_test(cx, |_| {});
20589
20590    // Simple insertion
20591    assert_highlighted_edits(
20592        "Hello, world!",
20593        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
20594        true,
20595        cx,
20596        |highlighted_edits, cx| {
20597            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
20598            assert_eq!(highlighted_edits.highlights.len(), 1);
20599            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
20600            assert_eq!(
20601                highlighted_edits.highlights[0].1.background_color,
20602                Some(cx.theme().status().created_background)
20603            );
20604        },
20605    )
20606    .await;
20607
20608    // Replacement
20609    assert_highlighted_edits(
20610        "This is a test.",
20611        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
20612        false,
20613        cx,
20614        |highlighted_edits, cx| {
20615            assert_eq!(highlighted_edits.text, "That is a test.");
20616            assert_eq!(highlighted_edits.highlights.len(), 1);
20617            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
20618            assert_eq!(
20619                highlighted_edits.highlights[0].1.background_color,
20620                Some(cx.theme().status().created_background)
20621            );
20622        },
20623    )
20624    .await;
20625
20626    // Multiple edits
20627    assert_highlighted_edits(
20628        "Hello, world!",
20629        vec![
20630            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
20631            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
20632        ],
20633        false,
20634        cx,
20635        |highlighted_edits, cx| {
20636            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
20637            assert_eq!(highlighted_edits.highlights.len(), 2);
20638            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
20639            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
20640            assert_eq!(
20641                highlighted_edits.highlights[0].1.background_color,
20642                Some(cx.theme().status().created_background)
20643            );
20644            assert_eq!(
20645                highlighted_edits.highlights[1].1.background_color,
20646                Some(cx.theme().status().created_background)
20647            );
20648        },
20649    )
20650    .await;
20651
20652    // Multiple lines with edits
20653    assert_highlighted_edits(
20654        "First line\nSecond line\nThird line\nFourth line",
20655        vec![
20656            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
20657            (
20658                Point::new(2, 0)..Point::new(2, 10),
20659                "New third line".to_string(),
20660            ),
20661            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
20662        ],
20663        false,
20664        cx,
20665        |highlighted_edits, cx| {
20666            assert_eq!(
20667                highlighted_edits.text,
20668                "Second modified\nNew third line\nFourth updated line"
20669            );
20670            assert_eq!(highlighted_edits.highlights.len(), 3);
20671            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
20672            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
20673            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
20674            for highlight in &highlighted_edits.highlights {
20675                assert_eq!(
20676                    highlight.1.background_color,
20677                    Some(cx.theme().status().created_background)
20678                );
20679            }
20680        },
20681    )
20682    .await;
20683}
20684
20685#[gpui::test]
20686async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
20687    init_test(cx, |_| {});
20688
20689    // Deletion
20690    assert_highlighted_edits(
20691        "Hello, world!",
20692        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
20693        true,
20694        cx,
20695        |highlighted_edits, cx| {
20696            assert_eq!(highlighted_edits.text, "Hello, world!");
20697            assert_eq!(highlighted_edits.highlights.len(), 1);
20698            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
20699            assert_eq!(
20700                highlighted_edits.highlights[0].1.background_color,
20701                Some(cx.theme().status().deleted_background)
20702            );
20703        },
20704    )
20705    .await;
20706
20707    // Insertion
20708    assert_highlighted_edits(
20709        "Hello, world!",
20710        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
20711        true,
20712        cx,
20713        |highlighted_edits, cx| {
20714            assert_eq!(highlighted_edits.highlights.len(), 1);
20715            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
20716            assert_eq!(
20717                highlighted_edits.highlights[0].1.background_color,
20718                Some(cx.theme().status().created_background)
20719            );
20720        },
20721    )
20722    .await;
20723}
20724
20725async fn assert_highlighted_edits(
20726    text: &str,
20727    edits: Vec<(Range<Point>, String)>,
20728    include_deletions: bool,
20729    cx: &mut TestAppContext,
20730    assertion_fn: impl Fn(HighlightedText, &App),
20731) {
20732    let window = cx.add_window(|window, cx| {
20733        let buffer = MultiBuffer::build_simple(text, cx);
20734        Editor::new(EditorMode::full(), buffer, None, window, cx)
20735    });
20736    let cx = &mut VisualTestContext::from_window(*window, cx);
20737
20738    let (buffer, snapshot) = window
20739        .update(cx, |editor, _window, cx| {
20740            (
20741                editor.buffer().clone(),
20742                editor.buffer().read(cx).snapshot(cx),
20743            )
20744        })
20745        .unwrap();
20746
20747    let edits = edits
20748        .into_iter()
20749        .map(|(range, edit)| {
20750            (
20751                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
20752                edit,
20753            )
20754        })
20755        .collect::<Vec<_>>();
20756
20757    let text_anchor_edits = edits
20758        .clone()
20759        .into_iter()
20760        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
20761        .collect::<Vec<_>>();
20762
20763    let edit_preview = window
20764        .update(cx, |_, _window, cx| {
20765            buffer
20766                .read(cx)
20767                .as_singleton()
20768                .unwrap()
20769                .read(cx)
20770                .preview_edits(text_anchor_edits.into(), cx)
20771        })
20772        .unwrap()
20773        .await;
20774
20775    cx.update(|_window, cx| {
20776        let highlighted_edits = edit_prediction_edit_text(
20777            &snapshot.as_singleton().unwrap().2,
20778            &edits,
20779            &edit_preview,
20780            include_deletions,
20781            cx,
20782        );
20783        assertion_fn(highlighted_edits, cx)
20784    });
20785}
20786
20787#[track_caller]
20788fn assert_breakpoint(
20789    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
20790    path: &Arc<Path>,
20791    expected: Vec<(u32, Breakpoint)>,
20792) {
20793    if expected.len() == 0usize {
20794        assert!(!breakpoints.contains_key(path), "{}", path.display());
20795    } else {
20796        let mut breakpoint = breakpoints
20797            .get(path)
20798            .unwrap()
20799            .into_iter()
20800            .map(|breakpoint| {
20801                (
20802                    breakpoint.row,
20803                    Breakpoint {
20804                        message: breakpoint.message.clone(),
20805                        state: breakpoint.state,
20806                        condition: breakpoint.condition.clone(),
20807                        hit_condition: breakpoint.hit_condition.clone(),
20808                    },
20809                )
20810            })
20811            .collect::<Vec<_>>();
20812
20813        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
20814
20815        assert_eq!(expected, breakpoint);
20816    }
20817}
20818
20819fn add_log_breakpoint_at_cursor(
20820    editor: &mut Editor,
20821    log_message: &str,
20822    window: &mut Window,
20823    cx: &mut Context<Editor>,
20824) {
20825    let (anchor, bp) = editor
20826        .breakpoints_at_cursors(window, cx)
20827        .first()
20828        .and_then(|(anchor, bp)| {
20829            if let Some(bp) = bp {
20830                Some((*anchor, bp.clone()))
20831            } else {
20832                None
20833            }
20834        })
20835        .unwrap_or_else(|| {
20836            let cursor_position: Point = editor.selections.newest(cx).head();
20837
20838            let breakpoint_position = editor
20839                .snapshot(window, cx)
20840                .display_snapshot
20841                .buffer_snapshot
20842                .anchor_before(Point::new(cursor_position.row, 0));
20843
20844            (breakpoint_position, Breakpoint::new_log(&log_message))
20845        });
20846
20847    editor.edit_breakpoint_at_anchor(
20848        anchor,
20849        bp,
20850        BreakpointEditAction::EditLogMessage(log_message.into()),
20851        cx,
20852    );
20853}
20854
20855#[gpui::test]
20856async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
20857    init_test(cx, |_| {});
20858
20859    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20860    let fs = FakeFs::new(cx.executor());
20861    fs.insert_tree(
20862        path!("/a"),
20863        json!({
20864            "main.rs": sample_text,
20865        }),
20866    )
20867    .await;
20868    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20869    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20870    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20871
20872    let fs = FakeFs::new(cx.executor());
20873    fs.insert_tree(
20874        path!("/a"),
20875        json!({
20876            "main.rs": sample_text,
20877        }),
20878    )
20879    .await;
20880    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20881    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20882    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20883    let worktree_id = workspace
20884        .update(cx, |workspace, _window, cx| {
20885            workspace.project().update(cx, |project, cx| {
20886                project.worktrees(cx).next().unwrap().read(cx).id()
20887            })
20888        })
20889        .unwrap();
20890
20891    let buffer = project
20892        .update(cx, |project, cx| {
20893            project.open_buffer((worktree_id, "main.rs"), cx)
20894        })
20895        .await
20896        .unwrap();
20897
20898    let (editor, cx) = cx.add_window_view(|window, cx| {
20899        Editor::new(
20900            EditorMode::full(),
20901            MultiBuffer::build_from_buffer(buffer, cx),
20902            Some(project.clone()),
20903            window,
20904            cx,
20905        )
20906    });
20907
20908    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20909    let abs_path = project.read_with(cx, |project, cx| {
20910        project
20911            .absolute_path(&project_path, cx)
20912            .map(|path_buf| Arc::from(path_buf.to_owned()))
20913            .unwrap()
20914    });
20915
20916    // assert we can add breakpoint on the first line
20917    editor.update_in(cx, |editor, window, cx| {
20918        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20919        editor.move_to_end(&MoveToEnd, window, cx);
20920        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20921    });
20922
20923    let breakpoints = editor.update(cx, |editor, cx| {
20924        editor
20925            .breakpoint_store()
20926            .as_ref()
20927            .unwrap()
20928            .read(cx)
20929            .all_source_breakpoints(cx)
20930            .clone()
20931    });
20932
20933    assert_eq!(1, breakpoints.len());
20934    assert_breakpoint(
20935        &breakpoints,
20936        &abs_path,
20937        vec![
20938            (0, Breakpoint::new_standard()),
20939            (3, Breakpoint::new_standard()),
20940        ],
20941    );
20942
20943    editor.update_in(cx, |editor, window, cx| {
20944        editor.move_to_beginning(&MoveToBeginning, window, cx);
20945        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20946    });
20947
20948    let breakpoints = editor.update(cx, |editor, cx| {
20949        editor
20950            .breakpoint_store()
20951            .as_ref()
20952            .unwrap()
20953            .read(cx)
20954            .all_source_breakpoints(cx)
20955            .clone()
20956    });
20957
20958    assert_eq!(1, breakpoints.len());
20959    assert_breakpoint(
20960        &breakpoints,
20961        &abs_path,
20962        vec![(3, Breakpoint::new_standard())],
20963    );
20964
20965    editor.update_in(cx, |editor, window, cx| {
20966        editor.move_to_end(&MoveToEnd, window, cx);
20967        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20968    });
20969
20970    let breakpoints = editor.update(cx, |editor, cx| {
20971        editor
20972            .breakpoint_store()
20973            .as_ref()
20974            .unwrap()
20975            .read(cx)
20976            .all_source_breakpoints(cx)
20977            .clone()
20978    });
20979
20980    assert_eq!(0, breakpoints.len());
20981    assert_breakpoint(&breakpoints, &abs_path, vec![]);
20982}
20983
20984#[gpui::test]
20985async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
20986    init_test(cx, |_| {});
20987
20988    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20989
20990    let fs = FakeFs::new(cx.executor());
20991    fs.insert_tree(
20992        path!("/a"),
20993        json!({
20994            "main.rs": sample_text,
20995        }),
20996    )
20997    .await;
20998    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20999    let (workspace, cx) =
21000        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21001
21002    let worktree_id = workspace.update(cx, |workspace, cx| {
21003        workspace.project().update(cx, |project, cx| {
21004            project.worktrees(cx).next().unwrap().read(cx).id()
21005        })
21006    });
21007
21008    let buffer = project
21009        .update(cx, |project, cx| {
21010            project.open_buffer((worktree_id, "main.rs"), cx)
21011        })
21012        .await
21013        .unwrap();
21014
21015    let (editor, cx) = cx.add_window_view(|window, cx| {
21016        Editor::new(
21017            EditorMode::full(),
21018            MultiBuffer::build_from_buffer(buffer, cx),
21019            Some(project.clone()),
21020            window,
21021            cx,
21022        )
21023    });
21024
21025    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
21026    let abs_path = project.read_with(cx, |project, cx| {
21027        project
21028            .absolute_path(&project_path, cx)
21029            .map(|path_buf| Arc::from(path_buf.to_owned()))
21030            .unwrap()
21031    });
21032
21033    editor.update_in(cx, |editor, window, cx| {
21034        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
21035    });
21036
21037    let breakpoints = editor.update(cx, |editor, cx| {
21038        editor
21039            .breakpoint_store()
21040            .as_ref()
21041            .unwrap()
21042            .read(cx)
21043            .all_source_breakpoints(cx)
21044            .clone()
21045    });
21046
21047    assert_breakpoint(
21048        &breakpoints,
21049        &abs_path,
21050        vec![(0, Breakpoint::new_log("hello world"))],
21051    );
21052
21053    // Removing a log message from a log breakpoint should remove it
21054    editor.update_in(cx, |editor, window, cx| {
21055        add_log_breakpoint_at_cursor(editor, "", window, cx);
21056    });
21057
21058    let breakpoints = editor.update(cx, |editor, cx| {
21059        editor
21060            .breakpoint_store()
21061            .as_ref()
21062            .unwrap()
21063            .read(cx)
21064            .all_source_breakpoints(cx)
21065            .clone()
21066    });
21067
21068    assert_breakpoint(&breakpoints, &abs_path, vec![]);
21069
21070    editor.update_in(cx, |editor, window, cx| {
21071        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21072        editor.move_to_end(&MoveToEnd, window, cx);
21073        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21074        // Not adding a log message to a standard breakpoint shouldn't remove it
21075        add_log_breakpoint_at_cursor(editor, "", window, cx);
21076    });
21077
21078    let breakpoints = editor.update(cx, |editor, cx| {
21079        editor
21080            .breakpoint_store()
21081            .as_ref()
21082            .unwrap()
21083            .read(cx)
21084            .all_source_breakpoints(cx)
21085            .clone()
21086    });
21087
21088    assert_breakpoint(
21089        &breakpoints,
21090        &abs_path,
21091        vec![
21092            (0, Breakpoint::new_standard()),
21093            (3, Breakpoint::new_standard()),
21094        ],
21095    );
21096
21097    editor.update_in(cx, |editor, window, cx| {
21098        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
21099    });
21100
21101    let breakpoints = editor.update(cx, |editor, cx| {
21102        editor
21103            .breakpoint_store()
21104            .as_ref()
21105            .unwrap()
21106            .read(cx)
21107            .all_source_breakpoints(cx)
21108            .clone()
21109    });
21110
21111    assert_breakpoint(
21112        &breakpoints,
21113        &abs_path,
21114        vec![
21115            (0, Breakpoint::new_standard()),
21116            (3, Breakpoint::new_log("hello world")),
21117        ],
21118    );
21119
21120    editor.update_in(cx, |editor, window, cx| {
21121        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
21122    });
21123
21124    let breakpoints = editor.update(cx, |editor, cx| {
21125        editor
21126            .breakpoint_store()
21127            .as_ref()
21128            .unwrap()
21129            .read(cx)
21130            .all_source_breakpoints(cx)
21131            .clone()
21132    });
21133
21134    assert_breakpoint(
21135        &breakpoints,
21136        &abs_path,
21137        vec![
21138            (0, Breakpoint::new_standard()),
21139            (3, Breakpoint::new_log("hello Earth!!")),
21140        ],
21141    );
21142}
21143
21144/// This also tests that Editor::breakpoint_at_cursor_head is working properly
21145/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
21146/// or when breakpoints were placed out of order. This tests for a regression too
21147#[gpui::test]
21148async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
21149    init_test(cx, |_| {});
21150
21151    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
21152    let fs = FakeFs::new(cx.executor());
21153    fs.insert_tree(
21154        path!("/a"),
21155        json!({
21156            "main.rs": sample_text,
21157        }),
21158    )
21159    .await;
21160    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21161    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21162    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21163
21164    let fs = FakeFs::new(cx.executor());
21165    fs.insert_tree(
21166        path!("/a"),
21167        json!({
21168            "main.rs": sample_text,
21169        }),
21170    )
21171    .await;
21172    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21173    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21174    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21175    let worktree_id = workspace
21176        .update(cx, |workspace, _window, cx| {
21177            workspace.project().update(cx, |project, cx| {
21178                project.worktrees(cx).next().unwrap().read(cx).id()
21179            })
21180        })
21181        .unwrap();
21182
21183    let buffer = project
21184        .update(cx, |project, cx| {
21185            project.open_buffer((worktree_id, "main.rs"), cx)
21186        })
21187        .await
21188        .unwrap();
21189
21190    let (editor, cx) = cx.add_window_view(|window, cx| {
21191        Editor::new(
21192            EditorMode::full(),
21193            MultiBuffer::build_from_buffer(buffer, cx),
21194            Some(project.clone()),
21195            window,
21196            cx,
21197        )
21198    });
21199
21200    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
21201    let abs_path = project.read_with(cx, |project, cx| {
21202        project
21203            .absolute_path(&project_path, cx)
21204            .map(|path_buf| Arc::from(path_buf.to_owned()))
21205            .unwrap()
21206    });
21207
21208    // assert we can add breakpoint on the first line
21209    editor.update_in(cx, |editor, window, cx| {
21210        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21211        editor.move_to_end(&MoveToEnd, window, cx);
21212        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21213        editor.move_up(&MoveUp, window, cx);
21214        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21215    });
21216
21217    let breakpoints = editor.update(cx, |editor, cx| {
21218        editor
21219            .breakpoint_store()
21220            .as_ref()
21221            .unwrap()
21222            .read(cx)
21223            .all_source_breakpoints(cx)
21224            .clone()
21225    });
21226
21227    assert_eq!(1, breakpoints.len());
21228    assert_breakpoint(
21229        &breakpoints,
21230        &abs_path,
21231        vec![
21232            (0, Breakpoint::new_standard()),
21233            (2, Breakpoint::new_standard()),
21234            (3, Breakpoint::new_standard()),
21235        ],
21236    );
21237
21238    editor.update_in(cx, |editor, window, cx| {
21239        editor.move_to_beginning(&MoveToBeginning, window, cx);
21240        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21241        editor.move_to_end(&MoveToEnd, window, cx);
21242        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21243        // Disabling a breakpoint that doesn't exist should do nothing
21244        editor.move_up(&MoveUp, window, cx);
21245        editor.move_up(&MoveUp, window, cx);
21246        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21247    });
21248
21249    let breakpoints = editor.update(cx, |editor, cx| {
21250        editor
21251            .breakpoint_store()
21252            .as_ref()
21253            .unwrap()
21254            .read(cx)
21255            .all_source_breakpoints(cx)
21256            .clone()
21257    });
21258
21259    let disable_breakpoint = {
21260        let mut bp = Breakpoint::new_standard();
21261        bp.state = BreakpointState::Disabled;
21262        bp
21263    };
21264
21265    assert_eq!(1, breakpoints.len());
21266    assert_breakpoint(
21267        &breakpoints,
21268        &abs_path,
21269        vec![
21270            (0, disable_breakpoint.clone()),
21271            (2, Breakpoint::new_standard()),
21272            (3, disable_breakpoint.clone()),
21273        ],
21274    );
21275
21276    editor.update_in(cx, |editor, window, cx| {
21277        editor.move_to_beginning(&MoveToBeginning, window, cx);
21278        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
21279        editor.move_to_end(&MoveToEnd, window, cx);
21280        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
21281        editor.move_up(&MoveUp, window, cx);
21282        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21283    });
21284
21285    let breakpoints = editor.update(cx, |editor, cx| {
21286        editor
21287            .breakpoint_store()
21288            .as_ref()
21289            .unwrap()
21290            .read(cx)
21291            .all_source_breakpoints(cx)
21292            .clone()
21293    });
21294
21295    assert_eq!(1, breakpoints.len());
21296    assert_breakpoint(
21297        &breakpoints,
21298        &abs_path,
21299        vec![
21300            (0, Breakpoint::new_standard()),
21301            (2, disable_breakpoint),
21302            (3, Breakpoint::new_standard()),
21303        ],
21304    );
21305}
21306
21307#[gpui::test]
21308async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
21309    init_test(cx, |_| {});
21310    let capabilities = lsp::ServerCapabilities {
21311        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
21312            prepare_provider: Some(true),
21313            work_done_progress_options: Default::default(),
21314        })),
21315        ..Default::default()
21316    };
21317    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
21318
21319    cx.set_state(indoc! {"
21320        struct Fˇoo {}
21321    "});
21322
21323    cx.update_editor(|editor, _, cx| {
21324        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
21325        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
21326        editor.highlight_background::<DocumentHighlightRead>(
21327            &[highlight_range],
21328            |theme| theme.colors().editor_document_highlight_read_background,
21329            cx,
21330        );
21331    });
21332
21333    let mut prepare_rename_handler = cx
21334        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
21335            move |_, _, _| async move {
21336                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
21337                    start: lsp::Position {
21338                        line: 0,
21339                        character: 7,
21340                    },
21341                    end: lsp::Position {
21342                        line: 0,
21343                        character: 10,
21344                    },
21345                })))
21346            },
21347        );
21348    let prepare_rename_task = cx
21349        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
21350        .expect("Prepare rename was not started");
21351    prepare_rename_handler.next().await.unwrap();
21352    prepare_rename_task.await.expect("Prepare rename failed");
21353
21354    let mut rename_handler =
21355        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
21356            let edit = lsp::TextEdit {
21357                range: lsp::Range {
21358                    start: lsp::Position {
21359                        line: 0,
21360                        character: 7,
21361                    },
21362                    end: lsp::Position {
21363                        line: 0,
21364                        character: 10,
21365                    },
21366                },
21367                new_text: "FooRenamed".to_string(),
21368            };
21369            Ok(Some(lsp::WorkspaceEdit::new(
21370                // Specify the same edit twice
21371                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
21372            )))
21373        });
21374    let rename_task = cx
21375        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
21376        .expect("Confirm rename was not started");
21377    rename_handler.next().await.unwrap();
21378    rename_task.await.expect("Confirm rename failed");
21379    cx.run_until_parked();
21380
21381    // Despite two edits, only one is actually applied as those are identical
21382    cx.assert_editor_state(indoc! {"
21383        struct FooRenamedˇ {}
21384    "});
21385}
21386
21387#[gpui::test]
21388async fn test_rename_without_prepare(cx: &mut TestAppContext) {
21389    init_test(cx, |_| {});
21390    // These capabilities indicate that the server does not support prepare rename.
21391    let capabilities = lsp::ServerCapabilities {
21392        rename_provider: Some(lsp::OneOf::Left(true)),
21393        ..Default::default()
21394    };
21395    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
21396
21397    cx.set_state(indoc! {"
21398        struct Fˇoo {}
21399    "});
21400
21401    cx.update_editor(|editor, _window, cx| {
21402        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
21403        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
21404        editor.highlight_background::<DocumentHighlightRead>(
21405            &[highlight_range],
21406            |theme| theme.colors().editor_document_highlight_read_background,
21407            cx,
21408        );
21409    });
21410
21411    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
21412        .expect("Prepare rename was not started")
21413        .await
21414        .expect("Prepare rename failed");
21415
21416    let mut rename_handler =
21417        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
21418            let edit = lsp::TextEdit {
21419                range: lsp::Range {
21420                    start: lsp::Position {
21421                        line: 0,
21422                        character: 7,
21423                    },
21424                    end: lsp::Position {
21425                        line: 0,
21426                        character: 10,
21427                    },
21428                },
21429                new_text: "FooRenamed".to_string(),
21430            };
21431            Ok(Some(lsp::WorkspaceEdit::new(
21432                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
21433            )))
21434        });
21435    let rename_task = cx
21436        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
21437        .expect("Confirm rename was not started");
21438    rename_handler.next().await.unwrap();
21439    rename_task.await.expect("Confirm rename failed");
21440    cx.run_until_parked();
21441
21442    // Correct range is renamed, as `surrounding_word` is used to find it.
21443    cx.assert_editor_state(indoc! {"
21444        struct FooRenamedˇ {}
21445    "});
21446}
21447
21448#[gpui::test]
21449async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
21450    init_test(cx, |_| {});
21451    let mut cx = EditorTestContext::new(cx).await;
21452
21453    let language = Arc::new(
21454        Language::new(
21455            LanguageConfig::default(),
21456            Some(tree_sitter_html::LANGUAGE.into()),
21457        )
21458        .with_brackets_query(
21459            r#"
21460            ("<" @open "/>" @close)
21461            ("</" @open ">" @close)
21462            ("<" @open ">" @close)
21463            ("\"" @open "\"" @close)
21464            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
21465        "#,
21466        )
21467        .unwrap(),
21468    );
21469    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
21470
21471    cx.set_state(indoc! {"
21472        <span>ˇ</span>
21473    "});
21474    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21475    cx.assert_editor_state(indoc! {"
21476        <span>
21477        ˇ
21478        </span>
21479    "});
21480
21481    cx.set_state(indoc! {"
21482        <span><span></span>ˇ</span>
21483    "});
21484    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21485    cx.assert_editor_state(indoc! {"
21486        <span><span></span>
21487        ˇ</span>
21488    "});
21489
21490    cx.set_state(indoc! {"
21491        <span>ˇ
21492        </span>
21493    "});
21494    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21495    cx.assert_editor_state(indoc! {"
21496        <span>
21497        ˇ
21498        </span>
21499    "});
21500}
21501
21502#[gpui::test(iterations = 10)]
21503async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
21504    init_test(cx, |_| {});
21505
21506    let fs = FakeFs::new(cx.executor());
21507    fs.insert_tree(
21508        path!("/dir"),
21509        json!({
21510            "a.ts": "a",
21511        }),
21512    )
21513    .await;
21514
21515    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
21516    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21517    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21518
21519    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21520    language_registry.add(Arc::new(Language::new(
21521        LanguageConfig {
21522            name: "TypeScript".into(),
21523            matcher: LanguageMatcher {
21524                path_suffixes: vec!["ts".to_string()],
21525                ..Default::default()
21526            },
21527            ..Default::default()
21528        },
21529        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
21530    )));
21531    let mut fake_language_servers = language_registry.register_fake_lsp(
21532        "TypeScript",
21533        FakeLspAdapter {
21534            capabilities: lsp::ServerCapabilities {
21535                code_lens_provider: Some(lsp::CodeLensOptions {
21536                    resolve_provider: Some(true),
21537                }),
21538                execute_command_provider: Some(lsp::ExecuteCommandOptions {
21539                    commands: vec!["_the/command".to_string()],
21540                    ..lsp::ExecuteCommandOptions::default()
21541                }),
21542                ..lsp::ServerCapabilities::default()
21543            },
21544            ..FakeLspAdapter::default()
21545        },
21546    );
21547
21548    let editor = workspace
21549        .update(cx, |workspace, window, cx| {
21550            workspace.open_abs_path(
21551                PathBuf::from(path!("/dir/a.ts")),
21552                OpenOptions::default(),
21553                window,
21554                cx,
21555            )
21556        })
21557        .unwrap()
21558        .await
21559        .unwrap()
21560        .downcast::<Editor>()
21561        .unwrap();
21562    cx.executor().run_until_parked();
21563
21564    let fake_server = fake_language_servers.next().await.unwrap();
21565
21566    let buffer = editor.update(cx, |editor, cx| {
21567        editor
21568            .buffer()
21569            .read(cx)
21570            .as_singleton()
21571            .expect("have opened a single file by path")
21572    });
21573
21574    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
21575    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
21576    drop(buffer_snapshot);
21577    let actions = cx
21578        .update_window(*workspace, |_, window, cx| {
21579            project.code_actions(&buffer, anchor..anchor, window, cx)
21580        })
21581        .unwrap();
21582
21583    fake_server
21584        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
21585            Ok(Some(vec![
21586                lsp::CodeLens {
21587                    range: lsp::Range::default(),
21588                    command: Some(lsp::Command {
21589                        title: "Code lens command".to_owned(),
21590                        command: "_the/command".to_owned(),
21591                        arguments: None,
21592                    }),
21593                    data: None,
21594                },
21595                lsp::CodeLens {
21596                    range: lsp::Range::default(),
21597                    command: Some(lsp::Command {
21598                        title: "Command not in capabilities".to_owned(),
21599                        command: "not in capabilities".to_owned(),
21600                        arguments: None,
21601                    }),
21602                    data: None,
21603                },
21604                lsp::CodeLens {
21605                    range: lsp::Range {
21606                        start: lsp::Position {
21607                            line: 1,
21608                            character: 1,
21609                        },
21610                        end: lsp::Position {
21611                            line: 1,
21612                            character: 1,
21613                        },
21614                    },
21615                    command: Some(lsp::Command {
21616                        title: "Command not in range".to_owned(),
21617                        command: "_the/command".to_owned(),
21618                        arguments: None,
21619                    }),
21620                    data: None,
21621                },
21622            ]))
21623        })
21624        .next()
21625        .await;
21626
21627    let actions = actions.await.unwrap();
21628    assert_eq!(
21629        actions.len(),
21630        1,
21631        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
21632    );
21633    let action = actions[0].clone();
21634    let apply = project.update(cx, |project, cx| {
21635        project.apply_code_action(buffer.clone(), action, true, cx)
21636    });
21637
21638    // Resolving the code action does not populate its edits. In absence of
21639    // edits, we must execute the given command.
21640    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
21641        |mut lens, _| async move {
21642            let lens_command = lens.command.as_mut().expect("should have a command");
21643            assert_eq!(lens_command.title, "Code lens command");
21644            lens_command.arguments = Some(vec![json!("the-argument")]);
21645            Ok(lens)
21646        },
21647    );
21648
21649    // While executing the command, the language server sends the editor
21650    // a `workspaceEdit` request.
21651    fake_server
21652        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
21653            let fake = fake_server.clone();
21654            move |params, _| {
21655                assert_eq!(params.command, "_the/command");
21656                let fake = fake.clone();
21657                async move {
21658                    fake.server
21659                        .request::<lsp::request::ApplyWorkspaceEdit>(
21660                            lsp::ApplyWorkspaceEditParams {
21661                                label: None,
21662                                edit: lsp::WorkspaceEdit {
21663                                    changes: Some(
21664                                        [(
21665                                            lsp::Url::from_file_path(path!("/dir/a.ts")).unwrap(),
21666                                            vec![lsp::TextEdit {
21667                                                range: lsp::Range::new(
21668                                                    lsp::Position::new(0, 0),
21669                                                    lsp::Position::new(0, 0),
21670                                                ),
21671                                                new_text: "X".into(),
21672                                            }],
21673                                        )]
21674                                        .into_iter()
21675                                        .collect(),
21676                                    ),
21677                                    ..lsp::WorkspaceEdit::default()
21678                                },
21679                            },
21680                        )
21681                        .await
21682                        .into_response()
21683                        .unwrap();
21684                    Ok(Some(json!(null)))
21685                }
21686            }
21687        })
21688        .next()
21689        .await;
21690
21691    // Applying the code lens command returns a project transaction containing the edits
21692    // sent by the language server in its `workspaceEdit` request.
21693    let transaction = apply.await.unwrap();
21694    assert!(transaction.0.contains_key(&buffer));
21695    buffer.update(cx, |buffer, cx| {
21696        assert_eq!(buffer.text(), "Xa");
21697        buffer.undo(cx);
21698        assert_eq!(buffer.text(), "a");
21699    });
21700
21701    let actions_after_edits = cx
21702        .update_window(*workspace, |_, window, cx| {
21703            project.code_actions(&buffer, anchor..anchor, window, cx)
21704        })
21705        .unwrap()
21706        .await
21707        .unwrap();
21708    assert_eq!(
21709        actions, actions_after_edits,
21710        "For the same selection, same code lens actions should be returned"
21711    );
21712
21713    let _responses =
21714        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
21715            panic!("No more code lens requests are expected");
21716        });
21717    editor.update_in(cx, |editor, window, cx| {
21718        editor.select_all(&SelectAll, window, cx);
21719    });
21720    cx.executor().run_until_parked();
21721    let new_actions = cx
21722        .update_window(*workspace, |_, window, cx| {
21723            project.code_actions(&buffer, anchor..anchor, window, cx)
21724        })
21725        .unwrap()
21726        .await
21727        .unwrap();
21728    assert_eq!(
21729        actions, new_actions,
21730        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
21731    );
21732}
21733
21734#[gpui::test]
21735async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
21736    init_test(cx, |_| {});
21737
21738    let fs = FakeFs::new(cx.executor());
21739    let main_text = r#"fn main() {
21740println!("1");
21741println!("2");
21742println!("3");
21743println!("4");
21744println!("5");
21745}"#;
21746    let lib_text = "mod foo {}";
21747    fs.insert_tree(
21748        path!("/a"),
21749        json!({
21750            "lib.rs": lib_text,
21751            "main.rs": main_text,
21752        }),
21753    )
21754    .await;
21755
21756    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21757    let (workspace, cx) =
21758        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21759    let worktree_id = workspace.update(cx, |workspace, cx| {
21760        workspace.project().update(cx, |project, cx| {
21761            project.worktrees(cx).next().unwrap().read(cx).id()
21762        })
21763    });
21764
21765    let expected_ranges = vec![
21766        Point::new(0, 0)..Point::new(0, 0),
21767        Point::new(1, 0)..Point::new(1, 1),
21768        Point::new(2, 0)..Point::new(2, 2),
21769        Point::new(3, 0)..Point::new(3, 3),
21770    ];
21771
21772    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21773    let editor_1 = workspace
21774        .update_in(cx, |workspace, window, cx| {
21775            workspace.open_path(
21776                (worktree_id, "main.rs"),
21777                Some(pane_1.downgrade()),
21778                true,
21779                window,
21780                cx,
21781            )
21782        })
21783        .unwrap()
21784        .await
21785        .downcast::<Editor>()
21786        .unwrap();
21787    pane_1.update(cx, |pane, cx| {
21788        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21789        open_editor.update(cx, |editor, cx| {
21790            assert_eq!(
21791                editor.display_text(cx),
21792                main_text,
21793                "Original main.rs text on initial open",
21794            );
21795            assert_eq!(
21796                editor
21797                    .selections
21798                    .all::<Point>(cx)
21799                    .into_iter()
21800                    .map(|s| s.range())
21801                    .collect::<Vec<_>>(),
21802                vec![Point::zero()..Point::zero()],
21803                "Default selections on initial open",
21804            );
21805        })
21806    });
21807    editor_1.update_in(cx, |editor, window, cx| {
21808        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21809            s.select_ranges(expected_ranges.clone());
21810        });
21811    });
21812
21813    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
21814        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
21815    });
21816    let editor_2 = workspace
21817        .update_in(cx, |workspace, window, cx| {
21818            workspace.open_path(
21819                (worktree_id, "main.rs"),
21820                Some(pane_2.downgrade()),
21821                true,
21822                window,
21823                cx,
21824            )
21825        })
21826        .unwrap()
21827        .await
21828        .downcast::<Editor>()
21829        .unwrap();
21830    pane_2.update(cx, |pane, cx| {
21831        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21832        open_editor.update(cx, |editor, cx| {
21833            assert_eq!(
21834                editor.display_text(cx),
21835                main_text,
21836                "Original main.rs text on initial open in another panel",
21837            );
21838            assert_eq!(
21839                editor
21840                    .selections
21841                    .all::<Point>(cx)
21842                    .into_iter()
21843                    .map(|s| s.range())
21844                    .collect::<Vec<_>>(),
21845                vec![Point::zero()..Point::zero()],
21846                "Default selections on initial open in another panel",
21847            );
21848        })
21849    });
21850
21851    editor_2.update_in(cx, |editor, window, cx| {
21852        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
21853    });
21854
21855    let _other_editor_1 = workspace
21856        .update_in(cx, |workspace, window, cx| {
21857            workspace.open_path(
21858                (worktree_id, "lib.rs"),
21859                Some(pane_1.downgrade()),
21860                true,
21861                window,
21862                cx,
21863            )
21864        })
21865        .unwrap()
21866        .await
21867        .downcast::<Editor>()
21868        .unwrap();
21869    pane_1
21870        .update_in(cx, |pane, window, cx| {
21871            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
21872        })
21873        .await
21874        .unwrap();
21875    drop(editor_1);
21876    pane_1.update(cx, |pane, cx| {
21877        pane.active_item()
21878            .unwrap()
21879            .downcast::<Editor>()
21880            .unwrap()
21881            .update(cx, |editor, cx| {
21882                assert_eq!(
21883                    editor.display_text(cx),
21884                    lib_text,
21885                    "Other file should be open and active",
21886                );
21887            });
21888        assert_eq!(pane.items().count(), 1, "No other editors should be open");
21889    });
21890
21891    let _other_editor_2 = workspace
21892        .update_in(cx, |workspace, window, cx| {
21893            workspace.open_path(
21894                (worktree_id, "lib.rs"),
21895                Some(pane_2.downgrade()),
21896                true,
21897                window,
21898                cx,
21899            )
21900        })
21901        .unwrap()
21902        .await
21903        .downcast::<Editor>()
21904        .unwrap();
21905    pane_2
21906        .update_in(cx, |pane, window, cx| {
21907            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
21908        })
21909        .await
21910        .unwrap();
21911    drop(editor_2);
21912    pane_2.update(cx, |pane, cx| {
21913        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21914        open_editor.update(cx, |editor, cx| {
21915            assert_eq!(
21916                editor.display_text(cx),
21917                lib_text,
21918                "Other file should be open and active in another panel too",
21919            );
21920        });
21921        assert_eq!(
21922            pane.items().count(),
21923            1,
21924            "No other editors should be open in another pane",
21925        );
21926    });
21927
21928    let _editor_1_reopened = workspace
21929        .update_in(cx, |workspace, window, cx| {
21930            workspace.open_path(
21931                (worktree_id, "main.rs"),
21932                Some(pane_1.downgrade()),
21933                true,
21934                window,
21935                cx,
21936            )
21937        })
21938        .unwrap()
21939        .await
21940        .downcast::<Editor>()
21941        .unwrap();
21942    let _editor_2_reopened = workspace
21943        .update_in(cx, |workspace, window, cx| {
21944            workspace.open_path(
21945                (worktree_id, "main.rs"),
21946                Some(pane_2.downgrade()),
21947                true,
21948                window,
21949                cx,
21950            )
21951        })
21952        .unwrap()
21953        .await
21954        .downcast::<Editor>()
21955        .unwrap();
21956    pane_1.update(cx, |pane, cx| {
21957        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21958        open_editor.update(cx, |editor, cx| {
21959            assert_eq!(
21960                editor.display_text(cx),
21961                main_text,
21962                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
21963            );
21964            assert_eq!(
21965                editor
21966                    .selections
21967                    .all::<Point>(cx)
21968                    .into_iter()
21969                    .map(|s| s.range())
21970                    .collect::<Vec<_>>(),
21971                expected_ranges,
21972                "Previous editor in the 1st panel had selections and should get them restored on reopen",
21973            );
21974        })
21975    });
21976    pane_2.update(cx, |pane, cx| {
21977        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21978        open_editor.update(cx, |editor, cx| {
21979            assert_eq!(
21980                editor.display_text(cx),
21981                r#"fn main() {
21982⋯rintln!("1");
21983⋯intln!("2");
21984⋯ntln!("3");
21985println!("4");
21986println!("5");
21987}"#,
21988                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
21989            );
21990            assert_eq!(
21991                editor
21992                    .selections
21993                    .all::<Point>(cx)
21994                    .into_iter()
21995                    .map(|s| s.range())
21996                    .collect::<Vec<_>>(),
21997                vec![Point::zero()..Point::zero()],
21998                "Previous editor in the 2nd pane had no selections changed hence should restore none",
21999            );
22000        })
22001    });
22002}
22003
22004#[gpui::test]
22005async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
22006    init_test(cx, |_| {});
22007
22008    let fs = FakeFs::new(cx.executor());
22009    let main_text = r#"fn main() {
22010println!("1");
22011println!("2");
22012println!("3");
22013println!("4");
22014println!("5");
22015}"#;
22016    let lib_text = "mod foo {}";
22017    fs.insert_tree(
22018        path!("/a"),
22019        json!({
22020            "lib.rs": lib_text,
22021            "main.rs": main_text,
22022        }),
22023    )
22024    .await;
22025
22026    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22027    let (workspace, cx) =
22028        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22029    let worktree_id = workspace.update(cx, |workspace, cx| {
22030        workspace.project().update(cx, |project, cx| {
22031            project.worktrees(cx).next().unwrap().read(cx).id()
22032        })
22033    });
22034
22035    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
22036    let editor = workspace
22037        .update_in(cx, |workspace, window, cx| {
22038            workspace.open_path(
22039                (worktree_id, "main.rs"),
22040                Some(pane.downgrade()),
22041                true,
22042                window,
22043                cx,
22044            )
22045        })
22046        .unwrap()
22047        .await
22048        .downcast::<Editor>()
22049        .unwrap();
22050    pane.update(cx, |pane, cx| {
22051        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22052        open_editor.update(cx, |editor, cx| {
22053            assert_eq!(
22054                editor.display_text(cx),
22055                main_text,
22056                "Original main.rs text on initial open",
22057            );
22058        })
22059    });
22060    editor.update_in(cx, |editor, window, cx| {
22061        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
22062    });
22063
22064    cx.update_global(|store: &mut SettingsStore, cx| {
22065        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
22066            s.restore_on_file_reopen = Some(false);
22067        });
22068    });
22069    editor.update_in(cx, |editor, window, cx| {
22070        editor.fold_ranges(
22071            vec![
22072                Point::new(1, 0)..Point::new(1, 1),
22073                Point::new(2, 0)..Point::new(2, 2),
22074                Point::new(3, 0)..Point::new(3, 3),
22075            ],
22076            false,
22077            window,
22078            cx,
22079        );
22080    });
22081    pane.update_in(cx, |pane, window, cx| {
22082        pane.close_all_items(&CloseAllItems::default(), window, cx)
22083    })
22084    .await
22085    .unwrap();
22086    pane.update(cx, |pane, _| {
22087        assert!(pane.active_item().is_none());
22088    });
22089    cx.update_global(|store: &mut SettingsStore, cx| {
22090        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
22091            s.restore_on_file_reopen = Some(true);
22092        });
22093    });
22094
22095    let _editor_reopened = workspace
22096        .update_in(cx, |workspace, window, cx| {
22097            workspace.open_path(
22098                (worktree_id, "main.rs"),
22099                Some(pane.downgrade()),
22100                true,
22101                window,
22102                cx,
22103            )
22104        })
22105        .unwrap()
22106        .await
22107        .downcast::<Editor>()
22108        .unwrap();
22109    pane.update(cx, |pane, cx| {
22110        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22111        open_editor.update(cx, |editor, cx| {
22112            assert_eq!(
22113                editor.display_text(cx),
22114                main_text,
22115                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
22116            );
22117        })
22118    });
22119}
22120
22121#[gpui::test]
22122async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
22123    struct EmptyModalView {
22124        focus_handle: gpui::FocusHandle,
22125    }
22126    impl EventEmitter<DismissEvent> for EmptyModalView {}
22127    impl Render for EmptyModalView {
22128        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
22129            div()
22130        }
22131    }
22132    impl Focusable for EmptyModalView {
22133        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
22134            self.focus_handle.clone()
22135        }
22136    }
22137    impl workspace::ModalView for EmptyModalView {}
22138    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
22139        EmptyModalView {
22140            focus_handle: cx.focus_handle(),
22141        }
22142    }
22143
22144    init_test(cx, |_| {});
22145
22146    let fs = FakeFs::new(cx.executor());
22147    let project = Project::test(fs, [], cx).await;
22148    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22149    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
22150    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22151    let editor = cx.new_window_entity(|window, cx| {
22152        Editor::new(
22153            EditorMode::full(),
22154            buffer,
22155            Some(project.clone()),
22156            window,
22157            cx,
22158        )
22159    });
22160    workspace
22161        .update(cx, |workspace, window, cx| {
22162            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
22163        })
22164        .unwrap();
22165    editor.update_in(cx, |editor, window, cx| {
22166        editor.open_context_menu(&OpenContextMenu, window, cx);
22167        assert!(editor.mouse_context_menu.is_some());
22168    });
22169    workspace
22170        .update(cx, |workspace, window, cx| {
22171            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
22172        })
22173        .unwrap();
22174    cx.read(|cx| {
22175        assert!(editor.read(cx).mouse_context_menu.is_none());
22176    });
22177}
22178
22179#[gpui::test]
22180async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
22181    init_test(cx, |_| {});
22182
22183    let fs = FakeFs::new(cx.executor());
22184    fs.insert_file(path!("/file.html"), Default::default())
22185        .await;
22186
22187    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
22188
22189    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22190    let html_language = Arc::new(Language::new(
22191        LanguageConfig {
22192            name: "HTML".into(),
22193            matcher: LanguageMatcher {
22194                path_suffixes: vec!["html".to_string()],
22195                ..LanguageMatcher::default()
22196            },
22197            brackets: BracketPairConfig {
22198                pairs: vec![BracketPair {
22199                    start: "<".into(),
22200                    end: ">".into(),
22201                    close: true,
22202                    ..Default::default()
22203                }],
22204                ..Default::default()
22205            },
22206            ..Default::default()
22207        },
22208        Some(tree_sitter_html::LANGUAGE.into()),
22209    ));
22210    language_registry.add(html_language);
22211    let mut fake_servers = language_registry.register_fake_lsp(
22212        "HTML",
22213        FakeLspAdapter {
22214            capabilities: lsp::ServerCapabilities {
22215                completion_provider: Some(lsp::CompletionOptions {
22216                    resolve_provider: Some(true),
22217                    ..Default::default()
22218                }),
22219                ..Default::default()
22220            },
22221            ..Default::default()
22222        },
22223    );
22224
22225    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22226    let cx = &mut VisualTestContext::from_window(*workspace, cx);
22227
22228    let worktree_id = workspace
22229        .update(cx, |workspace, _window, cx| {
22230            workspace.project().update(cx, |project, cx| {
22231                project.worktrees(cx).next().unwrap().read(cx).id()
22232            })
22233        })
22234        .unwrap();
22235    project
22236        .update(cx, |project, cx| {
22237            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
22238        })
22239        .await
22240        .unwrap();
22241    let editor = workspace
22242        .update(cx, |workspace, window, cx| {
22243            workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
22244        })
22245        .unwrap()
22246        .await
22247        .unwrap()
22248        .downcast::<Editor>()
22249        .unwrap();
22250
22251    let fake_server = fake_servers.next().await.unwrap();
22252    editor.update_in(cx, |editor, window, cx| {
22253        editor.set_text("<ad></ad>", window, cx);
22254        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22255            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
22256        });
22257        let Some((buffer, _)) = editor
22258            .buffer
22259            .read(cx)
22260            .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
22261        else {
22262            panic!("Failed to get buffer for selection position");
22263        };
22264        let buffer = buffer.read(cx);
22265        let buffer_id = buffer.remote_id();
22266        let opening_range =
22267            buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3));
22268        let closing_range =
22269            buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8));
22270        let mut linked_ranges = HashMap::default();
22271        linked_ranges.insert(
22272            buffer_id,
22273            vec![(opening_range.clone(), vec![closing_range.clone()])],
22274        );
22275        editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
22276    });
22277    let mut completion_handle =
22278        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
22279            Ok(Some(lsp::CompletionResponse::Array(vec![
22280                lsp::CompletionItem {
22281                    label: "head".to_string(),
22282                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22283                        lsp::InsertReplaceEdit {
22284                            new_text: "head".to_string(),
22285                            insert: lsp::Range::new(
22286                                lsp::Position::new(0, 1),
22287                                lsp::Position::new(0, 3),
22288                            ),
22289                            replace: lsp::Range::new(
22290                                lsp::Position::new(0, 1),
22291                                lsp::Position::new(0, 3),
22292                            ),
22293                        },
22294                    )),
22295                    ..Default::default()
22296                },
22297            ])))
22298        });
22299    editor.update_in(cx, |editor, window, cx| {
22300        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
22301    });
22302    cx.run_until_parked();
22303    completion_handle.next().await.unwrap();
22304    editor.update(cx, |editor, _| {
22305        assert!(
22306            editor.context_menu_visible(),
22307            "Completion menu should be visible"
22308        );
22309    });
22310    editor.update_in(cx, |editor, window, cx| {
22311        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
22312    });
22313    cx.executor().run_until_parked();
22314    editor.update(cx, |editor, cx| {
22315        assert_eq!(editor.text(cx), "<head></head>");
22316    });
22317}
22318
22319#[gpui::test]
22320async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
22321    init_test(cx, |_| {});
22322
22323    let fs = FakeFs::new(cx.executor());
22324    fs.insert_tree(
22325        path!("/root"),
22326        json!({
22327            "a": {
22328                "main.rs": "fn main() {}",
22329            },
22330            "foo": {
22331                "bar": {
22332                    "external_file.rs": "pub mod external {}",
22333                }
22334            }
22335        }),
22336    )
22337    .await;
22338
22339    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
22340    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22341    language_registry.add(rust_lang());
22342    let _fake_servers = language_registry.register_fake_lsp(
22343        "Rust",
22344        FakeLspAdapter {
22345            ..FakeLspAdapter::default()
22346        },
22347    );
22348    let (workspace, cx) =
22349        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22350    let worktree_id = workspace.update(cx, |workspace, cx| {
22351        workspace.project().update(cx, |project, cx| {
22352            project.worktrees(cx).next().unwrap().read(cx).id()
22353        })
22354    });
22355
22356    let assert_language_servers_count =
22357        |expected: usize, context: &str, cx: &mut VisualTestContext| {
22358            project.update(cx, |project, cx| {
22359                let current = project
22360                    .lsp_store()
22361                    .read(cx)
22362                    .as_local()
22363                    .unwrap()
22364                    .language_servers
22365                    .len();
22366                assert_eq!(expected, current, "{context}");
22367            });
22368        };
22369
22370    assert_language_servers_count(
22371        0,
22372        "No servers should be running before any file is open",
22373        cx,
22374    );
22375    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
22376    let main_editor = workspace
22377        .update_in(cx, |workspace, window, cx| {
22378            workspace.open_path(
22379                (worktree_id, "main.rs"),
22380                Some(pane.downgrade()),
22381                true,
22382                window,
22383                cx,
22384            )
22385        })
22386        .unwrap()
22387        .await
22388        .downcast::<Editor>()
22389        .unwrap();
22390    pane.update(cx, |pane, cx| {
22391        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22392        open_editor.update(cx, |editor, cx| {
22393            assert_eq!(
22394                editor.display_text(cx),
22395                "fn main() {}",
22396                "Original main.rs text on initial open",
22397            );
22398        });
22399        assert_eq!(open_editor, main_editor);
22400    });
22401    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
22402
22403    let external_editor = workspace
22404        .update_in(cx, |workspace, window, cx| {
22405            workspace.open_abs_path(
22406                PathBuf::from("/root/foo/bar/external_file.rs"),
22407                OpenOptions::default(),
22408                window,
22409                cx,
22410            )
22411        })
22412        .await
22413        .expect("opening external file")
22414        .downcast::<Editor>()
22415        .expect("downcasted external file's open element to editor");
22416    pane.update(cx, |pane, cx| {
22417        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22418        open_editor.update(cx, |editor, cx| {
22419            assert_eq!(
22420                editor.display_text(cx),
22421                "pub mod external {}",
22422                "External file is open now",
22423            );
22424        });
22425        assert_eq!(open_editor, external_editor);
22426    });
22427    assert_language_servers_count(
22428        1,
22429        "Second, external, *.rs file should join the existing server",
22430        cx,
22431    );
22432
22433    pane.update_in(cx, |pane, window, cx| {
22434        pane.close_active_item(&CloseActiveItem::default(), window, cx)
22435    })
22436    .await
22437    .unwrap();
22438    pane.update_in(cx, |pane, window, cx| {
22439        pane.navigate_backward(window, cx);
22440    });
22441    cx.run_until_parked();
22442    pane.update(cx, |pane, cx| {
22443        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22444        open_editor.update(cx, |editor, cx| {
22445            assert_eq!(
22446                editor.display_text(cx),
22447                "pub mod external {}",
22448                "External file is open now",
22449            );
22450        });
22451    });
22452    assert_language_servers_count(
22453        1,
22454        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
22455        cx,
22456    );
22457
22458    cx.update(|_, cx| {
22459        workspace::reload(&workspace::Reload::default(), cx);
22460    });
22461    assert_language_servers_count(
22462        1,
22463        "After reloading the worktree with local and external files opened, only one project should be started",
22464        cx,
22465    );
22466}
22467
22468#[gpui::test]
22469async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
22470    init_test(cx, |_| {});
22471
22472    let mut cx = EditorTestContext::new(cx).await;
22473    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22474    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22475
22476    // test cursor move to start of each line on tab
22477    // for `if`, `elif`, `else`, `while`, `with` and `for`
22478    cx.set_state(indoc! {"
22479        def main():
22480        ˇ    for item in items:
22481        ˇ        while item.active:
22482        ˇ            if item.value > 10:
22483        ˇ                continue
22484        ˇ            elif item.value < 0:
22485        ˇ                break
22486        ˇ            else:
22487        ˇ                with item.context() as ctx:
22488        ˇ                    yield count
22489        ˇ        else:
22490        ˇ            log('while else')
22491        ˇ    else:
22492        ˇ        log('for else')
22493    "});
22494    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22495    cx.assert_editor_state(indoc! {"
22496        def main():
22497            ˇfor item in items:
22498                ˇwhile item.active:
22499                    ˇif item.value > 10:
22500                        ˇcontinue
22501                    ˇelif item.value < 0:
22502                        ˇbreak
22503                    ˇelse:
22504                        ˇwith item.context() as ctx:
22505                            ˇyield count
22506                ˇelse:
22507                    ˇlog('while else')
22508            ˇelse:
22509                ˇlog('for else')
22510    "});
22511    // test relative indent is preserved when tab
22512    // for `if`, `elif`, `else`, `while`, `with` and `for`
22513    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22514    cx.assert_editor_state(indoc! {"
22515        def main():
22516                ˇfor item in items:
22517                    ˇwhile item.active:
22518                        ˇif item.value > 10:
22519                            ˇcontinue
22520                        ˇelif item.value < 0:
22521                            ˇbreak
22522                        ˇelse:
22523                            ˇwith item.context() as ctx:
22524                                ˇyield count
22525                    ˇelse:
22526                        ˇlog('while else')
22527                ˇelse:
22528                    ˇlog('for else')
22529    "});
22530
22531    // test cursor move to start of each line on tab
22532    // for `try`, `except`, `else`, `finally`, `match` and `def`
22533    cx.set_state(indoc! {"
22534        def main():
22535        ˇ    try:
22536        ˇ        fetch()
22537        ˇ    except ValueError:
22538        ˇ        handle_error()
22539        ˇ    else:
22540        ˇ        match value:
22541        ˇ            case _:
22542        ˇ    finally:
22543        ˇ        def status():
22544        ˇ            return 0
22545    "});
22546    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22547    cx.assert_editor_state(indoc! {"
22548        def main():
22549            ˇtry:
22550                ˇfetch()
22551            ˇexcept ValueError:
22552                ˇhandle_error()
22553            ˇelse:
22554                ˇmatch value:
22555                    ˇcase _:
22556            ˇfinally:
22557                ˇdef status():
22558                    ˇreturn 0
22559    "});
22560    // test relative indent is preserved when tab
22561    // for `try`, `except`, `else`, `finally`, `match` and `def`
22562    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22563    cx.assert_editor_state(indoc! {"
22564        def main():
22565                ˇtry:
22566                    ˇfetch()
22567                ˇexcept ValueError:
22568                    ˇhandle_error()
22569                ˇelse:
22570                    ˇmatch value:
22571                        ˇcase _:
22572                ˇfinally:
22573                    ˇdef status():
22574                        ˇreturn 0
22575    "});
22576}
22577
22578#[gpui::test]
22579async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
22580    init_test(cx, |_| {});
22581
22582    let mut cx = EditorTestContext::new(cx).await;
22583    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22584    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22585
22586    // test `else` auto outdents when typed inside `if` block
22587    cx.set_state(indoc! {"
22588        def main():
22589            if i == 2:
22590                return
22591                ˇ
22592    "});
22593    cx.update_editor(|editor, window, cx| {
22594        editor.handle_input("else:", window, cx);
22595    });
22596    cx.assert_editor_state(indoc! {"
22597        def main():
22598            if i == 2:
22599                return
22600            else:ˇ
22601    "});
22602
22603    // test `except` auto outdents when typed inside `try` block
22604    cx.set_state(indoc! {"
22605        def main():
22606            try:
22607                i = 2
22608                ˇ
22609    "});
22610    cx.update_editor(|editor, window, cx| {
22611        editor.handle_input("except:", window, cx);
22612    });
22613    cx.assert_editor_state(indoc! {"
22614        def main():
22615            try:
22616                i = 2
22617            except:ˇ
22618    "});
22619
22620    // test `else` auto outdents when typed inside `except` block
22621    cx.set_state(indoc! {"
22622        def main():
22623            try:
22624                i = 2
22625            except:
22626                j = 2
22627                ˇ
22628    "});
22629    cx.update_editor(|editor, window, cx| {
22630        editor.handle_input("else:", window, cx);
22631    });
22632    cx.assert_editor_state(indoc! {"
22633        def main():
22634            try:
22635                i = 2
22636            except:
22637                j = 2
22638            else:ˇ
22639    "});
22640
22641    // test `finally` auto outdents when typed inside `else` block
22642    cx.set_state(indoc! {"
22643        def main():
22644            try:
22645                i = 2
22646            except:
22647                j = 2
22648            else:
22649                k = 2
22650                ˇ
22651    "});
22652    cx.update_editor(|editor, window, cx| {
22653        editor.handle_input("finally:", window, cx);
22654    });
22655    cx.assert_editor_state(indoc! {"
22656        def main():
22657            try:
22658                i = 2
22659            except:
22660                j = 2
22661            else:
22662                k = 2
22663            finally:ˇ
22664    "});
22665
22666    // test `else` does not outdents when typed inside `except` block right after for block
22667    cx.set_state(indoc! {"
22668        def main():
22669            try:
22670                i = 2
22671            except:
22672                for i in range(n):
22673                    pass
22674                ˇ
22675    "});
22676    cx.update_editor(|editor, window, cx| {
22677        editor.handle_input("else:", window, cx);
22678    });
22679    cx.assert_editor_state(indoc! {"
22680        def main():
22681            try:
22682                i = 2
22683            except:
22684                for i in range(n):
22685                    pass
22686                else:ˇ
22687    "});
22688
22689    // test `finally` auto outdents when typed inside `else` block right after for block
22690    cx.set_state(indoc! {"
22691        def main():
22692            try:
22693                i = 2
22694            except:
22695                j = 2
22696            else:
22697                for i in range(n):
22698                    pass
22699                ˇ
22700    "});
22701    cx.update_editor(|editor, window, cx| {
22702        editor.handle_input("finally:", window, cx);
22703    });
22704    cx.assert_editor_state(indoc! {"
22705        def main():
22706            try:
22707                i = 2
22708            except:
22709                j = 2
22710            else:
22711                for i in range(n):
22712                    pass
22713            finally:ˇ
22714    "});
22715
22716    // test `except` outdents to inner "try" block
22717    cx.set_state(indoc! {"
22718        def main():
22719            try:
22720                i = 2
22721                if i == 2:
22722                    try:
22723                        i = 3
22724                        ˇ
22725    "});
22726    cx.update_editor(|editor, window, cx| {
22727        editor.handle_input("except:", window, cx);
22728    });
22729    cx.assert_editor_state(indoc! {"
22730        def main():
22731            try:
22732                i = 2
22733                if i == 2:
22734                    try:
22735                        i = 3
22736                    except:ˇ
22737    "});
22738
22739    // test `except` outdents to outer "try" block
22740    cx.set_state(indoc! {"
22741        def main():
22742            try:
22743                i = 2
22744                if i == 2:
22745                    try:
22746                        i = 3
22747                ˇ
22748    "});
22749    cx.update_editor(|editor, window, cx| {
22750        editor.handle_input("except:", window, cx);
22751    });
22752    cx.assert_editor_state(indoc! {"
22753        def main():
22754            try:
22755                i = 2
22756                if i == 2:
22757                    try:
22758                        i = 3
22759            except:ˇ
22760    "});
22761
22762    // test `else` stays at correct indent when typed after `for` block
22763    cx.set_state(indoc! {"
22764        def main():
22765            for i in range(10):
22766                if i == 3:
22767                    break
22768            ˇ
22769    "});
22770    cx.update_editor(|editor, window, cx| {
22771        editor.handle_input("else:", window, cx);
22772    });
22773    cx.assert_editor_state(indoc! {"
22774        def main():
22775            for i in range(10):
22776                if i == 3:
22777                    break
22778            else:ˇ
22779    "});
22780
22781    // test does not outdent on typing after line with square brackets
22782    cx.set_state(indoc! {"
22783        def f() -> list[str]:
22784            ˇ
22785    "});
22786    cx.update_editor(|editor, window, cx| {
22787        editor.handle_input("a", window, cx);
22788    });
22789    cx.assert_editor_state(indoc! {"
22790        def f() -> list[str]:
2279122792    "});
22793
22794    // test does not outdent on typing : after case keyword
22795    cx.set_state(indoc! {"
22796        match 1:
22797            caseˇ
22798    "});
22799    cx.update_editor(|editor, window, cx| {
22800        editor.handle_input(":", window, cx);
22801    });
22802    cx.assert_editor_state(indoc! {"
22803        match 1:
22804            case:ˇ
22805    "});
22806}
22807
22808#[gpui::test]
22809async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
22810    init_test(cx, |_| {});
22811    update_test_language_settings(cx, |settings| {
22812        settings.defaults.extend_comment_on_newline = Some(false);
22813    });
22814    let mut cx = EditorTestContext::new(cx).await;
22815    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22816    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22817
22818    // test correct indent after newline on comment
22819    cx.set_state(indoc! {"
22820        # COMMENT:ˇ
22821    "});
22822    cx.update_editor(|editor, window, cx| {
22823        editor.newline(&Newline, window, cx);
22824    });
22825    cx.assert_editor_state(indoc! {"
22826        # COMMENT:
22827        ˇ
22828    "});
22829
22830    // test correct indent after newline in brackets
22831    cx.set_state(indoc! {"
22832        {ˇ}
22833    "});
22834    cx.update_editor(|editor, window, cx| {
22835        editor.newline(&Newline, window, cx);
22836    });
22837    cx.run_until_parked();
22838    cx.assert_editor_state(indoc! {"
22839        {
22840            ˇ
22841        }
22842    "});
22843
22844    cx.set_state(indoc! {"
22845        (ˇ)
22846    "});
22847    cx.update_editor(|editor, window, cx| {
22848        editor.newline(&Newline, window, cx);
22849    });
22850    cx.run_until_parked();
22851    cx.assert_editor_state(indoc! {"
22852        (
22853            ˇ
22854        )
22855    "});
22856
22857    // do not indent after empty lists or dictionaries
22858    cx.set_state(indoc! {"
22859        a = []ˇ
22860    "});
22861    cx.update_editor(|editor, window, cx| {
22862        editor.newline(&Newline, window, cx);
22863    });
22864    cx.run_until_parked();
22865    cx.assert_editor_state(indoc! {"
22866        a = []
22867        ˇ
22868    "});
22869}
22870
22871#[gpui::test]
22872async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
22873    init_test(cx, |_| {});
22874
22875    let mut cx = EditorTestContext::new(cx).await;
22876    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
22877    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22878
22879    // test cursor move to start of each line on tab
22880    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
22881    cx.set_state(indoc! {"
22882        function main() {
22883        ˇ    for item in $items; do
22884        ˇ        while [ -n \"$item\" ]; do
22885        ˇ            if [ \"$value\" -gt 10 ]; then
22886        ˇ                continue
22887        ˇ            elif [ \"$value\" -lt 0 ]; then
22888        ˇ                break
22889        ˇ            else
22890        ˇ                echo \"$item\"
22891        ˇ            fi
22892        ˇ        done
22893        ˇ    done
22894        ˇ}
22895    "});
22896    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22897    cx.assert_editor_state(indoc! {"
22898        function main() {
22899            ˇfor item in $items; do
22900                ˇwhile [ -n \"$item\" ]; do
22901                    ˇif [ \"$value\" -gt 10 ]; then
22902                        ˇcontinue
22903                    ˇelif [ \"$value\" -lt 0 ]; then
22904                        ˇbreak
22905                    ˇelse
22906                        ˇecho \"$item\"
22907                    ˇfi
22908                ˇdone
22909            ˇdone
22910        ˇ}
22911    "});
22912    // test relative indent is preserved when tab
22913    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22914    cx.assert_editor_state(indoc! {"
22915        function main() {
22916                ˇfor item in $items; do
22917                    ˇwhile [ -n \"$item\" ]; do
22918                        ˇif [ \"$value\" -gt 10 ]; then
22919                            ˇcontinue
22920                        ˇelif [ \"$value\" -lt 0 ]; then
22921                            ˇbreak
22922                        ˇelse
22923                            ˇecho \"$item\"
22924                        ˇfi
22925                    ˇdone
22926                ˇdone
22927            ˇ}
22928    "});
22929
22930    // test cursor move to start of each line on tab
22931    // for `case` statement with patterns
22932    cx.set_state(indoc! {"
22933        function handle() {
22934        ˇ    case \"$1\" in
22935        ˇ        start)
22936        ˇ            echo \"a\"
22937        ˇ            ;;
22938        ˇ        stop)
22939        ˇ            echo \"b\"
22940        ˇ            ;;
22941        ˇ        *)
22942        ˇ            echo \"c\"
22943        ˇ            ;;
22944        ˇ    esac
22945        ˇ}
22946    "});
22947    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22948    cx.assert_editor_state(indoc! {"
22949        function handle() {
22950            ˇcase \"$1\" in
22951                ˇstart)
22952                    ˇecho \"a\"
22953                    ˇ;;
22954                ˇstop)
22955                    ˇecho \"b\"
22956                    ˇ;;
22957                ˇ*)
22958                    ˇecho \"c\"
22959                    ˇ;;
22960            ˇesac
22961        ˇ}
22962    "});
22963}
22964
22965#[gpui::test]
22966async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
22967    init_test(cx, |_| {});
22968
22969    let mut cx = EditorTestContext::new(cx).await;
22970    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
22971    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22972
22973    // test indents on comment insert
22974    cx.set_state(indoc! {"
22975        function main() {
22976        ˇ    for item in $items; do
22977        ˇ        while [ -n \"$item\" ]; do
22978        ˇ            if [ \"$value\" -gt 10 ]; then
22979        ˇ                continue
22980        ˇ            elif [ \"$value\" -lt 0 ]; then
22981        ˇ                break
22982        ˇ            else
22983        ˇ                echo \"$item\"
22984        ˇ            fi
22985        ˇ        done
22986        ˇ    done
22987        ˇ}
22988    "});
22989    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
22990    cx.assert_editor_state(indoc! {"
22991        function main() {
22992        #ˇ    for item in $items; do
22993        #ˇ        while [ -n \"$item\" ]; do
22994        #ˇ            if [ \"$value\" -gt 10 ]; then
22995        #ˇ                continue
22996        #ˇ            elif [ \"$value\" -lt 0 ]; then
22997        #ˇ                break
22998        #ˇ            else
22999        #ˇ                echo \"$item\"
23000        #ˇ            fi
23001        #ˇ        done
23002        #ˇ    done
23003        #ˇ}
23004    "});
23005}
23006
23007#[gpui::test]
23008async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
23009    init_test(cx, |_| {});
23010
23011    let mut cx = EditorTestContext::new(cx).await;
23012    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
23013    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23014
23015    // test `else` auto outdents when typed inside `if` block
23016    cx.set_state(indoc! {"
23017        if [ \"$1\" = \"test\" ]; then
23018            echo \"foo bar\"
23019            ˇ
23020    "});
23021    cx.update_editor(|editor, window, cx| {
23022        editor.handle_input("else", window, cx);
23023    });
23024    cx.assert_editor_state(indoc! {"
23025        if [ \"$1\" = \"test\" ]; then
23026            echo \"foo bar\"
23027        elseˇ
23028    "});
23029
23030    // test `elif` auto outdents when typed inside `if` block
23031    cx.set_state(indoc! {"
23032        if [ \"$1\" = \"test\" ]; then
23033            echo \"foo bar\"
23034            ˇ
23035    "});
23036    cx.update_editor(|editor, window, cx| {
23037        editor.handle_input("elif", window, cx);
23038    });
23039    cx.assert_editor_state(indoc! {"
23040        if [ \"$1\" = \"test\" ]; then
23041            echo \"foo bar\"
23042        elifˇ
23043    "});
23044
23045    // test `fi` auto outdents when typed inside `else` block
23046    cx.set_state(indoc! {"
23047        if [ \"$1\" = \"test\" ]; then
23048            echo \"foo bar\"
23049        else
23050            echo \"bar baz\"
23051            ˇ
23052    "});
23053    cx.update_editor(|editor, window, cx| {
23054        editor.handle_input("fi", window, cx);
23055    });
23056    cx.assert_editor_state(indoc! {"
23057        if [ \"$1\" = \"test\" ]; then
23058            echo \"foo bar\"
23059        else
23060            echo \"bar baz\"
23061        fiˇ
23062    "});
23063
23064    // test `done` auto outdents when typed inside `while` block
23065    cx.set_state(indoc! {"
23066        while read line; do
23067            echo \"$line\"
23068            ˇ
23069    "});
23070    cx.update_editor(|editor, window, cx| {
23071        editor.handle_input("done", window, cx);
23072    });
23073    cx.assert_editor_state(indoc! {"
23074        while read line; do
23075            echo \"$line\"
23076        doneˇ
23077    "});
23078
23079    // test `done` auto outdents when typed inside `for` block
23080    cx.set_state(indoc! {"
23081        for file in *.txt; do
23082            cat \"$file\"
23083            ˇ
23084    "});
23085    cx.update_editor(|editor, window, cx| {
23086        editor.handle_input("done", window, cx);
23087    });
23088    cx.assert_editor_state(indoc! {"
23089        for file in *.txt; do
23090            cat \"$file\"
23091        doneˇ
23092    "});
23093
23094    // test `esac` auto outdents when typed inside `case` block
23095    cx.set_state(indoc! {"
23096        case \"$1\" in
23097            start)
23098                echo \"foo bar\"
23099                ;;
23100            stop)
23101                echo \"bar baz\"
23102                ;;
23103            ˇ
23104    "});
23105    cx.update_editor(|editor, window, cx| {
23106        editor.handle_input("esac", window, cx);
23107    });
23108    cx.assert_editor_state(indoc! {"
23109        case \"$1\" in
23110            start)
23111                echo \"foo bar\"
23112                ;;
23113            stop)
23114                echo \"bar baz\"
23115                ;;
23116        esacˇ
23117    "});
23118
23119    // test `*)` auto outdents when typed inside `case` block
23120    cx.set_state(indoc! {"
23121        case \"$1\" in
23122            start)
23123                echo \"foo bar\"
23124                ;;
23125                ˇ
23126    "});
23127    cx.update_editor(|editor, window, cx| {
23128        editor.handle_input("*)", window, cx);
23129    });
23130    cx.assert_editor_state(indoc! {"
23131        case \"$1\" in
23132            start)
23133                echo \"foo bar\"
23134                ;;
23135            *)ˇ
23136    "});
23137
23138    // test `fi` outdents to correct level with nested if blocks
23139    cx.set_state(indoc! {"
23140        if [ \"$1\" = \"test\" ]; then
23141            echo \"outer if\"
23142            if [ \"$2\" = \"debug\" ]; then
23143                echo \"inner if\"
23144                ˇ
23145    "});
23146    cx.update_editor(|editor, window, cx| {
23147        editor.handle_input("fi", window, cx);
23148    });
23149    cx.assert_editor_state(indoc! {"
23150        if [ \"$1\" = \"test\" ]; then
23151            echo \"outer if\"
23152            if [ \"$2\" = \"debug\" ]; then
23153                echo \"inner if\"
23154            fiˇ
23155    "});
23156}
23157
23158#[gpui::test]
23159async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
23160    init_test(cx, |_| {});
23161    update_test_language_settings(cx, |settings| {
23162        settings.defaults.extend_comment_on_newline = Some(false);
23163    });
23164    let mut cx = EditorTestContext::new(cx).await;
23165    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
23166    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23167
23168    // test correct indent after newline on comment
23169    cx.set_state(indoc! {"
23170        # COMMENT:ˇ
23171    "});
23172    cx.update_editor(|editor, window, cx| {
23173        editor.newline(&Newline, window, cx);
23174    });
23175    cx.assert_editor_state(indoc! {"
23176        # COMMENT:
23177        ˇ
23178    "});
23179
23180    // test correct indent after newline after `then`
23181    cx.set_state(indoc! {"
23182
23183        if [ \"$1\" = \"test\" ]; thenˇ
23184    "});
23185    cx.update_editor(|editor, window, cx| {
23186        editor.newline(&Newline, window, cx);
23187    });
23188    cx.run_until_parked();
23189    cx.assert_editor_state(indoc! {"
23190
23191        if [ \"$1\" = \"test\" ]; then
23192            ˇ
23193    "});
23194
23195    // test correct indent after newline after `else`
23196    cx.set_state(indoc! {"
23197        if [ \"$1\" = \"test\" ]; then
23198        elseˇ
23199    "});
23200    cx.update_editor(|editor, window, cx| {
23201        editor.newline(&Newline, window, cx);
23202    });
23203    cx.run_until_parked();
23204    cx.assert_editor_state(indoc! {"
23205        if [ \"$1\" = \"test\" ]; then
23206        else
23207            ˇ
23208    "});
23209
23210    // test correct indent after newline after `elif`
23211    cx.set_state(indoc! {"
23212        if [ \"$1\" = \"test\" ]; then
23213        elifˇ
23214    "});
23215    cx.update_editor(|editor, window, cx| {
23216        editor.newline(&Newline, window, cx);
23217    });
23218    cx.run_until_parked();
23219    cx.assert_editor_state(indoc! {"
23220        if [ \"$1\" = \"test\" ]; then
23221        elif
23222            ˇ
23223    "});
23224
23225    // test correct indent after newline after `do`
23226    cx.set_state(indoc! {"
23227        for file in *.txt; doˇ
23228    "});
23229    cx.update_editor(|editor, window, cx| {
23230        editor.newline(&Newline, window, cx);
23231    });
23232    cx.run_until_parked();
23233    cx.assert_editor_state(indoc! {"
23234        for file in *.txt; do
23235            ˇ
23236    "});
23237
23238    // test correct indent after newline after case pattern
23239    cx.set_state(indoc! {"
23240        case \"$1\" in
23241            start)ˇ
23242    "});
23243    cx.update_editor(|editor, window, cx| {
23244        editor.newline(&Newline, window, cx);
23245    });
23246    cx.run_until_parked();
23247    cx.assert_editor_state(indoc! {"
23248        case \"$1\" in
23249            start)
23250                ˇ
23251    "});
23252
23253    // test correct indent after newline after case pattern
23254    cx.set_state(indoc! {"
23255        case \"$1\" in
23256            start)
23257                ;;
23258            *)ˇ
23259    "});
23260    cx.update_editor(|editor, window, cx| {
23261        editor.newline(&Newline, window, cx);
23262    });
23263    cx.run_until_parked();
23264    cx.assert_editor_state(indoc! {"
23265        case \"$1\" in
23266            start)
23267                ;;
23268            *)
23269                ˇ
23270    "});
23271
23272    // test correct indent after newline after function opening brace
23273    cx.set_state(indoc! {"
23274        function test() {ˇ}
23275    "});
23276    cx.update_editor(|editor, window, cx| {
23277        editor.newline(&Newline, window, cx);
23278    });
23279    cx.run_until_parked();
23280    cx.assert_editor_state(indoc! {"
23281        function test() {
23282            ˇ
23283        }
23284    "});
23285
23286    // test no extra indent after semicolon on same line
23287    cx.set_state(indoc! {"
23288        echo \"test\"23289    "});
23290    cx.update_editor(|editor, window, cx| {
23291        editor.newline(&Newline, window, cx);
23292    });
23293    cx.run_until_parked();
23294    cx.assert_editor_state(indoc! {"
23295        echo \"test\";
23296        ˇ
23297    "});
23298}
23299
23300fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
23301    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
23302    point..point
23303}
23304
23305#[track_caller]
23306fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
23307    let (text, ranges) = marked_text_ranges(marked_text, true);
23308    assert_eq!(editor.text(cx), text);
23309    assert_eq!(
23310        editor.selections.ranges(cx),
23311        ranges,
23312        "Assert selections are {}",
23313        marked_text
23314    );
23315}
23316
23317pub fn handle_signature_help_request(
23318    cx: &mut EditorLspTestContext,
23319    mocked_response: lsp::SignatureHelp,
23320) -> impl Future<Output = ()> + use<> {
23321    let mut request =
23322        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
23323            let mocked_response = mocked_response.clone();
23324            async move { Ok(Some(mocked_response)) }
23325        });
23326
23327    async move {
23328        request.next().await;
23329    }
23330}
23331
23332#[track_caller]
23333pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
23334    cx.update_editor(|editor, _, _| {
23335        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
23336            let entries = menu.entries.borrow();
23337            let entries = entries
23338                .iter()
23339                .map(|entry| entry.string.as_str())
23340                .collect::<Vec<_>>();
23341            assert_eq!(entries, expected);
23342        } else {
23343            panic!("Expected completions menu");
23344        }
23345    });
23346}
23347
23348/// Handle completion request passing a marked string specifying where the completion
23349/// should be triggered from using '|' character, what range should be replaced, and what completions
23350/// should be returned using '<' and '>' to delimit the range.
23351///
23352/// Also see `handle_completion_request_with_insert_and_replace`.
23353#[track_caller]
23354pub fn handle_completion_request(
23355    marked_string: &str,
23356    completions: Vec<&'static str>,
23357    is_incomplete: bool,
23358    counter: Arc<AtomicUsize>,
23359    cx: &mut EditorLspTestContext,
23360) -> impl Future<Output = ()> {
23361    let complete_from_marker: TextRangeMarker = '|'.into();
23362    let replace_range_marker: TextRangeMarker = ('<', '>').into();
23363    let (_, mut marked_ranges) = marked_text_ranges_by(
23364        marked_string,
23365        vec![complete_from_marker.clone(), replace_range_marker.clone()],
23366    );
23367
23368    let complete_from_position =
23369        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
23370    let replace_range =
23371        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
23372
23373    let mut request =
23374        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
23375            let completions = completions.clone();
23376            counter.fetch_add(1, atomic::Ordering::Release);
23377            async move {
23378                assert_eq!(params.text_document_position.text_document.uri, url.clone());
23379                assert_eq!(
23380                    params.text_document_position.position,
23381                    complete_from_position
23382                );
23383                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
23384                    is_incomplete: is_incomplete,
23385                    item_defaults: None,
23386                    items: completions
23387                        .iter()
23388                        .map(|completion_text| lsp::CompletionItem {
23389                            label: completion_text.to_string(),
23390                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
23391                                range: replace_range,
23392                                new_text: completion_text.to_string(),
23393                            })),
23394                            ..Default::default()
23395                        })
23396                        .collect(),
23397                })))
23398            }
23399        });
23400
23401    async move {
23402        request.next().await;
23403    }
23404}
23405
23406/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
23407/// given instead, which also contains an `insert` range.
23408///
23409/// This function uses markers to define ranges:
23410/// - `|` marks the cursor position
23411/// - `<>` marks the replace range
23412/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
23413pub fn handle_completion_request_with_insert_and_replace(
23414    cx: &mut EditorLspTestContext,
23415    marked_string: &str,
23416    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
23417    counter: Arc<AtomicUsize>,
23418) -> impl Future<Output = ()> {
23419    let complete_from_marker: TextRangeMarker = '|'.into();
23420    let replace_range_marker: TextRangeMarker = ('<', '>').into();
23421    let insert_range_marker: TextRangeMarker = ('{', '}').into();
23422
23423    let (_, mut marked_ranges) = marked_text_ranges_by(
23424        marked_string,
23425        vec![
23426            complete_from_marker.clone(),
23427            replace_range_marker.clone(),
23428            insert_range_marker.clone(),
23429        ],
23430    );
23431
23432    let complete_from_position =
23433        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
23434    let replace_range =
23435        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
23436
23437    let insert_range = match marked_ranges.remove(&insert_range_marker) {
23438        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
23439        _ => lsp::Range {
23440            start: replace_range.start,
23441            end: complete_from_position,
23442        },
23443    };
23444
23445    let mut request =
23446        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
23447            let completions = completions.clone();
23448            counter.fetch_add(1, atomic::Ordering::Release);
23449            async move {
23450                assert_eq!(params.text_document_position.text_document.uri, url.clone());
23451                assert_eq!(
23452                    params.text_document_position.position, complete_from_position,
23453                    "marker `|` position doesn't match",
23454                );
23455                Ok(Some(lsp::CompletionResponse::Array(
23456                    completions
23457                        .iter()
23458                        .map(|(label, new_text)| lsp::CompletionItem {
23459                            label: label.to_string(),
23460                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23461                                lsp::InsertReplaceEdit {
23462                                    insert: insert_range,
23463                                    replace: replace_range,
23464                                    new_text: new_text.to_string(),
23465                                },
23466                            )),
23467                            ..Default::default()
23468                        })
23469                        .collect(),
23470                )))
23471            }
23472        });
23473
23474    async move {
23475        request.next().await;
23476    }
23477}
23478
23479fn handle_resolve_completion_request(
23480    cx: &mut EditorLspTestContext,
23481    edits: Option<Vec<(&'static str, &'static str)>>,
23482) -> impl Future<Output = ()> {
23483    let edits = edits.map(|edits| {
23484        edits
23485            .iter()
23486            .map(|(marked_string, new_text)| {
23487                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
23488                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
23489                lsp::TextEdit::new(replace_range, new_text.to_string())
23490            })
23491            .collect::<Vec<_>>()
23492    });
23493
23494    let mut request =
23495        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
23496            let edits = edits.clone();
23497            async move {
23498                Ok(lsp::CompletionItem {
23499                    additional_text_edits: edits,
23500                    ..Default::default()
23501                })
23502            }
23503        });
23504
23505    async move {
23506        request.next().await;
23507    }
23508}
23509
23510pub(crate) fn update_test_language_settings(
23511    cx: &mut TestAppContext,
23512    f: impl Fn(&mut AllLanguageSettingsContent),
23513) {
23514    cx.update(|cx| {
23515        SettingsStore::update_global(cx, |store, cx| {
23516            store.update_user_settings::<AllLanguageSettings>(cx, f);
23517        });
23518    });
23519}
23520
23521pub(crate) fn update_test_project_settings(
23522    cx: &mut TestAppContext,
23523    f: impl Fn(&mut ProjectSettings),
23524) {
23525    cx.update(|cx| {
23526        SettingsStore::update_global(cx, |store, cx| {
23527            store.update_user_settings::<ProjectSettings>(cx, f);
23528        });
23529    });
23530}
23531
23532pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
23533    cx.update(|cx| {
23534        assets::Assets.load_test_fonts(cx);
23535        let store = SettingsStore::test(cx);
23536        cx.set_global(store);
23537        theme::init(theme::LoadThemes::JustBase, cx);
23538        release_channel::init(SemanticVersion::default(), cx);
23539        client::init_settings(cx);
23540        language::init(cx);
23541        Project::init_settings(cx);
23542        workspace::init_settings(cx);
23543        crate::init(cx);
23544    });
23545    zlog::init_test();
23546    update_test_language_settings(cx, f);
23547}
23548
23549#[track_caller]
23550fn assert_hunk_revert(
23551    not_reverted_text_with_selections: &str,
23552    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
23553    expected_reverted_text_with_selections: &str,
23554    base_text: &str,
23555    cx: &mut EditorLspTestContext,
23556) {
23557    cx.set_state(not_reverted_text_with_selections);
23558    cx.set_head_text(base_text);
23559    cx.executor().run_until_parked();
23560
23561    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
23562        let snapshot = editor.snapshot(window, cx);
23563        let reverted_hunk_statuses = snapshot
23564            .buffer_snapshot
23565            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
23566            .map(|hunk| hunk.status().kind)
23567            .collect::<Vec<_>>();
23568
23569        editor.git_restore(&Default::default(), window, cx);
23570        reverted_hunk_statuses
23571    });
23572    cx.executor().run_until_parked();
23573    cx.assert_editor_state(expected_reverted_text_with_selections);
23574    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
23575}
23576
23577#[gpui::test(iterations = 10)]
23578async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
23579    init_test(cx, |_| {});
23580
23581    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
23582    let counter = diagnostic_requests.clone();
23583
23584    let fs = FakeFs::new(cx.executor());
23585    fs.insert_tree(
23586        path!("/a"),
23587        json!({
23588            "first.rs": "fn main() { let a = 5; }",
23589            "second.rs": "// Test file",
23590        }),
23591    )
23592    .await;
23593
23594    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23595    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23596    let cx = &mut VisualTestContext::from_window(*workspace, cx);
23597
23598    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23599    language_registry.add(rust_lang());
23600    let mut fake_servers = language_registry.register_fake_lsp(
23601        "Rust",
23602        FakeLspAdapter {
23603            capabilities: lsp::ServerCapabilities {
23604                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
23605                    lsp::DiagnosticOptions {
23606                        identifier: None,
23607                        inter_file_dependencies: true,
23608                        workspace_diagnostics: true,
23609                        work_done_progress_options: Default::default(),
23610                    },
23611                )),
23612                ..Default::default()
23613            },
23614            ..Default::default()
23615        },
23616    );
23617
23618    let editor = workspace
23619        .update(cx, |workspace, window, cx| {
23620            workspace.open_abs_path(
23621                PathBuf::from(path!("/a/first.rs")),
23622                OpenOptions::default(),
23623                window,
23624                cx,
23625            )
23626        })
23627        .unwrap()
23628        .await
23629        .unwrap()
23630        .downcast::<Editor>()
23631        .unwrap();
23632    let fake_server = fake_servers.next().await.unwrap();
23633    let server_id = fake_server.server.server_id();
23634    let mut first_request = fake_server
23635        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
23636            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
23637            let result_id = Some(new_result_id.to_string());
23638            assert_eq!(
23639                params.text_document.uri,
23640                lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
23641            );
23642            async move {
23643                Ok(lsp::DocumentDiagnosticReportResult::Report(
23644                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
23645                        related_documents: None,
23646                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
23647                            items: Vec::new(),
23648                            result_id,
23649                        },
23650                    }),
23651                ))
23652            }
23653        });
23654
23655    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
23656        project.update(cx, |project, cx| {
23657            let buffer_id = editor
23658                .read(cx)
23659                .buffer()
23660                .read(cx)
23661                .as_singleton()
23662                .expect("created a singleton buffer")
23663                .read(cx)
23664                .remote_id();
23665            let buffer_result_id = project
23666                .lsp_store()
23667                .read(cx)
23668                .result_id(server_id, buffer_id, cx);
23669            assert_eq!(expected, buffer_result_id);
23670        });
23671    };
23672
23673    ensure_result_id(None, cx);
23674    cx.executor().advance_clock(Duration::from_millis(60));
23675    cx.executor().run_until_parked();
23676    assert_eq!(
23677        diagnostic_requests.load(atomic::Ordering::Acquire),
23678        1,
23679        "Opening file should trigger diagnostic request"
23680    );
23681    first_request
23682        .next()
23683        .await
23684        .expect("should have sent the first diagnostics pull request");
23685    ensure_result_id(Some("1".to_string()), cx);
23686
23687    // Editing should trigger diagnostics
23688    editor.update_in(cx, |editor, window, cx| {
23689        editor.handle_input("2", window, cx)
23690    });
23691    cx.executor().advance_clock(Duration::from_millis(60));
23692    cx.executor().run_until_parked();
23693    assert_eq!(
23694        diagnostic_requests.load(atomic::Ordering::Acquire),
23695        2,
23696        "Editing should trigger diagnostic request"
23697    );
23698    ensure_result_id(Some("2".to_string()), cx);
23699
23700    // Moving cursor should not trigger diagnostic request
23701    editor.update_in(cx, |editor, window, cx| {
23702        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23703            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
23704        });
23705    });
23706    cx.executor().advance_clock(Duration::from_millis(60));
23707    cx.executor().run_until_parked();
23708    assert_eq!(
23709        diagnostic_requests.load(atomic::Ordering::Acquire),
23710        2,
23711        "Cursor movement should not trigger diagnostic request"
23712    );
23713    ensure_result_id(Some("2".to_string()), cx);
23714    // Multiple rapid edits should be debounced
23715    for _ in 0..5 {
23716        editor.update_in(cx, |editor, window, cx| {
23717            editor.handle_input("x", window, cx)
23718        });
23719    }
23720    cx.executor().advance_clock(Duration::from_millis(60));
23721    cx.executor().run_until_parked();
23722
23723    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
23724    assert!(
23725        final_requests <= 4,
23726        "Multiple rapid edits should be debounced (got {final_requests} requests)",
23727    );
23728    ensure_result_id(Some(final_requests.to_string()), cx);
23729}
23730
23731#[gpui::test]
23732async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
23733    // Regression test for issue #11671
23734    // Previously, adding a cursor after moving multiple cursors would reset
23735    // the cursor count instead of adding to the existing cursors.
23736    init_test(cx, |_| {});
23737    let mut cx = EditorTestContext::new(cx).await;
23738
23739    // Create a simple buffer with cursor at start
23740    cx.set_state(indoc! {"
23741        ˇaaaa
23742        bbbb
23743        cccc
23744        dddd
23745        eeee
23746        ffff
23747        gggg
23748        hhhh"});
23749
23750    // Add 2 cursors below (so we have 3 total)
23751    cx.update_editor(|editor, window, cx| {
23752        editor.add_selection_below(&Default::default(), window, cx);
23753        editor.add_selection_below(&Default::default(), window, cx);
23754    });
23755
23756    // Verify we have 3 cursors
23757    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
23758    assert_eq!(
23759        initial_count, 3,
23760        "Should have 3 cursors after adding 2 below"
23761    );
23762
23763    // Move down one line
23764    cx.update_editor(|editor, window, cx| {
23765        editor.move_down(&MoveDown, window, cx);
23766    });
23767
23768    // Add another cursor below
23769    cx.update_editor(|editor, window, cx| {
23770        editor.add_selection_below(&Default::default(), window, cx);
23771    });
23772
23773    // Should now have 4 cursors (3 original + 1 new)
23774    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
23775    assert_eq!(
23776        final_count, 4,
23777        "Should have 4 cursors after moving and adding another"
23778    );
23779}
23780
23781#[gpui::test(iterations = 10)]
23782async fn test_document_colors(cx: &mut TestAppContext) {
23783    let expected_color = Rgba {
23784        r: 0.33,
23785        g: 0.33,
23786        b: 0.33,
23787        a: 0.33,
23788    };
23789
23790    init_test(cx, |_| {});
23791
23792    let fs = FakeFs::new(cx.executor());
23793    fs.insert_tree(
23794        path!("/a"),
23795        json!({
23796            "first.rs": "fn main() { let a = 5; }",
23797        }),
23798    )
23799    .await;
23800
23801    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23802    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23803    let cx = &mut VisualTestContext::from_window(*workspace, cx);
23804
23805    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23806    language_registry.add(rust_lang());
23807    let mut fake_servers = language_registry.register_fake_lsp(
23808        "Rust",
23809        FakeLspAdapter {
23810            capabilities: lsp::ServerCapabilities {
23811                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
23812                ..lsp::ServerCapabilities::default()
23813            },
23814            name: "rust-analyzer",
23815            ..FakeLspAdapter::default()
23816        },
23817    );
23818    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
23819        "Rust",
23820        FakeLspAdapter {
23821            capabilities: lsp::ServerCapabilities {
23822                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
23823                ..lsp::ServerCapabilities::default()
23824            },
23825            name: "not-rust-analyzer",
23826            ..FakeLspAdapter::default()
23827        },
23828    );
23829
23830    let editor = workspace
23831        .update(cx, |workspace, window, cx| {
23832            workspace.open_abs_path(
23833                PathBuf::from(path!("/a/first.rs")),
23834                OpenOptions::default(),
23835                window,
23836                cx,
23837            )
23838        })
23839        .unwrap()
23840        .await
23841        .unwrap()
23842        .downcast::<Editor>()
23843        .unwrap();
23844    let fake_language_server = fake_servers.next().await.unwrap();
23845    let fake_language_server_without_capabilities =
23846        fake_servers_without_capabilities.next().await.unwrap();
23847    let requests_made = Arc::new(AtomicUsize::new(0));
23848    let closure_requests_made = Arc::clone(&requests_made);
23849    let mut color_request_handle = fake_language_server
23850        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
23851            let requests_made = Arc::clone(&closure_requests_made);
23852            async move {
23853                assert_eq!(
23854                    params.text_document.uri,
23855                    lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
23856                );
23857                requests_made.fetch_add(1, atomic::Ordering::Release);
23858                Ok(vec![
23859                    lsp::ColorInformation {
23860                        range: lsp::Range {
23861                            start: lsp::Position {
23862                                line: 0,
23863                                character: 0,
23864                            },
23865                            end: lsp::Position {
23866                                line: 0,
23867                                character: 1,
23868                            },
23869                        },
23870                        color: lsp::Color {
23871                            red: 0.33,
23872                            green: 0.33,
23873                            blue: 0.33,
23874                            alpha: 0.33,
23875                        },
23876                    },
23877                    lsp::ColorInformation {
23878                        range: lsp::Range {
23879                            start: lsp::Position {
23880                                line: 0,
23881                                character: 0,
23882                            },
23883                            end: lsp::Position {
23884                                line: 0,
23885                                character: 1,
23886                            },
23887                        },
23888                        color: lsp::Color {
23889                            red: 0.33,
23890                            green: 0.33,
23891                            blue: 0.33,
23892                            alpha: 0.33,
23893                        },
23894                    },
23895                ])
23896            }
23897        });
23898
23899    let _handle = fake_language_server_without_capabilities
23900        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
23901            panic!("Should not be called");
23902        });
23903    cx.executor().advance_clock(Duration::from_millis(100));
23904    color_request_handle.next().await.unwrap();
23905    cx.run_until_parked();
23906    assert_eq!(
23907        1,
23908        requests_made.load(atomic::Ordering::Acquire),
23909        "Should query for colors once per editor open"
23910    );
23911    editor.update_in(cx, |editor, _, cx| {
23912        assert_eq!(
23913            vec![expected_color],
23914            extract_color_inlays(editor, cx),
23915            "Should have an initial inlay"
23916        );
23917    });
23918
23919    // opening another file in a split should not influence the LSP query counter
23920    workspace
23921        .update(cx, |workspace, window, cx| {
23922            assert_eq!(
23923                workspace.panes().len(),
23924                1,
23925                "Should have one pane with one editor"
23926            );
23927            workspace.move_item_to_pane_in_direction(
23928                &MoveItemToPaneInDirection {
23929                    direction: SplitDirection::Right,
23930                    focus: false,
23931                    clone: true,
23932                },
23933                window,
23934                cx,
23935            );
23936        })
23937        .unwrap();
23938    cx.run_until_parked();
23939    workspace
23940        .update(cx, |workspace, _, cx| {
23941            let panes = workspace.panes();
23942            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
23943            for pane in panes {
23944                let editor = pane
23945                    .read(cx)
23946                    .active_item()
23947                    .and_then(|item| item.downcast::<Editor>())
23948                    .expect("Should have opened an editor in each split");
23949                let editor_file = editor
23950                    .read(cx)
23951                    .buffer()
23952                    .read(cx)
23953                    .as_singleton()
23954                    .expect("test deals with singleton buffers")
23955                    .read(cx)
23956                    .file()
23957                    .expect("test buffese should have a file")
23958                    .path();
23959                assert_eq!(
23960                    editor_file.as_ref(),
23961                    Path::new("first.rs"),
23962                    "Both editors should be opened for the same file"
23963                )
23964            }
23965        })
23966        .unwrap();
23967
23968    cx.executor().advance_clock(Duration::from_millis(500));
23969    let save = editor.update_in(cx, |editor, window, cx| {
23970        editor.move_to_end(&MoveToEnd, window, cx);
23971        editor.handle_input("dirty", window, cx);
23972        editor.save(
23973            SaveOptions {
23974                format: true,
23975                autosave: true,
23976            },
23977            project.clone(),
23978            window,
23979            cx,
23980        )
23981    });
23982    save.await.unwrap();
23983
23984    color_request_handle.next().await.unwrap();
23985    cx.run_until_parked();
23986    assert_eq!(
23987        3,
23988        requests_made.load(atomic::Ordering::Acquire),
23989        "Should query for colors once per save and once per formatting after save"
23990    );
23991
23992    drop(editor);
23993    let close = workspace
23994        .update(cx, |workspace, window, cx| {
23995            workspace.active_pane().update(cx, |pane, cx| {
23996                pane.close_active_item(&CloseActiveItem::default(), window, cx)
23997            })
23998        })
23999        .unwrap();
24000    close.await.unwrap();
24001    let close = workspace
24002        .update(cx, |workspace, window, cx| {
24003            workspace.active_pane().update(cx, |pane, cx| {
24004                pane.close_active_item(&CloseActiveItem::default(), window, cx)
24005            })
24006        })
24007        .unwrap();
24008    close.await.unwrap();
24009    assert_eq!(
24010        3,
24011        requests_made.load(atomic::Ordering::Acquire),
24012        "After saving and closing all editors, no extra requests should be made"
24013    );
24014    workspace
24015        .update(cx, |workspace, _, cx| {
24016            assert!(
24017                workspace.active_item(cx).is_none(),
24018                "Should close all editors"
24019            )
24020        })
24021        .unwrap();
24022
24023    workspace
24024        .update(cx, |workspace, window, cx| {
24025            workspace.active_pane().update(cx, |pane, cx| {
24026                pane.navigate_backward(window, cx);
24027            })
24028        })
24029        .unwrap();
24030    cx.executor().advance_clock(Duration::from_millis(100));
24031    cx.run_until_parked();
24032    let editor = workspace
24033        .update(cx, |workspace, _, cx| {
24034            workspace
24035                .active_item(cx)
24036                .expect("Should have reopened the editor again after navigating back")
24037                .downcast::<Editor>()
24038                .expect("Should be an editor")
24039        })
24040        .unwrap();
24041    color_request_handle.next().await.unwrap();
24042    assert_eq!(
24043        3,
24044        requests_made.load(atomic::Ordering::Acquire),
24045        "Cache should be reused on buffer close and reopen"
24046    );
24047    editor.update(cx, |editor, cx| {
24048        assert_eq!(
24049            vec![expected_color],
24050            extract_color_inlays(editor, cx),
24051            "Should have an initial inlay"
24052        );
24053    });
24054}
24055
24056#[gpui::test]
24057async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
24058    init_test(cx, |_| {});
24059    let (editor, cx) = cx.add_window_view(Editor::single_line);
24060    editor.update_in(cx, |editor, window, cx| {
24061        editor.set_text("oops\n\nwow\n", window, cx)
24062    });
24063    cx.run_until_parked();
24064    editor.update(cx, |editor, cx| {
24065        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
24066    });
24067    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
24068    cx.run_until_parked();
24069    editor.update(cx, |editor, cx| {
24070        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
24071    });
24072}
24073
24074#[track_caller]
24075fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
24076    editor
24077        .all_inlays(cx)
24078        .into_iter()
24079        .filter_map(|inlay| inlay.get_color())
24080        .map(Rgba::from)
24081        .collect()
24082}