editor_tests.rs

    1use super::*;
    2use crate::{
    3    JoinLines,
    4    code_context_menus::CodeContextMenu,
    5    inline_completion_tests::FakeInlineCompletionProvider,
    6    linked_editing_ranges::LinkedEditingRanges,
    7    scroll::scroll_amount::ScrollAmount,
    8    test::{
    9        assert_text_with_selections, build_editor,
   10        editor_lsp_test_context::{EditorLspTestContext, git_commit_lang},
   11        editor_test_context::EditorTestContext,
   12        select_ranges,
   13    },
   14};
   15use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
   16use futures::StreamExt;
   17use gpui::{
   18    BackgroundExecutor, DismissEvent, Rgba, SemanticVersion, TestAppContext, UpdateGlobal,
   19    VisualTestContext, WindowBounds, WindowOptions, div,
   20};
   21use indoc::indoc;
   22use language::{
   23    BracketPairConfig,
   24    Capability::ReadWrite,
   25    DiagnosticSourceKind, FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageMatcher,
   26    LanguageName, Override, Point,
   27    language_settings::{
   28        AllLanguageSettings, AllLanguageSettingsContent, CompletionSettings, FormatterList,
   29        LanguageSettingsContent, LspInsertMode, PrettierSettings, SelectedFormatter,
   30    },
   31    tree_sitter_python,
   32};
   33use language_settings::{Formatter, IndentGuideSettings};
   34use lsp::CompletionParams;
   35use multi_buffer::{IndentGuide, PathKey};
   36use parking_lot::Mutex;
   37use pretty_assertions::{assert_eq, assert_ne};
   38use project::{
   39    FakeFs,
   40    debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
   41    project_settings::{LspSettings, ProjectSettings},
   42};
   43use serde_json::{self, json};
   44use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
   45use std::{
   46    iter,
   47    sync::atomic::{self, AtomicUsize},
   48};
   49use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
   50use text::ToPoint as _;
   51use unindent::Unindent;
   52use util::{
   53    assert_set_eq, path,
   54    test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text},
   55    uri,
   56};
   57use workspace::{
   58    CloseActiveItem, CloseAllItems, CloseOtherItems, MoveItemToPaneInDirection, NavigationEntry,
   59    OpenOptions, ViewId,
   60    item::{FollowEvent, FollowableItem, Item, ItemHandle, SaveOptions},
   61};
   62
   63#[gpui::test]
   64fn test_edit_events(cx: &mut TestAppContext) {
   65    init_test(cx, |_| {});
   66
   67    let buffer = cx.new(|cx| {
   68        let mut buffer = language::Buffer::local("123456", cx);
   69        buffer.set_group_interval(Duration::from_secs(1));
   70        buffer
   71    });
   72
   73    let events = Rc::new(RefCell::new(Vec::new()));
   74    let editor1 = cx.add_window({
   75        let events = events.clone();
   76        |window, cx| {
   77            let entity = cx.entity().clone();
   78            cx.subscribe_in(
   79                &entity,
   80                window,
   81                move |_, _, event: &EditorEvent, _, _| match event {
   82                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
   83                    EditorEvent::BufferEdited => {
   84                        events.borrow_mut().push(("editor1", "buffer edited"))
   85                    }
   86                    _ => {}
   87                },
   88            )
   89            .detach();
   90            Editor::for_buffer(buffer.clone(), None, window, cx)
   91        }
   92    });
   93
   94    let editor2 = cx.add_window({
   95        let events = events.clone();
   96        |window, cx| {
   97            cx.subscribe_in(
   98                &cx.entity().clone(),
   99                window,
  100                move |_, _, event: &EditorEvent, _, _| match event {
  101                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
  102                    EditorEvent::BufferEdited => {
  103                        events.borrow_mut().push(("editor2", "buffer edited"))
  104                    }
  105                    _ => {}
  106                },
  107            )
  108            .detach();
  109            Editor::for_buffer(buffer.clone(), None, window, cx)
  110        }
  111    });
  112
  113    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  114
  115    // Mutating editor 1 will emit an `Edited` event only for that editor.
  116    _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
  117    assert_eq!(
  118        mem::take(&mut *events.borrow_mut()),
  119        [
  120            ("editor1", "edited"),
  121            ("editor1", "buffer edited"),
  122            ("editor2", "buffer edited"),
  123        ]
  124    );
  125
  126    // Mutating editor 2 will emit an `Edited` event only for that editor.
  127    _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
  128    assert_eq!(
  129        mem::take(&mut *events.borrow_mut()),
  130        [
  131            ("editor2", "edited"),
  132            ("editor1", "buffer edited"),
  133            ("editor2", "buffer edited"),
  134        ]
  135    );
  136
  137    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  138    _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  139    assert_eq!(
  140        mem::take(&mut *events.borrow_mut()),
  141        [
  142            ("editor1", "edited"),
  143            ("editor1", "buffer edited"),
  144            ("editor2", "buffer edited"),
  145        ]
  146    );
  147
  148    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  149    _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  150    assert_eq!(
  151        mem::take(&mut *events.borrow_mut()),
  152        [
  153            ("editor1", "edited"),
  154            ("editor1", "buffer edited"),
  155            ("editor2", "buffer edited"),
  156        ]
  157    );
  158
  159    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  160    _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  161    assert_eq!(
  162        mem::take(&mut *events.borrow_mut()),
  163        [
  164            ("editor2", "edited"),
  165            ("editor1", "buffer edited"),
  166            ("editor2", "buffer edited"),
  167        ]
  168    );
  169
  170    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  171    _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  172    assert_eq!(
  173        mem::take(&mut *events.borrow_mut()),
  174        [
  175            ("editor2", "edited"),
  176            ("editor1", "buffer edited"),
  177            ("editor2", "buffer edited"),
  178        ]
  179    );
  180
  181    // No event is emitted when the mutation is a no-op.
  182    _ = editor2.update(cx, |editor, window, cx| {
  183        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  184            s.select_ranges([0..0])
  185        });
  186
  187        editor.backspace(&Backspace, window, cx);
  188    });
  189    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  190}
  191
  192#[gpui::test]
  193fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  194    init_test(cx, |_| {});
  195
  196    let mut now = Instant::now();
  197    let group_interval = Duration::from_millis(1);
  198    let buffer = cx.new(|cx| {
  199        let mut buf = language::Buffer::local("123456", cx);
  200        buf.set_group_interval(group_interval);
  201        buf
  202    });
  203    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  204    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
  205
  206    _ = editor.update(cx, |editor, window, cx| {
  207        editor.start_transaction_at(now, window, cx);
  208        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  209            s.select_ranges([2..4])
  210        });
  211
  212        editor.insert("cd", window, cx);
  213        editor.end_transaction_at(now, cx);
  214        assert_eq!(editor.text(cx), "12cd56");
  215        assert_eq!(editor.selections.ranges(cx), vec![4..4]);
  216
  217        editor.start_transaction_at(now, window, cx);
  218        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  219            s.select_ranges([4..5])
  220        });
  221        editor.insert("e", window, cx);
  222        editor.end_transaction_at(now, cx);
  223        assert_eq!(editor.text(cx), "12cde6");
  224        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  225
  226        now += group_interval + Duration::from_millis(1);
  227        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  228            s.select_ranges([2..2])
  229        });
  230
  231        // Simulate an edit in another editor
  232        buffer.update(cx, |buffer, cx| {
  233            buffer.start_transaction_at(now, cx);
  234            buffer.edit([(0..1, "a")], None, cx);
  235            buffer.edit([(1..1, "b")], None, cx);
  236            buffer.end_transaction_at(now, cx);
  237        });
  238
  239        assert_eq!(editor.text(cx), "ab2cde6");
  240        assert_eq!(editor.selections.ranges(cx), vec![3..3]);
  241
  242        // Last transaction happened past the group interval in a different editor.
  243        // Undo it individually and don't restore selections.
  244        editor.undo(&Undo, window, cx);
  245        assert_eq!(editor.text(cx), "12cde6");
  246        assert_eq!(editor.selections.ranges(cx), vec![2..2]);
  247
  248        // First two transactions happened within the group interval in this editor.
  249        // Undo them together and restore selections.
  250        editor.undo(&Undo, window, cx);
  251        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  252        assert_eq!(editor.text(cx), "123456");
  253        assert_eq!(editor.selections.ranges(cx), vec![0..0]);
  254
  255        // Redo the first two transactions together.
  256        editor.redo(&Redo, window, cx);
  257        assert_eq!(editor.text(cx), "12cde6");
  258        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  259
  260        // Redo the last transaction on its own.
  261        editor.redo(&Redo, window, cx);
  262        assert_eq!(editor.text(cx), "ab2cde6");
  263        assert_eq!(editor.selections.ranges(cx), vec![6..6]);
  264
  265        // Test empty transactions.
  266        editor.start_transaction_at(now, window, cx);
  267        editor.end_transaction_at(now, cx);
  268        editor.undo(&Undo, window, cx);
  269        assert_eq!(editor.text(cx), "12cde6");
  270    });
  271}
  272
  273#[gpui::test]
  274fn test_ime_composition(cx: &mut TestAppContext) {
  275    init_test(cx, |_| {});
  276
  277    let buffer = cx.new(|cx| {
  278        let mut buffer = language::Buffer::local("abcde", cx);
  279        // Ensure automatic grouping doesn't occur.
  280        buffer.set_group_interval(Duration::ZERO);
  281        buffer
  282    });
  283
  284    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  285    cx.add_window(|window, cx| {
  286        let mut editor = build_editor(buffer.clone(), window, cx);
  287
  288        // Start a new IME composition.
  289        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  290        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
  291        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  292        assert_eq!(editor.text(cx), "äbcde");
  293        assert_eq!(
  294            editor.marked_text_ranges(cx),
  295            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  296        );
  297
  298        // Finalize IME composition.
  299        editor.replace_text_in_range(None, "ā", window, cx);
  300        assert_eq!(editor.text(cx), "ābcde");
  301        assert_eq!(editor.marked_text_ranges(cx), None);
  302
  303        // IME composition edits are grouped and are undone/redone at once.
  304        editor.undo(&Default::default(), window, cx);
  305        assert_eq!(editor.text(cx), "abcde");
  306        assert_eq!(editor.marked_text_ranges(cx), None);
  307        editor.redo(&Default::default(), window, cx);
  308        assert_eq!(editor.text(cx), "ābcde");
  309        assert_eq!(editor.marked_text_ranges(cx), None);
  310
  311        // Start a new IME composition.
  312        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  313        assert_eq!(
  314            editor.marked_text_ranges(cx),
  315            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  316        );
  317
  318        // Undoing during an IME composition cancels it.
  319        editor.undo(&Default::default(), window, cx);
  320        assert_eq!(editor.text(cx), "ābcde");
  321        assert_eq!(editor.marked_text_ranges(cx), None);
  322
  323        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  324        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  325        assert_eq!(editor.text(cx), "ābcdè");
  326        assert_eq!(
  327            editor.marked_text_ranges(cx),
  328            Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
  329        );
  330
  331        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  332        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  333        assert_eq!(editor.text(cx), "ābcdę");
  334        assert_eq!(editor.marked_text_ranges(cx), None);
  335
  336        // Start a new IME composition with multiple cursors.
  337        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  338            s.select_ranges([
  339                OffsetUtf16(1)..OffsetUtf16(1),
  340                OffsetUtf16(3)..OffsetUtf16(3),
  341                OffsetUtf16(5)..OffsetUtf16(5),
  342            ])
  343        });
  344        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  345        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  346        assert_eq!(
  347            editor.marked_text_ranges(cx),
  348            Some(vec![
  349                OffsetUtf16(0)..OffsetUtf16(3),
  350                OffsetUtf16(4)..OffsetUtf16(7),
  351                OffsetUtf16(8)..OffsetUtf16(11)
  352            ])
  353        );
  354
  355        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  356        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  357        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  358        assert_eq!(
  359            editor.marked_text_ranges(cx),
  360            Some(vec![
  361                OffsetUtf16(1)..OffsetUtf16(2),
  362                OffsetUtf16(5)..OffsetUtf16(6),
  363                OffsetUtf16(9)..OffsetUtf16(10)
  364            ])
  365        );
  366
  367        // Finalize IME composition with multiple cursors.
  368        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  369        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  370        assert_eq!(editor.marked_text_ranges(cx), None);
  371
  372        editor
  373    });
  374}
  375
  376#[gpui::test]
  377fn test_selection_with_mouse(cx: &mut TestAppContext) {
  378    init_test(cx, |_| {});
  379
  380    let editor = cx.add_window(|window, cx| {
  381        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  382        build_editor(buffer, window, cx)
  383    });
  384
  385    _ = editor.update(cx, |editor, window, cx| {
  386        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  387    });
  388    assert_eq!(
  389        editor
  390            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  391            .unwrap(),
  392        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  393    );
  394
  395    _ = editor.update(cx, |editor, window, cx| {
  396        editor.update_selection(
  397            DisplayPoint::new(DisplayRow(3), 3),
  398            0,
  399            gpui::Point::<f32>::default(),
  400            window,
  401            cx,
  402        );
  403    });
  404
  405    assert_eq!(
  406        editor
  407            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  408            .unwrap(),
  409        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  410    );
  411
  412    _ = editor.update(cx, |editor, window, cx| {
  413        editor.update_selection(
  414            DisplayPoint::new(DisplayRow(1), 1),
  415            0,
  416            gpui::Point::<f32>::default(),
  417            window,
  418            cx,
  419        );
  420    });
  421
  422    assert_eq!(
  423        editor
  424            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  425            .unwrap(),
  426        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  427    );
  428
  429    _ = editor.update(cx, |editor, window, cx| {
  430        editor.end_selection(window, cx);
  431        editor.update_selection(
  432            DisplayPoint::new(DisplayRow(3), 3),
  433            0,
  434            gpui::Point::<f32>::default(),
  435            window,
  436            cx,
  437        );
  438    });
  439
  440    assert_eq!(
  441        editor
  442            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  443            .unwrap(),
  444        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  445    );
  446
  447    _ = editor.update(cx, |editor, window, cx| {
  448        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  449        editor.update_selection(
  450            DisplayPoint::new(DisplayRow(0), 0),
  451            0,
  452            gpui::Point::<f32>::default(),
  453            window,
  454            cx,
  455        );
  456    });
  457
  458    assert_eq!(
  459        editor
  460            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  461            .unwrap(),
  462        [
  463            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  464            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  465        ]
  466    );
  467
  468    _ = editor.update(cx, |editor, window, cx| {
  469        editor.end_selection(window, cx);
  470    });
  471
  472    assert_eq!(
  473        editor
  474            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  475            .unwrap(),
  476        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  477    );
  478}
  479
  480#[gpui::test]
  481fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  482    init_test(cx, |_| {});
  483
  484    let editor = cx.add_window(|window, cx| {
  485        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  486        build_editor(buffer, window, cx)
  487    });
  488
  489    _ = editor.update(cx, |editor, window, cx| {
  490        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  491    });
  492
  493    _ = editor.update(cx, |editor, window, cx| {
  494        editor.end_selection(window, cx);
  495    });
  496
  497    _ = editor.update(cx, |editor, window, cx| {
  498        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  499    });
  500
  501    _ = editor.update(cx, |editor, window, cx| {
  502        editor.end_selection(window, cx);
  503    });
  504
  505    assert_eq!(
  506        editor
  507            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  508            .unwrap(),
  509        [
  510            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  511            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  512        ]
  513    );
  514
  515    _ = editor.update(cx, |editor, window, cx| {
  516        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  517    });
  518
  519    _ = editor.update(cx, |editor, window, cx| {
  520        editor.end_selection(window, cx);
  521    });
  522
  523    assert_eq!(
  524        editor
  525            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  526            .unwrap(),
  527        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  528    );
  529}
  530
  531#[gpui::test]
  532fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  533    init_test(cx, |_| {});
  534
  535    let editor = cx.add_window(|window, cx| {
  536        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  537        build_editor(buffer, window, cx)
  538    });
  539
  540    _ = editor.update(cx, |editor, window, cx| {
  541        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  542        assert_eq!(
  543            editor.selections.display_ranges(cx),
  544            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  545        );
  546    });
  547
  548    _ = editor.update(cx, |editor, window, cx| {
  549        editor.update_selection(
  550            DisplayPoint::new(DisplayRow(3), 3),
  551            0,
  552            gpui::Point::<f32>::default(),
  553            window,
  554            cx,
  555        );
  556        assert_eq!(
  557            editor.selections.display_ranges(cx),
  558            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  559        );
  560    });
  561
  562    _ = editor.update(cx, |editor, window, cx| {
  563        editor.cancel(&Cancel, window, cx);
  564        editor.update_selection(
  565            DisplayPoint::new(DisplayRow(1), 1),
  566            0,
  567            gpui::Point::<f32>::default(),
  568            window,
  569            cx,
  570        );
  571        assert_eq!(
  572            editor.selections.display_ranges(cx),
  573            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  574        );
  575    });
  576}
  577
  578#[gpui::test]
  579fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  580    init_test(cx, |_| {});
  581
  582    let editor = cx.add_window(|window, cx| {
  583        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  584        build_editor(buffer, window, cx)
  585    });
  586
  587    _ = editor.update(cx, |editor, window, cx| {
  588        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  589        assert_eq!(
  590            editor.selections.display_ranges(cx),
  591            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  592        );
  593
  594        editor.move_down(&Default::default(), window, cx);
  595        assert_eq!(
  596            editor.selections.display_ranges(cx),
  597            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  598        );
  599
  600        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  601        assert_eq!(
  602            editor.selections.display_ranges(cx),
  603            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  604        );
  605
  606        editor.move_up(&Default::default(), window, cx);
  607        assert_eq!(
  608            editor.selections.display_ranges(cx),
  609            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  610        );
  611    });
  612}
  613
  614#[gpui::test]
  615fn test_clone(cx: &mut TestAppContext) {
  616    init_test(cx, |_| {});
  617
  618    let (text, selection_ranges) = marked_text_ranges(
  619        indoc! {"
  620            one
  621            two
  622            threeˇ
  623            four
  624            fiveˇ
  625        "},
  626        true,
  627    );
  628
  629    let editor = cx.add_window(|window, cx| {
  630        let buffer = MultiBuffer::build_simple(&text, cx);
  631        build_editor(buffer, window, cx)
  632    });
  633
  634    _ = editor.update(cx, |editor, window, cx| {
  635        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  636            s.select_ranges(selection_ranges.clone())
  637        });
  638        editor.fold_creases(
  639            vec![
  640                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  641                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  642            ],
  643            true,
  644            window,
  645            cx,
  646        );
  647    });
  648
  649    let cloned_editor = editor
  650        .update(cx, |editor, _, cx| {
  651            cx.open_window(Default::default(), |window, cx| {
  652                cx.new(|cx| editor.clone(window, cx))
  653            })
  654        })
  655        .unwrap()
  656        .unwrap();
  657
  658    let snapshot = editor
  659        .update(cx, |e, window, cx| e.snapshot(window, cx))
  660        .unwrap();
  661    let cloned_snapshot = cloned_editor
  662        .update(cx, |e, window, cx| e.snapshot(window, cx))
  663        .unwrap();
  664
  665    assert_eq!(
  666        cloned_editor
  667            .update(cx, |e, _, cx| e.display_text(cx))
  668            .unwrap(),
  669        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  670    );
  671    assert_eq!(
  672        cloned_snapshot
  673            .folds_in_range(0..text.len())
  674            .collect::<Vec<_>>(),
  675        snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
  676    );
  677    assert_set_eq!(
  678        cloned_editor
  679            .update(cx, |editor, _, cx| editor.selections.ranges::<Point>(cx))
  680            .unwrap(),
  681        editor
  682            .update(cx, |editor, _, cx| editor.selections.ranges(cx))
  683            .unwrap()
  684    );
  685    assert_set_eq!(
  686        cloned_editor
  687            .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
  688            .unwrap(),
  689        editor
  690            .update(cx, |e, _, cx| e.selections.display_ranges(cx))
  691            .unwrap()
  692    );
  693}
  694
  695#[gpui::test]
  696async fn test_navigation_history(cx: &mut TestAppContext) {
  697    init_test(cx, |_| {});
  698
  699    use workspace::item::Item;
  700
  701    let fs = FakeFs::new(cx.executor());
  702    let project = Project::test(fs, [], cx).await;
  703    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  704    let pane = workspace
  705        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  706        .unwrap();
  707
  708    _ = workspace.update(cx, |_v, window, cx| {
  709        cx.new(|cx| {
  710            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  711            let mut editor = build_editor(buffer.clone(), window, cx);
  712            let handle = cx.entity();
  713            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  714
  715            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  716                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  717            }
  718
  719            // Move the cursor a small distance.
  720            // Nothing is added to the navigation history.
  721            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  722                s.select_display_ranges([
  723                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  724                ])
  725            });
  726            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  727                s.select_display_ranges([
  728                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  729                ])
  730            });
  731            assert!(pop_history(&mut editor, cx).is_none());
  732
  733            // Move the cursor a large distance.
  734            // The history can jump back to the previous position.
  735            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  736                s.select_display_ranges([
  737                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  738                ])
  739            });
  740            let nav_entry = pop_history(&mut editor, cx).unwrap();
  741            editor.navigate(nav_entry.data.unwrap(), window, cx);
  742            assert_eq!(nav_entry.item.id(), cx.entity_id());
  743            assert_eq!(
  744                editor.selections.display_ranges(cx),
  745                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  746            );
  747            assert!(pop_history(&mut editor, cx).is_none());
  748
  749            // Move the cursor a small distance via the mouse.
  750            // Nothing is added to the navigation history.
  751            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  752            editor.end_selection(window, cx);
  753            assert_eq!(
  754                editor.selections.display_ranges(cx),
  755                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  756            );
  757            assert!(pop_history(&mut editor, cx).is_none());
  758
  759            // Move the cursor a large distance via the mouse.
  760            // The history can jump back to the previous position.
  761            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  762            editor.end_selection(window, cx);
  763            assert_eq!(
  764                editor.selections.display_ranges(cx),
  765                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  766            );
  767            let nav_entry = pop_history(&mut editor, cx).unwrap();
  768            editor.navigate(nav_entry.data.unwrap(), window, cx);
  769            assert_eq!(nav_entry.item.id(), cx.entity_id());
  770            assert_eq!(
  771                editor.selections.display_ranges(cx),
  772                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  773            );
  774            assert!(pop_history(&mut editor, cx).is_none());
  775
  776            // Set scroll position to check later
  777            editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), window, cx);
  778            let original_scroll_position = editor.scroll_manager.anchor();
  779
  780            // Jump to the end of the document and adjust scroll
  781            editor.move_to_end(&MoveToEnd, window, cx);
  782            editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), window, cx);
  783            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  784
  785            let nav_entry = pop_history(&mut editor, cx).unwrap();
  786            editor.navigate(nav_entry.data.unwrap(), window, cx);
  787            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  788
  789            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  790            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  791            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  792            let invalid_point = Point::new(9999, 0);
  793            editor.navigate(
  794                Box::new(NavigationData {
  795                    cursor_anchor: invalid_anchor,
  796                    cursor_position: invalid_point,
  797                    scroll_anchor: ScrollAnchor {
  798                        anchor: invalid_anchor,
  799                        offset: Default::default(),
  800                    },
  801                    scroll_top_row: invalid_point.row,
  802                }),
  803                window,
  804                cx,
  805            );
  806            assert_eq!(
  807                editor.selections.display_ranges(cx),
  808                &[editor.max_point(cx)..editor.max_point(cx)]
  809            );
  810            assert_eq!(
  811                editor.scroll_position(cx),
  812                gpui::Point::new(0., editor.max_point(cx).row().as_f32())
  813            );
  814
  815            editor
  816        })
  817    });
  818}
  819
  820#[gpui::test]
  821fn test_cancel(cx: &mut TestAppContext) {
  822    init_test(cx, |_| {});
  823
  824    let editor = cx.add_window(|window, cx| {
  825        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  826        build_editor(buffer, window, cx)
  827    });
  828
  829    _ = editor.update(cx, |editor, window, cx| {
  830        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
  831        editor.update_selection(
  832            DisplayPoint::new(DisplayRow(1), 1),
  833            0,
  834            gpui::Point::<f32>::default(),
  835            window,
  836            cx,
  837        );
  838        editor.end_selection(window, cx);
  839
  840        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
  841        editor.update_selection(
  842            DisplayPoint::new(DisplayRow(0), 3),
  843            0,
  844            gpui::Point::<f32>::default(),
  845            window,
  846            cx,
  847        );
  848        editor.end_selection(window, cx);
  849        assert_eq!(
  850            editor.selections.display_ranges(cx),
  851            [
  852                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
  853                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
  854            ]
  855        );
  856    });
  857
  858    _ = editor.update(cx, |editor, window, cx| {
  859        editor.cancel(&Cancel, window, cx);
  860        assert_eq!(
  861            editor.selections.display_ranges(cx),
  862            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
  863        );
  864    });
  865
  866    _ = editor.update(cx, |editor, window, cx| {
  867        editor.cancel(&Cancel, window, cx);
  868        assert_eq!(
  869            editor.selections.display_ranges(cx),
  870            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
  871        );
  872    });
  873}
  874
  875#[gpui::test]
  876fn test_fold_action(cx: &mut TestAppContext) {
  877    init_test(cx, |_| {});
  878
  879    let editor = cx.add_window(|window, cx| {
  880        let buffer = MultiBuffer::build_simple(
  881            &"
  882                impl Foo {
  883                    // Hello!
  884
  885                    fn a() {
  886                        1
  887                    }
  888
  889                    fn b() {
  890                        2
  891                    }
  892
  893                    fn c() {
  894                        3
  895                    }
  896                }
  897            "
  898            .unindent(),
  899            cx,
  900        );
  901        build_editor(buffer.clone(), window, cx)
  902    });
  903
  904    _ = editor.update(cx, |editor, window, cx| {
  905        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  906            s.select_display_ranges([
  907                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
  908            ]);
  909        });
  910        editor.fold(&Fold, window, cx);
  911        assert_eq!(
  912            editor.display_text(cx),
  913            "
  914                impl Foo {
  915                    // Hello!
  916
  917                    fn a() {
  918                        1
  919                    }
  920
  921                    fn b() {⋯
  922                    }
  923
  924                    fn c() {⋯
  925                    }
  926                }
  927            "
  928            .unindent(),
  929        );
  930
  931        editor.fold(&Fold, window, cx);
  932        assert_eq!(
  933            editor.display_text(cx),
  934            "
  935                impl Foo {⋯
  936                }
  937            "
  938            .unindent(),
  939        );
  940
  941        editor.unfold_lines(&UnfoldLines, window, cx);
  942        assert_eq!(
  943            editor.display_text(cx),
  944            "
  945                impl Foo {
  946                    // Hello!
  947
  948                    fn a() {
  949                        1
  950                    }
  951
  952                    fn b() {⋯
  953                    }
  954
  955                    fn c() {⋯
  956                    }
  957                }
  958            "
  959            .unindent(),
  960        );
  961
  962        editor.unfold_lines(&UnfoldLines, window, cx);
  963        assert_eq!(
  964            editor.display_text(cx),
  965            editor.buffer.read(cx).read(cx).text()
  966        );
  967    });
  968}
  969
  970#[gpui::test]
  971fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
  972    init_test(cx, |_| {});
  973
  974    let editor = cx.add_window(|window, cx| {
  975        let buffer = MultiBuffer::build_simple(
  976            &"
  977                class Foo:
  978                    # Hello!
  979
  980                    def a():
  981                        print(1)
  982
  983                    def b():
  984                        print(2)
  985
  986                    def c():
  987                        print(3)
  988            "
  989            .unindent(),
  990            cx,
  991        );
  992        build_editor(buffer.clone(), window, cx)
  993    });
  994
  995    _ = editor.update(cx, |editor, window, cx| {
  996        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  997            s.select_display_ranges([
  998                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
  999            ]);
 1000        });
 1001        editor.fold(&Fold, window, cx);
 1002        assert_eq!(
 1003            editor.display_text(cx),
 1004            "
 1005                class Foo:
 1006                    # Hello!
 1007
 1008                    def a():
 1009                        print(1)
 1010
 1011                    def b():⋯
 1012
 1013                    def c():⋯
 1014            "
 1015            .unindent(),
 1016        );
 1017
 1018        editor.fold(&Fold, window, cx);
 1019        assert_eq!(
 1020            editor.display_text(cx),
 1021            "
 1022                class Foo:⋯
 1023            "
 1024            .unindent(),
 1025        );
 1026
 1027        editor.unfold_lines(&UnfoldLines, window, cx);
 1028        assert_eq!(
 1029            editor.display_text(cx),
 1030            "
 1031                class Foo:
 1032                    # Hello!
 1033
 1034                    def a():
 1035                        print(1)
 1036
 1037                    def b():⋯
 1038
 1039                    def c():⋯
 1040            "
 1041            .unindent(),
 1042        );
 1043
 1044        editor.unfold_lines(&UnfoldLines, window, cx);
 1045        assert_eq!(
 1046            editor.display_text(cx),
 1047            editor.buffer.read(cx).read(cx).text()
 1048        );
 1049    });
 1050}
 1051
 1052#[gpui::test]
 1053fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1054    init_test(cx, |_| {});
 1055
 1056    let editor = cx.add_window(|window, cx| {
 1057        let buffer = MultiBuffer::build_simple(
 1058            &"
 1059                class Foo:
 1060                    # Hello!
 1061
 1062                    def a():
 1063                        print(1)
 1064
 1065                    def b():
 1066                        print(2)
 1067
 1068
 1069                    def c():
 1070                        print(3)
 1071
 1072
 1073            "
 1074            .unindent(),
 1075            cx,
 1076        );
 1077        build_editor(buffer.clone(), window, cx)
 1078    });
 1079
 1080    _ = editor.update(cx, |editor, window, cx| {
 1081        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1082            s.select_display_ranges([
 1083                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1084            ]);
 1085        });
 1086        editor.fold(&Fold, window, cx);
 1087        assert_eq!(
 1088            editor.display_text(cx),
 1089            "
 1090                class Foo:
 1091                    # Hello!
 1092
 1093                    def a():
 1094                        print(1)
 1095
 1096                    def b():⋯
 1097
 1098
 1099                    def c():⋯
 1100
 1101
 1102            "
 1103            .unindent(),
 1104        );
 1105
 1106        editor.fold(&Fold, window, cx);
 1107        assert_eq!(
 1108            editor.display_text(cx),
 1109            "
 1110                class Foo:⋯
 1111
 1112
 1113            "
 1114            .unindent(),
 1115        );
 1116
 1117        editor.unfold_lines(&UnfoldLines, window, cx);
 1118        assert_eq!(
 1119            editor.display_text(cx),
 1120            "
 1121                class Foo:
 1122                    # Hello!
 1123
 1124                    def a():
 1125                        print(1)
 1126
 1127                    def b():⋯
 1128
 1129
 1130                    def c():⋯
 1131
 1132
 1133            "
 1134            .unindent(),
 1135        );
 1136
 1137        editor.unfold_lines(&UnfoldLines, window, cx);
 1138        assert_eq!(
 1139            editor.display_text(cx),
 1140            editor.buffer.read(cx).read(cx).text()
 1141        );
 1142    });
 1143}
 1144
 1145#[gpui::test]
 1146fn test_fold_at_level(cx: &mut TestAppContext) {
 1147    init_test(cx, |_| {});
 1148
 1149    let editor = cx.add_window(|window, cx| {
 1150        let buffer = MultiBuffer::build_simple(
 1151            &"
 1152                class Foo:
 1153                    # Hello!
 1154
 1155                    def a():
 1156                        print(1)
 1157
 1158                    def b():
 1159                        print(2)
 1160
 1161
 1162                class Bar:
 1163                    # World!
 1164
 1165                    def a():
 1166                        print(1)
 1167
 1168                    def b():
 1169                        print(2)
 1170
 1171
 1172            "
 1173            .unindent(),
 1174            cx,
 1175        );
 1176        build_editor(buffer.clone(), window, cx)
 1177    });
 1178
 1179    _ = editor.update(cx, |editor, window, cx| {
 1180        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1181        assert_eq!(
 1182            editor.display_text(cx),
 1183            "
 1184                class Foo:
 1185                    # Hello!
 1186
 1187                    def a():⋯
 1188
 1189                    def b():⋯
 1190
 1191
 1192                class Bar:
 1193                    # World!
 1194
 1195                    def a():⋯
 1196
 1197                    def b():⋯
 1198
 1199
 1200            "
 1201            .unindent(),
 1202        );
 1203
 1204        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1205        assert_eq!(
 1206            editor.display_text(cx),
 1207            "
 1208                class Foo:⋯
 1209
 1210
 1211                class Bar:⋯
 1212
 1213
 1214            "
 1215            .unindent(),
 1216        );
 1217
 1218        editor.unfold_all(&UnfoldAll, window, cx);
 1219        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1220        assert_eq!(
 1221            editor.display_text(cx),
 1222            "
 1223                class Foo:
 1224                    # Hello!
 1225
 1226                    def a():
 1227                        print(1)
 1228
 1229                    def b():
 1230                        print(2)
 1231
 1232
 1233                class Bar:
 1234                    # World!
 1235
 1236                    def a():
 1237                        print(1)
 1238
 1239                    def b():
 1240                        print(2)
 1241
 1242
 1243            "
 1244            .unindent(),
 1245        );
 1246
 1247        assert_eq!(
 1248            editor.display_text(cx),
 1249            editor.buffer.read(cx).read(cx).text()
 1250        );
 1251    });
 1252}
 1253
 1254#[gpui::test]
 1255fn test_move_cursor(cx: &mut TestAppContext) {
 1256    init_test(cx, |_| {});
 1257
 1258    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1259    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1260
 1261    buffer.update(cx, |buffer, cx| {
 1262        buffer.edit(
 1263            vec![
 1264                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1265                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1266            ],
 1267            None,
 1268            cx,
 1269        );
 1270    });
 1271    _ = editor.update(cx, |editor, window, cx| {
 1272        assert_eq!(
 1273            editor.selections.display_ranges(cx),
 1274            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1275        );
 1276
 1277        editor.move_down(&MoveDown, window, cx);
 1278        assert_eq!(
 1279            editor.selections.display_ranges(cx),
 1280            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1281        );
 1282
 1283        editor.move_right(&MoveRight, window, cx);
 1284        assert_eq!(
 1285            editor.selections.display_ranges(cx),
 1286            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1287        );
 1288
 1289        editor.move_left(&MoveLeft, window, cx);
 1290        assert_eq!(
 1291            editor.selections.display_ranges(cx),
 1292            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1293        );
 1294
 1295        editor.move_up(&MoveUp, window, cx);
 1296        assert_eq!(
 1297            editor.selections.display_ranges(cx),
 1298            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1299        );
 1300
 1301        editor.move_to_end(&MoveToEnd, window, cx);
 1302        assert_eq!(
 1303            editor.selections.display_ranges(cx),
 1304            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1305        );
 1306
 1307        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1308        assert_eq!(
 1309            editor.selections.display_ranges(cx),
 1310            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1311        );
 1312
 1313        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1314            s.select_display_ranges([
 1315                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1316            ]);
 1317        });
 1318        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1319        assert_eq!(
 1320            editor.selections.display_ranges(cx),
 1321            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1322        );
 1323
 1324        editor.select_to_end(&SelectToEnd, window, cx);
 1325        assert_eq!(
 1326            editor.selections.display_ranges(cx),
 1327            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1328        );
 1329    });
 1330}
 1331
 1332#[gpui::test]
 1333fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1334    init_test(cx, |_| {});
 1335
 1336    let editor = cx.add_window(|window, cx| {
 1337        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1338        build_editor(buffer.clone(), window, cx)
 1339    });
 1340
 1341    assert_eq!('🟥'.len_utf8(), 4);
 1342    assert_eq!('α'.len_utf8(), 2);
 1343
 1344    _ = editor.update(cx, |editor, window, cx| {
 1345        editor.fold_creases(
 1346            vec![
 1347                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1348                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1349                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1350            ],
 1351            true,
 1352            window,
 1353            cx,
 1354        );
 1355        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1356
 1357        editor.move_right(&MoveRight, window, cx);
 1358        assert_eq!(
 1359            editor.selections.display_ranges(cx),
 1360            &[empty_range(0, "🟥".len())]
 1361        );
 1362        editor.move_right(&MoveRight, window, cx);
 1363        assert_eq!(
 1364            editor.selections.display_ranges(cx),
 1365            &[empty_range(0, "🟥🟧".len())]
 1366        );
 1367        editor.move_right(&MoveRight, window, cx);
 1368        assert_eq!(
 1369            editor.selections.display_ranges(cx),
 1370            &[empty_range(0, "🟥🟧⋯".len())]
 1371        );
 1372
 1373        editor.move_down(&MoveDown, window, cx);
 1374        assert_eq!(
 1375            editor.selections.display_ranges(cx),
 1376            &[empty_range(1, "ab⋯e".len())]
 1377        );
 1378        editor.move_left(&MoveLeft, window, cx);
 1379        assert_eq!(
 1380            editor.selections.display_ranges(cx),
 1381            &[empty_range(1, "ab⋯".len())]
 1382        );
 1383        editor.move_left(&MoveLeft, window, cx);
 1384        assert_eq!(
 1385            editor.selections.display_ranges(cx),
 1386            &[empty_range(1, "ab".len())]
 1387        );
 1388        editor.move_left(&MoveLeft, window, cx);
 1389        assert_eq!(
 1390            editor.selections.display_ranges(cx),
 1391            &[empty_range(1, "a".len())]
 1392        );
 1393
 1394        editor.move_down(&MoveDown, window, cx);
 1395        assert_eq!(
 1396            editor.selections.display_ranges(cx),
 1397            &[empty_range(2, "α".len())]
 1398        );
 1399        editor.move_right(&MoveRight, window, cx);
 1400        assert_eq!(
 1401            editor.selections.display_ranges(cx),
 1402            &[empty_range(2, "αβ".len())]
 1403        );
 1404        editor.move_right(&MoveRight, window, cx);
 1405        assert_eq!(
 1406            editor.selections.display_ranges(cx),
 1407            &[empty_range(2, "αβ⋯".len())]
 1408        );
 1409        editor.move_right(&MoveRight, window, cx);
 1410        assert_eq!(
 1411            editor.selections.display_ranges(cx),
 1412            &[empty_range(2, "αβ⋯ε".len())]
 1413        );
 1414
 1415        editor.move_up(&MoveUp, window, cx);
 1416        assert_eq!(
 1417            editor.selections.display_ranges(cx),
 1418            &[empty_range(1, "ab⋯e".len())]
 1419        );
 1420        editor.move_down(&MoveDown, window, cx);
 1421        assert_eq!(
 1422            editor.selections.display_ranges(cx),
 1423            &[empty_range(2, "αβ⋯ε".len())]
 1424        );
 1425        editor.move_up(&MoveUp, window, cx);
 1426        assert_eq!(
 1427            editor.selections.display_ranges(cx),
 1428            &[empty_range(1, "ab⋯e".len())]
 1429        );
 1430
 1431        editor.move_up(&MoveUp, window, cx);
 1432        assert_eq!(
 1433            editor.selections.display_ranges(cx),
 1434            &[empty_range(0, "🟥🟧".len())]
 1435        );
 1436        editor.move_left(&MoveLeft, window, cx);
 1437        assert_eq!(
 1438            editor.selections.display_ranges(cx),
 1439            &[empty_range(0, "🟥".len())]
 1440        );
 1441        editor.move_left(&MoveLeft, window, cx);
 1442        assert_eq!(
 1443            editor.selections.display_ranges(cx),
 1444            &[empty_range(0, "".len())]
 1445        );
 1446    });
 1447}
 1448
 1449#[gpui::test]
 1450fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1451    init_test(cx, |_| {});
 1452
 1453    let editor = cx.add_window(|window, cx| {
 1454        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1455        build_editor(buffer.clone(), window, cx)
 1456    });
 1457    _ = editor.update(cx, |editor, window, cx| {
 1458        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1459            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1460        });
 1461
 1462        // moving above start of document should move selection to start of document,
 1463        // but the next move down should still be at the original goal_x
 1464        editor.move_up(&MoveUp, window, cx);
 1465        assert_eq!(
 1466            editor.selections.display_ranges(cx),
 1467            &[empty_range(0, "".len())]
 1468        );
 1469
 1470        editor.move_down(&MoveDown, window, cx);
 1471        assert_eq!(
 1472            editor.selections.display_ranges(cx),
 1473            &[empty_range(1, "abcd".len())]
 1474        );
 1475
 1476        editor.move_down(&MoveDown, window, cx);
 1477        assert_eq!(
 1478            editor.selections.display_ranges(cx),
 1479            &[empty_range(2, "αβγ".len())]
 1480        );
 1481
 1482        editor.move_down(&MoveDown, window, cx);
 1483        assert_eq!(
 1484            editor.selections.display_ranges(cx),
 1485            &[empty_range(3, "abcd".len())]
 1486        );
 1487
 1488        editor.move_down(&MoveDown, window, cx);
 1489        assert_eq!(
 1490            editor.selections.display_ranges(cx),
 1491            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1492        );
 1493
 1494        // moving past end of document should not change goal_x
 1495        editor.move_down(&MoveDown, window, cx);
 1496        assert_eq!(
 1497            editor.selections.display_ranges(cx),
 1498            &[empty_range(5, "".len())]
 1499        );
 1500
 1501        editor.move_down(&MoveDown, window, cx);
 1502        assert_eq!(
 1503            editor.selections.display_ranges(cx),
 1504            &[empty_range(5, "".len())]
 1505        );
 1506
 1507        editor.move_up(&MoveUp, window, cx);
 1508        assert_eq!(
 1509            editor.selections.display_ranges(cx),
 1510            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1511        );
 1512
 1513        editor.move_up(&MoveUp, window, cx);
 1514        assert_eq!(
 1515            editor.selections.display_ranges(cx),
 1516            &[empty_range(3, "abcd".len())]
 1517        );
 1518
 1519        editor.move_up(&MoveUp, window, cx);
 1520        assert_eq!(
 1521            editor.selections.display_ranges(cx),
 1522            &[empty_range(2, "αβγ".len())]
 1523        );
 1524    });
 1525}
 1526
 1527#[gpui::test]
 1528fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1529    init_test(cx, |_| {});
 1530    let move_to_beg = MoveToBeginningOfLine {
 1531        stop_at_soft_wraps: true,
 1532        stop_at_indent: true,
 1533    };
 1534
 1535    let delete_to_beg = DeleteToBeginningOfLine {
 1536        stop_at_indent: false,
 1537    };
 1538
 1539    let move_to_end = MoveToEndOfLine {
 1540        stop_at_soft_wraps: true,
 1541    };
 1542
 1543    let editor = cx.add_window(|window, cx| {
 1544        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1545        build_editor(buffer, window, cx)
 1546    });
 1547    _ = editor.update(cx, |editor, window, cx| {
 1548        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1549            s.select_display_ranges([
 1550                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1551                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1552            ]);
 1553        });
 1554    });
 1555
 1556    _ = editor.update(cx, |editor, window, cx| {
 1557        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1558        assert_eq!(
 1559            editor.selections.display_ranges(cx),
 1560            &[
 1561                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1562                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1563            ]
 1564        );
 1565    });
 1566
 1567    _ = editor.update(cx, |editor, window, cx| {
 1568        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1569        assert_eq!(
 1570            editor.selections.display_ranges(cx),
 1571            &[
 1572                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1573                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1574            ]
 1575        );
 1576    });
 1577
 1578    _ = editor.update(cx, |editor, window, cx| {
 1579        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1580        assert_eq!(
 1581            editor.selections.display_ranges(cx),
 1582            &[
 1583                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1584                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1585            ]
 1586        );
 1587    });
 1588
 1589    _ = editor.update(cx, |editor, window, cx| {
 1590        editor.move_to_end_of_line(&move_to_end, window, cx);
 1591        assert_eq!(
 1592            editor.selections.display_ranges(cx),
 1593            &[
 1594                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1595                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1596            ]
 1597        );
 1598    });
 1599
 1600    // Moving to the end of line again is a no-op.
 1601    _ = editor.update(cx, |editor, window, cx| {
 1602        editor.move_to_end_of_line(&move_to_end, window, cx);
 1603        assert_eq!(
 1604            editor.selections.display_ranges(cx),
 1605            &[
 1606                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1607                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1608            ]
 1609        );
 1610    });
 1611
 1612    _ = editor.update(cx, |editor, window, cx| {
 1613        editor.move_left(&MoveLeft, window, cx);
 1614        editor.select_to_beginning_of_line(
 1615            &SelectToBeginningOfLine {
 1616                stop_at_soft_wraps: true,
 1617                stop_at_indent: true,
 1618            },
 1619            window,
 1620            cx,
 1621        );
 1622        assert_eq!(
 1623            editor.selections.display_ranges(cx),
 1624            &[
 1625                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1626                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1627            ]
 1628        );
 1629    });
 1630
 1631    _ = editor.update(cx, |editor, window, cx| {
 1632        editor.select_to_beginning_of_line(
 1633            &SelectToBeginningOfLine {
 1634                stop_at_soft_wraps: true,
 1635                stop_at_indent: true,
 1636            },
 1637            window,
 1638            cx,
 1639        );
 1640        assert_eq!(
 1641            editor.selections.display_ranges(cx),
 1642            &[
 1643                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1644                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1645            ]
 1646        );
 1647    });
 1648
 1649    _ = editor.update(cx, |editor, window, cx| {
 1650        editor.select_to_beginning_of_line(
 1651            &SelectToBeginningOfLine {
 1652                stop_at_soft_wraps: true,
 1653                stop_at_indent: true,
 1654            },
 1655            window,
 1656            cx,
 1657        );
 1658        assert_eq!(
 1659            editor.selections.display_ranges(cx),
 1660            &[
 1661                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1662                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1663            ]
 1664        );
 1665    });
 1666
 1667    _ = editor.update(cx, |editor, window, cx| {
 1668        editor.select_to_end_of_line(
 1669            &SelectToEndOfLine {
 1670                stop_at_soft_wraps: true,
 1671            },
 1672            window,
 1673            cx,
 1674        );
 1675        assert_eq!(
 1676            editor.selections.display_ranges(cx),
 1677            &[
 1678                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1679                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1680            ]
 1681        );
 1682    });
 1683
 1684    _ = editor.update(cx, |editor, window, cx| {
 1685        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1686        assert_eq!(editor.display_text(cx), "ab\n  de");
 1687        assert_eq!(
 1688            editor.selections.display_ranges(cx),
 1689            &[
 1690                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1691                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1692            ]
 1693        );
 1694    });
 1695
 1696    _ = editor.update(cx, |editor, window, cx| {
 1697        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1698        assert_eq!(editor.display_text(cx), "\n");
 1699        assert_eq!(
 1700            editor.selections.display_ranges(cx),
 1701            &[
 1702                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1703                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1704            ]
 1705        );
 1706    });
 1707}
 1708
 1709#[gpui::test]
 1710fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1711    init_test(cx, |_| {});
 1712    let move_to_beg = MoveToBeginningOfLine {
 1713        stop_at_soft_wraps: false,
 1714        stop_at_indent: false,
 1715    };
 1716
 1717    let move_to_end = MoveToEndOfLine {
 1718        stop_at_soft_wraps: false,
 1719    };
 1720
 1721    let editor = cx.add_window(|window, cx| {
 1722        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1723        build_editor(buffer, window, cx)
 1724    });
 1725
 1726    _ = editor.update(cx, |editor, window, cx| {
 1727        editor.set_wrap_width(Some(140.0.into()), cx);
 1728
 1729        // We expect the following lines after wrapping
 1730        // ```
 1731        // thequickbrownfox
 1732        // jumpedoverthelazydo
 1733        // gs
 1734        // ```
 1735        // The final `gs` was soft-wrapped onto a new line.
 1736        assert_eq!(
 1737            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1738            editor.display_text(cx),
 1739        );
 1740
 1741        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1742        // Start the cursor at the `k` on the first line
 1743        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1744            s.select_display_ranges([
 1745                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1746            ]);
 1747        });
 1748
 1749        // Moving to the beginning of the line should put us at the beginning of the line.
 1750        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1751        assert_eq!(
 1752            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1753            editor.selections.display_ranges(cx)
 1754        );
 1755
 1756        // Moving to the end of the line should put us at the end of the line.
 1757        editor.move_to_end_of_line(&move_to_end, window, cx);
 1758        assert_eq!(
 1759            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1760            editor.selections.display_ranges(cx)
 1761        );
 1762
 1763        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1764        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1765        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1766            s.select_display_ranges([
 1767                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1768            ]);
 1769        });
 1770
 1771        // Moving to the beginning of the line should put us at the start of the second line of
 1772        // display text, i.e., the `j`.
 1773        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1774        assert_eq!(
 1775            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1776            editor.selections.display_ranges(cx)
 1777        );
 1778
 1779        // Moving to the beginning of the line again should be a no-op.
 1780        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1781        assert_eq!(
 1782            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1783            editor.selections.display_ranges(cx)
 1784        );
 1785
 1786        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1787        // next display line.
 1788        editor.move_to_end_of_line(&move_to_end, window, cx);
 1789        assert_eq!(
 1790            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1791            editor.selections.display_ranges(cx)
 1792        );
 1793
 1794        // Moving to the end of the line again should be a no-op.
 1795        editor.move_to_end_of_line(&move_to_end, window, cx);
 1796        assert_eq!(
 1797            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1798            editor.selections.display_ranges(cx)
 1799        );
 1800    });
 1801}
 1802
 1803#[gpui::test]
 1804fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1805    init_test(cx, |_| {});
 1806
 1807    let move_to_beg = MoveToBeginningOfLine {
 1808        stop_at_soft_wraps: true,
 1809        stop_at_indent: true,
 1810    };
 1811
 1812    let select_to_beg = SelectToBeginningOfLine {
 1813        stop_at_soft_wraps: true,
 1814        stop_at_indent: true,
 1815    };
 1816
 1817    let delete_to_beg = DeleteToBeginningOfLine {
 1818        stop_at_indent: true,
 1819    };
 1820
 1821    let move_to_end = MoveToEndOfLine {
 1822        stop_at_soft_wraps: false,
 1823    };
 1824
 1825    let editor = cx.add_window(|window, cx| {
 1826        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1827        build_editor(buffer, window, cx)
 1828    });
 1829
 1830    _ = editor.update(cx, |editor, window, cx| {
 1831        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1832            s.select_display_ranges([
 1833                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1834                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1835            ]);
 1836        });
 1837
 1838        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 1839        // and the second cursor at the first non-whitespace character in the line.
 1840        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1841        assert_eq!(
 1842            editor.selections.display_ranges(cx),
 1843            &[
 1844                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1845                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1846            ]
 1847        );
 1848
 1849        // Moving to the beginning of the line again should be a no-op for the first cursor,
 1850        // and should move the second cursor to the beginning of the line.
 1851        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1852        assert_eq!(
 1853            editor.selections.display_ranges(cx),
 1854            &[
 1855                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1856                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1857            ]
 1858        );
 1859
 1860        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 1861        // and should move the second cursor back to the first non-whitespace character in the line.
 1862        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1863        assert_eq!(
 1864            editor.selections.display_ranges(cx),
 1865            &[
 1866                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1867                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1868            ]
 1869        );
 1870
 1871        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 1872        // and to the first non-whitespace character in the line for the second cursor.
 1873        editor.move_to_end_of_line(&move_to_end, window, cx);
 1874        editor.move_left(&MoveLeft, window, cx);
 1875        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1876        assert_eq!(
 1877            editor.selections.display_ranges(cx),
 1878            &[
 1879                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1880                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1881            ]
 1882        );
 1883
 1884        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 1885        // and should select to the beginning of the line for the second cursor.
 1886        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1887        assert_eq!(
 1888            editor.selections.display_ranges(cx),
 1889            &[
 1890                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1891                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1892            ]
 1893        );
 1894
 1895        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 1896        // and should delete to the first non-whitespace character in the line for the second cursor.
 1897        editor.move_to_end_of_line(&move_to_end, window, cx);
 1898        editor.move_left(&MoveLeft, window, cx);
 1899        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1900        assert_eq!(editor.text(cx), "c\n  f");
 1901    });
 1902}
 1903
 1904#[gpui::test]
 1905fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 1906    init_test(cx, |_| {});
 1907
 1908    let editor = cx.add_window(|window, cx| {
 1909        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 1910        build_editor(buffer, window, cx)
 1911    });
 1912    _ = editor.update(cx, |editor, window, cx| {
 1913        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1914            s.select_display_ranges([
 1915                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 1916                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 1917            ])
 1918        });
 1919        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1920        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 1921
 1922        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1923        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 1924
 1925        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1926        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1927
 1928        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1929        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1930
 1931        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1932        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 1933
 1934        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1935        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1936
 1937        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1938        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 1939
 1940        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1941        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1942
 1943        editor.move_right(&MoveRight, window, cx);
 1944        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1945        assert_selection_ranges(
 1946            "use std::«ˇs»tr::{foo, bar}\n«ˇ\n»  {baz.qux()}",
 1947            editor,
 1948            cx,
 1949        );
 1950
 1951        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1952        assert_selection_ranges(
 1953            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 1954            editor,
 1955            cx,
 1956        );
 1957
 1958        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 1959        assert_selection_ranges(
 1960            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 1961            editor,
 1962            cx,
 1963        );
 1964    });
 1965}
 1966
 1967#[gpui::test]
 1968fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 1969    init_test(cx, |_| {});
 1970
 1971    let editor = cx.add_window(|window, cx| {
 1972        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 1973        build_editor(buffer, window, cx)
 1974    });
 1975
 1976    _ = editor.update(cx, |editor, window, cx| {
 1977        editor.set_wrap_width(Some(140.0.into()), cx);
 1978        assert_eq!(
 1979            editor.display_text(cx),
 1980            "use one::{\n    two::three::\n    four::five\n};"
 1981        );
 1982
 1983        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1984            s.select_display_ranges([
 1985                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 1986            ]);
 1987        });
 1988
 1989        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1990        assert_eq!(
 1991            editor.selections.display_ranges(cx),
 1992            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 1993        );
 1994
 1995        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1996        assert_eq!(
 1997            editor.selections.display_ranges(cx),
 1998            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 1999        );
 2000
 2001        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2002        assert_eq!(
 2003            editor.selections.display_ranges(cx),
 2004            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2005        );
 2006
 2007        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2008        assert_eq!(
 2009            editor.selections.display_ranges(cx),
 2010            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2011        );
 2012
 2013        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2014        assert_eq!(
 2015            editor.selections.display_ranges(cx),
 2016            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2017        );
 2018
 2019        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2020        assert_eq!(
 2021            editor.selections.display_ranges(cx),
 2022            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2023        );
 2024    });
 2025}
 2026
 2027#[gpui::test]
 2028async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2029    init_test(cx, |_| {});
 2030    let mut cx = EditorTestContext::new(cx).await;
 2031
 2032    let line_height = cx.editor(|editor, window, _| {
 2033        editor
 2034            .style()
 2035            .unwrap()
 2036            .text
 2037            .line_height_in_pixels(window.rem_size())
 2038    });
 2039    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2040
 2041    cx.set_state(
 2042        &r#"ˇone
 2043        two
 2044
 2045        three
 2046        fourˇ
 2047        five
 2048
 2049        six"#
 2050            .unindent(),
 2051    );
 2052
 2053    cx.update_editor(|editor, window, cx| {
 2054        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2055    });
 2056    cx.assert_editor_state(
 2057        &r#"one
 2058        two
 2059        ˇ
 2060        three
 2061        four
 2062        five
 2063        ˇ
 2064        six"#
 2065            .unindent(),
 2066    );
 2067
 2068    cx.update_editor(|editor, window, cx| {
 2069        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2070    });
 2071    cx.assert_editor_state(
 2072        &r#"one
 2073        two
 2074
 2075        three
 2076        four
 2077        five
 2078        ˇ
 2079        sixˇ"#
 2080            .unindent(),
 2081    );
 2082
 2083    cx.update_editor(|editor, window, cx| {
 2084        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2085    });
 2086    cx.assert_editor_state(
 2087        &r#"one
 2088        two
 2089
 2090        three
 2091        four
 2092        five
 2093
 2094        sixˇ"#
 2095            .unindent(),
 2096    );
 2097
 2098    cx.update_editor(|editor, window, cx| {
 2099        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2100    });
 2101    cx.assert_editor_state(
 2102        &r#"one
 2103        two
 2104
 2105        three
 2106        four
 2107        five
 2108        ˇ
 2109        six"#
 2110            .unindent(),
 2111    );
 2112
 2113    cx.update_editor(|editor, window, cx| {
 2114        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2115    });
 2116    cx.assert_editor_state(
 2117        &r#"one
 2118        two
 2119        ˇ
 2120        three
 2121        four
 2122        five
 2123
 2124        six"#
 2125            .unindent(),
 2126    );
 2127
 2128    cx.update_editor(|editor, window, cx| {
 2129        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2130    });
 2131    cx.assert_editor_state(
 2132        &r#"ˇone
 2133        two
 2134
 2135        three
 2136        four
 2137        five
 2138
 2139        six"#
 2140            .unindent(),
 2141    );
 2142}
 2143
 2144#[gpui::test]
 2145async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2146    init_test(cx, |_| {});
 2147    let mut cx = EditorTestContext::new(cx).await;
 2148    let line_height = cx.editor(|editor, window, _| {
 2149        editor
 2150            .style()
 2151            .unwrap()
 2152            .text
 2153            .line_height_in_pixels(window.rem_size())
 2154    });
 2155    let window = cx.window;
 2156    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2157
 2158    cx.set_state(
 2159        r#"ˇone
 2160        two
 2161        three
 2162        four
 2163        five
 2164        six
 2165        seven
 2166        eight
 2167        nine
 2168        ten
 2169        "#,
 2170    );
 2171
 2172    cx.update_editor(|editor, window, cx| {
 2173        assert_eq!(
 2174            editor.snapshot(window, cx).scroll_position(),
 2175            gpui::Point::new(0., 0.)
 2176        );
 2177        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2178        assert_eq!(
 2179            editor.snapshot(window, cx).scroll_position(),
 2180            gpui::Point::new(0., 3.)
 2181        );
 2182        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2183        assert_eq!(
 2184            editor.snapshot(window, cx).scroll_position(),
 2185            gpui::Point::new(0., 6.)
 2186        );
 2187        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2188        assert_eq!(
 2189            editor.snapshot(window, cx).scroll_position(),
 2190            gpui::Point::new(0., 3.)
 2191        );
 2192
 2193        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2194        assert_eq!(
 2195            editor.snapshot(window, cx).scroll_position(),
 2196            gpui::Point::new(0., 1.)
 2197        );
 2198        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2199        assert_eq!(
 2200            editor.snapshot(window, cx).scroll_position(),
 2201            gpui::Point::new(0., 3.)
 2202        );
 2203    });
 2204}
 2205
 2206#[gpui::test]
 2207async fn test_autoscroll(cx: &mut TestAppContext) {
 2208    init_test(cx, |_| {});
 2209    let mut cx = EditorTestContext::new(cx).await;
 2210
 2211    let line_height = cx.update_editor(|editor, window, cx| {
 2212        editor.set_vertical_scroll_margin(2, cx);
 2213        editor
 2214            .style()
 2215            .unwrap()
 2216            .text
 2217            .line_height_in_pixels(window.rem_size())
 2218    });
 2219    let window = cx.window;
 2220    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2221
 2222    cx.set_state(
 2223        r#"ˇone
 2224            two
 2225            three
 2226            four
 2227            five
 2228            six
 2229            seven
 2230            eight
 2231            nine
 2232            ten
 2233        "#,
 2234    );
 2235    cx.update_editor(|editor, window, cx| {
 2236        assert_eq!(
 2237            editor.snapshot(window, cx).scroll_position(),
 2238            gpui::Point::new(0., 0.0)
 2239        );
 2240    });
 2241
 2242    // Add a cursor below the visible area. Since both cursors cannot fit
 2243    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2244    // allows the vertical scroll margin below that cursor.
 2245    cx.update_editor(|editor, window, cx| {
 2246        editor.change_selections(Default::default(), window, cx, |selections| {
 2247            selections.select_ranges([
 2248                Point::new(0, 0)..Point::new(0, 0),
 2249                Point::new(6, 0)..Point::new(6, 0),
 2250            ]);
 2251        })
 2252    });
 2253    cx.update_editor(|editor, window, cx| {
 2254        assert_eq!(
 2255            editor.snapshot(window, cx).scroll_position(),
 2256            gpui::Point::new(0., 3.0)
 2257        );
 2258    });
 2259
 2260    // Move down. The editor cursor scrolls down to track the newest cursor.
 2261    cx.update_editor(|editor, window, cx| {
 2262        editor.move_down(&Default::default(), window, cx);
 2263    });
 2264    cx.update_editor(|editor, window, cx| {
 2265        assert_eq!(
 2266            editor.snapshot(window, cx).scroll_position(),
 2267            gpui::Point::new(0., 4.0)
 2268        );
 2269    });
 2270
 2271    // Add a cursor above the visible area. Since both cursors fit on screen,
 2272    // the editor scrolls to show both.
 2273    cx.update_editor(|editor, window, cx| {
 2274        editor.change_selections(Default::default(), window, cx, |selections| {
 2275            selections.select_ranges([
 2276                Point::new(1, 0)..Point::new(1, 0),
 2277                Point::new(6, 0)..Point::new(6, 0),
 2278            ]);
 2279        })
 2280    });
 2281    cx.update_editor(|editor, window, cx| {
 2282        assert_eq!(
 2283            editor.snapshot(window, cx).scroll_position(),
 2284            gpui::Point::new(0., 1.0)
 2285        );
 2286    });
 2287}
 2288
 2289#[gpui::test]
 2290async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2291    init_test(cx, |_| {});
 2292    let mut cx = EditorTestContext::new(cx).await;
 2293
 2294    let line_height = cx.editor(|editor, window, _cx| {
 2295        editor
 2296            .style()
 2297            .unwrap()
 2298            .text
 2299            .line_height_in_pixels(window.rem_size())
 2300    });
 2301    let window = cx.window;
 2302    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2303    cx.set_state(
 2304        &r#"
 2305        ˇone
 2306        two
 2307        threeˇ
 2308        four
 2309        five
 2310        six
 2311        seven
 2312        eight
 2313        nine
 2314        ten
 2315        "#
 2316        .unindent(),
 2317    );
 2318
 2319    cx.update_editor(|editor, window, cx| {
 2320        editor.move_page_down(&MovePageDown::default(), window, cx)
 2321    });
 2322    cx.assert_editor_state(
 2323        &r#"
 2324        one
 2325        two
 2326        three
 2327        ˇfour
 2328        five
 2329        sixˇ
 2330        seven
 2331        eight
 2332        nine
 2333        ten
 2334        "#
 2335        .unindent(),
 2336    );
 2337
 2338    cx.update_editor(|editor, window, cx| {
 2339        editor.move_page_down(&MovePageDown::default(), window, cx)
 2340    });
 2341    cx.assert_editor_state(
 2342        &r#"
 2343        one
 2344        two
 2345        three
 2346        four
 2347        five
 2348        six
 2349        ˇseven
 2350        eight
 2351        nineˇ
 2352        ten
 2353        "#
 2354        .unindent(),
 2355    );
 2356
 2357    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2358    cx.assert_editor_state(
 2359        &r#"
 2360        one
 2361        two
 2362        three
 2363        ˇfour
 2364        five
 2365        sixˇ
 2366        seven
 2367        eight
 2368        nine
 2369        ten
 2370        "#
 2371        .unindent(),
 2372    );
 2373
 2374    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2375    cx.assert_editor_state(
 2376        &r#"
 2377        ˇone
 2378        two
 2379        threeˇ
 2380        four
 2381        five
 2382        six
 2383        seven
 2384        eight
 2385        nine
 2386        ten
 2387        "#
 2388        .unindent(),
 2389    );
 2390
 2391    // Test select collapsing
 2392    cx.update_editor(|editor, window, cx| {
 2393        editor.move_page_down(&MovePageDown::default(), window, cx);
 2394        editor.move_page_down(&MovePageDown::default(), window, cx);
 2395        editor.move_page_down(&MovePageDown::default(), window, cx);
 2396    });
 2397    cx.assert_editor_state(
 2398        &r#"
 2399        one
 2400        two
 2401        three
 2402        four
 2403        five
 2404        six
 2405        seven
 2406        eight
 2407        nine
 2408        ˇten
 2409        ˇ"#
 2410        .unindent(),
 2411    );
 2412}
 2413
 2414#[gpui::test]
 2415async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2416    init_test(cx, |_| {});
 2417    let mut cx = EditorTestContext::new(cx).await;
 2418    cx.set_state("one «two threeˇ» four");
 2419    cx.update_editor(|editor, window, cx| {
 2420        editor.delete_to_beginning_of_line(
 2421            &DeleteToBeginningOfLine {
 2422                stop_at_indent: false,
 2423            },
 2424            window,
 2425            cx,
 2426        );
 2427        assert_eq!(editor.text(cx), " four");
 2428    });
 2429}
 2430
 2431#[gpui::test]
 2432fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2433    init_test(cx, |_| {});
 2434
 2435    let editor = cx.add_window(|window, cx| {
 2436        let buffer = MultiBuffer::build_simple("one two three four", cx);
 2437        build_editor(buffer.clone(), window, cx)
 2438    });
 2439
 2440    _ = editor.update(cx, |editor, window, cx| {
 2441        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2442            s.select_display_ranges([
 2443                // an empty selection - the preceding word fragment is deleted
 2444                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2445                // characters selected - they are deleted
 2446                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
 2447            ])
 2448        });
 2449        editor.delete_to_previous_word_start(
 2450            &DeleteToPreviousWordStart {
 2451                ignore_newlines: false,
 2452            },
 2453            window,
 2454            cx,
 2455        );
 2456        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e two te four");
 2457    });
 2458
 2459    _ = editor.update(cx, |editor, window, cx| {
 2460        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2461            s.select_display_ranges([
 2462                // an empty selection - the following word fragment is deleted
 2463                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 2464                // characters selected - they are deleted
 2465                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
 2466            ])
 2467        });
 2468        editor.delete_to_next_word_end(
 2469            &DeleteToNextWordEnd {
 2470                ignore_newlines: false,
 2471            },
 2472            window,
 2473            cx,
 2474        );
 2475        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e t te our");
 2476    });
 2477}
 2478
 2479#[gpui::test]
 2480fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2481    init_test(cx, |_| {});
 2482
 2483    let editor = cx.add_window(|window, cx| {
 2484        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 2485        build_editor(buffer.clone(), window, cx)
 2486    });
 2487    let del_to_prev_word_start = DeleteToPreviousWordStart {
 2488        ignore_newlines: false,
 2489    };
 2490    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 2491        ignore_newlines: true,
 2492    };
 2493
 2494    _ = editor.update(cx, |editor, window, cx| {
 2495        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2496            s.select_display_ranges([
 2497                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 2498            ])
 2499        });
 2500        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2501        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 2502        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2503        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 2504        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2505        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 2506        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2507        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 2508        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2509        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 2510        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2511        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2512    });
 2513}
 2514
 2515#[gpui::test]
 2516fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 2517    init_test(cx, |_| {});
 2518
 2519    let editor = cx.add_window(|window, cx| {
 2520        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 2521        build_editor(buffer.clone(), window, cx)
 2522    });
 2523    let del_to_next_word_end = DeleteToNextWordEnd {
 2524        ignore_newlines: false,
 2525    };
 2526    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 2527        ignore_newlines: true,
 2528    };
 2529
 2530    _ = editor.update(cx, |editor, window, cx| {
 2531        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2532            s.select_display_ranges([
 2533                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 2534            ])
 2535        });
 2536        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2537        assert_eq!(
 2538            editor.buffer.read(cx).read(cx).text(),
 2539            "one\n   two\nthree\n   four"
 2540        );
 2541        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2542        assert_eq!(
 2543            editor.buffer.read(cx).read(cx).text(),
 2544            "\n   two\nthree\n   four"
 2545        );
 2546        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2547        assert_eq!(
 2548            editor.buffer.read(cx).read(cx).text(),
 2549            "two\nthree\n   four"
 2550        );
 2551        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2552        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 2553        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2554        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 2555        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2556        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2557    });
 2558}
 2559
 2560#[gpui::test]
 2561fn test_newline(cx: &mut TestAppContext) {
 2562    init_test(cx, |_| {});
 2563
 2564    let editor = cx.add_window(|window, cx| {
 2565        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 2566        build_editor(buffer.clone(), window, cx)
 2567    });
 2568
 2569    _ = editor.update(cx, |editor, window, cx| {
 2570        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2571            s.select_display_ranges([
 2572                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2573                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2574                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 2575            ])
 2576        });
 2577
 2578        editor.newline(&Newline, window, cx);
 2579        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 2580    });
 2581}
 2582
 2583#[gpui::test]
 2584fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 2585    init_test(cx, |_| {});
 2586
 2587    let editor = cx.add_window(|window, cx| {
 2588        let buffer = MultiBuffer::build_simple(
 2589            "
 2590                a
 2591                b(
 2592                    X
 2593                )
 2594                c(
 2595                    X
 2596                )
 2597            "
 2598            .unindent()
 2599            .as_str(),
 2600            cx,
 2601        );
 2602        let mut editor = build_editor(buffer.clone(), window, cx);
 2603        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2604            s.select_ranges([
 2605                Point::new(2, 4)..Point::new(2, 5),
 2606                Point::new(5, 4)..Point::new(5, 5),
 2607            ])
 2608        });
 2609        editor
 2610    });
 2611
 2612    _ = editor.update(cx, |editor, window, cx| {
 2613        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 2614        editor.buffer.update(cx, |buffer, cx| {
 2615            buffer.edit(
 2616                [
 2617                    (Point::new(1, 2)..Point::new(3, 0), ""),
 2618                    (Point::new(4, 2)..Point::new(6, 0), ""),
 2619                ],
 2620                None,
 2621                cx,
 2622            );
 2623            assert_eq!(
 2624                buffer.read(cx).text(),
 2625                "
 2626                    a
 2627                    b()
 2628                    c()
 2629                "
 2630                .unindent()
 2631            );
 2632        });
 2633        assert_eq!(
 2634            editor.selections.ranges(cx),
 2635            &[
 2636                Point::new(1, 2)..Point::new(1, 2),
 2637                Point::new(2, 2)..Point::new(2, 2),
 2638            ],
 2639        );
 2640
 2641        editor.newline(&Newline, window, cx);
 2642        assert_eq!(
 2643            editor.text(cx),
 2644            "
 2645                a
 2646                b(
 2647                )
 2648                c(
 2649                )
 2650            "
 2651            .unindent()
 2652        );
 2653
 2654        // The selections are moved after the inserted newlines
 2655        assert_eq!(
 2656            editor.selections.ranges(cx),
 2657            &[
 2658                Point::new(2, 0)..Point::new(2, 0),
 2659                Point::new(4, 0)..Point::new(4, 0),
 2660            ],
 2661        );
 2662    });
 2663}
 2664
 2665#[gpui::test]
 2666async fn test_newline_above(cx: &mut TestAppContext) {
 2667    init_test(cx, |settings| {
 2668        settings.defaults.tab_size = NonZeroU32::new(4)
 2669    });
 2670
 2671    let language = Arc::new(
 2672        Language::new(
 2673            LanguageConfig::default(),
 2674            Some(tree_sitter_rust::LANGUAGE.into()),
 2675        )
 2676        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2677        .unwrap(),
 2678    );
 2679
 2680    let mut cx = EditorTestContext::new(cx).await;
 2681    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2682    cx.set_state(indoc! {"
 2683        const a: ˇA = (
 2684 2685                «const_functionˇ»(ˇ),
 2686                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2687 2688        ˇ);ˇ
 2689    "});
 2690
 2691    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 2692    cx.assert_editor_state(indoc! {"
 2693        ˇ
 2694        const a: A = (
 2695            ˇ
 2696            (
 2697                ˇ
 2698                ˇ
 2699                const_function(),
 2700                ˇ
 2701                ˇ
 2702                ˇ
 2703                ˇ
 2704                something_else,
 2705                ˇ
 2706            )
 2707            ˇ
 2708            ˇ
 2709        );
 2710    "});
 2711}
 2712
 2713#[gpui::test]
 2714async fn test_newline_below(cx: &mut TestAppContext) {
 2715    init_test(cx, |settings| {
 2716        settings.defaults.tab_size = NonZeroU32::new(4)
 2717    });
 2718
 2719    let language = Arc::new(
 2720        Language::new(
 2721            LanguageConfig::default(),
 2722            Some(tree_sitter_rust::LANGUAGE.into()),
 2723        )
 2724        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2725        .unwrap(),
 2726    );
 2727
 2728    let mut cx = EditorTestContext::new(cx).await;
 2729    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2730    cx.set_state(indoc! {"
 2731        const a: ˇA = (
 2732 2733                «const_functionˇ»(ˇ),
 2734                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2735 2736        ˇ);ˇ
 2737    "});
 2738
 2739    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 2740    cx.assert_editor_state(indoc! {"
 2741        const a: A = (
 2742            ˇ
 2743            (
 2744                ˇ
 2745                const_function(),
 2746                ˇ
 2747                ˇ
 2748                something_else,
 2749                ˇ
 2750                ˇ
 2751                ˇ
 2752                ˇ
 2753            )
 2754            ˇ
 2755        );
 2756        ˇ
 2757        ˇ
 2758    "});
 2759}
 2760
 2761#[gpui::test]
 2762async fn test_newline_comments(cx: &mut TestAppContext) {
 2763    init_test(cx, |settings| {
 2764        settings.defaults.tab_size = NonZeroU32::new(4)
 2765    });
 2766
 2767    let language = Arc::new(Language::new(
 2768        LanguageConfig {
 2769            line_comments: vec!["// ".into()],
 2770            ..LanguageConfig::default()
 2771        },
 2772        None,
 2773    ));
 2774    {
 2775        let mut cx = EditorTestContext::new(cx).await;
 2776        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2777        cx.set_state(indoc! {"
 2778        // Fooˇ
 2779    "});
 2780
 2781        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2782        cx.assert_editor_state(indoc! {"
 2783        // Foo
 2784        // ˇ
 2785    "});
 2786        // Ensure that we add comment prefix when existing line contains space
 2787        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2788        cx.assert_editor_state(
 2789            indoc! {"
 2790        // Foo
 2791        //s
 2792        // ˇ
 2793    "}
 2794            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2795            .as_str(),
 2796        );
 2797        // Ensure that we add comment prefix when existing line does not contain space
 2798        cx.set_state(indoc! {"
 2799        // Foo
 2800        //ˇ
 2801    "});
 2802        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2803        cx.assert_editor_state(indoc! {"
 2804        // Foo
 2805        //
 2806        // ˇ
 2807    "});
 2808        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 2809        cx.set_state(indoc! {"
 2810        ˇ// Foo
 2811    "});
 2812        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2813        cx.assert_editor_state(indoc! {"
 2814
 2815        ˇ// Foo
 2816    "});
 2817    }
 2818    // Ensure that comment continuations can be disabled.
 2819    update_test_language_settings(cx, |settings| {
 2820        settings.defaults.extend_comment_on_newline = Some(false);
 2821    });
 2822    let mut cx = EditorTestContext::new(cx).await;
 2823    cx.set_state(indoc! {"
 2824        // Fooˇ
 2825    "});
 2826    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2827    cx.assert_editor_state(indoc! {"
 2828        // Foo
 2829        ˇ
 2830    "});
 2831}
 2832
 2833#[gpui::test]
 2834async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 2835    init_test(cx, |settings| {
 2836        settings.defaults.tab_size = NonZeroU32::new(4)
 2837    });
 2838
 2839    let language = Arc::new(Language::new(
 2840        LanguageConfig {
 2841            line_comments: vec!["// ".into(), "/// ".into()],
 2842            ..LanguageConfig::default()
 2843        },
 2844        None,
 2845    ));
 2846    {
 2847        let mut cx = EditorTestContext::new(cx).await;
 2848        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2849        cx.set_state(indoc! {"
 2850        //ˇ
 2851    "});
 2852        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2853        cx.assert_editor_state(indoc! {"
 2854        //
 2855        // ˇ
 2856    "});
 2857
 2858        cx.set_state(indoc! {"
 2859        ///ˇ
 2860    "});
 2861        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2862        cx.assert_editor_state(indoc! {"
 2863        ///
 2864        /// ˇ
 2865    "});
 2866    }
 2867}
 2868
 2869#[gpui::test]
 2870async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 2871    init_test(cx, |settings| {
 2872        settings.defaults.tab_size = NonZeroU32::new(4)
 2873    });
 2874
 2875    let language = Arc::new(
 2876        Language::new(
 2877            LanguageConfig {
 2878                documentation_comment: Some(language::BlockCommentConfig {
 2879                    start: "/**".into(),
 2880                    end: "*/".into(),
 2881                    prefix: "* ".into(),
 2882                    tab_size: 1,
 2883                }),
 2884
 2885                ..LanguageConfig::default()
 2886            },
 2887            Some(tree_sitter_rust::LANGUAGE.into()),
 2888        )
 2889        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 2890        .unwrap(),
 2891    );
 2892
 2893    {
 2894        let mut cx = EditorTestContext::new(cx).await;
 2895        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2896        cx.set_state(indoc! {"
 2897        /**ˇ
 2898    "});
 2899
 2900        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2901        cx.assert_editor_state(indoc! {"
 2902        /**
 2903         * ˇ
 2904    "});
 2905        // Ensure that if cursor is before the comment start,
 2906        // we do not actually insert a comment prefix.
 2907        cx.set_state(indoc! {"
 2908        ˇ/**
 2909    "});
 2910        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2911        cx.assert_editor_state(indoc! {"
 2912
 2913        ˇ/**
 2914    "});
 2915        // Ensure that if cursor is between it doesn't add comment prefix.
 2916        cx.set_state(indoc! {"
 2917        /*ˇ*
 2918    "});
 2919        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2920        cx.assert_editor_state(indoc! {"
 2921        /*
 2922        ˇ*
 2923    "});
 2924        // Ensure that if suffix exists on same line after cursor it adds new line.
 2925        cx.set_state(indoc! {"
 2926        /**ˇ*/
 2927    "});
 2928        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2929        cx.assert_editor_state(indoc! {"
 2930        /**
 2931         * ˇ
 2932         */
 2933    "});
 2934        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2935        cx.set_state(indoc! {"
 2936        /**ˇ */
 2937    "});
 2938        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2939        cx.assert_editor_state(indoc! {"
 2940        /**
 2941         * ˇ
 2942         */
 2943    "});
 2944        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2945        cx.set_state(indoc! {"
 2946        /** ˇ*/
 2947    "});
 2948        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2949        cx.assert_editor_state(
 2950            indoc! {"
 2951        /**s
 2952         * ˇ
 2953         */
 2954    "}
 2955            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2956            .as_str(),
 2957        );
 2958        // Ensure that delimiter space is preserved when newline on already
 2959        // spaced delimiter.
 2960        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2961        cx.assert_editor_state(
 2962            indoc! {"
 2963        /**s
 2964         *s
 2965         * ˇ
 2966         */
 2967    "}
 2968            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2969            .as_str(),
 2970        );
 2971        // Ensure that delimiter space is preserved when space is not
 2972        // on existing delimiter.
 2973        cx.set_state(indoc! {"
 2974        /**
 2975 2976         */
 2977    "});
 2978        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2979        cx.assert_editor_state(indoc! {"
 2980        /**
 2981         *
 2982         * ˇ
 2983         */
 2984    "});
 2985        // Ensure that if suffix exists on same line after cursor it
 2986        // doesn't add extra new line if prefix is not on same line.
 2987        cx.set_state(indoc! {"
 2988        /**
 2989        ˇ*/
 2990    "});
 2991        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2992        cx.assert_editor_state(indoc! {"
 2993        /**
 2994
 2995        ˇ*/
 2996    "});
 2997        // Ensure that it detects suffix after existing prefix.
 2998        cx.set_state(indoc! {"
 2999        /**ˇ/
 3000    "});
 3001        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3002        cx.assert_editor_state(indoc! {"
 3003        /**
 3004        ˇ/
 3005    "});
 3006        // Ensure that if suffix exists on same line before
 3007        // cursor it does not add comment prefix.
 3008        cx.set_state(indoc! {"
 3009        /** */ˇ
 3010    "});
 3011        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3012        cx.assert_editor_state(indoc! {"
 3013        /** */
 3014        ˇ
 3015    "});
 3016        // Ensure that if suffix exists on same line before
 3017        // cursor it does not add comment prefix.
 3018        cx.set_state(indoc! {"
 3019        /**
 3020         *
 3021         */ˇ
 3022    "});
 3023        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3024        cx.assert_editor_state(indoc! {"
 3025        /**
 3026         *
 3027         */
 3028         ˇ
 3029    "});
 3030
 3031        // Ensure that inline comment followed by code
 3032        // doesn't add comment prefix on newline
 3033        cx.set_state(indoc! {"
 3034        /** */ textˇ
 3035    "});
 3036        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3037        cx.assert_editor_state(indoc! {"
 3038        /** */ text
 3039        ˇ
 3040    "});
 3041
 3042        // Ensure that text after comment end tag
 3043        // doesn't add comment prefix on newline
 3044        cx.set_state(indoc! {"
 3045        /**
 3046         *
 3047         */ˇtext
 3048    "});
 3049        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3050        cx.assert_editor_state(indoc! {"
 3051        /**
 3052         *
 3053         */
 3054         ˇtext
 3055    "});
 3056
 3057        // Ensure if not comment block it doesn't
 3058        // add comment prefix on newline
 3059        cx.set_state(indoc! {"
 3060        * textˇ
 3061    "});
 3062        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3063        cx.assert_editor_state(indoc! {"
 3064        * text
 3065        ˇ
 3066    "});
 3067    }
 3068    // Ensure that comment continuations can be disabled.
 3069    update_test_language_settings(cx, |settings| {
 3070        settings.defaults.extend_comment_on_newline = Some(false);
 3071    });
 3072    let mut cx = EditorTestContext::new(cx).await;
 3073    cx.set_state(indoc! {"
 3074        /**ˇ
 3075    "});
 3076    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3077    cx.assert_editor_state(indoc! {"
 3078        /**
 3079        ˇ
 3080    "});
 3081}
 3082
 3083#[gpui::test]
 3084async fn test_newline_comments_with_block_comment(cx: &mut TestAppContext) {
 3085    init_test(cx, |settings| {
 3086        settings.defaults.tab_size = NonZeroU32::new(4)
 3087    });
 3088
 3089    let lua_language = Arc::new(Language::new(
 3090        LanguageConfig {
 3091            line_comments: vec!["--".into()],
 3092            block_comment: Some(language::BlockCommentConfig {
 3093                start: "--[[".into(),
 3094                prefix: "".into(),
 3095                end: "]]".into(),
 3096                tab_size: 0,
 3097            }),
 3098            ..LanguageConfig::default()
 3099        },
 3100        None,
 3101    ));
 3102
 3103    let mut cx = EditorTestContext::new(cx).await;
 3104    cx.update_buffer(|buffer, cx| buffer.set_language(Some(lua_language), cx));
 3105
 3106    // Line with line comment should extend
 3107    cx.set_state(indoc! {"
 3108        --ˇ
 3109    "});
 3110    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3111    cx.assert_editor_state(indoc! {"
 3112        --
 3113        --ˇ
 3114    "});
 3115
 3116    // Line with block comment that matches line comment should not extend
 3117    cx.set_state(indoc! {"
 3118        --[[ˇ
 3119    "});
 3120    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3121    cx.assert_editor_state(indoc! {"
 3122        --[[
 3123        ˇ
 3124    "});
 3125}
 3126
 3127#[gpui::test]
 3128fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3129    init_test(cx, |_| {});
 3130
 3131    let editor = cx.add_window(|window, cx| {
 3132        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3133        let mut editor = build_editor(buffer.clone(), window, cx);
 3134        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3135            s.select_ranges([3..4, 11..12, 19..20])
 3136        });
 3137        editor
 3138    });
 3139
 3140    _ = editor.update(cx, |editor, window, cx| {
 3141        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3142        editor.buffer.update(cx, |buffer, cx| {
 3143            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 3144            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3145        });
 3146        assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
 3147
 3148        editor.insert("Z", window, cx);
 3149        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3150
 3151        // The selections are moved after the inserted characters
 3152        assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
 3153    });
 3154}
 3155
 3156#[gpui::test]
 3157async fn test_tab(cx: &mut TestAppContext) {
 3158    init_test(cx, |settings| {
 3159        settings.defaults.tab_size = NonZeroU32::new(3)
 3160    });
 3161
 3162    let mut cx = EditorTestContext::new(cx).await;
 3163    cx.set_state(indoc! {"
 3164        ˇabˇc
 3165        ˇ🏀ˇ🏀ˇefg
 3166 3167    "});
 3168    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3169    cx.assert_editor_state(indoc! {"
 3170           ˇab ˇc
 3171           ˇ🏀  ˇ🏀  ˇefg
 3172        d  ˇ
 3173    "});
 3174
 3175    cx.set_state(indoc! {"
 3176        a
 3177        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3178    "});
 3179    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3180    cx.assert_editor_state(indoc! {"
 3181        a
 3182           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3183    "});
 3184}
 3185
 3186#[gpui::test]
 3187async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3188    init_test(cx, |_| {});
 3189
 3190    let mut cx = EditorTestContext::new(cx).await;
 3191    let language = Arc::new(
 3192        Language::new(
 3193            LanguageConfig::default(),
 3194            Some(tree_sitter_rust::LANGUAGE.into()),
 3195        )
 3196        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3197        .unwrap(),
 3198    );
 3199    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3200
 3201    // test when all cursors are not at suggested indent
 3202    // then simply move to their suggested indent location
 3203    cx.set_state(indoc! {"
 3204        const a: B = (
 3205            c(
 3206        ˇ
 3207        ˇ    )
 3208        );
 3209    "});
 3210    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3211    cx.assert_editor_state(indoc! {"
 3212        const a: B = (
 3213            c(
 3214                ˇ
 3215            ˇ)
 3216        );
 3217    "});
 3218
 3219    // test cursor already at suggested indent not moving when
 3220    // other cursors are yet to reach their suggested indents
 3221    cx.set_state(indoc! {"
 3222        ˇ
 3223        const a: B = (
 3224            c(
 3225                d(
 3226        ˇ
 3227                )
 3228        ˇ
 3229        ˇ    )
 3230        );
 3231    "});
 3232    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3233    cx.assert_editor_state(indoc! {"
 3234        ˇ
 3235        const a: B = (
 3236            c(
 3237                d(
 3238                    ˇ
 3239                )
 3240                ˇ
 3241            ˇ)
 3242        );
 3243    "});
 3244    // test when all cursors are at suggested indent then tab is inserted
 3245    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3246    cx.assert_editor_state(indoc! {"
 3247            ˇ
 3248        const a: B = (
 3249            c(
 3250                d(
 3251                        ˇ
 3252                )
 3253                    ˇ
 3254                ˇ)
 3255        );
 3256    "});
 3257
 3258    // test when current indent is less than suggested indent,
 3259    // we adjust line to match suggested indent and move cursor to it
 3260    //
 3261    // when no other cursor is at word boundary, all of them should move
 3262    cx.set_state(indoc! {"
 3263        const a: B = (
 3264            c(
 3265                d(
 3266        ˇ
 3267        ˇ   )
 3268        ˇ   )
 3269        );
 3270    "});
 3271    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3272    cx.assert_editor_state(indoc! {"
 3273        const a: B = (
 3274            c(
 3275                d(
 3276                    ˇ
 3277                ˇ)
 3278            ˇ)
 3279        );
 3280    "});
 3281
 3282    // test when current indent is less than suggested indent,
 3283    // we adjust line to match suggested indent and move cursor to it
 3284    //
 3285    // when some other cursor is at word boundary, it should not move
 3286    cx.set_state(indoc! {"
 3287        const a: B = (
 3288            c(
 3289                d(
 3290        ˇ
 3291        ˇ   )
 3292           ˇ)
 3293        );
 3294    "});
 3295    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3296    cx.assert_editor_state(indoc! {"
 3297        const a: B = (
 3298            c(
 3299                d(
 3300                    ˇ
 3301                ˇ)
 3302            ˇ)
 3303        );
 3304    "});
 3305
 3306    // test when current indent is more than suggested indent,
 3307    // we just move cursor to current indent instead of suggested indent
 3308    //
 3309    // when no other cursor is at word boundary, all of them should move
 3310    cx.set_state(indoc! {"
 3311        const a: B = (
 3312            c(
 3313                d(
 3314        ˇ
 3315        ˇ                )
 3316        ˇ   )
 3317        );
 3318    "});
 3319    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3320    cx.assert_editor_state(indoc! {"
 3321        const a: B = (
 3322            c(
 3323                d(
 3324                    ˇ
 3325                        ˇ)
 3326            ˇ)
 3327        );
 3328    "});
 3329    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3330    cx.assert_editor_state(indoc! {"
 3331        const a: B = (
 3332            c(
 3333                d(
 3334                        ˇ
 3335                            ˇ)
 3336                ˇ)
 3337        );
 3338    "});
 3339
 3340    // test when current indent is more than suggested indent,
 3341    // we just move cursor to current indent instead of suggested indent
 3342    //
 3343    // when some other cursor is at word boundary, it doesn't move
 3344    cx.set_state(indoc! {"
 3345        const a: B = (
 3346            c(
 3347                d(
 3348        ˇ
 3349        ˇ                )
 3350            ˇ)
 3351        );
 3352    "});
 3353    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3354    cx.assert_editor_state(indoc! {"
 3355        const a: B = (
 3356            c(
 3357                d(
 3358                    ˇ
 3359                        ˇ)
 3360            ˇ)
 3361        );
 3362    "});
 3363
 3364    // handle auto-indent when there are multiple cursors on the same line
 3365    cx.set_state(indoc! {"
 3366        const a: B = (
 3367            c(
 3368        ˇ    ˇ
 3369        ˇ    )
 3370        );
 3371    "});
 3372    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3373    cx.assert_editor_state(indoc! {"
 3374        const a: B = (
 3375            c(
 3376                ˇ
 3377            ˇ)
 3378        );
 3379    "});
 3380}
 3381
 3382#[gpui::test]
 3383async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 3384    init_test(cx, |settings| {
 3385        settings.defaults.tab_size = NonZeroU32::new(3)
 3386    });
 3387
 3388    let mut cx = EditorTestContext::new(cx).await;
 3389    cx.set_state(indoc! {"
 3390         ˇ
 3391        \t ˇ
 3392        \t  ˇ
 3393        \t   ˇ
 3394         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 3395    "});
 3396
 3397    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3398    cx.assert_editor_state(indoc! {"
 3399           ˇ
 3400        \t   ˇ
 3401        \t   ˇ
 3402        \t      ˇ
 3403         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 3404    "});
 3405}
 3406
 3407#[gpui::test]
 3408async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 3409    init_test(cx, |settings| {
 3410        settings.defaults.tab_size = NonZeroU32::new(4)
 3411    });
 3412
 3413    let language = Arc::new(
 3414        Language::new(
 3415            LanguageConfig::default(),
 3416            Some(tree_sitter_rust::LANGUAGE.into()),
 3417        )
 3418        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 3419        .unwrap(),
 3420    );
 3421
 3422    let mut cx = EditorTestContext::new(cx).await;
 3423    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3424    cx.set_state(indoc! {"
 3425        fn a() {
 3426            if b {
 3427        \t ˇc
 3428            }
 3429        }
 3430    "});
 3431
 3432    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3433    cx.assert_editor_state(indoc! {"
 3434        fn a() {
 3435            if b {
 3436                ˇc
 3437            }
 3438        }
 3439    "});
 3440}
 3441
 3442#[gpui::test]
 3443async fn test_indent_outdent(cx: &mut TestAppContext) {
 3444    init_test(cx, |settings| {
 3445        settings.defaults.tab_size = NonZeroU32::new(4);
 3446    });
 3447
 3448    let mut cx = EditorTestContext::new(cx).await;
 3449
 3450    cx.set_state(indoc! {"
 3451          «oneˇ» «twoˇ»
 3452        three
 3453         four
 3454    "});
 3455    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3456    cx.assert_editor_state(indoc! {"
 3457            «oneˇ» «twoˇ»
 3458        three
 3459         four
 3460    "});
 3461
 3462    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3463    cx.assert_editor_state(indoc! {"
 3464        «oneˇ» «twoˇ»
 3465        three
 3466         four
 3467    "});
 3468
 3469    // select across line ending
 3470    cx.set_state(indoc! {"
 3471        one two
 3472        t«hree
 3473        ˇ» four
 3474    "});
 3475    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3476    cx.assert_editor_state(indoc! {"
 3477        one two
 3478            t«hree
 3479        ˇ» four
 3480    "});
 3481
 3482    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3483    cx.assert_editor_state(indoc! {"
 3484        one two
 3485        t«hree
 3486        ˇ» four
 3487    "});
 3488
 3489    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3490    cx.set_state(indoc! {"
 3491        one two
 3492        ˇthree
 3493            four
 3494    "});
 3495    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3496    cx.assert_editor_state(indoc! {"
 3497        one two
 3498            ˇthree
 3499            four
 3500    "});
 3501
 3502    cx.set_state(indoc! {"
 3503        one two
 3504        ˇ    three
 3505            four
 3506    "});
 3507    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3508    cx.assert_editor_state(indoc! {"
 3509        one two
 3510        ˇthree
 3511            four
 3512    "});
 3513}
 3514
 3515#[gpui::test]
 3516async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3517    // This is a regression test for issue #33761
 3518    init_test(cx, |_| {});
 3519
 3520    let mut cx = EditorTestContext::new(cx).await;
 3521    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3522    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3523
 3524    cx.set_state(
 3525        r#"ˇ#     ingress:
 3526ˇ#         api:
 3527ˇ#             enabled: false
 3528ˇ#             pathType: Prefix
 3529ˇ#           console:
 3530ˇ#               enabled: false
 3531ˇ#               pathType: Prefix
 3532"#,
 3533    );
 3534
 3535    // Press tab to indent all lines
 3536    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3537
 3538    cx.assert_editor_state(
 3539        r#"    ˇ#     ingress:
 3540    ˇ#         api:
 3541    ˇ#             enabled: false
 3542    ˇ#             pathType: Prefix
 3543    ˇ#           console:
 3544    ˇ#               enabled: false
 3545    ˇ#               pathType: Prefix
 3546"#,
 3547    );
 3548}
 3549
 3550#[gpui::test]
 3551async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3552    // This is a test to make sure our fix for issue #33761 didn't break anything
 3553    init_test(cx, |_| {});
 3554
 3555    let mut cx = EditorTestContext::new(cx).await;
 3556    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3557    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3558
 3559    cx.set_state(
 3560        r#"ˇingress:
 3561ˇ  api:
 3562ˇ    enabled: false
 3563ˇ    pathType: Prefix
 3564"#,
 3565    );
 3566
 3567    // Press tab to indent all lines
 3568    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3569
 3570    cx.assert_editor_state(
 3571        r#"ˇingress:
 3572    ˇapi:
 3573        ˇenabled: false
 3574        ˇpathType: Prefix
 3575"#,
 3576    );
 3577}
 3578
 3579#[gpui::test]
 3580async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 3581    init_test(cx, |settings| {
 3582        settings.defaults.hard_tabs = Some(true);
 3583    });
 3584
 3585    let mut cx = EditorTestContext::new(cx).await;
 3586
 3587    // select two ranges on one line
 3588    cx.set_state(indoc! {"
 3589        «oneˇ» «twoˇ»
 3590        three
 3591        four
 3592    "});
 3593    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3594    cx.assert_editor_state(indoc! {"
 3595        \t«oneˇ» «twoˇ»
 3596        three
 3597        four
 3598    "});
 3599    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3600    cx.assert_editor_state(indoc! {"
 3601        \t\t«oneˇ» «twoˇ»
 3602        three
 3603        four
 3604    "});
 3605    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3606    cx.assert_editor_state(indoc! {"
 3607        \t«oneˇ» «twoˇ»
 3608        three
 3609        four
 3610    "});
 3611    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3612    cx.assert_editor_state(indoc! {"
 3613        «oneˇ» «twoˇ»
 3614        three
 3615        four
 3616    "});
 3617
 3618    // select across a line ending
 3619    cx.set_state(indoc! {"
 3620        one two
 3621        t«hree
 3622        ˇ»four
 3623    "});
 3624    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3625    cx.assert_editor_state(indoc! {"
 3626        one two
 3627        \tt«hree
 3628        ˇ»four
 3629    "});
 3630    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3631    cx.assert_editor_state(indoc! {"
 3632        one two
 3633        \t\tt«hree
 3634        ˇ»four
 3635    "});
 3636    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3637    cx.assert_editor_state(indoc! {"
 3638        one two
 3639        \tt«hree
 3640        ˇ»four
 3641    "});
 3642    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3643    cx.assert_editor_state(indoc! {"
 3644        one two
 3645        t«hree
 3646        ˇ»four
 3647    "});
 3648
 3649    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3650    cx.set_state(indoc! {"
 3651        one two
 3652        ˇthree
 3653        four
 3654    "});
 3655    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3656    cx.assert_editor_state(indoc! {"
 3657        one two
 3658        ˇthree
 3659        four
 3660    "});
 3661    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3662    cx.assert_editor_state(indoc! {"
 3663        one two
 3664        \tˇthree
 3665        four
 3666    "});
 3667    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3668    cx.assert_editor_state(indoc! {"
 3669        one two
 3670        ˇthree
 3671        four
 3672    "});
 3673}
 3674
 3675#[gpui::test]
 3676fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 3677    init_test(cx, |settings| {
 3678        settings.languages.0.extend([
 3679            (
 3680                "TOML".into(),
 3681                LanguageSettingsContent {
 3682                    tab_size: NonZeroU32::new(2),
 3683                    ..Default::default()
 3684                },
 3685            ),
 3686            (
 3687                "Rust".into(),
 3688                LanguageSettingsContent {
 3689                    tab_size: NonZeroU32::new(4),
 3690                    ..Default::default()
 3691                },
 3692            ),
 3693        ]);
 3694    });
 3695
 3696    let toml_language = Arc::new(Language::new(
 3697        LanguageConfig {
 3698            name: "TOML".into(),
 3699            ..Default::default()
 3700        },
 3701        None,
 3702    ));
 3703    let rust_language = Arc::new(Language::new(
 3704        LanguageConfig {
 3705            name: "Rust".into(),
 3706            ..Default::default()
 3707        },
 3708        None,
 3709    ));
 3710
 3711    let toml_buffer =
 3712        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 3713    let rust_buffer =
 3714        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 3715    let multibuffer = cx.new(|cx| {
 3716        let mut multibuffer = MultiBuffer::new(ReadWrite);
 3717        multibuffer.push_excerpts(
 3718            toml_buffer.clone(),
 3719            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 3720            cx,
 3721        );
 3722        multibuffer.push_excerpts(
 3723            rust_buffer.clone(),
 3724            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 3725            cx,
 3726        );
 3727        multibuffer
 3728    });
 3729
 3730    cx.add_window(|window, cx| {
 3731        let mut editor = build_editor(multibuffer, window, cx);
 3732
 3733        assert_eq!(
 3734            editor.text(cx),
 3735            indoc! {"
 3736                a = 1
 3737                b = 2
 3738
 3739                const c: usize = 3;
 3740            "}
 3741        );
 3742
 3743        select_ranges(
 3744            &mut editor,
 3745            indoc! {"
 3746                «aˇ» = 1
 3747                b = 2
 3748
 3749                «const c:ˇ» usize = 3;
 3750            "},
 3751            window,
 3752            cx,
 3753        );
 3754
 3755        editor.tab(&Tab, window, cx);
 3756        assert_text_with_selections(
 3757            &mut editor,
 3758            indoc! {"
 3759                  «aˇ» = 1
 3760                b = 2
 3761
 3762                    «const c:ˇ» usize = 3;
 3763            "},
 3764            cx,
 3765        );
 3766        editor.backtab(&Backtab, window, cx);
 3767        assert_text_with_selections(
 3768            &mut editor,
 3769            indoc! {"
 3770                «aˇ» = 1
 3771                b = 2
 3772
 3773                «const c:ˇ» usize = 3;
 3774            "},
 3775            cx,
 3776        );
 3777
 3778        editor
 3779    });
 3780}
 3781
 3782#[gpui::test]
 3783async fn test_backspace(cx: &mut TestAppContext) {
 3784    init_test(cx, |_| {});
 3785
 3786    let mut cx = EditorTestContext::new(cx).await;
 3787
 3788    // Basic backspace
 3789    cx.set_state(indoc! {"
 3790        onˇe two three
 3791        fou«rˇ» five six
 3792        seven «ˇeight nine
 3793        »ten
 3794    "});
 3795    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3796    cx.assert_editor_state(indoc! {"
 3797        oˇe two three
 3798        fouˇ five six
 3799        seven ˇten
 3800    "});
 3801
 3802    // Test backspace inside and around indents
 3803    cx.set_state(indoc! {"
 3804        zero
 3805            ˇone
 3806                ˇtwo
 3807            ˇ ˇ ˇ  three
 3808        ˇ  ˇ  four
 3809    "});
 3810    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3811    cx.assert_editor_state(indoc! {"
 3812        zero
 3813        ˇone
 3814            ˇtwo
 3815        ˇ  threeˇ  four
 3816    "});
 3817}
 3818
 3819#[gpui::test]
 3820async fn test_delete(cx: &mut TestAppContext) {
 3821    init_test(cx, |_| {});
 3822
 3823    let mut cx = EditorTestContext::new(cx).await;
 3824    cx.set_state(indoc! {"
 3825        onˇe two three
 3826        fou«rˇ» five six
 3827        seven «ˇeight nine
 3828        »ten
 3829    "});
 3830    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 3831    cx.assert_editor_state(indoc! {"
 3832        onˇ two three
 3833        fouˇ five six
 3834        seven ˇten
 3835    "});
 3836}
 3837
 3838#[gpui::test]
 3839fn test_delete_line(cx: &mut TestAppContext) {
 3840    init_test(cx, |_| {});
 3841
 3842    let editor = cx.add_window(|window, cx| {
 3843        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3844        build_editor(buffer, window, cx)
 3845    });
 3846    _ = editor.update(cx, |editor, window, cx| {
 3847        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3848            s.select_display_ranges([
 3849                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 3850                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 3851                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 3852            ])
 3853        });
 3854        editor.delete_line(&DeleteLine, window, cx);
 3855        assert_eq!(editor.display_text(cx), "ghi");
 3856        assert_eq!(
 3857            editor.selections.display_ranges(cx),
 3858            vec![
 3859                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 3860                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
 3861            ]
 3862        );
 3863    });
 3864
 3865    let editor = cx.add_window(|window, cx| {
 3866        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3867        build_editor(buffer, window, cx)
 3868    });
 3869    _ = editor.update(cx, |editor, window, cx| {
 3870        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3871            s.select_display_ranges([
 3872                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 3873            ])
 3874        });
 3875        editor.delete_line(&DeleteLine, window, cx);
 3876        assert_eq!(editor.display_text(cx), "ghi\n");
 3877        assert_eq!(
 3878            editor.selections.display_ranges(cx),
 3879            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 3880        );
 3881    });
 3882}
 3883
 3884#[gpui::test]
 3885fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 3886    init_test(cx, |_| {});
 3887
 3888    cx.add_window(|window, cx| {
 3889        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3890        let mut editor = build_editor(buffer.clone(), window, cx);
 3891        let buffer = buffer.read(cx).as_singleton().unwrap();
 3892
 3893        assert_eq!(
 3894            editor.selections.ranges::<Point>(cx),
 3895            &[Point::new(0, 0)..Point::new(0, 0)]
 3896        );
 3897
 3898        // When on single line, replace newline at end by space
 3899        editor.join_lines(&JoinLines, window, cx);
 3900        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3901        assert_eq!(
 3902            editor.selections.ranges::<Point>(cx),
 3903            &[Point::new(0, 3)..Point::new(0, 3)]
 3904        );
 3905
 3906        // When multiple lines are selected, remove newlines that are spanned by the selection
 3907        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3908            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 3909        });
 3910        editor.join_lines(&JoinLines, window, cx);
 3911        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 3912        assert_eq!(
 3913            editor.selections.ranges::<Point>(cx),
 3914            &[Point::new(0, 11)..Point::new(0, 11)]
 3915        );
 3916
 3917        // Undo should be transactional
 3918        editor.undo(&Undo, window, cx);
 3919        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3920        assert_eq!(
 3921            editor.selections.ranges::<Point>(cx),
 3922            &[Point::new(0, 5)..Point::new(2, 2)]
 3923        );
 3924
 3925        // When joining an empty line don't insert a space
 3926        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3927            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 3928        });
 3929        editor.join_lines(&JoinLines, window, cx);
 3930        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 3931        assert_eq!(
 3932            editor.selections.ranges::<Point>(cx),
 3933            [Point::new(2, 3)..Point::new(2, 3)]
 3934        );
 3935
 3936        // We can remove trailing newlines
 3937        editor.join_lines(&JoinLines, window, cx);
 3938        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3939        assert_eq!(
 3940            editor.selections.ranges::<Point>(cx),
 3941            [Point::new(2, 3)..Point::new(2, 3)]
 3942        );
 3943
 3944        // We don't blow up on the last line
 3945        editor.join_lines(&JoinLines, window, cx);
 3946        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3947        assert_eq!(
 3948            editor.selections.ranges::<Point>(cx),
 3949            [Point::new(2, 3)..Point::new(2, 3)]
 3950        );
 3951
 3952        // reset to test indentation
 3953        editor.buffer.update(cx, |buffer, cx| {
 3954            buffer.edit(
 3955                [
 3956                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 3957                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 3958                ],
 3959                None,
 3960                cx,
 3961            )
 3962        });
 3963
 3964        // We remove any leading spaces
 3965        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 3966        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3967            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 3968        });
 3969        editor.join_lines(&JoinLines, window, cx);
 3970        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 3971
 3972        // We don't insert a space for a line containing only spaces
 3973        editor.join_lines(&JoinLines, window, cx);
 3974        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 3975
 3976        // We ignore any leading tabs
 3977        editor.join_lines(&JoinLines, window, cx);
 3978        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 3979
 3980        editor
 3981    });
 3982}
 3983
 3984#[gpui::test]
 3985fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 3986    init_test(cx, |_| {});
 3987
 3988    cx.add_window(|window, cx| {
 3989        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3990        let mut editor = build_editor(buffer.clone(), window, cx);
 3991        let buffer = buffer.read(cx).as_singleton().unwrap();
 3992
 3993        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3994            s.select_ranges([
 3995                Point::new(0, 2)..Point::new(1, 1),
 3996                Point::new(1, 2)..Point::new(1, 2),
 3997                Point::new(3, 1)..Point::new(3, 2),
 3998            ])
 3999        });
 4000
 4001        editor.join_lines(&JoinLines, window, cx);
 4002        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 4003
 4004        assert_eq!(
 4005            editor.selections.ranges::<Point>(cx),
 4006            [
 4007                Point::new(0, 7)..Point::new(0, 7),
 4008                Point::new(1, 3)..Point::new(1, 3)
 4009            ]
 4010        );
 4011        editor
 4012    });
 4013}
 4014
 4015#[gpui::test]
 4016async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 4017    init_test(cx, |_| {});
 4018
 4019    let mut cx = EditorTestContext::new(cx).await;
 4020
 4021    let diff_base = r#"
 4022        Line 0
 4023        Line 1
 4024        Line 2
 4025        Line 3
 4026        "#
 4027    .unindent();
 4028
 4029    cx.set_state(
 4030        &r#"
 4031        ˇLine 0
 4032        Line 1
 4033        Line 2
 4034        Line 3
 4035        "#
 4036        .unindent(),
 4037    );
 4038
 4039    cx.set_head_text(&diff_base);
 4040    executor.run_until_parked();
 4041
 4042    // Join lines
 4043    cx.update_editor(|editor, window, cx| {
 4044        editor.join_lines(&JoinLines, window, cx);
 4045    });
 4046    executor.run_until_parked();
 4047
 4048    cx.assert_editor_state(
 4049        &r#"
 4050        Line 0ˇ Line 1
 4051        Line 2
 4052        Line 3
 4053        "#
 4054        .unindent(),
 4055    );
 4056    // Join again
 4057    cx.update_editor(|editor, window, cx| {
 4058        editor.join_lines(&JoinLines, window, cx);
 4059    });
 4060    executor.run_until_parked();
 4061
 4062    cx.assert_editor_state(
 4063        &r#"
 4064        Line 0 Line 1ˇ Line 2
 4065        Line 3
 4066        "#
 4067        .unindent(),
 4068    );
 4069}
 4070
 4071#[gpui::test]
 4072async fn test_custom_newlines_cause_no_false_positive_diffs(
 4073    executor: BackgroundExecutor,
 4074    cx: &mut TestAppContext,
 4075) {
 4076    init_test(cx, |_| {});
 4077    let mut cx = EditorTestContext::new(cx).await;
 4078    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 4079    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 4080    executor.run_until_parked();
 4081
 4082    cx.update_editor(|editor, window, cx| {
 4083        let snapshot = editor.snapshot(window, cx);
 4084        assert_eq!(
 4085            snapshot
 4086                .buffer_snapshot
 4087                .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
 4088                .collect::<Vec<_>>(),
 4089            Vec::new(),
 4090            "Should not have any diffs for files with custom newlines"
 4091        );
 4092    });
 4093}
 4094
 4095#[gpui::test]
 4096async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 4097    init_test(cx, |_| {});
 4098
 4099    let mut cx = EditorTestContext::new(cx).await;
 4100
 4101    // Test sort_lines_case_insensitive()
 4102    cx.set_state(indoc! {"
 4103        «z
 4104        y
 4105        x
 4106        Z
 4107        Y
 4108        Xˇ»
 4109    "});
 4110    cx.update_editor(|e, window, cx| {
 4111        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4112    });
 4113    cx.assert_editor_state(indoc! {"
 4114        «x
 4115        X
 4116        y
 4117        Y
 4118        z
 4119        Zˇ»
 4120    "});
 4121
 4122    // Test sort_lines_by_length()
 4123    //
 4124    // Demonstrates:
 4125    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 4126    // - sort is stable
 4127    cx.set_state(indoc! {"
 4128        «123
 4129        æ
 4130        12
 4131 4132        1
 4133        æˇ»
 4134    "});
 4135    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 4136    cx.assert_editor_state(indoc! {"
 4137        «æ
 4138 4139        1
 4140        æ
 4141        12
 4142        123ˇ»
 4143    "});
 4144
 4145    // Test reverse_lines()
 4146    cx.set_state(indoc! {"
 4147        «5
 4148        4
 4149        3
 4150        2
 4151        1ˇ»
 4152    "});
 4153    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4154    cx.assert_editor_state(indoc! {"
 4155        «1
 4156        2
 4157        3
 4158        4
 4159        5ˇ»
 4160    "});
 4161
 4162    // Skip testing shuffle_line()
 4163
 4164    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4165    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4166
 4167    // Don't manipulate when cursor is on single line, but expand the selection
 4168    cx.set_state(indoc! {"
 4169        ddˇdd
 4170        ccc
 4171        bb
 4172        a
 4173    "});
 4174    cx.update_editor(|e, window, cx| {
 4175        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4176    });
 4177    cx.assert_editor_state(indoc! {"
 4178        «ddddˇ»
 4179        ccc
 4180        bb
 4181        a
 4182    "});
 4183
 4184    // Basic manipulate case
 4185    // Start selection moves to column 0
 4186    // End of selection shrinks to fit shorter line
 4187    cx.set_state(indoc! {"
 4188        dd«d
 4189        ccc
 4190        bb
 4191        aaaaaˇ»
 4192    "});
 4193    cx.update_editor(|e, window, cx| {
 4194        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4195    });
 4196    cx.assert_editor_state(indoc! {"
 4197        «aaaaa
 4198        bb
 4199        ccc
 4200        dddˇ»
 4201    "});
 4202
 4203    // Manipulate case with newlines
 4204    cx.set_state(indoc! {"
 4205        dd«d
 4206        ccc
 4207
 4208        bb
 4209        aaaaa
 4210
 4211        ˇ»
 4212    "});
 4213    cx.update_editor(|e, window, cx| {
 4214        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4215    });
 4216    cx.assert_editor_state(indoc! {"
 4217        «
 4218
 4219        aaaaa
 4220        bb
 4221        ccc
 4222        dddˇ»
 4223
 4224    "});
 4225
 4226    // Adding new line
 4227    cx.set_state(indoc! {"
 4228        aa«a
 4229        bbˇ»b
 4230    "});
 4231    cx.update_editor(|e, window, cx| {
 4232        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4233    });
 4234    cx.assert_editor_state(indoc! {"
 4235        «aaa
 4236        bbb
 4237        added_lineˇ»
 4238    "});
 4239
 4240    // Removing line
 4241    cx.set_state(indoc! {"
 4242        aa«a
 4243        bbbˇ»
 4244    "});
 4245    cx.update_editor(|e, window, cx| {
 4246        e.manipulate_immutable_lines(window, cx, |lines| {
 4247            lines.pop();
 4248        })
 4249    });
 4250    cx.assert_editor_state(indoc! {"
 4251        «aaaˇ»
 4252    "});
 4253
 4254    // Removing all lines
 4255    cx.set_state(indoc! {"
 4256        aa«a
 4257        bbbˇ»
 4258    "});
 4259    cx.update_editor(|e, window, cx| {
 4260        e.manipulate_immutable_lines(window, cx, |lines| {
 4261            lines.drain(..);
 4262        })
 4263    });
 4264    cx.assert_editor_state(indoc! {"
 4265        ˇ
 4266    "});
 4267}
 4268
 4269#[gpui::test]
 4270async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4271    init_test(cx, |_| {});
 4272
 4273    let mut cx = EditorTestContext::new(cx).await;
 4274
 4275    // Consider continuous selection as single selection
 4276    cx.set_state(indoc! {"
 4277        Aaa«aa
 4278        cˇ»c«c
 4279        bb
 4280        aaaˇ»aa
 4281    "});
 4282    cx.update_editor(|e, window, cx| {
 4283        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4284    });
 4285    cx.assert_editor_state(indoc! {"
 4286        «Aaaaa
 4287        ccc
 4288        bb
 4289        aaaaaˇ»
 4290    "});
 4291
 4292    cx.set_state(indoc! {"
 4293        Aaa«aa
 4294        cˇ»c«c
 4295        bb
 4296        aaaˇ»aa
 4297    "});
 4298    cx.update_editor(|e, window, cx| {
 4299        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4300    });
 4301    cx.assert_editor_state(indoc! {"
 4302        «Aaaaa
 4303        ccc
 4304        bbˇ»
 4305    "});
 4306
 4307    // Consider non continuous selection as distinct dedup operations
 4308    cx.set_state(indoc! {"
 4309        «aaaaa
 4310        bb
 4311        aaaaa
 4312        aaaaaˇ»
 4313
 4314        aaa«aaˇ»
 4315    "});
 4316    cx.update_editor(|e, window, cx| {
 4317        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4318    });
 4319    cx.assert_editor_state(indoc! {"
 4320        «aaaaa
 4321        bbˇ»
 4322
 4323        «aaaaaˇ»
 4324    "});
 4325}
 4326
 4327#[gpui::test]
 4328async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4329    init_test(cx, |_| {});
 4330
 4331    let mut cx = EditorTestContext::new(cx).await;
 4332
 4333    cx.set_state(indoc! {"
 4334        «Aaa
 4335        aAa
 4336        Aaaˇ»
 4337    "});
 4338    cx.update_editor(|e, window, cx| {
 4339        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4340    });
 4341    cx.assert_editor_state(indoc! {"
 4342        «Aaa
 4343        aAaˇ»
 4344    "});
 4345
 4346    cx.set_state(indoc! {"
 4347        «Aaa
 4348        aAa
 4349        aaAˇ»
 4350    "});
 4351    cx.update_editor(|e, window, cx| {
 4352        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4353    });
 4354    cx.assert_editor_state(indoc! {"
 4355        «Aaaˇ»
 4356    "});
 4357}
 4358
 4359#[gpui::test]
 4360async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 4361    init_test(cx, |_| {});
 4362
 4363    let mut cx = EditorTestContext::new(cx).await;
 4364
 4365    // Manipulate with multiple selections on a single line
 4366    cx.set_state(indoc! {"
 4367        dd«dd
 4368        cˇ»c«c
 4369        bb
 4370        aaaˇ»aa
 4371    "});
 4372    cx.update_editor(|e, window, cx| {
 4373        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4374    });
 4375    cx.assert_editor_state(indoc! {"
 4376        «aaaaa
 4377        bb
 4378        ccc
 4379        ddddˇ»
 4380    "});
 4381
 4382    // Manipulate with multiple disjoin selections
 4383    cx.set_state(indoc! {"
 4384 4385        4
 4386        3
 4387        2
 4388        1ˇ»
 4389
 4390        dd«dd
 4391        ccc
 4392        bb
 4393        aaaˇ»aa
 4394    "});
 4395    cx.update_editor(|e, window, cx| {
 4396        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4397    });
 4398    cx.assert_editor_state(indoc! {"
 4399        «1
 4400        2
 4401        3
 4402        4
 4403        5ˇ»
 4404
 4405        «aaaaa
 4406        bb
 4407        ccc
 4408        ddddˇ»
 4409    "});
 4410
 4411    // Adding lines on each selection
 4412    cx.set_state(indoc! {"
 4413 4414        1ˇ»
 4415
 4416        bb«bb
 4417        aaaˇ»aa
 4418    "});
 4419    cx.update_editor(|e, window, cx| {
 4420        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 4421    });
 4422    cx.assert_editor_state(indoc! {"
 4423        «2
 4424        1
 4425        added lineˇ»
 4426
 4427        «bbbb
 4428        aaaaa
 4429        added lineˇ»
 4430    "});
 4431
 4432    // Removing lines on each selection
 4433    cx.set_state(indoc! {"
 4434 4435        1ˇ»
 4436
 4437        bb«bb
 4438        aaaˇ»aa
 4439    "});
 4440    cx.update_editor(|e, window, cx| {
 4441        e.manipulate_immutable_lines(window, cx, |lines| {
 4442            lines.pop();
 4443        })
 4444    });
 4445    cx.assert_editor_state(indoc! {"
 4446        «2ˇ»
 4447
 4448        «bbbbˇ»
 4449    "});
 4450}
 4451
 4452#[gpui::test]
 4453async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 4454    init_test(cx, |settings| {
 4455        settings.defaults.tab_size = NonZeroU32::new(3)
 4456    });
 4457
 4458    let mut cx = EditorTestContext::new(cx).await;
 4459
 4460    // MULTI SELECTION
 4461    // Ln.1 "«" tests empty lines
 4462    // Ln.9 tests just leading whitespace
 4463    cx.set_state(indoc! {"
 4464        «
 4465        abc                 // No indentationˇ»
 4466        «\tabc              // 1 tabˇ»
 4467        \t\tabc «      ˇ»   // 2 tabs
 4468        \t ab«c             // Tab followed by space
 4469         \tabc              // Space followed by tab (3 spaces should be the result)
 4470        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4471           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 4472        \t
 4473        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4474    "});
 4475    cx.update_editor(|e, window, cx| {
 4476        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4477    });
 4478    cx.assert_editor_state(
 4479        indoc! {"
 4480            «
 4481            abc                 // No indentation
 4482               abc              // 1 tab
 4483                  abc          // 2 tabs
 4484                abc             // Tab followed by space
 4485               abc              // Space followed by tab (3 spaces should be the result)
 4486                           abc   // Mixed indentation (tab conversion depends on the column)
 4487               abc         // Already space indented
 4488               ·
 4489               abc\tdef          // Only the leading tab is manipulatedˇ»
 4490        "}
 4491        .replace("·", "")
 4492        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4493    );
 4494
 4495    // Test on just a few lines, the others should remain unchanged
 4496    // Only lines (3, 5, 10, 11) should change
 4497    cx.set_state(
 4498        indoc! {"
 4499            ·
 4500            abc                 // No indentation
 4501            \tabcˇ               // 1 tab
 4502            \t\tabc             // 2 tabs
 4503            \t abcˇ              // Tab followed by space
 4504             \tabc              // Space followed by tab (3 spaces should be the result)
 4505            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4506               abc              // Already space indented
 4507            «\t
 4508            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4509        "}
 4510        .replace("·", "")
 4511        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4512    );
 4513    cx.update_editor(|e, window, cx| {
 4514        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4515    });
 4516    cx.assert_editor_state(
 4517        indoc! {"
 4518            ·
 4519            abc                 // No indentation
 4520            «   abc               // 1 tabˇ»
 4521            \t\tabc             // 2 tabs
 4522            «    abc              // Tab followed by spaceˇ»
 4523             \tabc              // Space followed by tab (3 spaces should be the result)
 4524            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4525               abc              // Already space indented
 4526            «   ·
 4527               abc\tdef          // Only the leading tab is manipulatedˇ»
 4528        "}
 4529        .replace("·", "")
 4530        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4531    );
 4532
 4533    // SINGLE SELECTION
 4534    // Ln.1 "«" tests empty lines
 4535    // Ln.9 tests just leading whitespace
 4536    cx.set_state(indoc! {"
 4537        «
 4538        abc                 // No indentation
 4539        \tabc               // 1 tab
 4540        \t\tabc             // 2 tabs
 4541        \t abc              // Tab followed by space
 4542         \tabc              // Space followed by tab (3 spaces should be the result)
 4543        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4544           abc              // Already space indented
 4545        \t
 4546        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4547    "});
 4548    cx.update_editor(|e, window, cx| {
 4549        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4550    });
 4551    cx.assert_editor_state(
 4552        indoc! {"
 4553            «
 4554            abc                 // No indentation
 4555               abc               // 1 tab
 4556                  abc             // 2 tabs
 4557                abc              // Tab followed by space
 4558               abc              // Space followed by tab (3 spaces should be the result)
 4559                           abc   // Mixed indentation (tab conversion depends on the column)
 4560               abc              // Already space indented
 4561               ·
 4562               abc\tdef          // Only the leading tab is manipulatedˇ»
 4563        "}
 4564        .replace("·", "")
 4565        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4566    );
 4567}
 4568
 4569#[gpui::test]
 4570async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 4571    init_test(cx, |settings| {
 4572        settings.defaults.tab_size = NonZeroU32::new(3)
 4573    });
 4574
 4575    let mut cx = EditorTestContext::new(cx).await;
 4576
 4577    // MULTI SELECTION
 4578    // Ln.1 "«" tests empty lines
 4579    // Ln.11 tests just leading whitespace
 4580    cx.set_state(indoc! {"
 4581        «
 4582        abˇ»ˇc                 // No indentation
 4583         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 4584          abc  «             // 2 spaces (< 3 so dont convert)
 4585           abc              // 3 spaces (convert)
 4586             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 4587        «\tˇ»\t«\tˇ»abc           // Already tab indented
 4588        «\t abc              // Tab followed by space
 4589         \tabc              // Space followed by tab (should be consumed due to tab)
 4590        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4591           \tˇ»  «\t
 4592           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 4593    "});
 4594    cx.update_editor(|e, window, cx| {
 4595        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4596    });
 4597    cx.assert_editor_state(indoc! {"
 4598        «
 4599        abc                 // No indentation
 4600         abc                // 1 space (< 3 so dont convert)
 4601          abc               // 2 spaces (< 3 so dont convert)
 4602        \tabc              // 3 spaces (convert)
 4603        \t  abc            // 5 spaces (1 tab + 2 spaces)
 4604        \t\t\tabc           // Already tab indented
 4605        \t abc              // Tab followed by space
 4606        \tabc              // Space followed by tab (should be consumed due to tab)
 4607        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4608        \t\t\t
 4609        \tabc   \t         // Only the leading spaces should be convertedˇ»
 4610    "});
 4611
 4612    // Test on just a few lines, the other should remain unchanged
 4613    // Only lines (4, 8, 11, 12) should change
 4614    cx.set_state(
 4615        indoc! {"
 4616            ·
 4617            abc                 // No indentation
 4618             abc                // 1 space (< 3 so dont convert)
 4619              abc               // 2 spaces (< 3 so dont convert)
 4620            «   abc              // 3 spaces (convert)ˇ»
 4621                 abc            // 5 spaces (1 tab + 2 spaces)
 4622            \t\t\tabc           // Already tab indented
 4623            \t abc              // Tab followed by space
 4624             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 4625               \t\t  \tabc      // Mixed indentation
 4626            \t \t  \t   \tabc   // Mixed indentation
 4627               \t  \tˇ
 4628            «   abc   \t         // Only the leading spaces should be convertedˇ»
 4629        "}
 4630        .replace("·", "")
 4631        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4632    );
 4633    cx.update_editor(|e, window, cx| {
 4634        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4635    });
 4636    cx.assert_editor_state(
 4637        indoc! {"
 4638            ·
 4639            abc                 // No indentation
 4640             abc                // 1 space (< 3 so dont convert)
 4641              abc               // 2 spaces (< 3 so dont convert)
 4642            «\tabc              // 3 spaces (convert)ˇ»
 4643                 abc            // 5 spaces (1 tab + 2 spaces)
 4644            \t\t\tabc           // Already tab indented
 4645            \t abc              // Tab followed by space
 4646            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 4647               \t\t  \tabc      // Mixed indentation
 4648            \t \t  \t   \tabc   // Mixed indentation
 4649            «\t\t\t
 4650            \tabc   \t         // Only the leading spaces should be convertedˇ»
 4651        "}
 4652        .replace("·", "")
 4653        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4654    );
 4655
 4656    // SINGLE SELECTION
 4657    // Ln.1 "«" tests empty lines
 4658    // Ln.11 tests just leading whitespace
 4659    cx.set_state(indoc! {"
 4660        «
 4661        abc                 // No indentation
 4662         abc                // 1 space (< 3 so dont convert)
 4663          abc               // 2 spaces (< 3 so dont convert)
 4664           abc              // 3 spaces (convert)
 4665             abc            // 5 spaces (1 tab + 2 spaces)
 4666        \t\t\tabc           // Already tab indented
 4667        \t abc              // Tab followed by space
 4668         \tabc              // Space followed by tab (should be consumed due to tab)
 4669        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4670           \t  \t
 4671           abc   \t         // Only the leading spaces should be convertedˇ»
 4672    "});
 4673    cx.update_editor(|e, window, cx| {
 4674        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4675    });
 4676    cx.assert_editor_state(indoc! {"
 4677        «
 4678        abc                 // No indentation
 4679         abc                // 1 space (< 3 so dont convert)
 4680          abc               // 2 spaces (< 3 so dont convert)
 4681        \tabc              // 3 spaces (convert)
 4682        \t  abc            // 5 spaces (1 tab + 2 spaces)
 4683        \t\t\tabc           // Already tab indented
 4684        \t abc              // Tab followed by space
 4685        \tabc              // Space followed by tab (should be consumed due to tab)
 4686        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4687        \t\t\t
 4688        \tabc   \t         // Only the leading spaces should be convertedˇ»
 4689    "});
 4690}
 4691
 4692#[gpui::test]
 4693async fn test_toggle_case(cx: &mut TestAppContext) {
 4694    init_test(cx, |_| {});
 4695
 4696    let mut cx = EditorTestContext::new(cx).await;
 4697
 4698    // If all lower case -> upper case
 4699    cx.set_state(indoc! {"
 4700        «hello worldˇ»
 4701    "});
 4702    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4703    cx.assert_editor_state(indoc! {"
 4704        «HELLO WORLDˇ»
 4705    "});
 4706
 4707    // If all upper case -> lower case
 4708    cx.set_state(indoc! {"
 4709        «HELLO WORLDˇ»
 4710    "});
 4711    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4712    cx.assert_editor_state(indoc! {"
 4713        «hello worldˇ»
 4714    "});
 4715
 4716    // If any upper case characters are identified -> lower case
 4717    // This matches JetBrains IDEs
 4718    cx.set_state(indoc! {"
 4719        «hEllo worldˇ»
 4720    "});
 4721    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4722    cx.assert_editor_state(indoc! {"
 4723        «hello worldˇ»
 4724    "});
 4725}
 4726
 4727#[gpui::test]
 4728async fn test_manipulate_text(cx: &mut TestAppContext) {
 4729    init_test(cx, |_| {});
 4730
 4731    let mut cx = EditorTestContext::new(cx).await;
 4732
 4733    // Test convert_to_upper_case()
 4734    cx.set_state(indoc! {"
 4735        «hello worldˇ»
 4736    "});
 4737    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4738    cx.assert_editor_state(indoc! {"
 4739        «HELLO WORLDˇ»
 4740    "});
 4741
 4742    // Test convert_to_lower_case()
 4743    cx.set_state(indoc! {"
 4744        «HELLO WORLDˇ»
 4745    "});
 4746    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 4747    cx.assert_editor_state(indoc! {"
 4748        «hello worldˇ»
 4749    "});
 4750
 4751    // Test multiple line, single selection case
 4752    cx.set_state(indoc! {"
 4753        «The quick brown
 4754        fox jumps over
 4755        the lazy dogˇ»
 4756    "});
 4757    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 4758    cx.assert_editor_state(indoc! {"
 4759        «The Quick Brown
 4760        Fox Jumps Over
 4761        The Lazy Dogˇ»
 4762    "});
 4763
 4764    // Test multiple line, single selection case
 4765    cx.set_state(indoc! {"
 4766        «The quick brown
 4767        fox jumps over
 4768        the lazy dogˇ»
 4769    "});
 4770    cx.update_editor(|e, window, cx| {
 4771        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 4772    });
 4773    cx.assert_editor_state(indoc! {"
 4774        «TheQuickBrown
 4775        FoxJumpsOver
 4776        TheLazyDogˇ»
 4777    "});
 4778
 4779    // From here on out, test more complex cases of manipulate_text()
 4780
 4781    // Test no selection case - should affect words cursors are in
 4782    // Cursor at beginning, middle, and end of word
 4783    cx.set_state(indoc! {"
 4784        ˇhello big beauˇtiful worldˇ
 4785    "});
 4786    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4787    cx.assert_editor_state(indoc! {"
 4788        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 4789    "});
 4790
 4791    // Test multiple selections on a single line and across multiple lines
 4792    cx.set_state(indoc! {"
 4793        «Theˇ» quick «brown
 4794        foxˇ» jumps «overˇ»
 4795        the «lazyˇ» dog
 4796    "});
 4797    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4798    cx.assert_editor_state(indoc! {"
 4799        «THEˇ» quick «BROWN
 4800        FOXˇ» jumps «OVERˇ»
 4801        the «LAZYˇ» dog
 4802    "});
 4803
 4804    // Test case where text length grows
 4805    cx.set_state(indoc! {"
 4806        «tschüߡ»
 4807    "});
 4808    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4809    cx.assert_editor_state(indoc! {"
 4810        «TSCHÜSSˇ»
 4811    "});
 4812
 4813    // Test to make sure we don't crash when text shrinks
 4814    cx.set_state(indoc! {"
 4815        aaa_bbbˇ
 4816    "});
 4817    cx.update_editor(|e, window, cx| {
 4818        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4819    });
 4820    cx.assert_editor_state(indoc! {"
 4821        «aaaBbbˇ»
 4822    "});
 4823
 4824    // Test to make sure we all aware of the fact that each word can grow and shrink
 4825    // Final selections should be aware of this fact
 4826    cx.set_state(indoc! {"
 4827        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 4828    "});
 4829    cx.update_editor(|e, window, cx| {
 4830        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4831    });
 4832    cx.assert_editor_state(indoc! {"
 4833        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 4834    "});
 4835
 4836    cx.set_state(indoc! {"
 4837        «hElLo, WoRld!ˇ»
 4838    "});
 4839    cx.update_editor(|e, window, cx| {
 4840        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 4841    });
 4842    cx.assert_editor_state(indoc! {"
 4843        «HeLlO, wOrLD!ˇ»
 4844    "});
 4845}
 4846
 4847#[gpui::test]
 4848fn test_duplicate_line(cx: &mut TestAppContext) {
 4849    init_test(cx, |_| {});
 4850
 4851    let editor = cx.add_window(|window, cx| {
 4852        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4853        build_editor(buffer, window, cx)
 4854    });
 4855    _ = editor.update(cx, |editor, window, cx| {
 4856        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4857            s.select_display_ranges([
 4858                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4859                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4860                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4861                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4862            ])
 4863        });
 4864        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4865        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4866        assert_eq!(
 4867            editor.selections.display_ranges(cx),
 4868            vec![
 4869                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4870                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 4871                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4872                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4873            ]
 4874        );
 4875    });
 4876
 4877    let editor = cx.add_window(|window, cx| {
 4878        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4879        build_editor(buffer, window, cx)
 4880    });
 4881    _ = editor.update(cx, |editor, window, cx| {
 4882        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4883            s.select_display_ranges([
 4884                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4885                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4886            ])
 4887        });
 4888        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4889        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4890        assert_eq!(
 4891            editor.selections.display_ranges(cx),
 4892            vec![
 4893                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 4894                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 4895            ]
 4896        );
 4897    });
 4898
 4899    // With `move_upwards` the selections stay in place, except for
 4900    // the lines inserted above them
 4901    let editor = cx.add_window(|window, cx| {
 4902        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4903        build_editor(buffer, window, cx)
 4904    });
 4905    _ = editor.update(cx, |editor, window, cx| {
 4906        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4907            s.select_display_ranges([
 4908                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4909                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4910                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4911                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4912            ])
 4913        });
 4914        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4915        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4916        assert_eq!(
 4917            editor.selections.display_ranges(cx),
 4918            vec![
 4919                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4920                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4921                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 4922                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4923            ]
 4924        );
 4925    });
 4926
 4927    let editor = cx.add_window(|window, cx| {
 4928        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4929        build_editor(buffer, window, cx)
 4930    });
 4931    _ = editor.update(cx, |editor, window, cx| {
 4932        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4933            s.select_display_ranges([
 4934                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4935                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4936            ])
 4937        });
 4938        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4939        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4940        assert_eq!(
 4941            editor.selections.display_ranges(cx),
 4942            vec![
 4943                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4944                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4945            ]
 4946        );
 4947    });
 4948
 4949    let editor = cx.add_window(|window, cx| {
 4950        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4951        build_editor(buffer, window, cx)
 4952    });
 4953    _ = editor.update(cx, |editor, window, cx| {
 4954        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4955            s.select_display_ranges([
 4956                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4957                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4958            ])
 4959        });
 4960        editor.duplicate_selection(&DuplicateSelection, window, cx);
 4961        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 4962        assert_eq!(
 4963            editor.selections.display_ranges(cx),
 4964            vec![
 4965                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4966                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 4967            ]
 4968        );
 4969    });
 4970}
 4971
 4972#[gpui::test]
 4973fn test_move_line_up_down(cx: &mut TestAppContext) {
 4974    init_test(cx, |_| {});
 4975
 4976    let editor = cx.add_window(|window, cx| {
 4977        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 4978        build_editor(buffer, window, cx)
 4979    });
 4980    _ = editor.update(cx, |editor, window, cx| {
 4981        editor.fold_creases(
 4982            vec![
 4983                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 4984                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 4985                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 4986            ],
 4987            true,
 4988            window,
 4989            cx,
 4990        );
 4991        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4992            s.select_display_ranges([
 4993                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4994                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4995                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4996                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 4997            ])
 4998        });
 4999        assert_eq!(
 5000            editor.display_text(cx),
 5001            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 5002        );
 5003
 5004        editor.move_line_up(&MoveLineUp, window, cx);
 5005        assert_eq!(
 5006            editor.display_text(cx),
 5007            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 5008        );
 5009        assert_eq!(
 5010            editor.selections.display_ranges(cx),
 5011            vec![
 5012                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5013                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5014                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5015                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5016            ]
 5017        );
 5018    });
 5019
 5020    _ = editor.update(cx, |editor, window, cx| {
 5021        editor.move_line_down(&MoveLineDown, window, cx);
 5022        assert_eq!(
 5023            editor.display_text(cx),
 5024            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 5025        );
 5026        assert_eq!(
 5027            editor.selections.display_ranges(cx),
 5028            vec![
 5029                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5030                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5031                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5032                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5033            ]
 5034        );
 5035    });
 5036
 5037    _ = editor.update(cx, |editor, window, cx| {
 5038        editor.move_line_down(&MoveLineDown, window, cx);
 5039        assert_eq!(
 5040            editor.display_text(cx),
 5041            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 5042        );
 5043        assert_eq!(
 5044            editor.selections.display_ranges(cx),
 5045            vec![
 5046                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5047                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5048                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5049                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5050            ]
 5051        );
 5052    });
 5053
 5054    _ = editor.update(cx, |editor, window, cx| {
 5055        editor.move_line_up(&MoveLineUp, window, cx);
 5056        assert_eq!(
 5057            editor.display_text(cx),
 5058            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 5059        );
 5060        assert_eq!(
 5061            editor.selections.display_ranges(cx),
 5062            vec![
 5063                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5064                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5065                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5066                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5067            ]
 5068        );
 5069    });
 5070}
 5071
 5072#[gpui::test]
 5073fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 5074    init_test(cx, |_| {});
 5075
 5076    let editor = cx.add_window(|window, cx| {
 5077        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5078        build_editor(buffer, window, cx)
 5079    });
 5080    _ = editor.update(cx, |editor, window, cx| {
 5081        let snapshot = editor.buffer.read(cx).snapshot(cx);
 5082        editor.insert_blocks(
 5083            [BlockProperties {
 5084                style: BlockStyle::Fixed,
 5085                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 5086                height: Some(1),
 5087                render: Arc::new(|_| div().into_any()),
 5088                priority: 0,
 5089            }],
 5090            Some(Autoscroll::fit()),
 5091            cx,
 5092        );
 5093        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5094            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 5095        });
 5096        editor.move_line_down(&MoveLineDown, window, cx);
 5097    });
 5098}
 5099
 5100#[gpui::test]
 5101async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 5102    init_test(cx, |_| {});
 5103
 5104    let mut cx = EditorTestContext::new(cx).await;
 5105    cx.set_state(
 5106        &"
 5107            ˇzero
 5108            one
 5109            two
 5110            three
 5111            four
 5112            five
 5113        "
 5114        .unindent(),
 5115    );
 5116
 5117    // Create a four-line block that replaces three lines of text.
 5118    cx.update_editor(|editor, window, cx| {
 5119        let snapshot = editor.snapshot(window, cx);
 5120        let snapshot = &snapshot.buffer_snapshot;
 5121        let placement = BlockPlacement::Replace(
 5122            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 5123        );
 5124        editor.insert_blocks(
 5125            [BlockProperties {
 5126                placement,
 5127                height: Some(4),
 5128                style: BlockStyle::Sticky,
 5129                render: Arc::new(|_| gpui::div().into_any_element()),
 5130                priority: 0,
 5131            }],
 5132            None,
 5133            cx,
 5134        );
 5135    });
 5136
 5137    // Move down so that the cursor touches the block.
 5138    cx.update_editor(|editor, window, cx| {
 5139        editor.move_down(&Default::default(), window, cx);
 5140    });
 5141    cx.assert_editor_state(
 5142        &"
 5143            zero
 5144            «one
 5145            two
 5146            threeˇ»
 5147            four
 5148            five
 5149        "
 5150        .unindent(),
 5151    );
 5152
 5153    // Move down past the block.
 5154    cx.update_editor(|editor, window, cx| {
 5155        editor.move_down(&Default::default(), window, cx);
 5156    });
 5157    cx.assert_editor_state(
 5158        &"
 5159            zero
 5160            one
 5161            two
 5162            three
 5163            ˇfour
 5164            five
 5165        "
 5166        .unindent(),
 5167    );
 5168}
 5169
 5170#[gpui::test]
 5171fn test_transpose(cx: &mut TestAppContext) {
 5172    init_test(cx, |_| {});
 5173
 5174    _ = cx.add_window(|window, cx| {
 5175        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 5176        editor.set_style(EditorStyle::default(), window, cx);
 5177        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5178            s.select_ranges([1..1])
 5179        });
 5180        editor.transpose(&Default::default(), window, cx);
 5181        assert_eq!(editor.text(cx), "bac");
 5182        assert_eq!(editor.selections.ranges(cx), [2..2]);
 5183
 5184        editor.transpose(&Default::default(), window, cx);
 5185        assert_eq!(editor.text(cx), "bca");
 5186        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5187
 5188        editor.transpose(&Default::default(), window, cx);
 5189        assert_eq!(editor.text(cx), "bac");
 5190        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5191
 5192        editor
 5193    });
 5194
 5195    _ = cx.add_window(|window, cx| {
 5196        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5197        editor.set_style(EditorStyle::default(), window, cx);
 5198        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5199            s.select_ranges([3..3])
 5200        });
 5201        editor.transpose(&Default::default(), window, cx);
 5202        assert_eq!(editor.text(cx), "acb\nde");
 5203        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5204
 5205        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5206            s.select_ranges([4..4])
 5207        });
 5208        editor.transpose(&Default::default(), window, cx);
 5209        assert_eq!(editor.text(cx), "acbd\ne");
 5210        assert_eq!(editor.selections.ranges(cx), [5..5]);
 5211
 5212        editor.transpose(&Default::default(), window, cx);
 5213        assert_eq!(editor.text(cx), "acbde\n");
 5214        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5215
 5216        editor.transpose(&Default::default(), window, cx);
 5217        assert_eq!(editor.text(cx), "acbd\ne");
 5218        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5219
 5220        editor
 5221    });
 5222
 5223    _ = cx.add_window(|window, cx| {
 5224        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5225        editor.set_style(EditorStyle::default(), window, cx);
 5226        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5227            s.select_ranges([1..1, 2..2, 4..4])
 5228        });
 5229        editor.transpose(&Default::default(), window, cx);
 5230        assert_eq!(editor.text(cx), "bacd\ne");
 5231        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 5232
 5233        editor.transpose(&Default::default(), window, cx);
 5234        assert_eq!(editor.text(cx), "bcade\n");
 5235        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 5236
 5237        editor.transpose(&Default::default(), window, cx);
 5238        assert_eq!(editor.text(cx), "bcda\ne");
 5239        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5240
 5241        editor.transpose(&Default::default(), window, cx);
 5242        assert_eq!(editor.text(cx), "bcade\n");
 5243        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5244
 5245        editor.transpose(&Default::default(), window, cx);
 5246        assert_eq!(editor.text(cx), "bcaed\n");
 5247        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 5248
 5249        editor
 5250    });
 5251
 5252    _ = cx.add_window(|window, cx| {
 5253        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 5254        editor.set_style(EditorStyle::default(), window, cx);
 5255        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5256            s.select_ranges([4..4])
 5257        });
 5258        editor.transpose(&Default::default(), window, cx);
 5259        assert_eq!(editor.text(cx), "🏀🍐✋");
 5260        assert_eq!(editor.selections.ranges(cx), [8..8]);
 5261
 5262        editor.transpose(&Default::default(), window, cx);
 5263        assert_eq!(editor.text(cx), "🏀✋🍐");
 5264        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5265
 5266        editor.transpose(&Default::default(), window, cx);
 5267        assert_eq!(editor.text(cx), "🏀🍐✋");
 5268        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5269
 5270        editor
 5271    });
 5272}
 5273
 5274#[gpui::test]
 5275async fn test_rewrap(cx: &mut TestAppContext) {
 5276    init_test(cx, |settings| {
 5277        settings.languages.0.extend([
 5278            (
 5279                "Markdown".into(),
 5280                LanguageSettingsContent {
 5281                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5282                    preferred_line_length: Some(40),
 5283                    ..Default::default()
 5284                },
 5285            ),
 5286            (
 5287                "Plain Text".into(),
 5288                LanguageSettingsContent {
 5289                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5290                    preferred_line_length: Some(40),
 5291                    ..Default::default()
 5292                },
 5293            ),
 5294            (
 5295                "C++".into(),
 5296                LanguageSettingsContent {
 5297                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5298                    preferred_line_length: Some(40),
 5299                    ..Default::default()
 5300                },
 5301            ),
 5302            (
 5303                "Python".into(),
 5304                LanguageSettingsContent {
 5305                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5306                    preferred_line_length: Some(40),
 5307                    ..Default::default()
 5308                },
 5309            ),
 5310            (
 5311                "Rust".into(),
 5312                LanguageSettingsContent {
 5313                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5314                    preferred_line_length: Some(40),
 5315                    ..Default::default()
 5316                },
 5317            ),
 5318        ])
 5319    });
 5320
 5321    let mut cx = EditorTestContext::new(cx).await;
 5322
 5323    let cpp_language = Arc::new(Language::new(
 5324        LanguageConfig {
 5325            name: "C++".into(),
 5326            line_comments: vec!["// ".into()],
 5327            ..LanguageConfig::default()
 5328        },
 5329        None,
 5330    ));
 5331    let python_language = Arc::new(Language::new(
 5332        LanguageConfig {
 5333            name: "Python".into(),
 5334            line_comments: vec!["# ".into()],
 5335            ..LanguageConfig::default()
 5336        },
 5337        None,
 5338    ));
 5339    let markdown_language = Arc::new(Language::new(
 5340        LanguageConfig {
 5341            name: "Markdown".into(),
 5342            rewrap_prefixes: vec![
 5343                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 5344                regex::Regex::new("[-*+]\\s+").unwrap(),
 5345            ],
 5346            ..LanguageConfig::default()
 5347        },
 5348        None,
 5349    ));
 5350    let rust_language = Arc::new(Language::new(
 5351        LanguageConfig {
 5352            name: "Rust".into(),
 5353            line_comments: vec!["// ".into(), "/// ".into()],
 5354            ..LanguageConfig::default()
 5355        },
 5356        Some(tree_sitter_rust::LANGUAGE.into()),
 5357    ));
 5358
 5359    let plaintext_language = Arc::new(Language::new(
 5360        LanguageConfig {
 5361            name: "Plain Text".into(),
 5362            ..LanguageConfig::default()
 5363        },
 5364        None,
 5365    ));
 5366
 5367    // Test basic rewrapping of a long line with a cursor
 5368    assert_rewrap(
 5369        indoc! {"
 5370            // ˇThis is a long comment that needs to be wrapped.
 5371        "},
 5372        indoc! {"
 5373            // ˇThis is a long comment that needs to
 5374            // be wrapped.
 5375        "},
 5376        cpp_language.clone(),
 5377        &mut cx,
 5378    );
 5379
 5380    // Test rewrapping a full selection
 5381    assert_rewrap(
 5382        indoc! {"
 5383            «// This selected long comment needs to be wrapped.ˇ»"
 5384        },
 5385        indoc! {"
 5386            «// This selected long comment needs to
 5387            // be wrapped.ˇ»"
 5388        },
 5389        cpp_language.clone(),
 5390        &mut cx,
 5391    );
 5392
 5393    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 5394    assert_rewrap(
 5395        indoc! {"
 5396            // ˇThis is the first line.
 5397            // Thisˇ is the second line.
 5398            // This is the thirdˇ line, all part of one paragraph.
 5399         "},
 5400        indoc! {"
 5401            // ˇThis is the first line. Thisˇ is the
 5402            // second line. This is the thirdˇ line,
 5403            // all part of one paragraph.
 5404         "},
 5405        cpp_language.clone(),
 5406        &mut cx,
 5407    );
 5408
 5409    // Test multiple cursors in different paragraphs trigger separate rewraps
 5410    assert_rewrap(
 5411        indoc! {"
 5412            // ˇThis is the first paragraph, first line.
 5413            // ˇThis is the first paragraph, second line.
 5414
 5415            // ˇThis is the second paragraph, first line.
 5416            // ˇThis is the second paragraph, second line.
 5417        "},
 5418        indoc! {"
 5419            // ˇThis is the first paragraph, first
 5420            // line. ˇThis is the first paragraph,
 5421            // second line.
 5422
 5423            // ˇThis is the second paragraph, first
 5424            // line. ˇThis is the second paragraph,
 5425            // second line.
 5426        "},
 5427        cpp_language.clone(),
 5428        &mut cx,
 5429    );
 5430
 5431    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 5432    assert_rewrap(
 5433        indoc! {"
 5434            «// A regular long long comment to be wrapped.
 5435            /// A documentation long comment to be wrapped.ˇ»
 5436          "},
 5437        indoc! {"
 5438            «// A regular long long comment to be
 5439            // wrapped.
 5440            /// A documentation long comment to be
 5441            /// wrapped.ˇ»
 5442          "},
 5443        rust_language.clone(),
 5444        &mut cx,
 5445    );
 5446
 5447    // Test that change in indentation level trigger seperate rewraps
 5448    assert_rewrap(
 5449        indoc! {"
 5450            fn foo() {
 5451                «// This is a long comment at the base indent.
 5452                    // This is a long comment at the next indent.ˇ»
 5453            }
 5454        "},
 5455        indoc! {"
 5456            fn foo() {
 5457                «// This is a long comment at the
 5458                // base indent.
 5459                    // This is a long comment at the
 5460                    // next indent.ˇ»
 5461            }
 5462        "},
 5463        rust_language.clone(),
 5464        &mut cx,
 5465    );
 5466
 5467    // Test that different comment prefix characters (e.g., '#') are handled correctly
 5468    assert_rewrap(
 5469        indoc! {"
 5470            # ˇThis is a long comment using a pound sign.
 5471        "},
 5472        indoc! {"
 5473            # ˇThis is a long comment using a pound
 5474            # sign.
 5475        "},
 5476        python_language.clone(),
 5477        &mut cx,
 5478    );
 5479
 5480    // Test rewrapping only affects comments, not code even when selected
 5481    assert_rewrap(
 5482        indoc! {"
 5483            «/// This doc comment is long and should be wrapped.
 5484            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 5485        "},
 5486        indoc! {"
 5487            «/// This doc comment is long and should
 5488            /// be wrapped.
 5489            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 5490        "},
 5491        rust_language.clone(),
 5492        &mut cx,
 5493    );
 5494
 5495    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 5496    assert_rewrap(
 5497        indoc! {"
 5498            # Header
 5499
 5500            A long long long line of markdown text to wrap.ˇ
 5501         "},
 5502        indoc! {"
 5503            # Header
 5504
 5505            A long long long line of markdown text
 5506            to wrap.ˇ
 5507         "},
 5508        markdown_language.clone(),
 5509        &mut cx,
 5510    );
 5511
 5512    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 5513    assert_rewrap(
 5514        indoc! {"
 5515            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 5516            2. This is a numbered list item that is very long and needs to be wrapped properly.
 5517            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 5518        "},
 5519        indoc! {"
 5520            «1. This is a numbered list item that is
 5521               very long and needs to be wrapped
 5522               properly.
 5523            2. This is a numbered list item that is
 5524               very long and needs to be wrapped
 5525               properly.
 5526            - This is an unordered list item that is
 5527              also very long and should not merge
 5528              with the numbered item.ˇ»
 5529        "},
 5530        markdown_language.clone(),
 5531        &mut cx,
 5532    );
 5533
 5534    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 5535    assert_rewrap(
 5536        indoc! {"
 5537            «1. This is a numbered list item that is
 5538            very long and needs to be wrapped
 5539            properly.
 5540            2. This is a numbered list item that is
 5541            very long and needs to be wrapped
 5542            properly.
 5543            - This is an unordered list item that is
 5544            also very long and should not merge with
 5545            the numbered item.ˇ»
 5546        "},
 5547        indoc! {"
 5548            «1. This is a numbered list item that is
 5549               very long and needs to be wrapped
 5550               properly.
 5551            2. This is a numbered list item that is
 5552               very long and needs to be wrapped
 5553               properly.
 5554            - This is an unordered list item that is
 5555              also very long and should not merge
 5556              with the numbered item.ˇ»
 5557        "},
 5558        markdown_language.clone(),
 5559        &mut cx,
 5560    );
 5561
 5562    // Test that rewrapping maintain indents even when they already exists.
 5563    assert_rewrap(
 5564        indoc! {"
 5565            «1. This is a numbered list
 5566               item that is very long and needs to be wrapped properly.
 5567            2. This is a numbered list
 5568               item that is very long and needs to be wrapped properly.
 5569            - This is an unordered list item that is also very long and
 5570              should not merge with the numbered item.ˇ»
 5571        "},
 5572        indoc! {"
 5573            «1. This is a numbered list item that is
 5574               very long and needs to be wrapped
 5575               properly.
 5576            2. This is a numbered list item that is
 5577               very long and needs to be wrapped
 5578               properly.
 5579            - This is an unordered list item that is
 5580              also very long and should not merge
 5581              with the numbered item.ˇ»
 5582        "},
 5583        markdown_language.clone(),
 5584        &mut cx,
 5585    );
 5586
 5587    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 5588    assert_rewrap(
 5589        indoc! {"
 5590            ˇThis is a very long line of plain text that will be wrapped.
 5591        "},
 5592        indoc! {"
 5593            ˇThis is a very long line of plain text
 5594            that will be wrapped.
 5595        "},
 5596        plaintext_language.clone(),
 5597        &mut cx,
 5598    );
 5599
 5600    // Test that non-commented code acts as a paragraph boundary within a selection
 5601    assert_rewrap(
 5602        indoc! {"
 5603               «// This is the first long comment block to be wrapped.
 5604               fn my_func(a: u32);
 5605               // This is the second long comment block to be wrapped.ˇ»
 5606           "},
 5607        indoc! {"
 5608               «// This is the first long comment block
 5609               // to be wrapped.
 5610               fn my_func(a: u32);
 5611               // This is the second long comment block
 5612               // to be wrapped.ˇ»
 5613           "},
 5614        rust_language.clone(),
 5615        &mut cx,
 5616    );
 5617
 5618    // Test rewrapping multiple selections, including ones with blank lines or tabs
 5619    assert_rewrap(
 5620        indoc! {"
 5621            «ˇThis is a very long line that will be wrapped.
 5622
 5623            This is another paragraph in the same selection.»
 5624
 5625            «\tThis is a very long indented line that will be wrapped.ˇ»
 5626         "},
 5627        indoc! {"
 5628            «ˇThis is a very long line that will be
 5629            wrapped.
 5630
 5631            This is another paragraph in the same
 5632            selection.»
 5633
 5634            «\tThis is a very long indented line
 5635            \tthat will be wrapped.ˇ»
 5636         "},
 5637        plaintext_language.clone(),
 5638        &mut cx,
 5639    );
 5640
 5641    // Test that an empty comment line acts as a paragraph boundary
 5642    assert_rewrap(
 5643        indoc! {"
 5644            // ˇThis is a long comment that will be wrapped.
 5645            //
 5646            // And this is another long comment that will also be wrapped.ˇ
 5647         "},
 5648        indoc! {"
 5649            // ˇThis is a long comment that will be
 5650            // wrapped.
 5651            //
 5652            // And this is another long comment that
 5653            // will also be wrapped.ˇ
 5654         "},
 5655        cpp_language,
 5656        &mut cx,
 5657    );
 5658
 5659    #[track_caller]
 5660    fn assert_rewrap(
 5661        unwrapped_text: &str,
 5662        wrapped_text: &str,
 5663        language: Arc<Language>,
 5664        cx: &mut EditorTestContext,
 5665    ) {
 5666        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 5667        cx.set_state(unwrapped_text);
 5668        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 5669        cx.assert_editor_state(wrapped_text);
 5670    }
 5671}
 5672
 5673#[gpui::test]
 5674async fn test_hard_wrap(cx: &mut TestAppContext) {
 5675    init_test(cx, |_| {});
 5676    let mut cx = EditorTestContext::new(cx).await;
 5677
 5678    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 5679    cx.update_editor(|editor, _, cx| {
 5680        editor.set_hard_wrap(Some(14), cx);
 5681    });
 5682
 5683    cx.set_state(indoc!(
 5684        "
 5685        one two three ˇ
 5686        "
 5687    ));
 5688    cx.simulate_input("four");
 5689    cx.run_until_parked();
 5690
 5691    cx.assert_editor_state(indoc!(
 5692        "
 5693        one two three
 5694        fourˇ
 5695        "
 5696    ));
 5697
 5698    cx.update_editor(|editor, window, cx| {
 5699        editor.newline(&Default::default(), window, cx);
 5700    });
 5701    cx.run_until_parked();
 5702    cx.assert_editor_state(indoc!(
 5703        "
 5704        one two three
 5705        four
 5706        ˇ
 5707        "
 5708    ));
 5709
 5710    cx.simulate_input("five");
 5711    cx.run_until_parked();
 5712    cx.assert_editor_state(indoc!(
 5713        "
 5714        one two three
 5715        four
 5716        fiveˇ
 5717        "
 5718    ));
 5719
 5720    cx.update_editor(|editor, window, cx| {
 5721        editor.newline(&Default::default(), window, cx);
 5722    });
 5723    cx.run_until_parked();
 5724    cx.simulate_input("# ");
 5725    cx.run_until_parked();
 5726    cx.assert_editor_state(indoc!(
 5727        "
 5728        one two three
 5729        four
 5730        five
 5731        # ˇ
 5732        "
 5733    ));
 5734
 5735    cx.update_editor(|editor, window, cx| {
 5736        editor.newline(&Default::default(), window, cx);
 5737    });
 5738    cx.run_until_parked();
 5739    cx.assert_editor_state(indoc!(
 5740        "
 5741        one two three
 5742        four
 5743        five
 5744        #\x20
 5745 5746        "
 5747    ));
 5748
 5749    cx.simulate_input(" 6");
 5750    cx.run_until_parked();
 5751    cx.assert_editor_state(indoc!(
 5752        "
 5753        one two three
 5754        four
 5755        five
 5756        #
 5757        # 6ˇ
 5758        "
 5759    ));
 5760}
 5761
 5762#[gpui::test]
 5763async fn test_clipboard(cx: &mut TestAppContext) {
 5764    init_test(cx, |_| {});
 5765
 5766    let mut cx = EditorTestContext::new(cx).await;
 5767
 5768    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 5769    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5770    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 5771
 5772    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 5773    cx.set_state("two ˇfour ˇsix ˇ");
 5774    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5775    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 5776
 5777    // Paste again but with only two cursors. Since the number of cursors doesn't
 5778    // match the number of slices in the clipboard, the entire clipboard text
 5779    // is pasted at each cursor.
 5780    cx.set_state("ˇtwo one✅ four three six five ˇ");
 5781    cx.update_editor(|e, window, cx| {
 5782        e.handle_input("( ", window, cx);
 5783        e.paste(&Paste, window, cx);
 5784        e.handle_input(") ", window, cx);
 5785    });
 5786    cx.assert_editor_state(
 5787        &([
 5788            "( one✅ ",
 5789            "three ",
 5790            "five ) ˇtwo one✅ four three six five ( one✅ ",
 5791            "three ",
 5792            "five ) ˇ",
 5793        ]
 5794        .join("\n")),
 5795    );
 5796
 5797    // Cut with three selections, one of which is full-line.
 5798    cx.set_state(indoc! {"
 5799        1«2ˇ»3
 5800        4ˇ567
 5801        «8ˇ»9"});
 5802    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5803    cx.assert_editor_state(indoc! {"
 5804        1ˇ3
 5805        ˇ9"});
 5806
 5807    // Paste with three selections, noticing how the copied selection that was full-line
 5808    // gets inserted before the second cursor.
 5809    cx.set_state(indoc! {"
 5810        1ˇ3
 5811 5812        «oˇ»ne"});
 5813    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5814    cx.assert_editor_state(indoc! {"
 5815        12ˇ3
 5816        4567
 5817 5818        8ˇne"});
 5819
 5820    // Copy with a single cursor only, which writes the whole line into the clipboard.
 5821    cx.set_state(indoc! {"
 5822        The quick brown
 5823        fox juˇmps over
 5824        the lazy dog"});
 5825    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5826    assert_eq!(
 5827        cx.read_from_clipboard()
 5828            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5829        Some("fox jumps over\n".to_string())
 5830    );
 5831
 5832    // Paste with three selections, noticing how the copied full-line selection is inserted
 5833    // before the empty selections but replaces the selection that is non-empty.
 5834    cx.set_state(indoc! {"
 5835        Tˇhe quick brown
 5836        «foˇ»x jumps over
 5837        tˇhe lazy dog"});
 5838    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5839    cx.assert_editor_state(indoc! {"
 5840        fox jumps over
 5841        Tˇhe quick brown
 5842        fox jumps over
 5843        ˇx jumps over
 5844        fox jumps over
 5845        tˇhe lazy dog"});
 5846}
 5847
 5848#[gpui::test]
 5849async fn test_copy_trim(cx: &mut TestAppContext) {
 5850    init_test(cx, |_| {});
 5851
 5852    let mut cx = EditorTestContext::new(cx).await;
 5853    cx.set_state(
 5854        r#"            «for selection in selections.iter() {
 5855            let mut start = selection.start;
 5856            let mut end = selection.end;
 5857            let is_entire_line = selection.is_empty();
 5858            if is_entire_line {
 5859                start = Point::new(start.row, 0);ˇ»
 5860                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5861            }
 5862        "#,
 5863    );
 5864    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5865    assert_eq!(
 5866        cx.read_from_clipboard()
 5867            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5868        Some(
 5869            "for selection in selections.iter() {
 5870            let mut start = selection.start;
 5871            let mut end = selection.end;
 5872            let is_entire_line = selection.is_empty();
 5873            if is_entire_line {
 5874                start = Point::new(start.row, 0);"
 5875                .to_string()
 5876        ),
 5877        "Regular copying preserves all indentation selected",
 5878    );
 5879    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5880    assert_eq!(
 5881        cx.read_from_clipboard()
 5882            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5883        Some(
 5884            "for selection in selections.iter() {
 5885let mut start = selection.start;
 5886let mut end = selection.end;
 5887let is_entire_line = selection.is_empty();
 5888if is_entire_line {
 5889    start = Point::new(start.row, 0);"
 5890                .to_string()
 5891        ),
 5892        "Copying with stripping should strip all leading whitespaces"
 5893    );
 5894
 5895    cx.set_state(
 5896        r#"       «     for selection in selections.iter() {
 5897            let mut start = selection.start;
 5898            let mut end = selection.end;
 5899            let is_entire_line = selection.is_empty();
 5900            if is_entire_line {
 5901                start = Point::new(start.row, 0);ˇ»
 5902                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5903            }
 5904        "#,
 5905    );
 5906    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5907    assert_eq!(
 5908        cx.read_from_clipboard()
 5909            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5910        Some(
 5911            "     for selection in selections.iter() {
 5912            let mut start = selection.start;
 5913            let mut end = selection.end;
 5914            let is_entire_line = selection.is_empty();
 5915            if is_entire_line {
 5916                start = Point::new(start.row, 0);"
 5917                .to_string()
 5918        ),
 5919        "Regular copying preserves all indentation selected",
 5920    );
 5921    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5922    assert_eq!(
 5923        cx.read_from_clipboard()
 5924            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5925        Some(
 5926            "for selection in selections.iter() {
 5927let mut start = selection.start;
 5928let mut end = selection.end;
 5929let is_entire_line = selection.is_empty();
 5930if is_entire_line {
 5931    start = Point::new(start.row, 0);"
 5932                .to_string()
 5933        ),
 5934        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 5935    );
 5936
 5937    cx.set_state(
 5938        r#"       «ˇ     for selection in selections.iter() {
 5939            let mut start = selection.start;
 5940            let mut end = selection.end;
 5941            let is_entire_line = selection.is_empty();
 5942            if is_entire_line {
 5943                start = Point::new(start.row, 0);»
 5944                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5945            }
 5946        "#,
 5947    );
 5948    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5949    assert_eq!(
 5950        cx.read_from_clipboard()
 5951            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5952        Some(
 5953            "     for selection in selections.iter() {
 5954            let mut start = selection.start;
 5955            let mut end = selection.end;
 5956            let is_entire_line = selection.is_empty();
 5957            if is_entire_line {
 5958                start = Point::new(start.row, 0);"
 5959                .to_string()
 5960        ),
 5961        "Regular copying for reverse selection works the same",
 5962    );
 5963    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5964    assert_eq!(
 5965        cx.read_from_clipboard()
 5966            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5967        Some(
 5968            "for selection in selections.iter() {
 5969let mut start = selection.start;
 5970let mut end = selection.end;
 5971let is_entire_line = selection.is_empty();
 5972if is_entire_line {
 5973    start = Point::new(start.row, 0);"
 5974                .to_string()
 5975        ),
 5976        "Copying with stripping for reverse selection works the same"
 5977    );
 5978
 5979    cx.set_state(
 5980        r#"            for selection «in selections.iter() {
 5981            let mut start = selection.start;
 5982            let mut end = selection.end;
 5983            let is_entire_line = selection.is_empty();
 5984            if is_entire_line {
 5985                start = Point::new(start.row, 0);ˇ»
 5986                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5987            }
 5988        "#,
 5989    );
 5990    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5991    assert_eq!(
 5992        cx.read_from_clipboard()
 5993            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5994        Some(
 5995            "in selections.iter() {
 5996            let mut start = selection.start;
 5997            let mut end = selection.end;
 5998            let is_entire_line = selection.is_empty();
 5999            if is_entire_line {
 6000                start = Point::new(start.row, 0);"
 6001                .to_string()
 6002        ),
 6003        "When selecting past the indent, the copying works as usual",
 6004    );
 6005    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6006    assert_eq!(
 6007        cx.read_from_clipboard()
 6008            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6009        Some(
 6010            "in selections.iter() {
 6011            let mut start = selection.start;
 6012            let mut end = selection.end;
 6013            let is_entire_line = selection.is_empty();
 6014            if is_entire_line {
 6015                start = Point::new(start.row, 0);"
 6016                .to_string()
 6017        ),
 6018        "When selecting past the indent, nothing is trimmed"
 6019    );
 6020
 6021    cx.set_state(
 6022        r#"            «for selection in selections.iter() {
 6023            let mut start = selection.start;
 6024
 6025            let mut end = selection.end;
 6026            let is_entire_line = selection.is_empty();
 6027            if is_entire_line {
 6028                start = Point::new(start.row, 0);
 6029ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6030            }
 6031        "#,
 6032    );
 6033    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6034    assert_eq!(
 6035        cx.read_from_clipboard()
 6036            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6037        Some(
 6038            "for selection in selections.iter() {
 6039let mut start = selection.start;
 6040
 6041let mut end = selection.end;
 6042let is_entire_line = selection.is_empty();
 6043if is_entire_line {
 6044    start = Point::new(start.row, 0);
 6045"
 6046            .to_string()
 6047        ),
 6048        "Copying with stripping should ignore empty lines"
 6049    );
 6050}
 6051
 6052#[gpui::test]
 6053async fn test_paste_multiline(cx: &mut TestAppContext) {
 6054    init_test(cx, |_| {});
 6055
 6056    let mut cx = EditorTestContext::new(cx).await;
 6057    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 6058
 6059    // Cut an indented block, without the leading whitespace.
 6060    cx.set_state(indoc! {"
 6061        const a: B = (
 6062            c(),
 6063            «d(
 6064                e,
 6065                f
 6066            )ˇ»
 6067        );
 6068    "});
 6069    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6070    cx.assert_editor_state(indoc! {"
 6071        const a: B = (
 6072            c(),
 6073            ˇ
 6074        );
 6075    "});
 6076
 6077    // Paste it at the same position.
 6078    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6079    cx.assert_editor_state(indoc! {"
 6080        const a: B = (
 6081            c(),
 6082            d(
 6083                e,
 6084                f
 6085 6086        );
 6087    "});
 6088
 6089    // Paste it at a line with a lower indent level.
 6090    cx.set_state(indoc! {"
 6091        ˇ
 6092        const a: B = (
 6093            c(),
 6094        );
 6095    "});
 6096    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6097    cx.assert_editor_state(indoc! {"
 6098        d(
 6099            e,
 6100            f
 6101 6102        const a: B = (
 6103            c(),
 6104        );
 6105    "});
 6106
 6107    // Cut an indented block, with the leading whitespace.
 6108    cx.set_state(indoc! {"
 6109        const a: B = (
 6110            c(),
 6111        «    d(
 6112                e,
 6113                f
 6114            )
 6115        ˇ»);
 6116    "});
 6117    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6118    cx.assert_editor_state(indoc! {"
 6119        const a: B = (
 6120            c(),
 6121        ˇ);
 6122    "});
 6123
 6124    // Paste it at the same position.
 6125    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6126    cx.assert_editor_state(indoc! {"
 6127        const a: B = (
 6128            c(),
 6129            d(
 6130                e,
 6131                f
 6132            )
 6133        ˇ);
 6134    "});
 6135
 6136    // Paste it at a line with a higher indent level.
 6137    cx.set_state(indoc! {"
 6138        const a: B = (
 6139            c(),
 6140            d(
 6141                e,
 6142 6143            )
 6144        );
 6145    "});
 6146    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6147    cx.assert_editor_state(indoc! {"
 6148        const a: B = (
 6149            c(),
 6150            d(
 6151                e,
 6152                f    d(
 6153                    e,
 6154                    f
 6155                )
 6156        ˇ
 6157            )
 6158        );
 6159    "});
 6160
 6161    // Copy an indented block, starting mid-line
 6162    cx.set_state(indoc! {"
 6163        const a: B = (
 6164            c(),
 6165            somethin«g(
 6166                e,
 6167                f
 6168            )ˇ»
 6169        );
 6170    "});
 6171    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6172
 6173    // Paste it on a line with a lower indent level
 6174    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 6175    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6176    cx.assert_editor_state(indoc! {"
 6177        const a: B = (
 6178            c(),
 6179            something(
 6180                e,
 6181                f
 6182            )
 6183        );
 6184        g(
 6185            e,
 6186            f
 6187"});
 6188}
 6189
 6190#[gpui::test]
 6191async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 6192    init_test(cx, |_| {});
 6193
 6194    cx.write_to_clipboard(ClipboardItem::new_string(
 6195        "    d(\n        e\n    );\n".into(),
 6196    ));
 6197
 6198    let mut cx = EditorTestContext::new(cx).await;
 6199    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 6200
 6201    cx.set_state(indoc! {"
 6202        fn a() {
 6203            b();
 6204            if c() {
 6205                ˇ
 6206            }
 6207        }
 6208    "});
 6209
 6210    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6211    cx.assert_editor_state(indoc! {"
 6212        fn a() {
 6213            b();
 6214            if c() {
 6215                d(
 6216                    e
 6217                );
 6218        ˇ
 6219            }
 6220        }
 6221    "});
 6222
 6223    cx.set_state(indoc! {"
 6224        fn a() {
 6225            b();
 6226            ˇ
 6227        }
 6228    "});
 6229
 6230    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6231    cx.assert_editor_state(indoc! {"
 6232        fn a() {
 6233            b();
 6234            d(
 6235                e
 6236            );
 6237        ˇ
 6238        }
 6239    "});
 6240}
 6241
 6242#[gpui::test]
 6243fn test_select_all(cx: &mut TestAppContext) {
 6244    init_test(cx, |_| {});
 6245
 6246    let editor = cx.add_window(|window, cx| {
 6247        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 6248        build_editor(buffer, window, cx)
 6249    });
 6250    _ = editor.update(cx, |editor, window, cx| {
 6251        editor.select_all(&SelectAll, window, cx);
 6252        assert_eq!(
 6253            editor.selections.display_ranges(cx),
 6254            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 6255        );
 6256    });
 6257}
 6258
 6259#[gpui::test]
 6260fn test_select_line(cx: &mut TestAppContext) {
 6261    init_test(cx, |_| {});
 6262
 6263    let editor = cx.add_window(|window, cx| {
 6264        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 6265        build_editor(buffer, window, cx)
 6266    });
 6267    _ = editor.update(cx, |editor, window, cx| {
 6268        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6269            s.select_display_ranges([
 6270                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6271                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6272                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6273                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 6274            ])
 6275        });
 6276        editor.select_line(&SelectLine, window, cx);
 6277        assert_eq!(
 6278            editor.selections.display_ranges(cx),
 6279            vec![
 6280                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 6281                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 6282            ]
 6283        );
 6284    });
 6285
 6286    _ = editor.update(cx, |editor, window, cx| {
 6287        editor.select_line(&SelectLine, window, cx);
 6288        assert_eq!(
 6289            editor.selections.display_ranges(cx),
 6290            vec![
 6291                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 6292                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 6293            ]
 6294        );
 6295    });
 6296
 6297    _ = editor.update(cx, |editor, window, cx| {
 6298        editor.select_line(&SelectLine, window, cx);
 6299        assert_eq!(
 6300            editor.selections.display_ranges(cx),
 6301            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 6302        );
 6303    });
 6304}
 6305
 6306#[gpui::test]
 6307async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 6308    init_test(cx, |_| {});
 6309    let mut cx = EditorTestContext::new(cx).await;
 6310
 6311    #[track_caller]
 6312    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 6313        cx.set_state(initial_state);
 6314        cx.update_editor(|e, window, cx| {
 6315            e.split_selection_into_lines(&SplitSelectionIntoLines, window, cx)
 6316        });
 6317        cx.assert_editor_state(expected_state);
 6318    }
 6319
 6320    // Selection starts and ends at the middle of lines, left-to-right
 6321    test(
 6322        &mut cx,
 6323        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 6324        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 6325    );
 6326    // Same thing, right-to-left
 6327    test(
 6328        &mut cx,
 6329        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 6330        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 6331    );
 6332
 6333    // Whole buffer, left-to-right, last line *doesn't* end with newline
 6334    test(
 6335        &mut cx,
 6336        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 6337        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 6338    );
 6339    // Same thing, right-to-left
 6340    test(
 6341        &mut cx,
 6342        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 6343        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 6344    );
 6345
 6346    // Whole buffer, left-to-right, last line ends with newline
 6347    test(
 6348        &mut cx,
 6349        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 6350        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 6351    );
 6352    // Same thing, right-to-left
 6353    test(
 6354        &mut cx,
 6355        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 6356        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 6357    );
 6358
 6359    // Starts at the end of a line, ends at the start of another
 6360    test(
 6361        &mut cx,
 6362        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 6363        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 6364    );
 6365}
 6366
 6367#[gpui::test]
 6368async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 6369    init_test(cx, |_| {});
 6370
 6371    let editor = cx.add_window(|window, cx| {
 6372        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 6373        build_editor(buffer, window, cx)
 6374    });
 6375
 6376    // setup
 6377    _ = editor.update(cx, |editor, window, cx| {
 6378        editor.fold_creases(
 6379            vec![
 6380                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 6381                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 6382                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 6383            ],
 6384            true,
 6385            window,
 6386            cx,
 6387        );
 6388        assert_eq!(
 6389            editor.display_text(cx),
 6390            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 6391        );
 6392    });
 6393
 6394    _ = editor.update(cx, |editor, window, cx| {
 6395        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6396            s.select_display_ranges([
 6397                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6398                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6399                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6400                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 6401            ])
 6402        });
 6403        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 6404        assert_eq!(
 6405            editor.display_text(cx),
 6406            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 6407        );
 6408    });
 6409    EditorTestContext::for_editor(editor, cx)
 6410        .await
 6411        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 6412
 6413    _ = editor.update(cx, |editor, window, cx| {
 6414        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6415            s.select_display_ranges([
 6416                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 6417            ])
 6418        });
 6419        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 6420        assert_eq!(
 6421            editor.display_text(cx),
 6422            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 6423        );
 6424        assert_eq!(
 6425            editor.selections.display_ranges(cx),
 6426            [
 6427                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 6428                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 6429                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 6430                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 6431                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 6432                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 6433                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 6434            ]
 6435        );
 6436    });
 6437    EditorTestContext::for_editor(editor, cx)
 6438        .await
 6439        .assert_editor_state(
 6440            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 6441        );
 6442}
 6443
 6444#[gpui::test]
 6445async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 6446    init_test(cx, |_| {});
 6447
 6448    let mut cx = EditorTestContext::new(cx).await;
 6449
 6450    cx.set_state(indoc!(
 6451        r#"abc
 6452           defˇghi
 6453
 6454           jk
 6455           nlmo
 6456           "#
 6457    ));
 6458
 6459    cx.update_editor(|editor, window, cx| {
 6460        editor.add_selection_above(&Default::default(), window, cx);
 6461    });
 6462
 6463    cx.assert_editor_state(indoc!(
 6464        r#"abcˇ
 6465           defˇghi
 6466
 6467           jk
 6468           nlmo
 6469           "#
 6470    ));
 6471
 6472    cx.update_editor(|editor, window, cx| {
 6473        editor.add_selection_above(&Default::default(), window, cx);
 6474    });
 6475
 6476    cx.assert_editor_state(indoc!(
 6477        r#"abcˇ
 6478            defˇghi
 6479
 6480            jk
 6481            nlmo
 6482            "#
 6483    ));
 6484
 6485    cx.update_editor(|editor, window, cx| {
 6486        editor.add_selection_below(&Default::default(), window, cx);
 6487    });
 6488
 6489    cx.assert_editor_state(indoc!(
 6490        r#"abc
 6491           defˇghi
 6492
 6493           jk
 6494           nlmo
 6495           "#
 6496    ));
 6497
 6498    cx.update_editor(|editor, window, cx| {
 6499        editor.undo_selection(&Default::default(), window, cx);
 6500    });
 6501
 6502    cx.assert_editor_state(indoc!(
 6503        r#"abcˇ
 6504           defˇghi
 6505
 6506           jk
 6507           nlmo
 6508           "#
 6509    ));
 6510
 6511    cx.update_editor(|editor, window, cx| {
 6512        editor.redo_selection(&Default::default(), window, cx);
 6513    });
 6514
 6515    cx.assert_editor_state(indoc!(
 6516        r#"abc
 6517           defˇghi
 6518
 6519           jk
 6520           nlmo
 6521           "#
 6522    ));
 6523
 6524    cx.update_editor(|editor, window, cx| {
 6525        editor.add_selection_below(&Default::default(), window, cx);
 6526    });
 6527
 6528    cx.assert_editor_state(indoc!(
 6529        r#"abc
 6530           defˇghi
 6531           ˇ
 6532           jk
 6533           nlmo
 6534           "#
 6535    ));
 6536
 6537    cx.update_editor(|editor, window, cx| {
 6538        editor.add_selection_below(&Default::default(), window, cx);
 6539    });
 6540
 6541    cx.assert_editor_state(indoc!(
 6542        r#"abc
 6543           defˇghi
 6544           ˇ
 6545           jkˇ
 6546           nlmo
 6547           "#
 6548    ));
 6549
 6550    cx.update_editor(|editor, window, cx| {
 6551        editor.add_selection_below(&Default::default(), window, cx);
 6552    });
 6553
 6554    cx.assert_editor_state(indoc!(
 6555        r#"abc
 6556           defˇghi
 6557           ˇ
 6558           jkˇ
 6559           nlmˇo
 6560           "#
 6561    ));
 6562
 6563    cx.update_editor(|editor, window, cx| {
 6564        editor.add_selection_below(&Default::default(), window, cx);
 6565    });
 6566
 6567    cx.assert_editor_state(indoc!(
 6568        r#"abc
 6569           defˇghi
 6570           ˇ
 6571           jkˇ
 6572           nlmˇo
 6573           ˇ"#
 6574    ));
 6575
 6576    // change selections
 6577    cx.set_state(indoc!(
 6578        r#"abc
 6579           def«ˇg»hi
 6580
 6581           jk
 6582           nlmo
 6583           "#
 6584    ));
 6585
 6586    cx.update_editor(|editor, window, cx| {
 6587        editor.add_selection_below(&Default::default(), window, cx);
 6588    });
 6589
 6590    cx.assert_editor_state(indoc!(
 6591        r#"abc
 6592           def«ˇg»hi
 6593
 6594           jk
 6595           nlm«ˇo»
 6596           "#
 6597    ));
 6598
 6599    cx.update_editor(|editor, window, cx| {
 6600        editor.add_selection_below(&Default::default(), window, cx);
 6601    });
 6602
 6603    cx.assert_editor_state(indoc!(
 6604        r#"abc
 6605           def«ˇg»hi
 6606
 6607           jk
 6608           nlm«ˇo»
 6609           "#
 6610    ));
 6611
 6612    cx.update_editor(|editor, window, cx| {
 6613        editor.add_selection_above(&Default::default(), window, cx);
 6614    });
 6615
 6616    cx.assert_editor_state(indoc!(
 6617        r#"abc
 6618           def«ˇg»hi
 6619
 6620           jk
 6621           nlmo
 6622           "#
 6623    ));
 6624
 6625    cx.update_editor(|editor, window, cx| {
 6626        editor.add_selection_above(&Default::default(), window, cx);
 6627    });
 6628
 6629    cx.assert_editor_state(indoc!(
 6630        r#"abc
 6631           def«ˇg»hi
 6632
 6633           jk
 6634           nlmo
 6635           "#
 6636    ));
 6637
 6638    // Change selections again
 6639    cx.set_state(indoc!(
 6640        r#"a«bc
 6641           defgˇ»hi
 6642
 6643           jk
 6644           nlmo
 6645           "#
 6646    ));
 6647
 6648    cx.update_editor(|editor, window, cx| {
 6649        editor.add_selection_below(&Default::default(), window, cx);
 6650    });
 6651
 6652    cx.assert_editor_state(indoc!(
 6653        r#"a«bcˇ»
 6654           d«efgˇ»hi
 6655
 6656           j«kˇ»
 6657           nlmo
 6658           "#
 6659    ));
 6660
 6661    cx.update_editor(|editor, window, cx| {
 6662        editor.add_selection_below(&Default::default(), window, cx);
 6663    });
 6664    cx.assert_editor_state(indoc!(
 6665        r#"a«bcˇ»
 6666           d«efgˇ»hi
 6667
 6668           j«kˇ»
 6669           n«lmoˇ»
 6670           "#
 6671    ));
 6672    cx.update_editor(|editor, window, cx| {
 6673        editor.add_selection_above(&Default::default(), window, cx);
 6674    });
 6675
 6676    cx.assert_editor_state(indoc!(
 6677        r#"a«bcˇ»
 6678           d«efgˇ»hi
 6679
 6680           j«kˇ»
 6681           nlmo
 6682           "#
 6683    ));
 6684
 6685    // Change selections again
 6686    cx.set_state(indoc!(
 6687        r#"abc
 6688           d«ˇefghi
 6689
 6690           jk
 6691           nlm»o
 6692           "#
 6693    ));
 6694
 6695    cx.update_editor(|editor, window, cx| {
 6696        editor.add_selection_above(&Default::default(), window, cx);
 6697    });
 6698
 6699    cx.assert_editor_state(indoc!(
 6700        r#"a«ˇbc»
 6701           d«ˇef»ghi
 6702
 6703           j«ˇk»
 6704           n«ˇlm»o
 6705           "#
 6706    ));
 6707
 6708    cx.update_editor(|editor, window, cx| {
 6709        editor.add_selection_below(&Default::default(), window, cx);
 6710    });
 6711
 6712    cx.assert_editor_state(indoc!(
 6713        r#"abc
 6714           d«ˇef»ghi
 6715
 6716           j«ˇk»
 6717           n«ˇlm»o
 6718           "#
 6719    ));
 6720}
 6721
 6722#[gpui::test]
 6723async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 6724    init_test(cx, |_| {});
 6725    let mut cx = EditorTestContext::new(cx).await;
 6726
 6727    cx.set_state(indoc!(
 6728        r#"line onˇe
 6729           liˇne two
 6730           line three
 6731           line four"#
 6732    ));
 6733
 6734    cx.update_editor(|editor, window, cx| {
 6735        editor.add_selection_below(&Default::default(), window, cx);
 6736    });
 6737
 6738    // test multiple cursors expand in the same direction
 6739    cx.assert_editor_state(indoc!(
 6740        r#"line onˇe
 6741           liˇne twˇo
 6742           liˇne three
 6743           line four"#
 6744    ));
 6745
 6746    cx.update_editor(|editor, window, cx| {
 6747        editor.add_selection_below(&Default::default(), window, cx);
 6748    });
 6749
 6750    cx.update_editor(|editor, window, cx| {
 6751        editor.add_selection_below(&Default::default(), window, cx);
 6752    });
 6753
 6754    // test multiple cursors expand below overflow
 6755    cx.assert_editor_state(indoc!(
 6756        r#"line onˇe
 6757           liˇne twˇo
 6758           liˇne thˇree
 6759           liˇne foˇur"#
 6760    ));
 6761
 6762    cx.update_editor(|editor, window, cx| {
 6763        editor.add_selection_above(&Default::default(), window, cx);
 6764    });
 6765
 6766    // test multiple cursors retrieves back correctly
 6767    cx.assert_editor_state(indoc!(
 6768        r#"line onˇe
 6769           liˇne twˇo
 6770           liˇne thˇree
 6771           line four"#
 6772    ));
 6773
 6774    cx.update_editor(|editor, window, cx| {
 6775        editor.add_selection_above(&Default::default(), window, cx);
 6776    });
 6777
 6778    cx.update_editor(|editor, window, cx| {
 6779        editor.add_selection_above(&Default::default(), window, cx);
 6780    });
 6781
 6782    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 6783    cx.assert_editor_state(indoc!(
 6784        r#"liˇne onˇe
 6785           liˇne two
 6786           line three
 6787           line four"#
 6788    ));
 6789
 6790    cx.update_editor(|editor, window, cx| {
 6791        editor.undo_selection(&Default::default(), window, cx);
 6792    });
 6793
 6794    // test undo
 6795    cx.assert_editor_state(indoc!(
 6796        r#"line onˇe
 6797           liˇne twˇo
 6798           line three
 6799           line four"#
 6800    ));
 6801
 6802    cx.update_editor(|editor, window, cx| {
 6803        editor.redo_selection(&Default::default(), window, cx);
 6804    });
 6805
 6806    // test redo
 6807    cx.assert_editor_state(indoc!(
 6808        r#"liˇne onˇe
 6809           liˇne two
 6810           line three
 6811           line four"#
 6812    ));
 6813
 6814    cx.set_state(indoc!(
 6815        r#"abcd
 6816           ef«ghˇ»
 6817           ijkl
 6818           «mˇ»nop"#
 6819    ));
 6820
 6821    cx.update_editor(|editor, window, cx| {
 6822        editor.add_selection_above(&Default::default(), window, cx);
 6823    });
 6824
 6825    // test multiple selections expand in the same direction
 6826    cx.assert_editor_state(indoc!(
 6827        r#"ab«cdˇ»
 6828           ef«ghˇ»
 6829           «iˇ»jkl
 6830           «mˇ»nop"#
 6831    ));
 6832
 6833    cx.update_editor(|editor, window, cx| {
 6834        editor.add_selection_above(&Default::default(), window, cx);
 6835    });
 6836
 6837    // test multiple selection upward overflow
 6838    cx.assert_editor_state(indoc!(
 6839        r#"ab«cdˇ»
 6840           «eˇ»f«ghˇ»
 6841           «iˇ»jkl
 6842           «mˇ»nop"#
 6843    ));
 6844
 6845    cx.update_editor(|editor, window, cx| {
 6846        editor.add_selection_below(&Default::default(), window, cx);
 6847    });
 6848
 6849    // test multiple selection retrieves back correctly
 6850    cx.assert_editor_state(indoc!(
 6851        r#"abcd
 6852           ef«ghˇ»
 6853           «iˇ»jkl
 6854           «mˇ»nop"#
 6855    ));
 6856
 6857    cx.update_editor(|editor, window, cx| {
 6858        editor.add_selection_below(&Default::default(), window, cx);
 6859    });
 6860
 6861    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 6862    cx.assert_editor_state(indoc!(
 6863        r#"abcd
 6864           ef«ghˇ»
 6865           ij«klˇ»
 6866           «mˇ»nop"#
 6867    ));
 6868
 6869    cx.update_editor(|editor, window, cx| {
 6870        editor.undo_selection(&Default::default(), window, cx);
 6871    });
 6872
 6873    // test undo
 6874    cx.assert_editor_state(indoc!(
 6875        r#"abcd
 6876           ef«ghˇ»
 6877           «iˇ»jkl
 6878           «mˇ»nop"#
 6879    ));
 6880
 6881    cx.update_editor(|editor, window, cx| {
 6882        editor.redo_selection(&Default::default(), window, cx);
 6883    });
 6884
 6885    // test redo
 6886    cx.assert_editor_state(indoc!(
 6887        r#"abcd
 6888           ef«ghˇ»
 6889           ij«klˇ»
 6890           «mˇ»nop"#
 6891    ));
 6892}
 6893
 6894#[gpui::test]
 6895async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 6896    init_test(cx, |_| {});
 6897    let mut cx = EditorTestContext::new(cx).await;
 6898
 6899    cx.set_state(indoc!(
 6900        r#"line onˇe
 6901           liˇne two
 6902           line three
 6903           line four"#
 6904    ));
 6905
 6906    cx.update_editor(|editor, window, cx| {
 6907        editor.add_selection_below(&Default::default(), window, cx);
 6908        editor.add_selection_below(&Default::default(), window, cx);
 6909        editor.add_selection_below(&Default::default(), window, cx);
 6910    });
 6911
 6912    // initial state with two multi cursor groups
 6913    cx.assert_editor_state(indoc!(
 6914        r#"line onˇe
 6915           liˇne twˇo
 6916           liˇne thˇree
 6917           liˇne foˇur"#
 6918    ));
 6919
 6920    // add single cursor in middle - simulate opt click
 6921    cx.update_editor(|editor, window, cx| {
 6922        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 6923        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 6924        editor.end_selection(window, cx);
 6925    });
 6926
 6927    cx.assert_editor_state(indoc!(
 6928        r#"line onˇe
 6929           liˇne twˇo
 6930           liˇneˇ thˇree
 6931           liˇne foˇur"#
 6932    ));
 6933
 6934    cx.update_editor(|editor, window, cx| {
 6935        editor.add_selection_above(&Default::default(), window, cx);
 6936    });
 6937
 6938    // test new added selection expands above and existing selection shrinks
 6939    cx.assert_editor_state(indoc!(
 6940        r#"line onˇe
 6941           liˇneˇ twˇo
 6942           liˇneˇ thˇree
 6943           line four"#
 6944    ));
 6945
 6946    cx.update_editor(|editor, window, cx| {
 6947        editor.add_selection_above(&Default::default(), window, cx);
 6948    });
 6949
 6950    // test new added selection expands above and existing selection shrinks
 6951    cx.assert_editor_state(indoc!(
 6952        r#"lineˇ onˇe
 6953           liˇneˇ twˇo
 6954           lineˇ three
 6955           line four"#
 6956    ));
 6957
 6958    // intial state with two selection groups
 6959    cx.set_state(indoc!(
 6960        r#"abcd
 6961           ef«ghˇ»
 6962           ijkl
 6963           «mˇ»nop"#
 6964    ));
 6965
 6966    cx.update_editor(|editor, window, cx| {
 6967        editor.add_selection_above(&Default::default(), window, cx);
 6968        editor.add_selection_above(&Default::default(), window, cx);
 6969    });
 6970
 6971    cx.assert_editor_state(indoc!(
 6972        r#"ab«cdˇ»
 6973           «eˇ»f«ghˇ»
 6974           «iˇ»jkl
 6975           «mˇ»nop"#
 6976    ));
 6977
 6978    // add single selection in middle - simulate opt drag
 6979    cx.update_editor(|editor, window, cx| {
 6980        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 6981        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 6982        editor.update_selection(
 6983            DisplayPoint::new(DisplayRow(2), 4),
 6984            0,
 6985            gpui::Point::<f32>::default(),
 6986            window,
 6987            cx,
 6988        );
 6989        editor.end_selection(window, cx);
 6990    });
 6991
 6992    cx.assert_editor_state(indoc!(
 6993        r#"ab«cdˇ»
 6994           «eˇ»f«ghˇ»
 6995           «iˇ»jk«lˇ»
 6996           «mˇ»nop"#
 6997    ));
 6998
 6999    cx.update_editor(|editor, window, cx| {
 7000        editor.add_selection_below(&Default::default(), window, cx);
 7001    });
 7002
 7003    // test new added selection expands below, others shrinks from above
 7004    cx.assert_editor_state(indoc!(
 7005        r#"abcd
 7006           ef«ghˇ»
 7007           «iˇ»jk«lˇ»
 7008           «mˇ»no«pˇ»"#
 7009    ));
 7010}
 7011
 7012#[gpui::test]
 7013async fn test_select_next(cx: &mut TestAppContext) {
 7014    init_test(cx, |_| {});
 7015
 7016    let mut cx = EditorTestContext::new(cx).await;
 7017    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7018
 7019    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7020        .unwrap();
 7021    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7022
 7023    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7024        .unwrap();
 7025    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 7026
 7027    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7028    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7029
 7030    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7031    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 7032
 7033    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7034        .unwrap();
 7035    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7036
 7037    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7038        .unwrap();
 7039    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7040
 7041    // Test selection direction should be preserved
 7042    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 7043
 7044    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7045        .unwrap();
 7046    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 7047}
 7048
 7049#[gpui::test]
 7050async fn test_select_all_matches(cx: &mut TestAppContext) {
 7051    init_test(cx, |_| {});
 7052
 7053    let mut cx = EditorTestContext::new(cx).await;
 7054
 7055    // Test caret-only selections
 7056    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7057    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7058        .unwrap();
 7059    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7060
 7061    // Test left-to-right selections
 7062    cx.set_state("abc\n«abcˇ»\nabc");
 7063    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7064        .unwrap();
 7065    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 7066
 7067    // Test right-to-left selections
 7068    cx.set_state("abc\n«ˇabc»\nabc");
 7069    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7070        .unwrap();
 7071    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 7072
 7073    // Test selecting whitespace with caret selection
 7074    cx.set_state("abc\nˇ   abc\nabc");
 7075    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7076        .unwrap();
 7077    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 7078
 7079    // Test selecting whitespace with left-to-right selection
 7080    cx.set_state("abc\n«ˇ  »abc\nabc");
 7081    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7082        .unwrap();
 7083    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 7084
 7085    // Test no matches with right-to-left selection
 7086    cx.set_state("abc\n«  ˇ»abc\nabc");
 7087    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7088        .unwrap();
 7089    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 7090
 7091    // Test with a single word and clip_at_line_ends=true (#29823)
 7092    cx.set_state("aˇbc");
 7093    cx.update_editor(|e, window, cx| {
 7094        e.set_clip_at_line_ends(true, cx);
 7095        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 7096        e.set_clip_at_line_ends(false, cx);
 7097    });
 7098    cx.assert_editor_state("«abcˇ»");
 7099}
 7100
 7101#[gpui::test]
 7102async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 7103    init_test(cx, |_| {});
 7104
 7105    let mut cx = EditorTestContext::new(cx).await;
 7106
 7107    let large_body_1 = "\nd".repeat(200);
 7108    let large_body_2 = "\ne".repeat(200);
 7109
 7110    cx.set_state(&format!(
 7111        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 7112    ));
 7113    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 7114        let scroll_position = editor.scroll_position(cx);
 7115        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 7116        scroll_position
 7117    });
 7118
 7119    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7120        .unwrap();
 7121    cx.assert_editor_state(&format!(
 7122        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 7123    ));
 7124    let scroll_position_after_selection =
 7125        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 7126    assert_eq!(
 7127        initial_scroll_position, scroll_position_after_selection,
 7128        "Scroll position should not change after selecting all matches"
 7129    );
 7130}
 7131
 7132#[gpui::test]
 7133async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 7134    init_test(cx, |_| {});
 7135
 7136    let mut cx = EditorLspTestContext::new_rust(
 7137        lsp::ServerCapabilities {
 7138            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 7139            ..Default::default()
 7140        },
 7141        cx,
 7142    )
 7143    .await;
 7144
 7145    cx.set_state(indoc! {"
 7146        line 1
 7147        line 2
 7148        linˇe 3
 7149        line 4
 7150        line 5
 7151    "});
 7152
 7153    // Make an edit
 7154    cx.update_editor(|editor, window, cx| {
 7155        editor.handle_input("X", window, cx);
 7156    });
 7157
 7158    // Move cursor to a different position
 7159    cx.update_editor(|editor, window, cx| {
 7160        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7161            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 7162        });
 7163    });
 7164
 7165    cx.assert_editor_state(indoc! {"
 7166        line 1
 7167        line 2
 7168        linXe 3
 7169        line 4
 7170        liˇne 5
 7171    "});
 7172
 7173    cx.lsp
 7174        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 7175            Ok(Some(vec![lsp::TextEdit::new(
 7176                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 7177                "PREFIX ".to_string(),
 7178            )]))
 7179        });
 7180
 7181    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 7182        .unwrap()
 7183        .await
 7184        .unwrap();
 7185
 7186    cx.assert_editor_state(indoc! {"
 7187        PREFIX line 1
 7188        line 2
 7189        linXe 3
 7190        line 4
 7191        liˇne 5
 7192    "});
 7193
 7194    // Undo formatting
 7195    cx.update_editor(|editor, window, cx| {
 7196        editor.undo(&Default::default(), window, cx);
 7197    });
 7198
 7199    // Verify cursor moved back to position after edit
 7200    cx.assert_editor_state(indoc! {"
 7201        line 1
 7202        line 2
 7203        linXˇe 3
 7204        line 4
 7205        line 5
 7206    "});
 7207}
 7208
 7209#[gpui::test]
 7210async fn test_undo_inline_completion_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 7211    init_test(cx, |_| {});
 7212
 7213    let mut cx = EditorTestContext::new(cx).await;
 7214
 7215    let provider = cx.new(|_| FakeInlineCompletionProvider::default());
 7216    cx.update_editor(|editor, window, cx| {
 7217        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 7218    });
 7219
 7220    cx.set_state(indoc! {"
 7221        line 1
 7222        line 2
 7223        linˇe 3
 7224        line 4
 7225        line 5
 7226        line 6
 7227        line 7
 7228        line 8
 7229        line 9
 7230        line 10
 7231    "});
 7232
 7233    let snapshot = cx.buffer_snapshot();
 7234    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 7235
 7236    cx.update(|_, cx| {
 7237        provider.update(cx, |provider, _| {
 7238            provider.set_inline_completion(Some(inline_completion::InlineCompletion {
 7239                id: None,
 7240                edits: vec![(edit_position..edit_position, "X".into())],
 7241                edit_preview: None,
 7242            }))
 7243        })
 7244    });
 7245
 7246    cx.update_editor(|editor, window, cx| editor.update_visible_inline_completion(window, cx));
 7247    cx.update_editor(|editor, window, cx| {
 7248        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 7249    });
 7250
 7251    cx.assert_editor_state(indoc! {"
 7252        line 1
 7253        line 2
 7254        lineXˇ 3
 7255        line 4
 7256        line 5
 7257        line 6
 7258        line 7
 7259        line 8
 7260        line 9
 7261        line 10
 7262    "});
 7263
 7264    cx.update_editor(|editor, window, cx| {
 7265        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7266            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 7267        });
 7268    });
 7269
 7270    cx.assert_editor_state(indoc! {"
 7271        line 1
 7272        line 2
 7273        lineX 3
 7274        line 4
 7275        line 5
 7276        line 6
 7277        line 7
 7278        line 8
 7279        line 9
 7280        liˇne 10
 7281    "});
 7282
 7283    cx.update_editor(|editor, window, cx| {
 7284        editor.undo(&Default::default(), window, cx);
 7285    });
 7286
 7287    cx.assert_editor_state(indoc! {"
 7288        line 1
 7289        line 2
 7290        lineˇ 3
 7291        line 4
 7292        line 5
 7293        line 6
 7294        line 7
 7295        line 8
 7296        line 9
 7297        line 10
 7298    "});
 7299}
 7300
 7301#[gpui::test]
 7302async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 7303    init_test(cx, |_| {});
 7304
 7305    let mut cx = EditorTestContext::new(cx).await;
 7306    cx.set_state(
 7307        r#"let foo = 2;
 7308lˇet foo = 2;
 7309let fooˇ = 2;
 7310let foo = 2;
 7311let foo = ˇ2;"#,
 7312    );
 7313
 7314    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7315        .unwrap();
 7316    cx.assert_editor_state(
 7317        r#"let foo = 2;
 7318«letˇ» foo = 2;
 7319let «fooˇ» = 2;
 7320let foo = 2;
 7321let foo = «2ˇ»;"#,
 7322    );
 7323
 7324    // noop for multiple selections with different contents
 7325    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7326        .unwrap();
 7327    cx.assert_editor_state(
 7328        r#"let foo = 2;
 7329«letˇ» foo = 2;
 7330let «fooˇ» = 2;
 7331let foo = 2;
 7332let foo = «2ˇ»;"#,
 7333    );
 7334
 7335    // Test last selection direction should be preserved
 7336    cx.set_state(
 7337        r#"let foo = 2;
 7338let foo = 2;
 7339let «fooˇ» = 2;
 7340let «ˇfoo» = 2;
 7341let foo = 2;"#,
 7342    );
 7343
 7344    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7345        .unwrap();
 7346    cx.assert_editor_state(
 7347        r#"let foo = 2;
 7348let foo = 2;
 7349let «fooˇ» = 2;
 7350let «ˇfoo» = 2;
 7351let «ˇfoo» = 2;"#,
 7352    );
 7353}
 7354
 7355#[gpui::test]
 7356async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 7357    init_test(cx, |_| {});
 7358
 7359    let mut cx =
 7360        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 7361
 7362    cx.assert_editor_state(indoc! {"
 7363        ˇbbb
 7364        ccc
 7365
 7366        bbb
 7367        ccc
 7368        "});
 7369    cx.dispatch_action(SelectPrevious::default());
 7370    cx.assert_editor_state(indoc! {"
 7371                «bbbˇ»
 7372                ccc
 7373
 7374                bbb
 7375                ccc
 7376                "});
 7377    cx.dispatch_action(SelectPrevious::default());
 7378    cx.assert_editor_state(indoc! {"
 7379                «bbbˇ»
 7380                ccc
 7381
 7382                «bbbˇ»
 7383                ccc
 7384                "});
 7385}
 7386
 7387#[gpui::test]
 7388async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 7389    init_test(cx, |_| {});
 7390
 7391    let mut cx = EditorTestContext::new(cx).await;
 7392    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7393
 7394    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7395        .unwrap();
 7396    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7397
 7398    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7399        .unwrap();
 7400    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 7401
 7402    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7403    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7404
 7405    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7406    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 7407
 7408    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7409        .unwrap();
 7410    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 7411
 7412    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7413        .unwrap();
 7414    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7415}
 7416
 7417#[gpui::test]
 7418async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 7419    init_test(cx, |_| {});
 7420
 7421    let mut cx = EditorTestContext::new(cx).await;
 7422    cx.set_state("");
 7423
 7424    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7425        .unwrap();
 7426    cx.assert_editor_state("«aˇ»");
 7427    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7428        .unwrap();
 7429    cx.assert_editor_state("«aˇ»");
 7430}
 7431
 7432#[gpui::test]
 7433async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 7434    init_test(cx, |_| {});
 7435
 7436    let mut cx = EditorTestContext::new(cx).await;
 7437    cx.set_state(
 7438        r#"let foo = 2;
 7439lˇet foo = 2;
 7440let fooˇ = 2;
 7441let foo = 2;
 7442let foo = ˇ2;"#,
 7443    );
 7444
 7445    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7446        .unwrap();
 7447    cx.assert_editor_state(
 7448        r#"let foo = 2;
 7449«letˇ» foo = 2;
 7450let «fooˇ» = 2;
 7451let foo = 2;
 7452let foo = «2ˇ»;"#,
 7453    );
 7454
 7455    // noop for multiple selections with different contents
 7456    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7457        .unwrap();
 7458    cx.assert_editor_state(
 7459        r#"let foo = 2;
 7460«letˇ» foo = 2;
 7461let «fooˇ» = 2;
 7462let foo = 2;
 7463let foo = «2ˇ»;"#,
 7464    );
 7465}
 7466
 7467#[gpui::test]
 7468async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 7469    init_test(cx, |_| {});
 7470
 7471    let mut cx = EditorTestContext::new(cx).await;
 7472    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 7473
 7474    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7475        .unwrap();
 7476    // selection direction is preserved
 7477    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 7478
 7479    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7480        .unwrap();
 7481    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7482
 7483    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7484    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 7485
 7486    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7487    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7488
 7489    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7490        .unwrap();
 7491    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 7492
 7493    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7494        .unwrap();
 7495    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 7496}
 7497
 7498#[gpui::test]
 7499async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 7500    init_test(cx, |_| {});
 7501
 7502    let language = Arc::new(Language::new(
 7503        LanguageConfig::default(),
 7504        Some(tree_sitter_rust::LANGUAGE.into()),
 7505    ));
 7506
 7507    let text = r#"
 7508        use mod1::mod2::{mod3, mod4};
 7509
 7510        fn fn_1(param1: bool, param2: &str) {
 7511            let var1 = "text";
 7512        }
 7513    "#
 7514    .unindent();
 7515
 7516    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7517    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7518    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7519
 7520    editor
 7521        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7522        .await;
 7523
 7524    editor.update_in(cx, |editor, window, cx| {
 7525        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7526            s.select_display_ranges([
 7527                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 7528                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 7529                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 7530            ]);
 7531        });
 7532        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7533    });
 7534    editor.update(cx, |editor, cx| {
 7535        assert_text_with_selections(
 7536            editor,
 7537            indoc! {r#"
 7538                use mod1::mod2::{mod3, «mod4ˇ»};
 7539
 7540                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7541                    let var1 = "«ˇtext»";
 7542                }
 7543            "#},
 7544            cx,
 7545        );
 7546    });
 7547
 7548    editor.update_in(cx, |editor, window, cx| {
 7549        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7550    });
 7551    editor.update(cx, |editor, cx| {
 7552        assert_text_with_selections(
 7553            editor,
 7554            indoc! {r#"
 7555                use mod1::mod2::«{mod3, mod4}ˇ»;
 7556
 7557                «ˇfn fn_1(param1: bool, param2: &str) {
 7558                    let var1 = "text";
 7559 7560            "#},
 7561            cx,
 7562        );
 7563    });
 7564
 7565    editor.update_in(cx, |editor, window, cx| {
 7566        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7567    });
 7568    assert_eq!(
 7569        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7570        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7571    );
 7572
 7573    // Trying to expand the selected syntax node one more time has no effect.
 7574    editor.update_in(cx, |editor, window, cx| {
 7575        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7576    });
 7577    assert_eq!(
 7578        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7579        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7580    );
 7581
 7582    editor.update_in(cx, |editor, window, cx| {
 7583        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7584    });
 7585    editor.update(cx, |editor, cx| {
 7586        assert_text_with_selections(
 7587            editor,
 7588            indoc! {r#"
 7589                use mod1::mod2::«{mod3, mod4}ˇ»;
 7590
 7591                «ˇfn fn_1(param1: bool, param2: &str) {
 7592                    let var1 = "text";
 7593 7594            "#},
 7595            cx,
 7596        );
 7597    });
 7598
 7599    editor.update_in(cx, |editor, window, cx| {
 7600        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7601    });
 7602    editor.update(cx, |editor, cx| {
 7603        assert_text_with_selections(
 7604            editor,
 7605            indoc! {r#"
 7606                use mod1::mod2::{mod3, «mod4ˇ»};
 7607
 7608                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7609                    let var1 = "«ˇtext»";
 7610                }
 7611            "#},
 7612            cx,
 7613        );
 7614    });
 7615
 7616    editor.update_in(cx, |editor, window, cx| {
 7617        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7618    });
 7619    editor.update(cx, |editor, cx| {
 7620        assert_text_with_selections(
 7621            editor,
 7622            indoc! {r#"
 7623                use mod1::mod2::{mod3, mo«ˇ»d4};
 7624
 7625                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7626                    let var1 = "te«ˇ»xt";
 7627                }
 7628            "#},
 7629            cx,
 7630        );
 7631    });
 7632
 7633    // Trying to shrink the selected syntax node one more time has no effect.
 7634    editor.update_in(cx, |editor, window, cx| {
 7635        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7636    });
 7637    editor.update_in(cx, |editor, _, cx| {
 7638        assert_text_with_selections(
 7639            editor,
 7640            indoc! {r#"
 7641                use mod1::mod2::{mod3, mo«ˇ»d4};
 7642
 7643                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7644                    let var1 = "te«ˇ»xt";
 7645                }
 7646            "#},
 7647            cx,
 7648        );
 7649    });
 7650
 7651    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 7652    // a fold.
 7653    editor.update_in(cx, |editor, window, cx| {
 7654        editor.fold_creases(
 7655            vec![
 7656                Crease::simple(
 7657                    Point::new(0, 21)..Point::new(0, 24),
 7658                    FoldPlaceholder::test(),
 7659                ),
 7660                Crease::simple(
 7661                    Point::new(3, 20)..Point::new(3, 22),
 7662                    FoldPlaceholder::test(),
 7663                ),
 7664            ],
 7665            true,
 7666            window,
 7667            cx,
 7668        );
 7669        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7670    });
 7671    editor.update(cx, |editor, cx| {
 7672        assert_text_with_selections(
 7673            editor,
 7674            indoc! {r#"
 7675                use mod1::mod2::«{mod3, mod4}ˇ»;
 7676
 7677                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7678                    let var1 = "«ˇtext»";
 7679                }
 7680            "#},
 7681            cx,
 7682        );
 7683    });
 7684}
 7685
 7686#[gpui::test]
 7687async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 7688    init_test(cx, |_| {});
 7689
 7690    let language = Arc::new(Language::new(
 7691        LanguageConfig::default(),
 7692        Some(tree_sitter_rust::LANGUAGE.into()),
 7693    ));
 7694
 7695    let text = "let a = 2;";
 7696
 7697    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7698    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7699    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7700
 7701    editor
 7702        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7703        .await;
 7704
 7705    // Test case 1: Cursor at end of word
 7706    editor.update_in(cx, |editor, window, cx| {
 7707        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7708            s.select_display_ranges([
 7709                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 7710            ]);
 7711        });
 7712    });
 7713    editor.update(cx, |editor, cx| {
 7714        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 7715    });
 7716    editor.update_in(cx, |editor, window, cx| {
 7717        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7718    });
 7719    editor.update(cx, |editor, cx| {
 7720        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 7721    });
 7722    editor.update_in(cx, |editor, window, cx| {
 7723        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7724    });
 7725    editor.update(cx, |editor, cx| {
 7726        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7727    });
 7728
 7729    // Test case 2: Cursor at end of statement
 7730    editor.update_in(cx, |editor, window, cx| {
 7731        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7732            s.select_display_ranges([
 7733                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 7734            ]);
 7735        });
 7736    });
 7737    editor.update(cx, |editor, cx| {
 7738        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 7739    });
 7740    editor.update_in(cx, |editor, window, cx| {
 7741        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7742    });
 7743    editor.update(cx, |editor, cx| {
 7744        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7745    });
 7746}
 7747
 7748#[gpui::test]
 7749async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 7750    init_test(cx, |_| {});
 7751
 7752    let language = Arc::new(Language::new(
 7753        LanguageConfig::default(),
 7754        Some(tree_sitter_rust::LANGUAGE.into()),
 7755    ));
 7756
 7757    let text = r#"
 7758        use mod1::mod2::{mod3, mod4};
 7759
 7760        fn fn_1(param1: bool, param2: &str) {
 7761            let var1 = "hello world";
 7762        }
 7763    "#
 7764    .unindent();
 7765
 7766    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7767    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7768    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7769
 7770    editor
 7771        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7772        .await;
 7773
 7774    // Test 1: Cursor on a letter of a string word
 7775    editor.update_in(cx, |editor, window, cx| {
 7776        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7777            s.select_display_ranges([
 7778                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 7779            ]);
 7780        });
 7781    });
 7782    editor.update_in(cx, |editor, window, cx| {
 7783        assert_text_with_selections(
 7784            editor,
 7785            indoc! {r#"
 7786                use mod1::mod2::{mod3, mod4};
 7787
 7788                fn fn_1(param1: bool, param2: &str) {
 7789                    let var1 = "hˇello world";
 7790                }
 7791            "#},
 7792            cx,
 7793        );
 7794        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7795        assert_text_with_selections(
 7796            editor,
 7797            indoc! {r#"
 7798                use mod1::mod2::{mod3, mod4};
 7799
 7800                fn fn_1(param1: bool, param2: &str) {
 7801                    let var1 = "«ˇhello» world";
 7802                }
 7803            "#},
 7804            cx,
 7805        );
 7806    });
 7807
 7808    // Test 2: Partial selection within a word
 7809    editor.update_in(cx, |editor, window, cx| {
 7810        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7811            s.select_display_ranges([
 7812                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 7813            ]);
 7814        });
 7815    });
 7816    editor.update_in(cx, |editor, window, cx| {
 7817        assert_text_with_selections(
 7818            editor,
 7819            indoc! {r#"
 7820                use mod1::mod2::{mod3, mod4};
 7821
 7822                fn fn_1(param1: bool, param2: &str) {
 7823                    let var1 = "h«elˇ»lo world";
 7824                }
 7825            "#},
 7826            cx,
 7827        );
 7828        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7829        assert_text_with_selections(
 7830            editor,
 7831            indoc! {r#"
 7832                use mod1::mod2::{mod3, mod4};
 7833
 7834                fn fn_1(param1: bool, param2: &str) {
 7835                    let var1 = "«ˇhello» world";
 7836                }
 7837            "#},
 7838            cx,
 7839        );
 7840    });
 7841
 7842    // Test 3: Complete word already selected
 7843    editor.update_in(cx, |editor, window, cx| {
 7844        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7845            s.select_display_ranges([
 7846                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 7847            ]);
 7848        });
 7849    });
 7850    editor.update_in(cx, |editor, window, cx| {
 7851        assert_text_with_selections(
 7852            editor,
 7853            indoc! {r#"
 7854                use mod1::mod2::{mod3, mod4};
 7855
 7856                fn fn_1(param1: bool, param2: &str) {
 7857                    let var1 = "«helloˇ» world";
 7858                }
 7859            "#},
 7860            cx,
 7861        );
 7862        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7863        assert_text_with_selections(
 7864            editor,
 7865            indoc! {r#"
 7866                use mod1::mod2::{mod3, mod4};
 7867
 7868                fn fn_1(param1: bool, param2: &str) {
 7869                    let var1 = "«hello worldˇ»";
 7870                }
 7871            "#},
 7872            cx,
 7873        );
 7874    });
 7875
 7876    // Test 4: Selection spanning across words
 7877    editor.update_in(cx, |editor, window, cx| {
 7878        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7879            s.select_display_ranges([
 7880                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 7881            ]);
 7882        });
 7883    });
 7884    editor.update_in(cx, |editor, window, cx| {
 7885        assert_text_with_selections(
 7886            editor,
 7887            indoc! {r#"
 7888                use mod1::mod2::{mod3, mod4};
 7889
 7890                fn fn_1(param1: bool, param2: &str) {
 7891                    let var1 = "hel«lo woˇ»rld";
 7892                }
 7893            "#},
 7894            cx,
 7895        );
 7896        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7897        assert_text_with_selections(
 7898            editor,
 7899            indoc! {r#"
 7900                use mod1::mod2::{mod3, mod4};
 7901
 7902                fn fn_1(param1: bool, param2: &str) {
 7903                    let var1 = "«ˇhello world»";
 7904                }
 7905            "#},
 7906            cx,
 7907        );
 7908    });
 7909
 7910    // Test 5: Expansion beyond string
 7911    editor.update_in(cx, |editor, window, cx| {
 7912        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7913        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7914        assert_text_with_selections(
 7915            editor,
 7916            indoc! {r#"
 7917                use mod1::mod2::{mod3, mod4};
 7918
 7919                fn fn_1(param1: bool, param2: &str) {
 7920                    «ˇlet var1 = "hello world";»
 7921                }
 7922            "#},
 7923            cx,
 7924        );
 7925    });
 7926}
 7927
 7928#[gpui::test]
 7929async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 7930    init_test(cx, |_| {});
 7931
 7932    let base_text = r#"
 7933        impl A {
 7934            // this is an uncommitted comment
 7935
 7936            fn b() {
 7937                c();
 7938            }
 7939
 7940            // this is another uncommitted comment
 7941
 7942            fn d() {
 7943                // e
 7944                // f
 7945            }
 7946        }
 7947
 7948        fn g() {
 7949            // h
 7950        }
 7951    "#
 7952    .unindent();
 7953
 7954    let text = r#"
 7955        ˇimpl A {
 7956
 7957            fn b() {
 7958                c();
 7959            }
 7960
 7961            fn d() {
 7962                // e
 7963                // f
 7964            }
 7965        }
 7966
 7967        fn g() {
 7968            // h
 7969        }
 7970    "#
 7971    .unindent();
 7972
 7973    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 7974    cx.set_state(&text);
 7975    cx.set_head_text(&base_text);
 7976    cx.update_editor(|editor, window, cx| {
 7977        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 7978    });
 7979
 7980    cx.assert_state_with_diff(
 7981        "
 7982        ˇimpl A {
 7983      -     // this is an uncommitted comment
 7984
 7985            fn b() {
 7986                c();
 7987            }
 7988
 7989      -     // this is another uncommitted comment
 7990      -
 7991            fn d() {
 7992                // e
 7993                // f
 7994            }
 7995        }
 7996
 7997        fn g() {
 7998            // h
 7999        }
 8000    "
 8001        .unindent(),
 8002    );
 8003
 8004    let expected_display_text = "
 8005        impl A {
 8006            // this is an uncommitted comment
 8007
 8008            fn b() {
 8009 8010            }
 8011
 8012            // this is another uncommitted comment
 8013
 8014            fn d() {
 8015 8016            }
 8017        }
 8018
 8019        fn g() {
 8020 8021        }
 8022        "
 8023    .unindent();
 8024
 8025    cx.update_editor(|editor, window, cx| {
 8026        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 8027        assert_eq!(editor.display_text(cx), expected_display_text);
 8028    });
 8029}
 8030
 8031#[gpui::test]
 8032async fn test_autoindent(cx: &mut TestAppContext) {
 8033    init_test(cx, |_| {});
 8034
 8035    let language = Arc::new(
 8036        Language::new(
 8037            LanguageConfig {
 8038                brackets: BracketPairConfig {
 8039                    pairs: vec![
 8040                        BracketPair {
 8041                            start: "{".to_string(),
 8042                            end: "}".to_string(),
 8043                            close: false,
 8044                            surround: false,
 8045                            newline: true,
 8046                        },
 8047                        BracketPair {
 8048                            start: "(".to_string(),
 8049                            end: ")".to_string(),
 8050                            close: false,
 8051                            surround: false,
 8052                            newline: true,
 8053                        },
 8054                    ],
 8055                    ..Default::default()
 8056                },
 8057                ..Default::default()
 8058            },
 8059            Some(tree_sitter_rust::LANGUAGE.into()),
 8060        )
 8061        .with_indents_query(
 8062            r#"
 8063                (_ "(" ")" @end) @indent
 8064                (_ "{" "}" @end) @indent
 8065            "#,
 8066        )
 8067        .unwrap(),
 8068    );
 8069
 8070    let text = "fn a() {}";
 8071
 8072    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8073    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8074    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8075    editor
 8076        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8077        .await;
 8078
 8079    editor.update_in(cx, |editor, window, cx| {
 8080        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8081            s.select_ranges([5..5, 8..8, 9..9])
 8082        });
 8083        editor.newline(&Newline, window, cx);
 8084        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 8085        assert_eq!(
 8086            editor.selections.ranges(cx),
 8087            &[
 8088                Point::new(1, 4)..Point::new(1, 4),
 8089                Point::new(3, 4)..Point::new(3, 4),
 8090                Point::new(5, 0)..Point::new(5, 0)
 8091            ]
 8092        );
 8093    });
 8094}
 8095
 8096#[gpui::test]
 8097async fn test_autoindent_selections(cx: &mut TestAppContext) {
 8098    init_test(cx, |_| {});
 8099
 8100    {
 8101        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 8102        cx.set_state(indoc! {"
 8103            impl A {
 8104
 8105                fn b() {}
 8106
 8107            «fn c() {
 8108
 8109            }ˇ»
 8110            }
 8111        "});
 8112
 8113        cx.update_editor(|editor, window, cx| {
 8114            editor.autoindent(&Default::default(), window, cx);
 8115        });
 8116
 8117        cx.assert_editor_state(indoc! {"
 8118            impl A {
 8119
 8120                fn b() {}
 8121
 8122                «fn c() {
 8123
 8124                }ˇ»
 8125            }
 8126        "});
 8127    }
 8128
 8129    {
 8130        let mut cx = EditorTestContext::new_multibuffer(
 8131            cx,
 8132            [indoc! { "
 8133                impl A {
 8134                «
 8135                // a
 8136                fn b(){}
 8137                »
 8138                «
 8139                    }
 8140                    fn c(){}
 8141                »
 8142            "}],
 8143        );
 8144
 8145        let buffer = cx.update_editor(|editor, _, cx| {
 8146            let buffer = editor.buffer().update(cx, |buffer, _| {
 8147                buffer.all_buffers().iter().next().unwrap().clone()
 8148            });
 8149            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 8150            buffer
 8151        });
 8152
 8153        cx.run_until_parked();
 8154        cx.update_editor(|editor, window, cx| {
 8155            editor.select_all(&Default::default(), window, cx);
 8156            editor.autoindent(&Default::default(), window, cx)
 8157        });
 8158        cx.run_until_parked();
 8159
 8160        cx.update(|_, cx| {
 8161            assert_eq!(
 8162                buffer.read(cx).text(),
 8163                indoc! { "
 8164                    impl A {
 8165
 8166                        // a
 8167                        fn b(){}
 8168
 8169
 8170                    }
 8171                    fn c(){}
 8172
 8173                " }
 8174            )
 8175        });
 8176    }
 8177}
 8178
 8179#[gpui::test]
 8180async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 8181    init_test(cx, |_| {});
 8182
 8183    let mut cx = EditorTestContext::new(cx).await;
 8184
 8185    let language = Arc::new(Language::new(
 8186        LanguageConfig {
 8187            brackets: BracketPairConfig {
 8188                pairs: vec![
 8189                    BracketPair {
 8190                        start: "{".to_string(),
 8191                        end: "}".to_string(),
 8192                        close: true,
 8193                        surround: true,
 8194                        newline: true,
 8195                    },
 8196                    BracketPair {
 8197                        start: "(".to_string(),
 8198                        end: ")".to_string(),
 8199                        close: true,
 8200                        surround: true,
 8201                        newline: true,
 8202                    },
 8203                    BracketPair {
 8204                        start: "/*".to_string(),
 8205                        end: " */".to_string(),
 8206                        close: true,
 8207                        surround: true,
 8208                        newline: true,
 8209                    },
 8210                    BracketPair {
 8211                        start: "[".to_string(),
 8212                        end: "]".to_string(),
 8213                        close: false,
 8214                        surround: false,
 8215                        newline: true,
 8216                    },
 8217                    BracketPair {
 8218                        start: "\"".to_string(),
 8219                        end: "\"".to_string(),
 8220                        close: true,
 8221                        surround: true,
 8222                        newline: false,
 8223                    },
 8224                    BracketPair {
 8225                        start: "<".to_string(),
 8226                        end: ">".to_string(),
 8227                        close: false,
 8228                        surround: true,
 8229                        newline: true,
 8230                    },
 8231                ],
 8232                ..Default::default()
 8233            },
 8234            autoclose_before: "})]".to_string(),
 8235            ..Default::default()
 8236        },
 8237        Some(tree_sitter_rust::LANGUAGE.into()),
 8238    ));
 8239
 8240    cx.language_registry().add(language.clone());
 8241    cx.update_buffer(|buffer, cx| {
 8242        buffer.set_language(Some(language), cx);
 8243    });
 8244
 8245    cx.set_state(
 8246        &r#"
 8247            🏀ˇ
 8248            εˇ
 8249            ❤️ˇ
 8250        "#
 8251        .unindent(),
 8252    );
 8253
 8254    // autoclose multiple nested brackets at multiple cursors
 8255    cx.update_editor(|editor, window, cx| {
 8256        editor.handle_input("{", window, cx);
 8257        editor.handle_input("{", window, cx);
 8258        editor.handle_input("{", window, cx);
 8259    });
 8260    cx.assert_editor_state(
 8261        &"
 8262            🏀{{{ˇ}}}
 8263            ε{{{ˇ}}}
 8264            ❤️{{{ˇ}}}
 8265        "
 8266        .unindent(),
 8267    );
 8268
 8269    // insert a different closing bracket
 8270    cx.update_editor(|editor, window, cx| {
 8271        editor.handle_input(")", window, cx);
 8272    });
 8273    cx.assert_editor_state(
 8274        &"
 8275            🏀{{{)ˇ}}}
 8276            ε{{{)ˇ}}}
 8277            ❤️{{{)ˇ}}}
 8278        "
 8279        .unindent(),
 8280    );
 8281
 8282    // skip over the auto-closed brackets when typing a closing bracket
 8283    cx.update_editor(|editor, window, cx| {
 8284        editor.move_right(&MoveRight, window, cx);
 8285        editor.handle_input("}", window, cx);
 8286        editor.handle_input("}", window, cx);
 8287        editor.handle_input("}", window, cx);
 8288    });
 8289    cx.assert_editor_state(
 8290        &"
 8291            🏀{{{)}}}}ˇ
 8292            ε{{{)}}}}ˇ
 8293            ❤️{{{)}}}}ˇ
 8294        "
 8295        .unindent(),
 8296    );
 8297
 8298    // autoclose multi-character pairs
 8299    cx.set_state(
 8300        &"
 8301            ˇ
 8302            ˇ
 8303        "
 8304        .unindent(),
 8305    );
 8306    cx.update_editor(|editor, window, cx| {
 8307        editor.handle_input("/", window, cx);
 8308        editor.handle_input("*", window, cx);
 8309    });
 8310    cx.assert_editor_state(
 8311        &"
 8312            /*ˇ */
 8313            /*ˇ */
 8314        "
 8315        .unindent(),
 8316    );
 8317
 8318    // one cursor autocloses a multi-character pair, one cursor
 8319    // does not autoclose.
 8320    cx.set_state(
 8321        &"
 8322 8323            ˇ
 8324        "
 8325        .unindent(),
 8326    );
 8327    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 8328    cx.assert_editor_state(
 8329        &"
 8330            /*ˇ */
 8331 8332        "
 8333        .unindent(),
 8334    );
 8335
 8336    // Don't autoclose if the next character isn't whitespace and isn't
 8337    // listed in the language's "autoclose_before" section.
 8338    cx.set_state("ˇa b");
 8339    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8340    cx.assert_editor_state("{ˇa b");
 8341
 8342    // Don't autoclose if `close` is false for the bracket pair
 8343    cx.set_state("ˇ");
 8344    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 8345    cx.assert_editor_state("");
 8346
 8347    // Surround with brackets if text is selected
 8348    cx.set_state("«aˇ» b");
 8349    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8350    cx.assert_editor_state("{«aˇ»} b");
 8351
 8352    // Autoclose when not immediately after a word character
 8353    cx.set_state("a ˇ");
 8354    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8355    cx.assert_editor_state("a \"ˇ\"");
 8356
 8357    // Autoclose pair where the start and end characters are the same
 8358    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8359    cx.assert_editor_state("a \"\"ˇ");
 8360
 8361    // Don't autoclose when immediately after a word character
 8362    cx.set_state("");
 8363    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8364    cx.assert_editor_state("a\"ˇ");
 8365
 8366    // Do autoclose when after a non-word character
 8367    cx.set_state("");
 8368    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8369    cx.assert_editor_state("{\"ˇ\"");
 8370
 8371    // Non identical pairs autoclose regardless of preceding character
 8372    cx.set_state("");
 8373    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8374    cx.assert_editor_state("a{ˇ}");
 8375
 8376    // Don't autoclose pair if autoclose is disabled
 8377    cx.set_state("ˇ");
 8378    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 8379    cx.assert_editor_state("");
 8380
 8381    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 8382    cx.set_state("«aˇ» b");
 8383    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 8384    cx.assert_editor_state("<«aˇ»> b");
 8385}
 8386
 8387#[gpui::test]
 8388async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 8389    init_test(cx, |settings| {
 8390        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 8391    });
 8392
 8393    let mut cx = EditorTestContext::new(cx).await;
 8394
 8395    let language = Arc::new(Language::new(
 8396        LanguageConfig {
 8397            brackets: BracketPairConfig {
 8398                pairs: vec![
 8399                    BracketPair {
 8400                        start: "{".to_string(),
 8401                        end: "}".to_string(),
 8402                        close: true,
 8403                        surround: true,
 8404                        newline: true,
 8405                    },
 8406                    BracketPair {
 8407                        start: "(".to_string(),
 8408                        end: ")".to_string(),
 8409                        close: true,
 8410                        surround: true,
 8411                        newline: true,
 8412                    },
 8413                    BracketPair {
 8414                        start: "[".to_string(),
 8415                        end: "]".to_string(),
 8416                        close: false,
 8417                        surround: false,
 8418                        newline: true,
 8419                    },
 8420                ],
 8421                ..Default::default()
 8422            },
 8423            autoclose_before: "})]".to_string(),
 8424            ..Default::default()
 8425        },
 8426        Some(tree_sitter_rust::LANGUAGE.into()),
 8427    ));
 8428
 8429    cx.language_registry().add(language.clone());
 8430    cx.update_buffer(|buffer, cx| {
 8431        buffer.set_language(Some(language), cx);
 8432    });
 8433
 8434    cx.set_state(
 8435        &"
 8436            ˇ
 8437            ˇ
 8438            ˇ
 8439        "
 8440        .unindent(),
 8441    );
 8442
 8443    // ensure only matching closing brackets are skipped over
 8444    cx.update_editor(|editor, window, cx| {
 8445        editor.handle_input("}", window, cx);
 8446        editor.move_left(&MoveLeft, window, cx);
 8447        editor.handle_input(")", window, cx);
 8448        editor.move_left(&MoveLeft, window, cx);
 8449    });
 8450    cx.assert_editor_state(
 8451        &"
 8452            ˇ)}
 8453            ˇ)}
 8454            ˇ)}
 8455        "
 8456        .unindent(),
 8457    );
 8458
 8459    // skip-over closing brackets at multiple cursors
 8460    cx.update_editor(|editor, window, cx| {
 8461        editor.handle_input(")", window, cx);
 8462        editor.handle_input("}", window, cx);
 8463    });
 8464    cx.assert_editor_state(
 8465        &"
 8466            )}ˇ
 8467            )}ˇ
 8468            )}ˇ
 8469        "
 8470        .unindent(),
 8471    );
 8472
 8473    // ignore non-close brackets
 8474    cx.update_editor(|editor, window, cx| {
 8475        editor.handle_input("]", window, cx);
 8476        editor.move_left(&MoveLeft, window, cx);
 8477        editor.handle_input("]", window, cx);
 8478    });
 8479    cx.assert_editor_state(
 8480        &"
 8481            )}]ˇ]
 8482            )}]ˇ]
 8483            )}]ˇ]
 8484        "
 8485        .unindent(),
 8486    );
 8487}
 8488
 8489#[gpui::test]
 8490async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
 8491    init_test(cx, |_| {});
 8492
 8493    let mut cx = EditorTestContext::new(cx).await;
 8494
 8495    let html_language = Arc::new(
 8496        Language::new(
 8497            LanguageConfig {
 8498                name: "HTML".into(),
 8499                brackets: BracketPairConfig {
 8500                    pairs: vec![
 8501                        BracketPair {
 8502                            start: "<".into(),
 8503                            end: ">".into(),
 8504                            close: true,
 8505                            ..Default::default()
 8506                        },
 8507                        BracketPair {
 8508                            start: "{".into(),
 8509                            end: "}".into(),
 8510                            close: true,
 8511                            ..Default::default()
 8512                        },
 8513                        BracketPair {
 8514                            start: "(".into(),
 8515                            end: ")".into(),
 8516                            close: true,
 8517                            ..Default::default()
 8518                        },
 8519                    ],
 8520                    ..Default::default()
 8521                },
 8522                autoclose_before: "})]>".into(),
 8523                ..Default::default()
 8524            },
 8525            Some(tree_sitter_html::LANGUAGE.into()),
 8526        )
 8527        .with_injection_query(
 8528            r#"
 8529            (script_element
 8530                (raw_text) @injection.content
 8531                (#set! injection.language "javascript"))
 8532            "#,
 8533        )
 8534        .unwrap(),
 8535    );
 8536
 8537    let javascript_language = Arc::new(Language::new(
 8538        LanguageConfig {
 8539            name: "JavaScript".into(),
 8540            brackets: BracketPairConfig {
 8541                pairs: vec![
 8542                    BracketPair {
 8543                        start: "/*".into(),
 8544                        end: " */".into(),
 8545                        close: true,
 8546                        ..Default::default()
 8547                    },
 8548                    BracketPair {
 8549                        start: "{".into(),
 8550                        end: "}".into(),
 8551                        close: true,
 8552                        ..Default::default()
 8553                    },
 8554                    BracketPair {
 8555                        start: "(".into(),
 8556                        end: ")".into(),
 8557                        close: true,
 8558                        ..Default::default()
 8559                    },
 8560                ],
 8561                ..Default::default()
 8562            },
 8563            autoclose_before: "})]>".into(),
 8564            ..Default::default()
 8565        },
 8566        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 8567    ));
 8568
 8569    cx.language_registry().add(html_language.clone());
 8570    cx.language_registry().add(javascript_language.clone());
 8571
 8572    cx.update_buffer(|buffer, cx| {
 8573        buffer.set_language(Some(html_language), cx);
 8574    });
 8575
 8576    cx.set_state(
 8577        &r#"
 8578            <body>ˇ
 8579                <script>
 8580                    var x = 1;ˇ
 8581                </script>
 8582            </body>ˇ
 8583        "#
 8584        .unindent(),
 8585    );
 8586
 8587    // Precondition: different languages are active at different locations.
 8588    cx.update_editor(|editor, window, cx| {
 8589        let snapshot = editor.snapshot(window, cx);
 8590        let cursors = editor.selections.ranges::<usize>(cx);
 8591        let languages = cursors
 8592            .iter()
 8593            .map(|c| snapshot.language_at(c.start).unwrap().name())
 8594            .collect::<Vec<_>>();
 8595        assert_eq!(
 8596            languages,
 8597            &["HTML".into(), "JavaScript".into(), "HTML".into()]
 8598        );
 8599    });
 8600
 8601    // Angle brackets autoclose in HTML, but not JavaScript.
 8602    cx.update_editor(|editor, window, cx| {
 8603        editor.handle_input("<", window, cx);
 8604        editor.handle_input("a", window, cx);
 8605    });
 8606    cx.assert_editor_state(
 8607        &r#"
 8608            <body><aˇ>
 8609                <script>
 8610                    var x = 1;<aˇ
 8611                </script>
 8612            </body><aˇ>
 8613        "#
 8614        .unindent(),
 8615    );
 8616
 8617    // Curly braces and parens autoclose in both HTML and JavaScript.
 8618    cx.update_editor(|editor, window, cx| {
 8619        editor.handle_input(" b=", window, cx);
 8620        editor.handle_input("{", window, cx);
 8621        editor.handle_input("c", window, cx);
 8622        editor.handle_input("(", window, cx);
 8623    });
 8624    cx.assert_editor_state(
 8625        &r#"
 8626            <body><a b={c(ˇ)}>
 8627                <script>
 8628                    var x = 1;<a b={c(ˇ)}
 8629                </script>
 8630            </body><a b={c(ˇ)}>
 8631        "#
 8632        .unindent(),
 8633    );
 8634
 8635    // Brackets that were already autoclosed are skipped.
 8636    cx.update_editor(|editor, window, cx| {
 8637        editor.handle_input(")", window, cx);
 8638        editor.handle_input("d", window, cx);
 8639        editor.handle_input("}", window, cx);
 8640    });
 8641    cx.assert_editor_state(
 8642        &r#"
 8643            <body><a b={c()d}ˇ>
 8644                <script>
 8645                    var x = 1;<a b={c()d}ˇ
 8646                </script>
 8647            </body><a b={c()d}ˇ>
 8648        "#
 8649        .unindent(),
 8650    );
 8651    cx.update_editor(|editor, window, cx| {
 8652        editor.handle_input(">", window, cx);
 8653    });
 8654    cx.assert_editor_state(
 8655        &r#"
 8656            <body><a b={c()d}>ˇ
 8657                <script>
 8658                    var x = 1;<a b={c()d}>ˇ
 8659                </script>
 8660            </body><a b={c()d}>ˇ
 8661        "#
 8662        .unindent(),
 8663    );
 8664
 8665    // Reset
 8666    cx.set_state(
 8667        &r#"
 8668            <body>ˇ
 8669                <script>
 8670                    var x = 1;ˇ
 8671                </script>
 8672            </body>ˇ
 8673        "#
 8674        .unindent(),
 8675    );
 8676
 8677    cx.update_editor(|editor, window, cx| {
 8678        editor.handle_input("<", window, cx);
 8679    });
 8680    cx.assert_editor_state(
 8681        &r#"
 8682            <body><ˇ>
 8683                <script>
 8684                    var x = 1;<ˇ
 8685                </script>
 8686            </body><ˇ>
 8687        "#
 8688        .unindent(),
 8689    );
 8690
 8691    // When backspacing, the closing angle brackets are removed.
 8692    cx.update_editor(|editor, window, cx| {
 8693        editor.backspace(&Backspace, window, cx);
 8694    });
 8695    cx.assert_editor_state(
 8696        &r#"
 8697            <body>ˇ
 8698                <script>
 8699                    var x = 1;ˇ
 8700                </script>
 8701            </body>ˇ
 8702        "#
 8703        .unindent(),
 8704    );
 8705
 8706    // Block comments autoclose in JavaScript, but not HTML.
 8707    cx.update_editor(|editor, window, cx| {
 8708        editor.handle_input("/", window, cx);
 8709        editor.handle_input("*", window, cx);
 8710    });
 8711    cx.assert_editor_state(
 8712        &r#"
 8713            <body>/*ˇ
 8714                <script>
 8715                    var x = 1;/*ˇ */
 8716                </script>
 8717            </body>/*ˇ
 8718        "#
 8719        .unindent(),
 8720    );
 8721}
 8722
 8723#[gpui::test]
 8724async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
 8725    init_test(cx, |_| {});
 8726
 8727    let mut cx = EditorTestContext::new(cx).await;
 8728
 8729    let rust_language = Arc::new(
 8730        Language::new(
 8731            LanguageConfig {
 8732                name: "Rust".into(),
 8733                brackets: serde_json::from_value(json!([
 8734                    { "start": "{", "end": "}", "close": true, "newline": true },
 8735                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
 8736                ]))
 8737                .unwrap(),
 8738                autoclose_before: "})]>".into(),
 8739                ..Default::default()
 8740            },
 8741            Some(tree_sitter_rust::LANGUAGE.into()),
 8742        )
 8743        .with_override_query("(string_literal) @string")
 8744        .unwrap(),
 8745    );
 8746
 8747    cx.language_registry().add(rust_language.clone());
 8748    cx.update_buffer(|buffer, cx| {
 8749        buffer.set_language(Some(rust_language), cx);
 8750    });
 8751
 8752    cx.set_state(
 8753        &r#"
 8754            let x = ˇ
 8755        "#
 8756        .unindent(),
 8757    );
 8758
 8759    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
 8760    cx.update_editor(|editor, window, cx| {
 8761        editor.handle_input("\"", window, cx);
 8762    });
 8763    cx.assert_editor_state(
 8764        &r#"
 8765            let x = "ˇ"
 8766        "#
 8767        .unindent(),
 8768    );
 8769
 8770    // Inserting another quotation mark. The cursor moves across the existing
 8771    // automatically-inserted quotation mark.
 8772    cx.update_editor(|editor, window, cx| {
 8773        editor.handle_input("\"", window, cx);
 8774    });
 8775    cx.assert_editor_state(
 8776        &r#"
 8777            let x = ""ˇ
 8778        "#
 8779        .unindent(),
 8780    );
 8781
 8782    // Reset
 8783    cx.set_state(
 8784        &r#"
 8785            let x = ˇ
 8786        "#
 8787        .unindent(),
 8788    );
 8789
 8790    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
 8791    cx.update_editor(|editor, window, cx| {
 8792        editor.handle_input("\"", window, cx);
 8793        editor.handle_input(" ", window, cx);
 8794        editor.move_left(&Default::default(), window, cx);
 8795        editor.handle_input("\\", window, cx);
 8796        editor.handle_input("\"", window, cx);
 8797    });
 8798    cx.assert_editor_state(
 8799        &r#"
 8800            let x = "\"ˇ "
 8801        "#
 8802        .unindent(),
 8803    );
 8804
 8805    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
 8806    // mark. Nothing is inserted.
 8807    cx.update_editor(|editor, window, cx| {
 8808        editor.move_right(&Default::default(), window, cx);
 8809        editor.handle_input("\"", window, cx);
 8810    });
 8811    cx.assert_editor_state(
 8812        &r#"
 8813            let x = "\" "ˇ
 8814        "#
 8815        .unindent(),
 8816    );
 8817}
 8818
 8819#[gpui::test]
 8820async fn test_surround_with_pair(cx: &mut TestAppContext) {
 8821    init_test(cx, |_| {});
 8822
 8823    let language = Arc::new(Language::new(
 8824        LanguageConfig {
 8825            brackets: BracketPairConfig {
 8826                pairs: vec![
 8827                    BracketPair {
 8828                        start: "{".to_string(),
 8829                        end: "}".to_string(),
 8830                        close: true,
 8831                        surround: true,
 8832                        newline: true,
 8833                    },
 8834                    BracketPair {
 8835                        start: "/* ".to_string(),
 8836                        end: "*/".to_string(),
 8837                        close: true,
 8838                        surround: true,
 8839                        ..Default::default()
 8840                    },
 8841                ],
 8842                ..Default::default()
 8843            },
 8844            ..Default::default()
 8845        },
 8846        Some(tree_sitter_rust::LANGUAGE.into()),
 8847    ));
 8848
 8849    let text = r#"
 8850        a
 8851        b
 8852        c
 8853    "#
 8854    .unindent();
 8855
 8856    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8857    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8858    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8859    editor
 8860        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8861        .await;
 8862
 8863    editor.update_in(cx, |editor, window, cx| {
 8864        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8865            s.select_display_ranges([
 8866                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8867                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8868                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
 8869            ])
 8870        });
 8871
 8872        editor.handle_input("{", window, cx);
 8873        editor.handle_input("{", window, cx);
 8874        editor.handle_input("{", window, cx);
 8875        assert_eq!(
 8876            editor.text(cx),
 8877            "
 8878                {{{a}}}
 8879                {{{b}}}
 8880                {{{c}}}
 8881            "
 8882            .unindent()
 8883        );
 8884        assert_eq!(
 8885            editor.selections.display_ranges(cx),
 8886            [
 8887                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
 8888                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
 8889                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
 8890            ]
 8891        );
 8892
 8893        editor.undo(&Undo, window, cx);
 8894        editor.undo(&Undo, window, cx);
 8895        editor.undo(&Undo, window, cx);
 8896        assert_eq!(
 8897            editor.text(cx),
 8898            "
 8899                a
 8900                b
 8901                c
 8902            "
 8903            .unindent()
 8904        );
 8905        assert_eq!(
 8906            editor.selections.display_ranges(cx),
 8907            [
 8908                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8909                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8910                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 8911            ]
 8912        );
 8913
 8914        // Ensure inserting the first character of a multi-byte bracket pair
 8915        // doesn't surround the selections with the bracket.
 8916        editor.handle_input("/", window, cx);
 8917        assert_eq!(
 8918            editor.text(cx),
 8919            "
 8920                /
 8921                /
 8922                /
 8923            "
 8924            .unindent()
 8925        );
 8926        assert_eq!(
 8927            editor.selections.display_ranges(cx),
 8928            [
 8929                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 8930                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 8931                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 8932            ]
 8933        );
 8934
 8935        editor.undo(&Undo, window, cx);
 8936        assert_eq!(
 8937            editor.text(cx),
 8938            "
 8939                a
 8940                b
 8941                c
 8942            "
 8943            .unindent()
 8944        );
 8945        assert_eq!(
 8946            editor.selections.display_ranges(cx),
 8947            [
 8948                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8949                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8950                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 8951            ]
 8952        );
 8953
 8954        // Ensure inserting the last character of a multi-byte bracket pair
 8955        // doesn't surround the selections with the bracket.
 8956        editor.handle_input("*", window, cx);
 8957        assert_eq!(
 8958            editor.text(cx),
 8959            "
 8960                *
 8961                *
 8962                *
 8963            "
 8964            .unindent()
 8965        );
 8966        assert_eq!(
 8967            editor.selections.display_ranges(cx),
 8968            [
 8969                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 8970                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 8971                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 8972            ]
 8973        );
 8974    });
 8975}
 8976
 8977#[gpui::test]
 8978async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
 8979    init_test(cx, |_| {});
 8980
 8981    let language = Arc::new(Language::new(
 8982        LanguageConfig {
 8983            brackets: BracketPairConfig {
 8984                pairs: vec![BracketPair {
 8985                    start: "{".to_string(),
 8986                    end: "}".to_string(),
 8987                    close: true,
 8988                    surround: true,
 8989                    newline: true,
 8990                }],
 8991                ..Default::default()
 8992            },
 8993            autoclose_before: "}".to_string(),
 8994            ..Default::default()
 8995        },
 8996        Some(tree_sitter_rust::LANGUAGE.into()),
 8997    ));
 8998
 8999    let text = r#"
 9000        a
 9001        b
 9002        c
 9003    "#
 9004    .unindent();
 9005
 9006    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9007    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9008    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9009    editor
 9010        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9011        .await;
 9012
 9013    editor.update_in(cx, |editor, window, cx| {
 9014        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9015            s.select_ranges([
 9016                Point::new(0, 1)..Point::new(0, 1),
 9017                Point::new(1, 1)..Point::new(1, 1),
 9018                Point::new(2, 1)..Point::new(2, 1),
 9019            ])
 9020        });
 9021
 9022        editor.handle_input("{", window, cx);
 9023        editor.handle_input("{", window, cx);
 9024        editor.handle_input("_", window, cx);
 9025        assert_eq!(
 9026            editor.text(cx),
 9027            "
 9028                a{{_}}
 9029                b{{_}}
 9030                c{{_}}
 9031            "
 9032            .unindent()
 9033        );
 9034        assert_eq!(
 9035            editor.selections.ranges::<Point>(cx),
 9036            [
 9037                Point::new(0, 4)..Point::new(0, 4),
 9038                Point::new(1, 4)..Point::new(1, 4),
 9039                Point::new(2, 4)..Point::new(2, 4)
 9040            ]
 9041        );
 9042
 9043        editor.backspace(&Default::default(), window, cx);
 9044        editor.backspace(&Default::default(), window, cx);
 9045        assert_eq!(
 9046            editor.text(cx),
 9047            "
 9048                a{}
 9049                b{}
 9050                c{}
 9051            "
 9052            .unindent()
 9053        );
 9054        assert_eq!(
 9055            editor.selections.ranges::<Point>(cx),
 9056            [
 9057                Point::new(0, 2)..Point::new(0, 2),
 9058                Point::new(1, 2)..Point::new(1, 2),
 9059                Point::new(2, 2)..Point::new(2, 2)
 9060            ]
 9061        );
 9062
 9063        editor.delete_to_previous_word_start(&Default::default(), window, cx);
 9064        assert_eq!(
 9065            editor.text(cx),
 9066            "
 9067                a
 9068                b
 9069                c
 9070            "
 9071            .unindent()
 9072        );
 9073        assert_eq!(
 9074            editor.selections.ranges::<Point>(cx),
 9075            [
 9076                Point::new(0, 1)..Point::new(0, 1),
 9077                Point::new(1, 1)..Point::new(1, 1),
 9078                Point::new(2, 1)..Point::new(2, 1)
 9079            ]
 9080        );
 9081    });
 9082}
 9083
 9084#[gpui::test]
 9085async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
 9086    init_test(cx, |settings| {
 9087        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 9088    });
 9089
 9090    let mut cx = EditorTestContext::new(cx).await;
 9091
 9092    let language = Arc::new(Language::new(
 9093        LanguageConfig {
 9094            brackets: BracketPairConfig {
 9095                pairs: vec![
 9096                    BracketPair {
 9097                        start: "{".to_string(),
 9098                        end: "}".to_string(),
 9099                        close: true,
 9100                        surround: true,
 9101                        newline: true,
 9102                    },
 9103                    BracketPair {
 9104                        start: "(".to_string(),
 9105                        end: ")".to_string(),
 9106                        close: true,
 9107                        surround: true,
 9108                        newline: true,
 9109                    },
 9110                    BracketPair {
 9111                        start: "[".to_string(),
 9112                        end: "]".to_string(),
 9113                        close: false,
 9114                        surround: true,
 9115                        newline: true,
 9116                    },
 9117                ],
 9118                ..Default::default()
 9119            },
 9120            autoclose_before: "})]".to_string(),
 9121            ..Default::default()
 9122        },
 9123        Some(tree_sitter_rust::LANGUAGE.into()),
 9124    ));
 9125
 9126    cx.language_registry().add(language.clone());
 9127    cx.update_buffer(|buffer, cx| {
 9128        buffer.set_language(Some(language), cx);
 9129    });
 9130
 9131    cx.set_state(
 9132        &"
 9133            {(ˇ)}
 9134            [[ˇ]]
 9135            {(ˇ)}
 9136        "
 9137        .unindent(),
 9138    );
 9139
 9140    cx.update_editor(|editor, window, cx| {
 9141        editor.backspace(&Default::default(), window, cx);
 9142        editor.backspace(&Default::default(), window, cx);
 9143    });
 9144
 9145    cx.assert_editor_state(
 9146        &"
 9147            ˇ
 9148            ˇ]]
 9149            ˇ
 9150        "
 9151        .unindent(),
 9152    );
 9153
 9154    cx.update_editor(|editor, window, cx| {
 9155        editor.handle_input("{", window, cx);
 9156        editor.handle_input("{", window, cx);
 9157        editor.move_right(&MoveRight, window, cx);
 9158        editor.move_right(&MoveRight, window, cx);
 9159        editor.move_left(&MoveLeft, window, cx);
 9160        editor.move_left(&MoveLeft, window, cx);
 9161        editor.backspace(&Default::default(), window, cx);
 9162    });
 9163
 9164    cx.assert_editor_state(
 9165        &"
 9166            {ˇ}
 9167            {ˇ}]]
 9168            {ˇ}
 9169        "
 9170        .unindent(),
 9171    );
 9172
 9173    cx.update_editor(|editor, window, cx| {
 9174        editor.backspace(&Default::default(), window, cx);
 9175    });
 9176
 9177    cx.assert_editor_state(
 9178        &"
 9179            ˇ
 9180            ˇ]]
 9181            ˇ
 9182        "
 9183        .unindent(),
 9184    );
 9185}
 9186
 9187#[gpui::test]
 9188async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
 9189    init_test(cx, |_| {});
 9190
 9191    let language = Arc::new(Language::new(
 9192        LanguageConfig::default(),
 9193        Some(tree_sitter_rust::LANGUAGE.into()),
 9194    ));
 9195
 9196    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
 9197    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9198    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9199    editor
 9200        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9201        .await;
 9202
 9203    editor.update_in(cx, |editor, window, cx| {
 9204        editor.set_auto_replace_emoji_shortcode(true);
 9205
 9206        editor.handle_input("Hello ", window, cx);
 9207        editor.handle_input(":wave", window, cx);
 9208        assert_eq!(editor.text(cx), "Hello :wave".unindent());
 9209
 9210        editor.handle_input(":", window, cx);
 9211        assert_eq!(editor.text(cx), "Hello 👋".unindent());
 9212
 9213        editor.handle_input(" :smile", window, cx);
 9214        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
 9215
 9216        editor.handle_input(":", window, cx);
 9217        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
 9218
 9219        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
 9220        editor.handle_input(":wave", window, cx);
 9221        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
 9222
 9223        editor.handle_input(":", window, cx);
 9224        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
 9225
 9226        editor.handle_input(":1", window, cx);
 9227        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
 9228
 9229        editor.handle_input(":", window, cx);
 9230        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
 9231
 9232        // Ensure shortcode does not get replaced when it is part of a word
 9233        editor.handle_input(" Test:wave", window, cx);
 9234        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
 9235
 9236        editor.handle_input(":", window, cx);
 9237        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
 9238
 9239        editor.set_auto_replace_emoji_shortcode(false);
 9240
 9241        // Ensure shortcode does not get replaced when auto replace is off
 9242        editor.handle_input(" :wave", window, cx);
 9243        assert_eq!(
 9244            editor.text(cx),
 9245            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
 9246        );
 9247
 9248        editor.handle_input(":", window, cx);
 9249        assert_eq!(
 9250            editor.text(cx),
 9251            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
 9252        );
 9253    });
 9254}
 9255
 9256#[gpui::test]
 9257async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
 9258    init_test(cx, |_| {});
 9259
 9260    let (text, insertion_ranges) = marked_text_ranges(
 9261        indoc! {"
 9262            ˇ
 9263        "},
 9264        false,
 9265    );
 9266
 9267    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
 9268    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9269
 9270    _ = editor.update_in(cx, |editor, window, cx| {
 9271        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
 9272
 9273        editor
 9274            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9275            .unwrap();
 9276
 9277        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
 9278            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
 9279            assert_eq!(editor.text(cx), expected_text);
 9280            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
 9281        }
 9282
 9283        assert(
 9284            editor,
 9285            cx,
 9286            indoc! {"
 9287            type «» =•
 9288            "},
 9289        );
 9290
 9291        assert!(editor.context_menu_visible(), "There should be a matches");
 9292    });
 9293}
 9294
 9295#[gpui::test]
 9296async fn test_snippets(cx: &mut TestAppContext) {
 9297    init_test(cx, |_| {});
 9298
 9299    let mut cx = EditorTestContext::new(cx).await;
 9300
 9301    cx.set_state(indoc! {"
 9302        a.ˇ b
 9303        a.ˇ b
 9304        a.ˇ b
 9305    "});
 9306
 9307    cx.update_editor(|editor, window, cx| {
 9308        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
 9309        let insertion_ranges = editor
 9310            .selections
 9311            .all(cx)
 9312            .iter()
 9313            .map(|s| s.range().clone())
 9314            .collect::<Vec<_>>();
 9315        editor
 9316            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9317            .unwrap();
 9318    });
 9319
 9320    cx.assert_editor_state(indoc! {"
 9321        a.f(«oneˇ», two, «threeˇ») b
 9322        a.f(«oneˇ», two, «threeˇ») b
 9323        a.f(«oneˇ», two, «threeˇ») b
 9324    "});
 9325
 9326    // Can't move earlier than the first tab stop
 9327    cx.update_editor(|editor, window, cx| {
 9328        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 9329    });
 9330    cx.assert_editor_state(indoc! {"
 9331        a.f(«oneˇ», two, «threeˇ») b
 9332        a.f(«oneˇ», two, «threeˇ») b
 9333        a.f(«oneˇ», two, «threeˇ») b
 9334    "});
 9335
 9336    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9337    cx.assert_editor_state(indoc! {"
 9338        a.f(one, «twoˇ», three) b
 9339        a.f(one, «twoˇ», three) b
 9340        a.f(one, «twoˇ», three) b
 9341    "});
 9342
 9343    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
 9344    cx.assert_editor_state(indoc! {"
 9345        a.f(«oneˇ», two, «threeˇ») b
 9346        a.f(«oneˇ», two, «threeˇ») b
 9347        a.f(«oneˇ», two, «threeˇ») b
 9348    "});
 9349
 9350    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9351    cx.assert_editor_state(indoc! {"
 9352        a.f(one, «twoˇ», three) b
 9353        a.f(one, «twoˇ», three) b
 9354        a.f(one, «twoˇ», three) b
 9355    "});
 9356    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9357    cx.assert_editor_state(indoc! {"
 9358        a.f(one, two, three)ˇ b
 9359        a.f(one, two, three)ˇ b
 9360        a.f(one, two, three)ˇ b
 9361    "});
 9362
 9363    // As soon as the last tab stop is reached, snippet state is gone
 9364    cx.update_editor(|editor, window, cx| {
 9365        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 9366    });
 9367    cx.assert_editor_state(indoc! {"
 9368        a.f(one, two, three)ˇ b
 9369        a.f(one, two, three)ˇ b
 9370        a.f(one, two, three)ˇ b
 9371    "});
 9372}
 9373
 9374#[gpui::test]
 9375async fn test_snippet_indentation(cx: &mut TestAppContext) {
 9376    init_test(cx, |_| {});
 9377
 9378    let mut cx = EditorTestContext::new(cx).await;
 9379
 9380    cx.update_editor(|editor, window, cx| {
 9381        let snippet = Snippet::parse(indoc! {"
 9382            /*
 9383             * Multiline comment with leading indentation
 9384             *
 9385             * $1
 9386             */
 9387            $0"})
 9388        .unwrap();
 9389        let insertion_ranges = editor
 9390            .selections
 9391            .all(cx)
 9392            .iter()
 9393            .map(|s| s.range().clone())
 9394            .collect::<Vec<_>>();
 9395        editor
 9396            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9397            .unwrap();
 9398    });
 9399
 9400    cx.assert_editor_state(indoc! {"
 9401        /*
 9402         * Multiline comment with leading indentation
 9403         *
 9404         * ˇ
 9405         */
 9406    "});
 9407
 9408    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9409    cx.assert_editor_state(indoc! {"
 9410        /*
 9411         * Multiline comment with leading indentation
 9412         *
 9413         *•
 9414         */
 9415        ˇ"});
 9416}
 9417
 9418#[gpui::test]
 9419async fn test_document_format_during_save(cx: &mut TestAppContext) {
 9420    init_test(cx, |_| {});
 9421
 9422    let fs = FakeFs::new(cx.executor());
 9423    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9424
 9425    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
 9426
 9427    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9428    language_registry.add(rust_lang());
 9429    let mut fake_servers = language_registry.register_fake_lsp(
 9430        "Rust",
 9431        FakeLspAdapter {
 9432            capabilities: lsp::ServerCapabilities {
 9433                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9434                ..Default::default()
 9435            },
 9436            ..Default::default()
 9437        },
 9438    );
 9439
 9440    let buffer = project
 9441        .update(cx, |project, cx| {
 9442            project.open_local_buffer(path!("/file.rs"), cx)
 9443        })
 9444        .await
 9445        .unwrap();
 9446
 9447    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9448    let (editor, cx) = cx.add_window_view(|window, cx| {
 9449        build_editor_with_project(project.clone(), buffer, window, cx)
 9450    });
 9451    editor.update_in(cx, |editor, window, cx| {
 9452        editor.set_text("one\ntwo\nthree\n", window, cx)
 9453    });
 9454    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9455
 9456    cx.executor().start_waiting();
 9457    let fake_server = fake_servers.next().await.unwrap();
 9458
 9459    {
 9460        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9461            move |params, _| async move {
 9462                assert_eq!(
 9463                    params.text_document.uri,
 9464                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9465                );
 9466                assert_eq!(params.options.tab_size, 4);
 9467                Ok(Some(vec![lsp::TextEdit::new(
 9468                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9469                    ", ".to_string(),
 9470                )]))
 9471            },
 9472        );
 9473        let save = editor
 9474            .update_in(cx, |editor, window, cx| {
 9475                editor.save(
 9476                    SaveOptions {
 9477                        format: true,
 9478                        autosave: false,
 9479                    },
 9480                    project.clone(),
 9481                    window,
 9482                    cx,
 9483                )
 9484            })
 9485            .unwrap();
 9486        cx.executor().start_waiting();
 9487        save.await;
 9488
 9489        assert_eq!(
 9490            editor.update(cx, |editor, cx| editor.text(cx)),
 9491            "one, two\nthree\n"
 9492        );
 9493        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9494    }
 9495
 9496    {
 9497        editor.update_in(cx, |editor, window, cx| {
 9498            editor.set_text("one\ntwo\nthree\n", window, cx)
 9499        });
 9500        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9501
 9502        // Ensure we can still save even if formatting hangs.
 9503        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9504            move |params, _| async move {
 9505                assert_eq!(
 9506                    params.text_document.uri,
 9507                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9508                );
 9509                futures::future::pending::<()>().await;
 9510                unreachable!()
 9511            },
 9512        );
 9513        let save = editor
 9514            .update_in(cx, |editor, window, cx| {
 9515                editor.save(
 9516                    SaveOptions {
 9517                        format: true,
 9518                        autosave: false,
 9519                    },
 9520                    project.clone(),
 9521                    window,
 9522                    cx,
 9523                )
 9524            })
 9525            .unwrap();
 9526        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9527        cx.executor().start_waiting();
 9528        save.await;
 9529        assert_eq!(
 9530            editor.update(cx, |editor, cx| editor.text(cx)),
 9531            "one\ntwo\nthree\n"
 9532        );
 9533    }
 9534
 9535    // Set rust language override and assert overridden tabsize is sent to language server
 9536    update_test_language_settings(cx, |settings| {
 9537        settings.languages.0.insert(
 9538            "Rust".into(),
 9539            LanguageSettingsContent {
 9540                tab_size: NonZeroU32::new(8),
 9541                ..Default::default()
 9542            },
 9543        );
 9544    });
 9545
 9546    {
 9547        editor.update_in(cx, |editor, window, cx| {
 9548            editor.set_text("somehting_new\n", window, cx)
 9549        });
 9550        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9551        let _formatting_request_signal = fake_server
 9552            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9553                assert_eq!(
 9554                    params.text_document.uri,
 9555                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9556                );
 9557                assert_eq!(params.options.tab_size, 8);
 9558                Ok(Some(vec![]))
 9559            });
 9560        let save = editor
 9561            .update_in(cx, |editor, window, cx| {
 9562                editor.save(
 9563                    SaveOptions {
 9564                        format: true,
 9565                        autosave: false,
 9566                    },
 9567                    project.clone(),
 9568                    window,
 9569                    cx,
 9570                )
 9571            })
 9572            .unwrap();
 9573        cx.executor().start_waiting();
 9574        save.await;
 9575    }
 9576}
 9577
 9578#[gpui::test]
 9579async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
 9580    init_test(cx, |settings| {
 9581        settings.defaults.ensure_final_newline_on_save = Some(false);
 9582    });
 9583
 9584    let fs = FakeFs::new(cx.executor());
 9585    fs.insert_file(path!("/file.txt"), "foo".into()).await;
 9586
 9587    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
 9588
 9589    let buffer = project
 9590        .update(cx, |project, cx| {
 9591            project.open_local_buffer(path!("/file.txt"), cx)
 9592        })
 9593        .await
 9594        .unwrap();
 9595
 9596    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9597    let (editor, cx) = cx.add_window_view(|window, cx| {
 9598        build_editor_with_project(project.clone(), buffer, window, cx)
 9599    });
 9600    editor.update_in(cx, |editor, window, cx| {
 9601        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 9602            s.select_ranges([0..0])
 9603        });
 9604    });
 9605    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9606
 9607    editor.update_in(cx, |editor, window, cx| {
 9608        editor.handle_input("\n", window, cx)
 9609    });
 9610    cx.run_until_parked();
 9611    save(&editor, &project, cx).await;
 9612    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
 9613
 9614    editor.update_in(cx, |editor, window, cx| {
 9615        editor.undo(&Default::default(), window, cx);
 9616    });
 9617    save(&editor, &project, cx).await;
 9618    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
 9619
 9620    editor.update_in(cx, |editor, window, cx| {
 9621        editor.redo(&Default::default(), window, cx);
 9622    });
 9623    cx.run_until_parked();
 9624    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
 9625
 9626    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
 9627        let save = editor
 9628            .update_in(cx, |editor, window, cx| {
 9629                editor.save(
 9630                    SaveOptions {
 9631                        format: true,
 9632                        autosave: false,
 9633                    },
 9634                    project.clone(),
 9635                    window,
 9636                    cx,
 9637                )
 9638            })
 9639            .unwrap();
 9640        cx.executor().start_waiting();
 9641        save.await;
 9642        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9643    }
 9644}
 9645
 9646#[gpui::test]
 9647async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
 9648    init_test(cx, |_| {});
 9649
 9650    let cols = 4;
 9651    let rows = 10;
 9652    let sample_text_1 = sample_text(rows, cols, 'a');
 9653    assert_eq!(
 9654        sample_text_1,
 9655        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
 9656    );
 9657    let sample_text_2 = sample_text(rows, cols, 'l');
 9658    assert_eq!(
 9659        sample_text_2,
 9660        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
 9661    );
 9662    let sample_text_3 = sample_text(rows, cols, 'v');
 9663    assert_eq!(
 9664        sample_text_3,
 9665        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
 9666    );
 9667
 9668    let fs = FakeFs::new(cx.executor());
 9669    fs.insert_tree(
 9670        path!("/a"),
 9671        json!({
 9672            "main.rs": sample_text_1,
 9673            "other.rs": sample_text_2,
 9674            "lib.rs": sample_text_3,
 9675        }),
 9676    )
 9677    .await;
 9678
 9679    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
 9680    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9681    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 9682
 9683    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9684    language_registry.add(rust_lang());
 9685    let mut fake_servers = language_registry.register_fake_lsp(
 9686        "Rust",
 9687        FakeLspAdapter {
 9688            capabilities: lsp::ServerCapabilities {
 9689                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9690                ..Default::default()
 9691            },
 9692            ..Default::default()
 9693        },
 9694    );
 9695
 9696    let worktree = project.update(cx, |project, cx| {
 9697        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
 9698        assert_eq!(worktrees.len(), 1);
 9699        worktrees.pop().unwrap()
 9700    });
 9701    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 9702
 9703    let buffer_1 = project
 9704        .update(cx, |project, cx| {
 9705            project.open_buffer((worktree_id, "main.rs"), cx)
 9706        })
 9707        .await
 9708        .unwrap();
 9709    let buffer_2 = project
 9710        .update(cx, |project, cx| {
 9711            project.open_buffer((worktree_id, "other.rs"), cx)
 9712        })
 9713        .await
 9714        .unwrap();
 9715    let buffer_3 = project
 9716        .update(cx, |project, cx| {
 9717            project.open_buffer((worktree_id, "lib.rs"), cx)
 9718        })
 9719        .await
 9720        .unwrap();
 9721
 9722    let multi_buffer = cx.new(|cx| {
 9723        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 9724        multi_buffer.push_excerpts(
 9725            buffer_1.clone(),
 9726            [
 9727                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9728                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9729                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9730            ],
 9731            cx,
 9732        );
 9733        multi_buffer.push_excerpts(
 9734            buffer_2.clone(),
 9735            [
 9736                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9737                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9738                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9739            ],
 9740            cx,
 9741        );
 9742        multi_buffer.push_excerpts(
 9743            buffer_3.clone(),
 9744            [
 9745                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9746                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9747                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9748            ],
 9749            cx,
 9750        );
 9751        multi_buffer
 9752    });
 9753    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
 9754        Editor::new(
 9755            EditorMode::full(),
 9756            multi_buffer,
 9757            Some(project.clone()),
 9758            window,
 9759            cx,
 9760        )
 9761    });
 9762
 9763    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 9764        editor.change_selections(
 9765            SelectionEffects::scroll(Autoscroll::Next),
 9766            window,
 9767            cx,
 9768            |s| s.select_ranges(Some(1..2)),
 9769        );
 9770        editor.insert("|one|two|three|", window, cx);
 9771    });
 9772    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 9773    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 9774        editor.change_selections(
 9775            SelectionEffects::scroll(Autoscroll::Next),
 9776            window,
 9777            cx,
 9778            |s| s.select_ranges(Some(60..70)),
 9779        );
 9780        editor.insert("|four|five|six|", window, cx);
 9781    });
 9782    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 9783
 9784    // First two buffers should be edited, but not the third one.
 9785    assert_eq!(
 9786        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 9787        "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}",
 9788    );
 9789    buffer_1.update(cx, |buffer, _| {
 9790        assert!(buffer.is_dirty());
 9791        assert_eq!(
 9792            buffer.text(),
 9793            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
 9794        )
 9795    });
 9796    buffer_2.update(cx, |buffer, _| {
 9797        assert!(buffer.is_dirty());
 9798        assert_eq!(
 9799            buffer.text(),
 9800            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
 9801        )
 9802    });
 9803    buffer_3.update(cx, |buffer, _| {
 9804        assert!(!buffer.is_dirty());
 9805        assert_eq!(buffer.text(), sample_text_3,)
 9806    });
 9807    cx.executor().run_until_parked();
 9808
 9809    cx.executor().start_waiting();
 9810    let save = multi_buffer_editor
 9811        .update_in(cx, |editor, window, cx| {
 9812            editor.save(
 9813                SaveOptions {
 9814                    format: true,
 9815                    autosave: false,
 9816                },
 9817                project.clone(),
 9818                window,
 9819                cx,
 9820            )
 9821        })
 9822        .unwrap();
 9823
 9824    let fake_server = fake_servers.next().await.unwrap();
 9825    fake_server
 9826        .server
 9827        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9828            Ok(Some(vec![lsp::TextEdit::new(
 9829                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9830                format!("[{} formatted]", params.text_document.uri),
 9831            )]))
 9832        })
 9833        .detach();
 9834    save.await;
 9835
 9836    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
 9837    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
 9838    assert_eq!(
 9839        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 9840        uri!(
 9841            "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}"
 9842        ),
 9843    );
 9844    buffer_1.update(cx, |buffer, _| {
 9845        assert!(!buffer.is_dirty());
 9846        assert_eq!(
 9847            buffer.text(),
 9848            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
 9849        )
 9850    });
 9851    buffer_2.update(cx, |buffer, _| {
 9852        assert!(!buffer.is_dirty());
 9853        assert_eq!(
 9854            buffer.text(),
 9855            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
 9856        )
 9857    });
 9858    buffer_3.update(cx, |buffer, _| {
 9859        assert!(!buffer.is_dirty());
 9860        assert_eq!(buffer.text(), sample_text_3,)
 9861    });
 9862}
 9863
 9864#[gpui::test]
 9865async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
 9866    init_test(cx, |_| {});
 9867
 9868    let fs = FakeFs::new(cx.executor());
 9869    fs.insert_tree(
 9870        path!("/dir"),
 9871        json!({
 9872            "file1.rs": "fn main() { println!(\"hello\"); }",
 9873            "file2.rs": "fn test() { println!(\"test\"); }",
 9874            "file3.rs": "fn other() { println!(\"other\"); }\n",
 9875        }),
 9876    )
 9877    .await;
 9878
 9879    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
 9880    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9881    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 9882
 9883    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9884    language_registry.add(rust_lang());
 9885
 9886    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
 9887    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 9888
 9889    // Open three buffers
 9890    let buffer_1 = project
 9891        .update(cx, |project, cx| {
 9892            project.open_buffer((worktree_id, "file1.rs"), cx)
 9893        })
 9894        .await
 9895        .unwrap();
 9896    let buffer_2 = project
 9897        .update(cx, |project, cx| {
 9898            project.open_buffer((worktree_id, "file2.rs"), cx)
 9899        })
 9900        .await
 9901        .unwrap();
 9902    let buffer_3 = project
 9903        .update(cx, |project, cx| {
 9904            project.open_buffer((worktree_id, "file3.rs"), cx)
 9905        })
 9906        .await
 9907        .unwrap();
 9908
 9909    // Create a multi-buffer with all three buffers
 9910    let multi_buffer = cx.new(|cx| {
 9911        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 9912        multi_buffer.push_excerpts(
 9913            buffer_1.clone(),
 9914            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9915            cx,
 9916        );
 9917        multi_buffer.push_excerpts(
 9918            buffer_2.clone(),
 9919            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9920            cx,
 9921        );
 9922        multi_buffer.push_excerpts(
 9923            buffer_3.clone(),
 9924            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9925            cx,
 9926        );
 9927        multi_buffer
 9928    });
 9929
 9930    let editor = cx.new_window_entity(|window, cx| {
 9931        Editor::new(
 9932            EditorMode::full(),
 9933            multi_buffer,
 9934            Some(project.clone()),
 9935            window,
 9936            cx,
 9937        )
 9938    });
 9939
 9940    // Edit only the first buffer
 9941    editor.update_in(cx, |editor, window, cx| {
 9942        editor.change_selections(
 9943            SelectionEffects::scroll(Autoscroll::Next),
 9944            window,
 9945            cx,
 9946            |s| s.select_ranges(Some(10..10)),
 9947        );
 9948        editor.insert("// edited", window, cx);
 9949    });
 9950
 9951    // Verify that only buffer 1 is dirty
 9952    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
 9953    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9954    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9955
 9956    // Get write counts after file creation (files were created with initial content)
 9957    // We expect each file to have been written once during creation
 9958    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
 9959    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
 9960    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
 9961
 9962    // Perform autosave
 9963    let save_task = editor.update_in(cx, |editor, window, cx| {
 9964        editor.save(
 9965            SaveOptions {
 9966                format: true,
 9967                autosave: true,
 9968            },
 9969            project.clone(),
 9970            window,
 9971            cx,
 9972        )
 9973    });
 9974    save_task.await.unwrap();
 9975
 9976    // Only the dirty buffer should have been saved
 9977    assert_eq!(
 9978        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
 9979        1,
 9980        "Buffer 1 was dirty, so it should have been written once during autosave"
 9981    );
 9982    assert_eq!(
 9983        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
 9984        0,
 9985        "Buffer 2 was clean, so it should not have been written during autosave"
 9986    );
 9987    assert_eq!(
 9988        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
 9989        0,
 9990        "Buffer 3 was clean, so it should not have been written during autosave"
 9991    );
 9992
 9993    // Verify buffer states after autosave
 9994    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9995    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9996    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9997
 9998    // Now perform a manual save (format = true)
 9999    let save_task = editor.update_in(cx, |editor, window, cx| {
10000        editor.save(
10001            SaveOptions {
10002                format: true,
10003                autosave: false,
10004            },
10005            project.clone(),
10006            window,
10007            cx,
10008        )
10009    });
10010    save_task.await.unwrap();
10011
10012    // During manual save, clean buffers don't get written to disk
10013    // They just get did_save called for language server notifications
10014    assert_eq!(
10015        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
10016        1,
10017        "Buffer 1 should only have been written once total (during autosave, not manual save)"
10018    );
10019    assert_eq!(
10020        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
10021        0,
10022        "Buffer 2 should not have been written at all"
10023    );
10024    assert_eq!(
10025        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
10026        0,
10027        "Buffer 3 should not have been written at all"
10028    );
10029}
10030
10031async fn setup_range_format_test(
10032    cx: &mut TestAppContext,
10033) -> (
10034    Entity<Project>,
10035    Entity<Editor>,
10036    &mut gpui::VisualTestContext,
10037    lsp::FakeLanguageServer,
10038) {
10039    init_test(cx, |_| {});
10040
10041    let fs = FakeFs::new(cx.executor());
10042    fs.insert_file(path!("/file.rs"), Default::default()).await;
10043
10044    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10045
10046    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10047    language_registry.add(rust_lang());
10048    let mut fake_servers = language_registry.register_fake_lsp(
10049        "Rust",
10050        FakeLspAdapter {
10051            capabilities: lsp::ServerCapabilities {
10052                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
10053                ..lsp::ServerCapabilities::default()
10054            },
10055            ..FakeLspAdapter::default()
10056        },
10057    );
10058
10059    let buffer = project
10060        .update(cx, |project, cx| {
10061            project.open_local_buffer(path!("/file.rs"), cx)
10062        })
10063        .await
10064        .unwrap();
10065
10066    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10067    let (editor, cx) = cx.add_window_view(|window, cx| {
10068        build_editor_with_project(project.clone(), buffer, window, cx)
10069    });
10070
10071    cx.executor().start_waiting();
10072    let fake_server = fake_servers.next().await.unwrap();
10073
10074    (project, editor, cx, fake_server)
10075}
10076
10077#[gpui::test]
10078async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
10079    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
10080
10081    editor.update_in(cx, |editor, window, cx| {
10082        editor.set_text("one\ntwo\nthree\n", window, cx)
10083    });
10084    assert!(cx.read(|cx| editor.is_dirty(cx)));
10085
10086    let save = editor
10087        .update_in(cx, |editor, window, cx| {
10088            editor.save(
10089                SaveOptions {
10090                    format: true,
10091                    autosave: false,
10092                },
10093                project.clone(),
10094                window,
10095                cx,
10096            )
10097        })
10098        .unwrap();
10099    fake_server
10100        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
10101            assert_eq!(
10102                params.text_document.uri,
10103                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10104            );
10105            assert_eq!(params.options.tab_size, 4);
10106            Ok(Some(vec![lsp::TextEdit::new(
10107                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10108                ", ".to_string(),
10109            )]))
10110        })
10111        .next()
10112        .await;
10113    cx.executor().start_waiting();
10114    save.await;
10115    assert_eq!(
10116        editor.update(cx, |editor, cx| editor.text(cx)),
10117        "one, two\nthree\n"
10118    );
10119    assert!(!cx.read(|cx| editor.is_dirty(cx)));
10120}
10121
10122#[gpui::test]
10123async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
10124    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
10125
10126    editor.update_in(cx, |editor, window, cx| {
10127        editor.set_text("one\ntwo\nthree\n", window, cx)
10128    });
10129    assert!(cx.read(|cx| editor.is_dirty(cx)));
10130
10131    // Test that save still works when formatting hangs
10132    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
10133        move |params, _| async move {
10134            assert_eq!(
10135                params.text_document.uri,
10136                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10137            );
10138            futures::future::pending::<()>().await;
10139            unreachable!()
10140        },
10141    );
10142    let save = editor
10143        .update_in(cx, |editor, window, cx| {
10144            editor.save(
10145                SaveOptions {
10146                    format: true,
10147                    autosave: false,
10148                },
10149                project.clone(),
10150                window,
10151                cx,
10152            )
10153        })
10154        .unwrap();
10155    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10156    cx.executor().start_waiting();
10157    save.await;
10158    assert_eq!(
10159        editor.update(cx, |editor, cx| editor.text(cx)),
10160        "one\ntwo\nthree\n"
10161    );
10162    assert!(!cx.read(|cx| editor.is_dirty(cx)));
10163}
10164
10165#[gpui::test]
10166async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
10167    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
10168
10169    // Buffer starts clean, no formatting should be requested
10170    let save = editor
10171        .update_in(cx, |editor, window, cx| {
10172            editor.save(
10173                SaveOptions {
10174                    format: false,
10175                    autosave: false,
10176                },
10177                project.clone(),
10178                window,
10179                cx,
10180            )
10181        })
10182        .unwrap();
10183    let _pending_format_request = fake_server
10184        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
10185            panic!("Should not be invoked");
10186        })
10187        .next();
10188    cx.executor().start_waiting();
10189    save.await;
10190    cx.run_until_parked();
10191}
10192
10193#[gpui::test]
10194async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
10195    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
10196
10197    // Set Rust language override and assert overridden tabsize is sent to language server
10198    update_test_language_settings(cx, |settings| {
10199        settings.languages.0.insert(
10200            "Rust".into(),
10201            LanguageSettingsContent {
10202                tab_size: NonZeroU32::new(8),
10203                ..Default::default()
10204            },
10205        );
10206    });
10207
10208    editor.update_in(cx, |editor, window, cx| {
10209        editor.set_text("something_new\n", window, cx)
10210    });
10211    assert!(cx.read(|cx| editor.is_dirty(cx)));
10212    let save = editor
10213        .update_in(cx, |editor, window, cx| {
10214            editor.save(
10215                SaveOptions {
10216                    format: true,
10217                    autosave: false,
10218                },
10219                project.clone(),
10220                window,
10221                cx,
10222            )
10223        })
10224        .unwrap();
10225    fake_server
10226        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
10227            assert_eq!(
10228                params.text_document.uri,
10229                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10230            );
10231            assert_eq!(params.options.tab_size, 8);
10232            Ok(Some(Vec::new()))
10233        })
10234        .next()
10235        .await;
10236    save.await;
10237}
10238
10239#[gpui::test]
10240async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
10241    init_test(cx, |settings| {
10242        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
10243            Formatter::LanguageServer { name: None },
10244        )))
10245    });
10246
10247    let fs = FakeFs::new(cx.executor());
10248    fs.insert_file(path!("/file.rs"), Default::default()).await;
10249
10250    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10251
10252    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10253    language_registry.add(Arc::new(Language::new(
10254        LanguageConfig {
10255            name: "Rust".into(),
10256            matcher: LanguageMatcher {
10257                path_suffixes: vec!["rs".to_string()],
10258                ..Default::default()
10259            },
10260            ..LanguageConfig::default()
10261        },
10262        Some(tree_sitter_rust::LANGUAGE.into()),
10263    )));
10264    update_test_language_settings(cx, |settings| {
10265        // Enable Prettier formatting for the same buffer, and ensure
10266        // LSP is called instead of Prettier.
10267        settings.defaults.prettier = Some(PrettierSettings {
10268            allowed: true,
10269            ..PrettierSettings::default()
10270        });
10271    });
10272    let mut fake_servers = language_registry.register_fake_lsp(
10273        "Rust",
10274        FakeLspAdapter {
10275            capabilities: lsp::ServerCapabilities {
10276                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10277                ..Default::default()
10278            },
10279            ..Default::default()
10280        },
10281    );
10282
10283    let buffer = project
10284        .update(cx, |project, cx| {
10285            project.open_local_buffer(path!("/file.rs"), cx)
10286        })
10287        .await
10288        .unwrap();
10289
10290    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10291    let (editor, cx) = cx.add_window_view(|window, cx| {
10292        build_editor_with_project(project.clone(), buffer, window, cx)
10293    });
10294    editor.update_in(cx, |editor, window, cx| {
10295        editor.set_text("one\ntwo\nthree\n", window, cx)
10296    });
10297
10298    cx.executor().start_waiting();
10299    let fake_server = fake_servers.next().await.unwrap();
10300
10301    let format = editor
10302        .update_in(cx, |editor, window, cx| {
10303            editor.perform_format(
10304                project.clone(),
10305                FormatTrigger::Manual,
10306                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10307                window,
10308                cx,
10309            )
10310        })
10311        .unwrap();
10312    fake_server
10313        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
10314            assert_eq!(
10315                params.text_document.uri,
10316                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10317            );
10318            assert_eq!(params.options.tab_size, 4);
10319            Ok(Some(vec![lsp::TextEdit::new(
10320                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10321                ", ".to_string(),
10322            )]))
10323        })
10324        .next()
10325        .await;
10326    cx.executor().start_waiting();
10327    format.await;
10328    assert_eq!(
10329        editor.update(cx, |editor, cx| editor.text(cx)),
10330        "one, two\nthree\n"
10331    );
10332
10333    editor.update_in(cx, |editor, window, cx| {
10334        editor.set_text("one\ntwo\nthree\n", window, cx)
10335    });
10336    // Ensure we don't lock if formatting hangs.
10337    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10338        move |params, _| async move {
10339            assert_eq!(
10340                params.text_document.uri,
10341                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10342            );
10343            futures::future::pending::<()>().await;
10344            unreachable!()
10345        },
10346    );
10347    let format = editor
10348        .update_in(cx, |editor, window, cx| {
10349            editor.perform_format(
10350                project,
10351                FormatTrigger::Manual,
10352                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10353                window,
10354                cx,
10355            )
10356        })
10357        .unwrap();
10358    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10359    cx.executor().start_waiting();
10360    format.await;
10361    assert_eq!(
10362        editor.update(cx, |editor, cx| editor.text(cx)),
10363        "one\ntwo\nthree\n"
10364    );
10365}
10366
10367#[gpui::test]
10368async fn test_multiple_formatters(cx: &mut TestAppContext) {
10369    init_test(cx, |settings| {
10370        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
10371        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
10372            Formatter::LanguageServer { name: None },
10373            Formatter::CodeActions(
10374                [
10375                    ("code-action-1".into(), true),
10376                    ("code-action-2".into(), true),
10377                ]
10378                .into_iter()
10379                .collect(),
10380            ),
10381        ])))
10382    });
10383
10384    let fs = FakeFs::new(cx.executor());
10385    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
10386        .await;
10387
10388    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10389    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10390    language_registry.add(rust_lang());
10391
10392    let mut fake_servers = language_registry.register_fake_lsp(
10393        "Rust",
10394        FakeLspAdapter {
10395            capabilities: lsp::ServerCapabilities {
10396                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10397                execute_command_provider: Some(lsp::ExecuteCommandOptions {
10398                    commands: vec!["the-command-for-code-action-1".into()],
10399                    ..Default::default()
10400                }),
10401                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10402                ..Default::default()
10403            },
10404            ..Default::default()
10405        },
10406    );
10407
10408    let buffer = project
10409        .update(cx, |project, cx| {
10410            project.open_local_buffer(path!("/file.rs"), cx)
10411        })
10412        .await
10413        .unwrap();
10414
10415    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10416    let (editor, cx) = cx.add_window_view(|window, cx| {
10417        build_editor_with_project(project.clone(), buffer, window, cx)
10418    });
10419
10420    cx.executor().start_waiting();
10421
10422    let fake_server = fake_servers.next().await.unwrap();
10423    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10424        move |_params, _| async move {
10425            Ok(Some(vec![lsp::TextEdit::new(
10426                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10427                "applied-formatting\n".to_string(),
10428            )]))
10429        },
10430    );
10431    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
10432        move |params, _| async move {
10433            assert_eq!(
10434                params.context.only,
10435                Some(vec!["code-action-1".into(), "code-action-2".into()])
10436            );
10437            let uri = lsp::Url::from_file_path(path!("/file.rs")).unwrap();
10438            Ok(Some(vec![
10439                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10440                    kind: Some("code-action-1".into()),
10441                    edit: Some(lsp::WorkspaceEdit::new(
10442                        [(
10443                            uri.clone(),
10444                            vec![lsp::TextEdit::new(
10445                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10446                                "applied-code-action-1-edit\n".to_string(),
10447                            )],
10448                        )]
10449                        .into_iter()
10450                        .collect(),
10451                    )),
10452                    command: Some(lsp::Command {
10453                        command: "the-command-for-code-action-1".into(),
10454                        ..Default::default()
10455                    }),
10456                    ..Default::default()
10457                }),
10458                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10459                    kind: Some("code-action-2".into()),
10460                    edit: Some(lsp::WorkspaceEdit::new(
10461                        [(
10462                            uri.clone(),
10463                            vec![lsp::TextEdit::new(
10464                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10465                                "applied-code-action-2-edit\n".to_string(),
10466                            )],
10467                        )]
10468                        .into_iter()
10469                        .collect(),
10470                    )),
10471                    ..Default::default()
10472                }),
10473            ]))
10474        },
10475    );
10476
10477    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
10478        move |params, _| async move { Ok(params) }
10479    });
10480
10481    let command_lock = Arc::new(futures::lock::Mutex::new(()));
10482    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
10483        let fake = fake_server.clone();
10484        let lock = command_lock.clone();
10485        move |params, _| {
10486            assert_eq!(params.command, "the-command-for-code-action-1");
10487            let fake = fake.clone();
10488            let lock = lock.clone();
10489            async move {
10490                lock.lock().await;
10491                fake.server
10492                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
10493                        label: None,
10494                        edit: lsp::WorkspaceEdit {
10495                            changes: Some(
10496                                [(
10497                                    lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
10498                                    vec![lsp::TextEdit {
10499                                        range: lsp::Range::new(
10500                                            lsp::Position::new(0, 0),
10501                                            lsp::Position::new(0, 0),
10502                                        ),
10503                                        new_text: "applied-code-action-1-command\n".into(),
10504                                    }],
10505                                )]
10506                                .into_iter()
10507                                .collect(),
10508                            ),
10509                            ..Default::default()
10510                        },
10511                    })
10512                    .await
10513                    .into_response()
10514                    .unwrap();
10515                Ok(Some(json!(null)))
10516            }
10517        }
10518    });
10519
10520    cx.executor().start_waiting();
10521    editor
10522        .update_in(cx, |editor, window, cx| {
10523            editor.perform_format(
10524                project.clone(),
10525                FormatTrigger::Manual,
10526                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10527                window,
10528                cx,
10529            )
10530        })
10531        .unwrap()
10532        .await;
10533    editor.update(cx, |editor, cx| {
10534        assert_eq!(
10535            editor.text(cx),
10536            r#"
10537                applied-code-action-2-edit
10538                applied-code-action-1-command
10539                applied-code-action-1-edit
10540                applied-formatting
10541                one
10542                two
10543                three
10544            "#
10545            .unindent()
10546        );
10547    });
10548
10549    editor.update_in(cx, |editor, window, cx| {
10550        editor.undo(&Default::default(), window, cx);
10551        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10552    });
10553
10554    // Perform a manual edit while waiting for an LSP command
10555    // that's being run as part of a formatting code action.
10556    let lock_guard = command_lock.lock().await;
10557    let format = editor
10558        .update_in(cx, |editor, window, cx| {
10559            editor.perform_format(
10560                project.clone(),
10561                FormatTrigger::Manual,
10562                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10563                window,
10564                cx,
10565            )
10566        })
10567        .unwrap();
10568    cx.run_until_parked();
10569    editor.update(cx, |editor, cx| {
10570        assert_eq!(
10571            editor.text(cx),
10572            r#"
10573                applied-code-action-1-edit
10574                applied-formatting
10575                one
10576                two
10577                three
10578            "#
10579            .unindent()
10580        );
10581
10582        editor.buffer.update(cx, |buffer, cx| {
10583            let ix = buffer.len(cx);
10584            buffer.edit([(ix..ix, "edited\n")], None, cx);
10585        });
10586    });
10587
10588    // Allow the LSP command to proceed. Because the buffer was edited,
10589    // the second code action will not be run.
10590    drop(lock_guard);
10591    format.await;
10592    editor.update_in(cx, |editor, window, cx| {
10593        assert_eq!(
10594            editor.text(cx),
10595            r#"
10596                applied-code-action-1-command
10597                applied-code-action-1-edit
10598                applied-formatting
10599                one
10600                two
10601                three
10602                edited
10603            "#
10604            .unindent()
10605        );
10606
10607        // The manual edit is undone first, because it is the last thing the user did
10608        // (even though the command completed afterwards).
10609        editor.undo(&Default::default(), window, cx);
10610        assert_eq!(
10611            editor.text(cx),
10612            r#"
10613                applied-code-action-1-command
10614                applied-code-action-1-edit
10615                applied-formatting
10616                one
10617                two
10618                three
10619            "#
10620            .unindent()
10621        );
10622
10623        // All the formatting (including the command, which completed after the manual edit)
10624        // is undone together.
10625        editor.undo(&Default::default(), window, cx);
10626        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10627    });
10628}
10629
10630#[gpui::test]
10631async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
10632    init_test(cx, |settings| {
10633        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
10634            Formatter::LanguageServer { name: None },
10635        ])))
10636    });
10637
10638    let fs = FakeFs::new(cx.executor());
10639    fs.insert_file(path!("/file.ts"), Default::default()).await;
10640
10641    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10642
10643    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10644    language_registry.add(Arc::new(Language::new(
10645        LanguageConfig {
10646            name: "TypeScript".into(),
10647            matcher: LanguageMatcher {
10648                path_suffixes: vec!["ts".to_string()],
10649                ..Default::default()
10650            },
10651            ..LanguageConfig::default()
10652        },
10653        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
10654    )));
10655    update_test_language_settings(cx, |settings| {
10656        settings.defaults.prettier = Some(PrettierSettings {
10657            allowed: true,
10658            ..PrettierSettings::default()
10659        });
10660    });
10661    let mut fake_servers = language_registry.register_fake_lsp(
10662        "TypeScript",
10663        FakeLspAdapter {
10664            capabilities: lsp::ServerCapabilities {
10665                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10666                ..Default::default()
10667            },
10668            ..Default::default()
10669        },
10670    );
10671
10672    let buffer = project
10673        .update(cx, |project, cx| {
10674            project.open_local_buffer(path!("/file.ts"), cx)
10675        })
10676        .await
10677        .unwrap();
10678
10679    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10680    let (editor, cx) = cx.add_window_view(|window, cx| {
10681        build_editor_with_project(project.clone(), buffer, window, cx)
10682    });
10683    editor.update_in(cx, |editor, window, cx| {
10684        editor.set_text(
10685            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
10686            window,
10687            cx,
10688        )
10689    });
10690
10691    cx.executor().start_waiting();
10692    let fake_server = fake_servers.next().await.unwrap();
10693
10694    let format = editor
10695        .update_in(cx, |editor, window, cx| {
10696            editor.perform_code_action_kind(
10697                project.clone(),
10698                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
10699                window,
10700                cx,
10701            )
10702        })
10703        .unwrap();
10704    fake_server
10705        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
10706            assert_eq!(
10707                params.text_document.uri,
10708                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
10709            );
10710            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
10711                lsp::CodeAction {
10712                    title: "Organize Imports".to_string(),
10713                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
10714                    edit: Some(lsp::WorkspaceEdit {
10715                        changes: Some(
10716                            [(
10717                                params.text_document.uri.clone(),
10718                                vec![lsp::TextEdit::new(
10719                                    lsp::Range::new(
10720                                        lsp::Position::new(1, 0),
10721                                        lsp::Position::new(2, 0),
10722                                    ),
10723                                    "".to_string(),
10724                                )],
10725                            )]
10726                            .into_iter()
10727                            .collect(),
10728                        ),
10729                        ..Default::default()
10730                    }),
10731                    ..Default::default()
10732                },
10733            )]))
10734        })
10735        .next()
10736        .await;
10737    cx.executor().start_waiting();
10738    format.await;
10739    assert_eq!(
10740        editor.update(cx, |editor, cx| editor.text(cx)),
10741        "import { a } from 'module';\n\nconst x = a;\n"
10742    );
10743
10744    editor.update_in(cx, |editor, window, cx| {
10745        editor.set_text(
10746            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
10747            window,
10748            cx,
10749        )
10750    });
10751    // Ensure we don't lock if code action hangs.
10752    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
10753        move |params, _| async move {
10754            assert_eq!(
10755                params.text_document.uri,
10756                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
10757            );
10758            futures::future::pending::<()>().await;
10759            unreachable!()
10760        },
10761    );
10762    let format = editor
10763        .update_in(cx, |editor, window, cx| {
10764            editor.perform_code_action_kind(
10765                project,
10766                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
10767                window,
10768                cx,
10769            )
10770        })
10771        .unwrap();
10772    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
10773    cx.executor().start_waiting();
10774    format.await;
10775    assert_eq!(
10776        editor.update(cx, |editor, cx| editor.text(cx)),
10777        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
10778    );
10779}
10780
10781#[gpui::test]
10782async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
10783    init_test(cx, |_| {});
10784
10785    let mut cx = EditorLspTestContext::new_rust(
10786        lsp::ServerCapabilities {
10787            document_formatting_provider: Some(lsp::OneOf::Left(true)),
10788            ..Default::default()
10789        },
10790        cx,
10791    )
10792    .await;
10793
10794    cx.set_state(indoc! {"
10795        one.twoˇ
10796    "});
10797
10798    // The format request takes a long time. When it completes, it inserts
10799    // a newline and an indent before the `.`
10800    cx.lsp
10801        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
10802            let executor = cx.background_executor().clone();
10803            async move {
10804                executor.timer(Duration::from_millis(100)).await;
10805                Ok(Some(vec![lsp::TextEdit {
10806                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
10807                    new_text: "\n    ".into(),
10808                }]))
10809            }
10810        });
10811
10812    // Submit a format request.
10813    let format_1 = cx
10814        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10815        .unwrap();
10816    cx.executor().run_until_parked();
10817
10818    // Submit a second format request.
10819    let format_2 = cx
10820        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10821        .unwrap();
10822    cx.executor().run_until_parked();
10823
10824    // Wait for both format requests to complete
10825    cx.executor().advance_clock(Duration::from_millis(200));
10826    cx.executor().start_waiting();
10827    format_1.await.unwrap();
10828    cx.executor().start_waiting();
10829    format_2.await.unwrap();
10830
10831    // The formatting edits only happens once.
10832    cx.assert_editor_state(indoc! {"
10833        one
10834            .twoˇ
10835    "});
10836}
10837
10838#[gpui::test]
10839async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
10840    init_test(cx, |settings| {
10841        settings.defaults.formatter = Some(SelectedFormatter::Auto)
10842    });
10843
10844    let mut cx = EditorLspTestContext::new_rust(
10845        lsp::ServerCapabilities {
10846            document_formatting_provider: Some(lsp::OneOf::Left(true)),
10847            ..Default::default()
10848        },
10849        cx,
10850    )
10851    .await;
10852
10853    // Set up a buffer white some trailing whitespace and no trailing newline.
10854    cx.set_state(
10855        &[
10856            "one ",   //
10857            "twoˇ",   //
10858            "three ", //
10859            "four",   //
10860        ]
10861        .join("\n"),
10862    );
10863
10864    // Submit a format request.
10865    let format = cx
10866        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10867        .unwrap();
10868
10869    // Record which buffer changes have been sent to the language server
10870    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
10871    cx.lsp
10872        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
10873            let buffer_changes = buffer_changes.clone();
10874            move |params, _| {
10875                buffer_changes.lock().extend(
10876                    params
10877                        .content_changes
10878                        .into_iter()
10879                        .map(|e| (e.range.unwrap(), e.text)),
10880                );
10881            }
10882        });
10883
10884    // Handle formatting requests to the language server.
10885    cx.lsp
10886        .set_request_handler::<lsp::request::Formatting, _, _>({
10887            let buffer_changes = buffer_changes.clone();
10888            move |_, _| {
10889                // When formatting is requested, trailing whitespace has already been stripped,
10890                // and the trailing newline has already been added.
10891                assert_eq!(
10892                    &buffer_changes.lock()[1..],
10893                    &[
10894                        (
10895                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
10896                            "".into()
10897                        ),
10898                        (
10899                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
10900                            "".into()
10901                        ),
10902                        (
10903                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
10904                            "\n".into()
10905                        ),
10906                    ]
10907                );
10908
10909                // Insert blank lines between each line of the buffer.
10910                async move {
10911                    Ok(Some(vec![
10912                        lsp::TextEdit {
10913                            range: lsp::Range::new(
10914                                lsp::Position::new(1, 0),
10915                                lsp::Position::new(1, 0),
10916                            ),
10917                            new_text: "\n".into(),
10918                        },
10919                        lsp::TextEdit {
10920                            range: lsp::Range::new(
10921                                lsp::Position::new(2, 0),
10922                                lsp::Position::new(2, 0),
10923                            ),
10924                            new_text: "\n".into(),
10925                        },
10926                    ]))
10927                }
10928            }
10929        });
10930
10931    // After formatting the buffer, the trailing whitespace is stripped,
10932    // a newline is appended, and the edits provided by the language server
10933    // have been applied.
10934    format.await.unwrap();
10935    cx.assert_editor_state(
10936        &[
10937            "one",   //
10938            "",      //
10939            "twoˇ",  //
10940            "",      //
10941            "three", //
10942            "four",  //
10943            "",      //
10944        ]
10945        .join("\n"),
10946    );
10947
10948    // Undoing the formatting undoes the trailing whitespace removal, the
10949    // trailing newline, and the LSP edits.
10950    cx.update_buffer(|buffer, cx| buffer.undo(cx));
10951    cx.assert_editor_state(
10952        &[
10953            "one ",   //
10954            "twoˇ",   //
10955            "three ", //
10956            "four",   //
10957        ]
10958        .join("\n"),
10959    );
10960}
10961
10962#[gpui::test]
10963async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
10964    cx: &mut TestAppContext,
10965) {
10966    init_test(cx, |_| {});
10967
10968    cx.update(|cx| {
10969        cx.update_global::<SettingsStore, _>(|settings, cx| {
10970            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10971                settings.auto_signature_help = Some(true);
10972            });
10973        });
10974    });
10975
10976    let mut cx = EditorLspTestContext::new_rust(
10977        lsp::ServerCapabilities {
10978            signature_help_provider: Some(lsp::SignatureHelpOptions {
10979                ..Default::default()
10980            }),
10981            ..Default::default()
10982        },
10983        cx,
10984    )
10985    .await;
10986
10987    let language = Language::new(
10988        LanguageConfig {
10989            name: "Rust".into(),
10990            brackets: BracketPairConfig {
10991                pairs: vec![
10992                    BracketPair {
10993                        start: "{".to_string(),
10994                        end: "}".to_string(),
10995                        close: true,
10996                        surround: true,
10997                        newline: true,
10998                    },
10999                    BracketPair {
11000                        start: "(".to_string(),
11001                        end: ")".to_string(),
11002                        close: true,
11003                        surround: true,
11004                        newline: true,
11005                    },
11006                    BracketPair {
11007                        start: "/*".to_string(),
11008                        end: " */".to_string(),
11009                        close: true,
11010                        surround: true,
11011                        newline: true,
11012                    },
11013                    BracketPair {
11014                        start: "[".to_string(),
11015                        end: "]".to_string(),
11016                        close: false,
11017                        surround: false,
11018                        newline: true,
11019                    },
11020                    BracketPair {
11021                        start: "\"".to_string(),
11022                        end: "\"".to_string(),
11023                        close: true,
11024                        surround: true,
11025                        newline: false,
11026                    },
11027                    BracketPair {
11028                        start: "<".to_string(),
11029                        end: ">".to_string(),
11030                        close: false,
11031                        surround: true,
11032                        newline: true,
11033                    },
11034                ],
11035                ..Default::default()
11036            },
11037            autoclose_before: "})]".to_string(),
11038            ..Default::default()
11039        },
11040        Some(tree_sitter_rust::LANGUAGE.into()),
11041    );
11042    let language = Arc::new(language);
11043
11044    cx.language_registry().add(language.clone());
11045    cx.update_buffer(|buffer, cx| {
11046        buffer.set_language(Some(language), cx);
11047    });
11048
11049    cx.set_state(
11050        &r#"
11051            fn main() {
11052                sampleˇ
11053            }
11054        "#
11055        .unindent(),
11056    );
11057
11058    cx.update_editor(|editor, window, cx| {
11059        editor.handle_input("(", window, cx);
11060    });
11061    cx.assert_editor_state(
11062        &"
11063            fn main() {
11064                sample(ˇ)
11065            }
11066        "
11067        .unindent(),
11068    );
11069
11070    let mocked_response = lsp::SignatureHelp {
11071        signatures: vec![lsp::SignatureInformation {
11072            label: "fn sample(param1: u8, param2: u8)".to_string(),
11073            documentation: None,
11074            parameters: Some(vec![
11075                lsp::ParameterInformation {
11076                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11077                    documentation: None,
11078                },
11079                lsp::ParameterInformation {
11080                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11081                    documentation: None,
11082                },
11083            ]),
11084            active_parameter: None,
11085        }],
11086        active_signature: Some(0),
11087        active_parameter: Some(0),
11088    };
11089    handle_signature_help_request(&mut cx, mocked_response).await;
11090
11091    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11092        .await;
11093
11094    cx.editor(|editor, _, _| {
11095        let signature_help_state = editor.signature_help_state.popover().cloned();
11096        let signature = signature_help_state.unwrap();
11097        assert_eq!(
11098            signature.signatures[signature.current_signature].label,
11099            "fn sample(param1: u8, param2: u8)"
11100        );
11101    });
11102}
11103
11104#[gpui::test]
11105async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
11106    init_test(cx, |_| {});
11107
11108    cx.update(|cx| {
11109        cx.update_global::<SettingsStore, _>(|settings, cx| {
11110            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11111                settings.auto_signature_help = Some(false);
11112                settings.show_signature_help_after_edits = Some(false);
11113            });
11114        });
11115    });
11116
11117    let mut cx = EditorLspTestContext::new_rust(
11118        lsp::ServerCapabilities {
11119            signature_help_provider: Some(lsp::SignatureHelpOptions {
11120                ..Default::default()
11121            }),
11122            ..Default::default()
11123        },
11124        cx,
11125    )
11126    .await;
11127
11128    let language = Language::new(
11129        LanguageConfig {
11130            name: "Rust".into(),
11131            brackets: BracketPairConfig {
11132                pairs: vec![
11133                    BracketPair {
11134                        start: "{".to_string(),
11135                        end: "}".to_string(),
11136                        close: true,
11137                        surround: true,
11138                        newline: true,
11139                    },
11140                    BracketPair {
11141                        start: "(".to_string(),
11142                        end: ")".to_string(),
11143                        close: true,
11144                        surround: true,
11145                        newline: true,
11146                    },
11147                    BracketPair {
11148                        start: "/*".to_string(),
11149                        end: " */".to_string(),
11150                        close: true,
11151                        surround: true,
11152                        newline: true,
11153                    },
11154                    BracketPair {
11155                        start: "[".to_string(),
11156                        end: "]".to_string(),
11157                        close: false,
11158                        surround: false,
11159                        newline: true,
11160                    },
11161                    BracketPair {
11162                        start: "\"".to_string(),
11163                        end: "\"".to_string(),
11164                        close: true,
11165                        surround: true,
11166                        newline: false,
11167                    },
11168                    BracketPair {
11169                        start: "<".to_string(),
11170                        end: ">".to_string(),
11171                        close: false,
11172                        surround: true,
11173                        newline: true,
11174                    },
11175                ],
11176                ..Default::default()
11177            },
11178            autoclose_before: "})]".to_string(),
11179            ..Default::default()
11180        },
11181        Some(tree_sitter_rust::LANGUAGE.into()),
11182    );
11183    let language = Arc::new(language);
11184
11185    cx.language_registry().add(language.clone());
11186    cx.update_buffer(|buffer, cx| {
11187        buffer.set_language(Some(language), cx);
11188    });
11189
11190    // Ensure that signature_help is not called when no signature help is enabled.
11191    cx.set_state(
11192        &r#"
11193            fn main() {
11194                sampleˇ
11195            }
11196        "#
11197        .unindent(),
11198    );
11199    cx.update_editor(|editor, window, cx| {
11200        editor.handle_input("(", window, cx);
11201    });
11202    cx.assert_editor_state(
11203        &"
11204            fn main() {
11205                sample(ˇ)
11206            }
11207        "
11208        .unindent(),
11209    );
11210    cx.editor(|editor, _, _| {
11211        assert!(editor.signature_help_state.task().is_none());
11212    });
11213
11214    let mocked_response = lsp::SignatureHelp {
11215        signatures: vec![lsp::SignatureInformation {
11216            label: "fn sample(param1: u8, param2: u8)".to_string(),
11217            documentation: None,
11218            parameters: Some(vec![
11219                lsp::ParameterInformation {
11220                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11221                    documentation: None,
11222                },
11223                lsp::ParameterInformation {
11224                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11225                    documentation: None,
11226                },
11227            ]),
11228            active_parameter: None,
11229        }],
11230        active_signature: Some(0),
11231        active_parameter: Some(0),
11232    };
11233
11234    // Ensure that signature_help is called when enabled afte edits
11235    cx.update(|_, cx| {
11236        cx.update_global::<SettingsStore, _>(|settings, cx| {
11237            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11238                settings.auto_signature_help = Some(false);
11239                settings.show_signature_help_after_edits = Some(true);
11240            });
11241        });
11242    });
11243    cx.set_state(
11244        &r#"
11245            fn main() {
11246                sampleˇ
11247            }
11248        "#
11249        .unindent(),
11250    );
11251    cx.update_editor(|editor, window, cx| {
11252        editor.handle_input("(", window, cx);
11253    });
11254    cx.assert_editor_state(
11255        &"
11256            fn main() {
11257                sample(ˇ)
11258            }
11259        "
11260        .unindent(),
11261    );
11262    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11263    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11264        .await;
11265    cx.update_editor(|editor, _, _| {
11266        let signature_help_state = editor.signature_help_state.popover().cloned();
11267        assert!(signature_help_state.is_some());
11268        let signature = signature_help_state.unwrap();
11269        assert_eq!(
11270            signature.signatures[signature.current_signature].label,
11271            "fn sample(param1: u8, param2: u8)"
11272        );
11273        editor.signature_help_state = SignatureHelpState::default();
11274    });
11275
11276    // Ensure that signature_help is called when auto signature help override is enabled
11277    cx.update(|_, cx| {
11278        cx.update_global::<SettingsStore, _>(|settings, cx| {
11279            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11280                settings.auto_signature_help = Some(true);
11281                settings.show_signature_help_after_edits = Some(false);
11282            });
11283        });
11284    });
11285    cx.set_state(
11286        &r#"
11287            fn main() {
11288                sampleˇ
11289            }
11290        "#
11291        .unindent(),
11292    );
11293    cx.update_editor(|editor, window, cx| {
11294        editor.handle_input("(", window, cx);
11295    });
11296    cx.assert_editor_state(
11297        &"
11298            fn main() {
11299                sample(ˇ)
11300            }
11301        "
11302        .unindent(),
11303    );
11304    handle_signature_help_request(&mut cx, mocked_response).await;
11305    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11306        .await;
11307    cx.editor(|editor, _, _| {
11308        let signature_help_state = editor.signature_help_state.popover().cloned();
11309        assert!(signature_help_state.is_some());
11310        let signature = signature_help_state.unwrap();
11311        assert_eq!(
11312            signature.signatures[signature.current_signature].label,
11313            "fn sample(param1: u8, param2: u8)"
11314        );
11315    });
11316}
11317
11318#[gpui::test]
11319async fn test_signature_help(cx: &mut TestAppContext) {
11320    init_test(cx, |_| {});
11321    cx.update(|cx| {
11322        cx.update_global::<SettingsStore, _>(|settings, cx| {
11323            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11324                settings.auto_signature_help = Some(true);
11325            });
11326        });
11327    });
11328
11329    let mut cx = EditorLspTestContext::new_rust(
11330        lsp::ServerCapabilities {
11331            signature_help_provider: Some(lsp::SignatureHelpOptions {
11332                ..Default::default()
11333            }),
11334            ..Default::default()
11335        },
11336        cx,
11337    )
11338    .await;
11339
11340    // A test that directly calls `show_signature_help`
11341    cx.update_editor(|editor, window, cx| {
11342        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11343    });
11344
11345    let mocked_response = lsp::SignatureHelp {
11346        signatures: vec![lsp::SignatureInformation {
11347            label: "fn sample(param1: u8, param2: u8)".to_string(),
11348            documentation: None,
11349            parameters: Some(vec![
11350                lsp::ParameterInformation {
11351                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11352                    documentation: None,
11353                },
11354                lsp::ParameterInformation {
11355                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11356                    documentation: None,
11357                },
11358            ]),
11359            active_parameter: None,
11360        }],
11361        active_signature: Some(0),
11362        active_parameter: Some(0),
11363    };
11364    handle_signature_help_request(&mut cx, mocked_response).await;
11365
11366    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11367        .await;
11368
11369    cx.editor(|editor, _, _| {
11370        let signature_help_state = editor.signature_help_state.popover().cloned();
11371        assert!(signature_help_state.is_some());
11372        let signature = signature_help_state.unwrap();
11373        assert_eq!(
11374            signature.signatures[signature.current_signature].label,
11375            "fn sample(param1: u8, param2: u8)"
11376        );
11377    });
11378
11379    // When exiting outside from inside the brackets, `signature_help` is closed.
11380    cx.set_state(indoc! {"
11381        fn main() {
11382            sample(ˇ);
11383        }
11384
11385        fn sample(param1: u8, param2: u8) {}
11386    "});
11387
11388    cx.update_editor(|editor, window, cx| {
11389        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11390            s.select_ranges([0..0])
11391        });
11392    });
11393
11394    let mocked_response = lsp::SignatureHelp {
11395        signatures: Vec::new(),
11396        active_signature: None,
11397        active_parameter: None,
11398    };
11399    handle_signature_help_request(&mut cx, mocked_response).await;
11400
11401    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11402        .await;
11403
11404    cx.editor(|editor, _, _| {
11405        assert!(!editor.signature_help_state.is_shown());
11406    });
11407
11408    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
11409    cx.set_state(indoc! {"
11410        fn main() {
11411            sample(ˇ);
11412        }
11413
11414        fn sample(param1: u8, param2: u8) {}
11415    "});
11416
11417    let mocked_response = lsp::SignatureHelp {
11418        signatures: vec![lsp::SignatureInformation {
11419            label: "fn sample(param1: u8, param2: u8)".to_string(),
11420            documentation: None,
11421            parameters: Some(vec![
11422                lsp::ParameterInformation {
11423                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11424                    documentation: None,
11425                },
11426                lsp::ParameterInformation {
11427                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11428                    documentation: None,
11429                },
11430            ]),
11431            active_parameter: None,
11432        }],
11433        active_signature: Some(0),
11434        active_parameter: Some(0),
11435    };
11436    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11437    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11438        .await;
11439    cx.editor(|editor, _, _| {
11440        assert!(editor.signature_help_state.is_shown());
11441    });
11442
11443    // Restore the popover with more parameter input
11444    cx.set_state(indoc! {"
11445        fn main() {
11446            sample(param1, param2ˇ);
11447        }
11448
11449        fn sample(param1: u8, param2: u8) {}
11450    "});
11451
11452    let mocked_response = lsp::SignatureHelp {
11453        signatures: vec![lsp::SignatureInformation {
11454            label: "fn sample(param1: u8, param2: u8)".to_string(),
11455            documentation: None,
11456            parameters: Some(vec![
11457                lsp::ParameterInformation {
11458                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11459                    documentation: None,
11460                },
11461                lsp::ParameterInformation {
11462                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11463                    documentation: None,
11464                },
11465            ]),
11466            active_parameter: None,
11467        }],
11468        active_signature: Some(0),
11469        active_parameter: Some(1),
11470    };
11471    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11472    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11473        .await;
11474
11475    // When selecting a range, the popover is gone.
11476    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
11477    cx.update_editor(|editor, window, cx| {
11478        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11479            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11480        })
11481    });
11482    cx.assert_editor_state(indoc! {"
11483        fn main() {
11484            sample(param1, «ˇparam2»);
11485        }
11486
11487        fn sample(param1: u8, param2: u8) {}
11488    "});
11489    cx.editor(|editor, _, _| {
11490        assert!(!editor.signature_help_state.is_shown());
11491    });
11492
11493    // When unselecting again, the popover is back if within the brackets.
11494    cx.update_editor(|editor, window, cx| {
11495        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11496            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11497        })
11498    });
11499    cx.assert_editor_state(indoc! {"
11500        fn main() {
11501            sample(param1, ˇparam2);
11502        }
11503
11504        fn sample(param1: u8, param2: u8) {}
11505    "});
11506    handle_signature_help_request(&mut cx, mocked_response).await;
11507    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11508        .await;
11509    cx.editor(|editor, _, _| {
11510        assert!(editor.signature_help_state.is_shown());
11511    });
11512
11513    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
11514    cx.update_editor(|editor, window, cx| {
11515        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11516            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
11517            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11518        })
11519    });
11520    cx.assert_editor_state(indoc! {"
11521        fn main() {
11522            sample(param1, ˇparam2);
11523        }
11524
11525        fn sample(param1: u8, param2: u8) {}
11526    "});
11527
11528    let mocked_response = lsp::SignatureHelp {
11529        signatures: vec![lsp::SignatureInformation {
11530            label: "fn sample(param1: u8, param2: u8)".to_string(),
11531            documentation: None,
11532            parameters: Some(vec![
11533                lsp::ParameterInformation {
11534                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11535                    documentation: None,
11536                },
11537                lsp::ParameterInformation {
11538                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11539                    documentation: None,
11540                },
11541            ]),
11542            active_parameter: None,
11543        }],
11544        active_signature: Some(0),
11545        active_parameter: Some(1),
11546    };
11547    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11548    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11549        .await;
11550    cx.update_editor(|editor, _, cx| {
11551        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
11552    });
11553    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11554        .await;
11555    cx.update_editor(|editor, window, cx| {
11556        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11557            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11558        })
11559    });
11560    cx.assert_editor_state(indoc! {"
11561        fn main() {
11562            sample(param1, «ˇparam2»);
11563        }
11564
11565        fn sample(param1: u8, param2: u8) {}
11566    "});
11567    cx.update_editor(|editor, window, cx| {
11568        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11569            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11570        })
11571    });
11572    cx.assert_editor_state(indoc! {"
11573        fn main() {
11574            sample(param1, ˇparam2);
11575        }
11576
11577        fn sample(param1: u8, param2: u8) {}
11578    "});
11579    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
11580        .await;
11581}
11582
11583#[gpui::test]
11584async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
11585    init_test(cx, |_| {});
11586
11587    let mut cx = EditorLspTestContext::new_rust(
11588        lsp::ServerCapabilities {
11589            signature_help_provider: Some(lsp::SignatureHelpOptions {
11590                ..Default::default()
11591            }),
11592            ..Default::default()
11593        },
11594        cx,
11595    )
11596    .await;
11597
11598    cx.set_state(indoc! {"
11599        fn main() {
11600            overloadedˇ
11601        }
11602    "});
11603
11604    cx.update_editor(|editor, window, cx| {
11605        editor.handle_input("(", window, cx);
11606        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11607    });
11608
11609    // Mock response with 3 signatures
11610    let mocked_response = lsp::SignatureHelp {
11611        signatures: vec![
11612            lsp::SignatureInformation {
11613                label: "fn overloaded(x: i32)".to_string(),
11614                documentation: None,
11615                parameters: Some(vec![lsp::ParameterInformation {
11616                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11617                    documentation: None,
11618                }]),
11619                active_parameter: None,
11620            },
11621            lsp::SignatureInformation {
11622                label: "fn overloaded(x: i32, y: i32)".to_string(),
11623                documentation: None,
11624                parameters: Some(vec![
11625                    lsp::ParameterInformation {
11626                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11627                        documentation: None,
11628                    },
11629                    lsp::ParameterInformation {
11630                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
11631                        documentation: None,
11632                    },
11633                ]),
11634                active_parameter: None,
11635            },
11636            lsp::SignatureInformation {
11637                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
11638                documentation: None,
11639                parameters: Some(vec![
11640                    lsp::ParameterInformation {
11641                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11642                        documentation: None,
11643                    },
11644                    lsp::ParameterInformation {
11645                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
11646                        documentation: None,
11647                    },
11648                    lsp::ParameterInformation {
11649                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
11650                        documentation: None,
11651                    },
11652                ]),
11653                active_parameter: None,
11654            },
11655        ],
11656        active_signature: Some(1),
11657        active_parameter: Some(0),
11658    };
11659    handle_signature_help_request(&mut cx, mocked_response).await;
11660
11661    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11662        .await;
11663
11664    // Verify we have multiple signatures and the right one is selected
11665    cx.editor(|editor, _, _| {
11666        let popover = editor.signature_help_state.popover().cloned().unwrap();
11667        assert_eq!(popover.signatures.len(), 3);
11668        // active_signature was 1, so that should be the current
11669        assert_eq!(popover.current_signature, 1);
11670        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
11671        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
11672        assert_eq!(
11673            popover.signatures[2].label,
11674            "fn overloaded(x: i32, y: i32, z: i32)"
11675        );
11676    });
11677
11678    // Test navigation functionality
11679    cx.update_editor(|editor, window, cx| {
11680        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
11681    });
11682
11683    cx.editor(|editor, _, _| {
11684        let popover = editor.signature_help_state.popover().cloned().unwrap();
11685        assert_eq!(popover.current_signature, 2);
11686    });
11687
11688    // Test wrap around
11689    cx.update_editor(|editor, window, cx| {
11690        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
11691    });
11692
11693    cx.editor(|editor, _, _| {
11694        let popover = editor.signature_help_state.popover().cloned().unwrap();
11695        assert_eq!(popover.current_signature, 0);
11696    });
11697
11698    // Test previous navigation
11699    cx.update_editor(|editor, window, cx| {
11700        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
11701    });
11702
11703    cx.editor(|editor, _, _| {
11704        let popover = editor.signature_help_state.popover().cloned().unwrap();
11705        assert_eq!(popover.current_signature, 2);
11706    });
11707}
11708
11709#[gpui::test]
11710async fn test_completion_mode(cx: &mut TestAppContext) {
11711    init_test(cx, |_| {});
11712    let mut cx = EditorLspTestContext::new_rust(
11713        lsp::ServerCapabilities {
11714            completion_provider: Some(lsp::CompletionOptions {
11715                resolve_provider: Some(true),
11716                ..Default::default()
11717            }),
11718            ..Default::default()
11719        },
11720        cx,
11721    )
11722    .await;
11723
11724    struct Run {
11725        run_description: &'static str,
11726        initial_state: String,
11727        buffer_marked_text: String,
11728        completion_label: &'static str,
11729        completion_text: &'static str,
11730        expected_with_insert_mode: String,
11731        expected_with_replace_mode: String,
11732        expected_with_replace_subsequence_mode: String,
11733        expected_with_replace_suffix_mode: String,
11734    }
11735
11736    let runs = [
11737        Run {
11738            run_description: "Start of word matches completion text",
11739            initial_state: "before ediˇ after".into(),
11740            buffer_marked_text: "before <edi|> after".into(),
11741            completion_label: "editor",
11742            completion_text: "editor",
11743            expected_with_insert_mode: "before editorˇ after".into(),
11744            expected_with_replace_mode: "before editorˇ after".into(),
11745            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11746            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11747        },
11748        Run {
11749            run_description: "Accept same text at the middle of the word",
11750            initial_state: "before ediˇtor after".into(),
11751            buffer_marked_text: "before <edi|tor> after".into(),
11752            completion_label: "editor",
11753            completion_text: "editor",
11754            expected_with_insert_mode: "before editorˇtor after".into(),
11755            expected_with_replace_mode: "before editorˇ after".into(),
11756            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11757            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11758        },
11759        Run {
11760            run_description: "End of word matches completion text -- cursor at end",
11761            initial_state: "before torˇ after".into(),
11762            buffer_marked_text: "before <tor|> after".into(),
11763            completion_label: "editor",
11764            completion_text: "editor",
11765            expected_with_insert_mode: "before editorˇ after".into(),
11766            expected_with_replace_mode: "before editorˇ after".into(),
11767            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11768            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11769        },
11770        Run {
11771            run_description: "End of word matches completion text -- cursor at start",
11772            initial_state: "before ˇtor after".into(),
11773            buffer_marked_text: "before <|tor> after".into(),
11774            completion_label: "editor",
11775            completion_text: "editor",
11776            expected_with_insert_mode: "before editorˇtor after".into(),
11777            expected_with_replace_mode: "before editorˇ after".into(),
11778            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11779            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11780        },
11781        Run {
11782            run_description: "Prepend text containing whitespace",
11783            initial_state: "pˇfield: bool".into(),
11784            buffer_marked_text: "<p|field>: bool".into(),
11785            completion_label: "pub ",
11786            completion_text: "pub ",
11787            expected_with_insert_mode: "pub ˇfield: bool".into(),
11788            expected_with_replace_mode: "pub ˇ: bool".into(),
11789            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
11790            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
11791        },
11792        Run {
11793            run_description: "Add element to start of list",
11794            initial_state: "[element_ˇelement_2]".into(),
11795            buffer_marked_text: "[<element_|element_2>]".into(),
11796            completion_label: "element_1",
11797            completion_text: "element_1",
11798            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
11799            expected_with_replace_mode: "[element_1ˇ]".into(),
11800            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
11801            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
11802        },
11803        Run {
11804            run_description: "Add element to start of list -- first and second elements are equal",
11805            initial_state: "[elˇelement]".into(),
11806            buffer_marked_text: "[<el|element>]".into(),
11807            completion_label: "element",
11808            completion_text: "element",
11809            expected_with_insert_mode: "[elementˇelement]".into(),
11810            expected_with_replace_mode: "[elementˇ]".into(),
11811            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
11812            expected_with_replace_suffix_mode: "[elementˇ]".into(),
11813        },
11814        Run {
11815            run_description: "Ends with matching suffix",
11816            initial_state: "SubˇError".into(),
11817            buffer_marked_text: "<Sub|Error>".into(),
11818            completion_label: "SubscriptionError",
11819            completion_text: "SubscriptionError",
11820            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
11821            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11822            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11823            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
11824        },
11825        Run {
11826            run_description: "Suffix is a subsequence -- contiguous",
11827            initial_state: "SubˇErr".into(),
11828            buffer_marked_text: "<Sub|Err>".into(),
11829            completion_label: "SubscriptionError",
11830            completion_text: "SubscriptionError",
11831            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
11832            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11833            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11834            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
11835        },
11836        Run {
11837            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
11838            initial_state: "Suˇscrirr".into(),
11839            buffer_marked_text: "<Su|scrirr>".into(),
11840            completion_label: "SubscriptionError",
11841            completion_text: "SubscriptionError",
11842            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
11843            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11844            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11845            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
11846        },
11847        Run {
11848            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
11849            initial_state: "foo(indˇix)".into(),
11850            buffer_marked_text: "foo(<ind|ix>)".into(),
11851            completion_label: "node_index",
11852            completion_text: "node_index",
11853            expected_with_insert_mode: "foo(node_indexˇix)".into(),
11854            expected_with_replace_mode: "foo(node_indexˇ)".into(),
11855            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
11856            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
11857        },
11858        Run {
11859            run_description: "Replace range ends before cursor - should extend to cursor",
11860            initial_state: "before editˇo after".into(),
11861            buffer_marked_text: "before <{ed}>it|o after".into(),
11862            completion_label: "editor",
11863            completion_text: "editor",
11864            expected_with_insert_mode: "before editorˇo after".into(),
11865            expected_with_replace_mode: "before editorˇo after".into(),
11866            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
11867            expected_with_replace_suffix_mode: "before editorˇo after".into(),
11868        },
11869        Run {
11870            run_description: "Uses label for suffix matching",
11871            initial_state: "before ediˇtor after".into(),
11872            buffer_marked_text: "before <edi|tor> after".into(),
11873            completion_label: "editor",
11874            completion_text: "editor()",
11875            expected_with_insert_mode: "before editor()ˇtor after".into(),
11876            expected_with_replace_mode: "before editor()ˇ after".into(),
11877            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
11878            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
11879        },
11880        Run {
11881            run_description: "Case insensitive subsequence and suffix matching",
11882            initial_state: "before EDiˇtoR after".into(),
11883            buffer_marked_text: "before <EDi|toR> after".into(),
11884            completion_label: "editor",
11885            completion_text: "editor",
11886            expected_with_insert_mode: "before editorˇtoR after".into(),
11887            expected_with_replace_mode: "before editorˇ after".into(),
11888            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11889            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11890        },
11891    ];
11892
11893    for run in runs {
11894        let run_variations = [
11895            (LspInsertMode::Insert, run.expected_with_insert_mode),
11896            (LspInsertMode::Replace, run.expected_with_replace_mode),
11897            (
11898                LspInsertMode::ReplaceSubsequence,
11899                run.expected_with_replace_subsequence_mode,
11900            ),
11901            (
11902                LspInsertMode::ReplaceSuffix,
11903                run.expected_with_replace_suffix_mode,
11904            ),
11905        ];
11906
11907        for (lsp_insert_mode, expected_text) in run_variations {
11908            eprintln!(
11909                "run = {:?}, mode = {lsp_insert_mode:.?}",
11910                run.run_description,
11911            );
11912
11913            update_test_language_settings(&mut cx, |settings| {
11914                settings.defaults.completions = Some(CompletionSettings {
11915                    lsp_insert_mode,
11916                    words: WordsCompletionMode::Disabled,
11917                    lsp: true,
11918                    lsp_fetch_timeout_ms: 0,
11919                });
11920            });
11921
11922            cx.set_state(&run.initial_state);
11923            cx.update_editor(|editor, window, cx| {
11924                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11925            });
11926
11927            let counter = Arc::new(AtomicUsize::new(0));
11928            handle_completion_request_with_insert_and_replace(
11929                &mut cx,
11930                &run.buffer_marked_text,
11931                vec![(run.completion_label, run.completion_text)],
11932                counter.clone(),
11933            )
11934            .await;
11935            cx.condition(|editor, _| editor.context_menu_visible())
11936                .await;
11937            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11938
11939            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11940                editor
11941                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
11942                    .unwrap()
11943            });
11944            cx.assert_editor_state(&expected_text);
11945            handle_resolve_completion_request(&mut cx, None).await;
11946            apply_additional_edits.await.unwrap();
11947        }
11948    }
11949}
11950
11951#[gpui::test]
11952async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
11953    init_test(cx, |_| {});
11954    let mut cx = EditorLspTestContext::new_rust(
11955        lsp::ServerCapabilities {
11956            completion_provider: Some(lsp::CompletionOptions {
11957                resolve_provider: Some(true),
11958                ..Default::default()
11959            }),
11960            ..Default::default()
11961        },
11962        cx,
11963    )
11964    .await;
11965
11966    let initial_state = "SubˇError";
11967    let buffer_marked_text = "<Sub|Error>";
11968    let completion_text = "SubscriptionError";
11969    let expected_with_insert_mode = "SubscriptionErrorˇError";
11970    let expected_with_replace_mode = "SubscriptionErrorˇ";
11971
11972    update_test_language_settings(&mut cx, |settings| {
11973        settings.defaults.completions = Some(CompletionSettings {
11974            words: WordsCompletionMode::Disabled,
11975            // set the opposite here to ensure that the action is overriding the default behavior
11976            lsp_insert_mode: LspInsertMode::Insert,
11977            lsp: true,
11978            lsp_fetch_timeout_ms: 0,
11979        });
11980    });
11981
11982    cx.set_state(initial_state);
11983    cx.update_editor(|editor, window, cx| {
11984        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11985    });
11986
11987    let counter = Arc::new(AtomicUsize::new(0));
11988    handle_completion_request_with_insert_and_replace(
11989        &mut cx,
11990        &buffer_marked_text,
11991        vec![(completion_text, completion_text)],
11992        counter.clone(),
11993    )
11994    .await;
11995    cx.condition(|editor, _| editor.context_menu_visible())
11996        .await;
11997    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11998
11999    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12000        editor
12001            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12002            .unwrap()
12003    });
12004    cx.assert_editor_state(&expected_with_replace_mode);
12005    handle_resolve_completion_request(&mut cx, None).await;
12006    apply_additional_edits.await.unwrap();
12007
12008    update_test_language_settings(&mut cx, |settings| {
12009        settings.defaults.completions = Some(CompletionSettings {
12010            words: WordsCompletionMode::Disabled,
12011            // set the opposite here to ensure that the action is overriding the default behavior
12012            lsp_insert_mode: LspInsertMode::Replace,
12013            lsp: true,
12014            lsp_fetch_timeout_ms: 0,
12015        });
12016    });
12017
12018    cx.set_state(initial_state);
12019    cx.update_editor(|editor, window, cx| {
12020        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12021    });
12022    handle_completion_request_with_insert_and_replace(
12023        &mut cx,
12024        &buffer_marked_text,
12025        vec![(completion_text, completion_text)],
12026        counter.clone(),
12027    )
12028    .await;
12029    cx.condition(|editor, _| editor.context_menu_visible())
12030        .await;
12031    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12032
12033    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12034        editor
12035            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
12036            .unwrap()
12037    });
12038    cx.assert_editor_state(&expected_with_insert_mode);
12039    handle_resolve_completion_request(&mut cx, None).await;
12040    apply_additional_edits.await.unwrap();
12041}
12042
12043#[gpui::test]
12044async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
12045    init_test(cx, |_| {});
12046    let mut cx = EditorLspTestContext::new_rust(
12047        lsp::ServerCapabilities {
12048            completion_provider: Some(lsp::CompletionOptions {
12049                resolve_provider: Some(true),
12050                ..Default::default()
12051            }),
12052            ..Default::default()
12053        },
12054        cx,
12055    )
12056    .await;
12057
12058    // scenario: surrounding text matches completion text
12059    let completion_text = "to_offset";
12060    let initial_state = indoc! {"
12061        1. buf.to_offˇsuffix
12062        2. buf.to_offˇsuf
12063        3. buf.to_offˇfix
12064        4. buf.to_offˇ
12065        5. into_offˇensive
12066        6. ˇsuffix
12067        7. let ˇ //
12068        8. aaˇzz
12069        9. buf.to_off«zzzzzˇ»suffix
12070        10. buf.«ˇzzzzz»suffix
12071        11. to_off«ˇzzzzz»
12072
12073        buf.to_offˇsuffix  // newest cursor
12074    "};
12075    let completion_marked_buffer = indoc! {"
12076        1. buf.to_offsuffix
12077        2. buf.to_offsuf
12078        3. buf.to_offfix
12079        4. buf.to_off
12080        5. into_offensive
12081        6. suffix
12082        7. let  //
12083        8. aazz
12084        9. buf.to_offzzzzzsuffix
12085        10. buf.zzzzzsuffix
12086        11. to_offzzzzz
12087
12088        buf.<to_off|suffix>  // newest cursor
12089    "};
12090    let expected = indoc! {"
12091        1. buf.to_offsetˇ
12092        2. buf.to_offsetˇsuf
12093        3. buf.to_offsetˇfix
12094        4. buf.to_offsetˇ
12095        5. into_offsetˇensive
12096        6. to_offsetˇsuffix
12097        7. let to_offsetˇ //
12098        8. aato_offsetˇzz
12099        9. buf.to_offsetˇ
12100        10. buf.to_offsetˇsuffix
12101        11. to_offsetˇ
12102
12103        buf.to_offsetˇ  // newest cursor
12104    "};
12105    cx.set_state(initial_state);
12106    cx.update_editor(|editor, window, cx| {
12107        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12108    });
12109    handle_completion_request_with_insert_and_replace(
12110        &mut cx,
12111        completion_marked_buffer,
12112        vec![(completion_text, completion_text)],
12113        Arc::new(AtomicUsize::new(0)),
12114    )
12115    .await;
12116    cx.condition(|editor, _| editor.context_menu_visible())
12117        .await;
12118    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12119        editor
12120            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12121            .unwrap()
12122    });
12123    cx.assert_editor_state(expected);
12124    handle_resolve_completion_request(&mut cx, None).await;
12125    apply_additional_edits.await.unwrap();
12126
12127    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
12128    let completion_text = "foo_and_bar";
12129    let initial_state = indoc! {"
12130        1. ooanbˇ
12131        2. zooanbˇ
12132        3. ooanbˇz
12133        4. zooanbˇz
12134        5. ooanˇ
12135        6. oanbˇ
12136
12137        ooanbˇ
12138    "};
12139    let completion_marked_buffer = indoc! {"
12140        1. ooanb
12141        2. zooanb
12142        3. ooanbz
12143        4. zooanbz
12144        5. ooan
12145        6. oanb
12146
12147        <ooanb|>
12148    "};
12149    let expected = indoc! {"
12150        1. foo_and_barˇ
12151        2. zfoo_and_barˇ
12152        3. foo_and_barˇz
12153        4. zfoo_and_barˇz
12154        5. ooanfoo_and_barˇ
12155        6. oanbfoo_and_barˇ
12156
12157        foo_and_barˇ
12158    "};
12159    cx.set_state(initial_state);
12160    cx.update_editor(|editor, window, cx| {
12161        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12162    });
12163    handle_completion_request_with_insert_and_replace(
12164        &mut cx,
12165        completion_marked_buffer,
12166        vec![(completion_text, completion_text)],
12167        Arc::new(AtomicUsize::new(0)),
12168    )
12169    .await;
12170    cx.condition(|editor, _| editor.context_menu_visible())
12171        .await;
12172    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12173        editor
12174            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12175            .unwrap()
12176    });
12177    cx.assert_editor_state(expected);
12178    handle_resolve_completion_request(&mut cx, None).await;
12179    apply_additional_edits.await.unwrap();
12180
12181    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
12182    // (expects the same as if it was inserted at the end)
12183    let completion_text = "foo_and_bar";
12184    let initial_state = indoc! {"
12185        1. ooˇanb
12186        2. zooˇanb
12187        3. ooˇanbz
12188        4. zooˇanbz
12189
12190        ooˇanb
12191    "};
12192    let completion_marked_buffer = indoc! {"
12193        1. ooanb
12194        2. zooanb
12195        3. ooanbz
12196        4. zooanbz
12197
12198        <oo|anb>
12199    "};
12200    let expected = indoc! {"
12201        1. foo_and_barˇ
12202        2. zfoo_and_barˇ
12203        3. foo_and_barˇz
12204        4. zfoo_and_barˇz
12205
12206        foo_and_barˇ
12207    "};
12208    cx.set_state(initial_state);
12209    cx.update_editor(|editor, window, cx| {
12210        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12211    });
12212    handle_completion_request_with_insert_and_replace(
12213        &mut cx,
12214        completion_marked_buffer,
12215        vec![(completion_text, completion_text)],
12216        Arc::new(AtomicUsize::new(0)),
12217    )
12218    .await;
12219    cx.condition(|editor, _| editor.context_menu_visible())
12220        .await;
12221    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12222        editor
12223            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12224            .unwrap()
12225    });
12226    cx.assert_editor_state(expected);
12227    handle_resolve_completion_request(&mut cx, None).await;
12228    apply_additional_edits.await.unwrap();
12229}
12230
12231// This used to crash
12232#[gpui::test]
12233async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
12234    init_test(cx, |_| {});
12235
12236    let buffer_text = indoc! {"
12237        fn main() {
12238            10.satu;
12239
12240            //
12241            // separate cursors so they open in different excerpts (manually reproducible)
12242            //
12243
12244            10.satu20;
12245        }
12246    "};
12247    let multibuffer_text_with_selections = indoc! {"
12248        fn main() {
12249            10.satuˇ;
12250
12251            //
12252
12253            //
12254
12255            10.satuˇ20;
12256        }
12257    "};
12258    let expected_multibuffer = indoc! {"
12259        fn main() {
12260            10.saturating_sub()ˇ;
12261
12262            //
12263
12264            //
12265
12266            10.saturating_sub()ˇ;
12267        }
12268    "};
12269
12270    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
12271    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
12272
12273    let fs = FakeFs::new(cx.executor());
12274    fs.insert_tree(
12275        path!("/a"),
12276        json!({
12277            "main.rs": buffer_text,
12278        }),
12279    )
12280    .await;
12281
12282    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12283    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12284    language_registry.add(rust_lang());
12285    let mut fake_servers = language_registry.register_fake_lsp(
12286        "Rust",
12287        FakeLspAdapter {
12288            capabilities: lsp::ServerCapabilities {
12289                completion_provider: Some(lsp::CompletionOptions {
12290                    resolve_provider: None,
12291                    ..lsp::CompletionOptions::default()
12292                }),
12293                ..lsp::ServerCapabilities::default()
12294            },
12295            ..FakeLspAdapter::default()
12296        },
12297    );
12298    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12299    let cx = &mut VisualTestContext::from_window(*workspace, cx);
12300    let buffer = project
12301        .update(cx, |project, cx| {
12302            project.open_local_buffer(path!("/a/main.rs"), cx)
12303        })
12304        .await
12305        .unwrap();
12306
12307    let multi_buffer = cx.new(|cx| {
12308        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
12309        multi_buffer.push_excerpts(
12310            buffer.clone(),
12311            [ExcerptRange::new(0..first_excerpt_end)],
12312            cx,
12313        );
12314        multi_buffer.push_excerpts(
12315            buffer.clone(),
12316            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
12317            cx,
12318        );
12319        multi_buffer
12320    });
12321
12322    let editor = workspace
12323        .update(cx, |_, window, cx| {
12324            cx.new(|cx| {
12325                Editor::new(
12326                    EditorMode::Full {
12327                        scale_ui_elements_with_buffer_font_size: false,
12328                        show_active_line_background: false,
12329                        sized_by_content: false,
12330                    },
12331                    multi_buffer.clone(),
12332                    Some(project.clone()),
12333                    window,
12334                    cx,
12335                )
12336            })
12337        })
12338        .unwrap();
12339
12340    let pane = workspace
12341        .update(cx, |workspace, _, _| workspace.active_pane().clone())
12342        .unwrap();
12343    pane.update_in(cx, |pane, window, cx| {
12344        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
12345    });
12346
12347    let fake_server = fake_servers.next().await.unwrap();
12348
12349    editor.update_in(cx, |editor, window, cx| {
12350        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12351            s.select_ranges([
12352                Point::new(1, 11)..Point::new(1, 11),
12353                Point::new(7, 11)..Point::new(7, 11),
12354            ])
12355        });
12356
12357        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
12358    });
12359
12360    editor.update_in(cx, |editor, window, cx| {
12361        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12362    });
12363
12364    fake_server
12365        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
12366            let completion_item = lsp::CompletionItem {
12367                label: "saturating_sub()".into(),
12368                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
12369                    lsp::InsertReplaceEdit {
12370                        new_text: "saturating_sub()".to_owned(),
12371                        insert: lsp::Range::new(
12372                            lsp::Position::new(7, 7),
12373                            lsp::Position::new(7, 11),
12374                        ),
12375                        replace: lsp::Range::new(
12376                            lsp::Position::new(7, 7),
12377                            lsp::Position::new(7, 13),
12378                        ),
12379                    },
12380                )),
12381                ..lsp::CompletionItem::default()
12382            };
12383
12384            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
12385        })
12386        .next()
12387        .await
12388        .unwrap();
12389
12390    cx.condition(&editor, |editor, _| editor.context_menu_visible())
12391        .await;
12392
12393    editor
12394        .update_in(cx, |editor, window, cx| {
12395            editor
12396                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12397                .unwrap()
12398        })
12399        .await
12400        .unwrap();
12401
12402    editor.update(cx, |editor, cx| {
12403        assert_text_with_selections(editor, expected_multibuffer, cx);
12404    })
12405}
12406
12407#[gpui::test]
12408async fn test_completion(cx: &mut TestAppContext) {
12409    init_test(cx, |_| {});
12410
12411    let mut cx = EditorLspTestContext::new_rust(
12412        lsp::ServerCapabilities {
12413            completion_provider: Some(lsp::CompletionOptions {
12414                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12415                resolve_provider: Some(true),
12416                ..Default::default()
12417            }),
12418            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12419            ..Default::default()
12420        },
12421        cx,
12422    )
12423    .await;
12424    let counter = Arc::new(AtomicUsize::new(0));
12425
12426    cx.set_state(indoc! {"
12427        oneˇ
12428        two
12429        three
12430    "});
12431    cx.simulate_keystroke(".");
12432    handle_completion_request(
12433        indoc! {"
12434            one.|<>
12435            two
12436            three
12437        "},
12438        vec!["first_completion", "second_completion"],
12439        true,
12440        counter.clone(),
12441        &mut cx,
12442    )
12443    .await;
12444    cx.condition(|editor, _| editor.context_menu_visible())
12445        .await;
12446    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12447
12448    let _handler = handle_signature_help_request(
12449        &mut cx,
12450        lsp::SignatureHelp {
12451            signatures: vec![lsp::SignatureInformation {
12452                label: "test signature".to_string(),
12453                documentation: None,
12454                parameters: Some(vec![lsp::ParameterInformation {
12455                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
12456                    documentation: None,
12457                }]),
12458                active_parameter: None,
12459            }],
12460            active_signature: None,
12461            active_parameter: None,
12462        },
12463    );
12464    cx.update_editor(|editor, window, cx| {
12465        assert!(
12466            !editor.signature_help_state.is_shown(),
12467            "No signature help was called for"
12468        );
12469        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12470    });
12471    cx.run_until_parked();
12472    cx.update_editor(|editor, _, _| {
12473        assert!(
12474            !editor.signature_help_state.is_shown(),
12475            "No signature help should be shown when completions menu is open"
12476        );
12477    });
12478
12479    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12480        editor.context_menu_next(&Default::default(), window, cx);
12481        editor
12482            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12483            .unwrap()
12484    });
12485    cx.assert_editor_state(indoc! {"
12486        one.second_completionˇ
12487        two
12488        three
12489    "});
12490
12491    handle_resolve_completion_request(
12492        &mut cx,
12493        Some(vec![
12494            (
12495                //This overlaps with the primary completion edit which is
12496                //misbehavior from the LSP spec, test that we filter it out
12497                indoc! {"
12498                    one.second_ˇcompletion
12499                    two
12500                    threeˇ
12501                "},
12502                "overlapping additional edit",
12503            ),
12504            (
12505                indoc! {"
12506                    one.second_completion
12507                    two
12508                    threeˇ
12509                "},
12510                "\nadditional edit",
12511            ),
12512        ]),
12513    )
12514    .await;
12515    apply_additional_edits.await.unwrap();
12516    cx.assert_editor_state(indoc! {"
12517        one.second_completionˇ
12518        two
12519        three
12520        additional edit
12521    "});
12522
12523    cx.set_state(indoc! {"
12524        one.second_completion
12525        twoˇ
12526        threeˇ
12527        additional edit
12528    "});
12529    cx.simulate_keystroke(" ");
12530    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12531    cx.simulate_keystroke("s");
12532    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12533
12534    cx.assert_editor_state(indoc! {"
12535        one.second_completion
12536        two sˇ
12537        three sˇ
12538        additional edit
12539    "});
12540    handle_completion_request(
12541        indoc! {"
12542            one.second_completion
12543            two s
12544            three <s|>
12545            additional edit
12546        "},
12547        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
12548        true,
12549        counter.clone(),
12550        &mut cx,
12551    )
12552    .await;
12553    cx.condition(|editor, _| editor.context_menu_visible())
12554        .await;
12555    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12556
12557    cx.simulate_keystroke("i");
12558
12559    handle_completion_request(
12560        indoc! {"
12561            one.second_completion
12562            two si
12563            three <si|>
12564            additional edit
12565        "},
12566        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
12567        true,
12568        counter.clone(),
12569        &mut cx,
12570    )
12571    .await;
12572    cx.condition(|editor, _| editor.context_menu_visible())
12573        .await;
12574    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12575
12576    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12577        editor
12578            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12579            .unwrap()
12580    });
12581    cx.assert_editor_state(indoc! {"
12582        one.second_completion
12583        two sixth_completionˇ
12584        three sixth_completionˇ
12585        additional edit
12586    "});
12587
12588    apply_additional_edits.await.unwrap();
12589
12590    update_test_language_settings(&mut cx, |settings| {
12591        settings.defaults.show_completions_on_input = Some(false);
12592    });
12593    cx.set_state("editorˇ");
12594    cx.simulate_keystroke(".");
12595    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12596    cx.simulate_keystrokes("c l o");
12597    cx.assert_editor_state("editor.cloˇ");
12598    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12599    cx.update_editor(|editor, window, cx| {
12600        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12601    });
12602    handle_completion_request(
12603        "editor.<clo|>",
12604        vec!["close", "clobber"],
12605        true,
12606        counter.clone(),
12607        &mut cx,
12608    )
12609    .await;
12610    cx.condition(|editor, _| editor.context_menu_visible())
12611        .await;
12612    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
12613
12614    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12615        editor
12616            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12617            .unwrap()
12618    });
12619    cx.assert_editor_state("editor.clobberˇ");
12620    handle_resolve_completion_request(&mut cx, None).await;
12621    apply_additional_edits.await.unwrap();
12622}
12623
12624#[gpui::test]
12625async fn test_completion_reuse(cx: &mut TestAppContext) {
12626    init_test(cx, |_| {});
12627
12628    let mut cx = EditorLspTestContext::new_rust(
12629        lsp::ServerCapabilities {
12630            completion_provider: Some(lsp::CompletionOptions {
12631                trigger_characters: Some(vec![".".to_string()]),
12632                ..Default::default()
12633            }),
12634            ..Default::default()
12635        },
12636        cx,
12637    )
12638    .await;
12639
12640    let counter = Arc::new(AtomicUsize::new(0));
12641    cx.set_state("objˇ");
12642    cx.simulate_keystroke(".");
12643
12644    // Initial completion request returns complete results
12645    let is_incomplete = false;
12646    handle_completion_request(
12647        "obj.|<>",
12648        vec!["a", "ab", "abc"],
12649        is_incomplete,
12650        counter.clone(),
12651        &mut cx,
12652    )
12653    .await;
12654    cx.run_until_parked();
12655    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12656    cx.assert_editor_state("obj.ˇ");
12657    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12658
12659    // Type "a" - filters existing completions
12660    cx.simulate_keystroke("a");
12661    cx.run_until_parked();
12662    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12663    cx.assert_editor_state("obj.aˇ");
12664    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12665
12666    // Type "b" - filters existing completions
12667    cx.simulate_keystroke("b");
12668    cx.run_until_parked();
12669    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12670    cx.assert_editor_state("obj.abˇ");
12671    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12672
12673    // Type "c" - filters existing completions
12674    cx.simulate_keystroke("c");
12675    cx.run_until_parked();
12676    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12677    cx.assert_editor_state("obj.abcˇ");
12678    check_displayed_completions(vec!["abc"], &mut cx);
12679
12680    // Backspace to delete "c" - filters existing completions
12681    cx.update_editor(|editor, window, cx| {
12682        editor.backspace(&Backspace, window, cx);
12683    });
12684    cx.run_until_parked();
12685    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12686    cx.assert_editor_state("obj.abˇ");
12687    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12688
12689    // Moving cursor to the left dismisses menu.
12690    cx.update_editor(|editor, window, cx| {
12691        editor.move_left(&MoveLeft, window, cx);
12692    });
12693    cx.run_until_parked();
12694    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12695    cx.assert_editor_state("obj.aˇb");
12696    cx.update_editor(|editor, _, _| {
12697        assert_eq!(editor.context_menu_visible(), false);
12698    });
12699
12700    // Type "b" - new request
12701    cx.simulate_keystroke("b");
12702    let is_incomplete = false;
12703    handle_completion_request(
12704        "obj.<ab|>a",
12705        vec!["ab", "abc"],
12706        is_incomplete,
12707        counter.clone(),
12708        &mut cx,
12709    )
12710    .await;
12711    cx.run_until_parked();
12712    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12713    cx.assert_editor_state("obj.abˇb");
12714    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12715
12716    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
12717    cx.update_editor(|editor, window, cx| {
12718        editor.backspace(&Backspace, window, cx);
12719    });
12720    let is_incomplete = false;
12721    handle_completion_request(
12722        "obj.<a|>b",
12723        vec!["a", "ab", "abc"],
12724        is_incomplete,
12725        counter.clone(),
12726        &mut cx,
12727    )
12728    .await;
12729    cx.run_until_parked();
12730    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12731    cx.assert_editor_state("obj.aˇb");
12732    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12733
12734    // Backspace to delete "a" - dismisses menu.
12735    cx.update_editor(|editor, window, cx| {
12736        editor.backspace(&Backspace, window, cx);
12737    });
12738    cx.run_until_parked();
12739    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12740    cx.assert_editor_state("obj.ˇb");
12741    cx.update_editor(|editor, _, _| {
12742        assert_eq!(editor.context_menu_visible(), false);
12743    });
12744}
12745
12746#[gpui::test]
12747async fn test_word_completion(cx: &mut TestAppContext) {
12748    let lsp_fetch_timeout_ms = 10;
12749    init_test(cx, |language_settings| {
12750        language_settings.defaults.completions = Some(CompletionSettings {
12751            words: WordsCompletionMode::Fallback,
12752            lsp: true,
12753            lsp_fetch_timeout_ms: 10,
12754            lsp_insert_mode: LspInsertMode::Insert,
12755        });
12756    });
12757
12758    let mut cx = EditorLspTestContext::new_rust(
12759        lsp::ServerCapabilities {
12760            completion_provider: Some(lsp::CompletionOptions {
12761                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12762                ..lsp::CompletionOptions::default()
12763            }),
12764            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12765            ..lsp::ServerCapabilities::default()
12766        },
12767        cx,
12768    )
12769    .await;
12770
12771    let throttle_completions = Arc::new(AtomicBool::new(false));
12772
12773    let lsp_throttle_completions = throttle_completions.clone();
12774    let _completion_requests_handler =
12775        cx.lsp
12776            .server
12777            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
12778                let lsp_throttle_completions = lsp_throttle_completions.clone();
12779                let cx = cx.clone();
12780                async move {
12781                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
12782                        cx.background_executor()
12783                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
12784                            .await;
12785                    }
12786                    Ok(Some(lsp::CompletionResponse::Array(vec![
12787                        lsp::CompletionItem {
12788                            label: "first".into(),
12789                            ..lsp::CompletionItem::default()
12790                        },
12791                        lsp::CompletionItem {
12792                            label: "last".into(),
12793                            ..lsp::CompletionItem::default()
12794                        },
12795                    ])))
12796                }
12797            });
12798
12799    cx.set_state(indoc! {"
12800        oneˇ
12801        two
12802        three
12803    "});
12804    cx.simulate_keystroke(".");
12805    cx.executor().run_until_parked();
12806    cx.condition(|editor, _| editor.context_menu_visible())
12807        .await;
12808    cx.update_editor(|editor, window, cx| {
12809        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12810        {
12811            assert_eq!(
12812                completion_menu_entries(&menu),
12813                &["first", "last"],
12814                "When LSP server is fast to reply, no fallback word completions are used"
12815            );
12816        } else {
12817            panic!("expected completion menu to be open");
12818        }
12819        editor.cancel(&Cancel, window, cx);
12820    });
12821    cx.executor().run_until_parked();
12822    cx.condition(|editor, _| !editor.context_menu_visible())
12823        .await;
12824
12825    throttle_completions.store(true, atomic::Ordering::Release);
12826    cx.simulate_keystroke(".");
12827    cx.executor()
12828        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
12829    cx.executor().run_until_parked();
12830    cx.condition(|editor, _| editor.context_menu_visible())
12831        .await;
12832    cx.update_editor(|editor, _, _| {
12833        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12834        {
12835            assert_eq!(completion_menu_entries(&menu), &["one", "three", "two"],
12836                "When LSP server is slow, document words can be shown instead, if configured accordingly");
12837        } else {
12838            panic!("expected completion menu to be open");
12839        }
12840    });
12841}
12842
12843#[gpui::test]
12844async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
12845    init_test(cx, |language_settings| {
12846        language_settings.defaults.completions = Some(CompletionSettings {
12847            words: WordsCompletionMode::Enabled,
12848            lsp: true,
12849            lsp_fetch_timeout_ms: 0,
12850            lsp_insert_mode: LspInsertMode::Insert,
12851        });
12852    });
12853
12854    let mut cx = EditorLspTestContext::new_rust(
12855        lsp::ServerCapabilities {
12856            completion_provider: Some(lsp::CompletionOptions {
12857                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12858                ..lsp::CompletionOptions::default()
12859            }),
12860            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12861            ..lsp::ServerCapabilities::default()
12862        },
12863        cx,
12864    )
12865    .await;
12866
12867    let _completion_requests_handler =
12868        cx.lsp
12869            .server
12870            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12871                Ok(Some(lsp::CompletionResponse::Array(vec![
12872                    lsp::CompletionItem {
12873                        label: "first".into(),
12874                        ..lsp::CompletionItem::default()
12875                    },
12876                    lsp::CompletionItem {
12877                        label: "last".into(),
12878                        ..lsp::CompletionItem::default()
12879                    },
12880                ])))
12881            });
12882
12883    cx.set_state(indoc! {"ˇ
12884        first
12885        last
12886        second
12887    "});
12888    cx.simulate_keystroke(".");
12889    cx.executor().run_until_parked();
12890    cx.condition(|editor, _| editor.context_menu_visible())
12891        .await;
12892    cx.update_editor(|editor, _, _| {
12893        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12894        {
12895            assert_eq!(
12896                completion_menu_entries(&menu),
12897                &["first", "last", "second"],
12898                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
12899            );
12900        } else {
12901            panic!("expected completion menu to be open");
12902        }
12903    });
12904}
12905
12906#[gpui::test]
12907async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
12908    init_test(cx, |language_settings| {
12909        language_settings.defaults.completions = Some(CompletionSettings {
12910            words: WordsCompletionMode::Disabled,
12911            lsp: true,
12912            lsp_fetch_timeout_ms: 0,
12913            lsp_insert_mode: LspInsertMode::Insert,
12914        });
12915    });
12916
12917    let mut cx = EditorLspTestContext::new_rust(
12918        lsp::ServerCapabilities {
12919            completion_provider: Some(lsp::CompletionOptions {
12920                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12921                ..lsp::CompletionOptions::default()
12922            }),
12923            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12924            ..lsp::ServerCapabilities::default()
12925        },
12926        cx,
12927    )
12928    .await;
12929
12930    let _completion_requests_handler =
12931        cx.lsp
12932            .server
12933            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12934                panic!("LSP completions should not be queried when dealing with word completions")
12935            });
12936
12937    cx.set_state(indoc! {"ˇ
12938        first
12939        last
12940        second
12941    "});
12942    cx.update_editor(|editor, window, cx| {
12943        editor.show_word_completions(&ShowWordCompletions, window, cx);
12944    });
12945    cx.executor().run_until_parked();
12946    cx.condition(|editor, _| editor.context_menu_visible())
12947        .await;
12948    cx.update_editor(|editor, _, _| {
12949        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12950        {
12951            assert_eq!(
12952                completion_menu_entries(&menu),
12953                &["first", "last", "second"],
12954                "`ShowWordCompletions` action should show word completions"
12955            );
12956        } else {
12957            panic!("expected completion menu to be open");
12958        }
12959    });
12960
12961    cx.simulate_keystroke("l");
12962    cx.executor().run_until_parked();
12963    cx.condition(|editor, _| editor.context_menu_visible())
12964        .await;
12965    cx.update_editor(|editor, _, _| {
12966        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12967        {
12968            assert_eq!(
12969                completion_menu_entries(&menu),
12970                &["last"],
12971                "After showing word completions, further editing should filter them and not query the LSP"
12972            );
12973        } else {
12974            panic!("expected completion menu to be open");
12975        }
12976    });
12977}
12978
12979#[gpui::test]
12980async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
12981    init_test(cx, |language_settings| {
12982        language_settings.defaults.completions = Some(CompletionSettings {
12983            words: WordsCompletionMode::Fallback,
12984            lsp: false,
12985            lsp_fetch_timeout_ms: 0,
12986            lsp_insert_mode: LspInsertMode::Insert,
12987        });
12988    });
12989
12990    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12991
12992    cx.set_state(indoc! {"ˇ
12993        0_usize
12994        let
12995        33
12996        4.5f32
12997    "});
12998    cx.update_editor(|editor, window, cx| {
12999        editor.show_completions(&ShowCompletions::default(), window, cx);
13000    });
13001    cx.executor().run_until_parked();
13002    cx.condition(|editor, _| editor.context_menu_visible())
13003        .await;
13004    cx.update_editor(|editor, window, cx| {
13005        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13006        {
13007            assert_eq!(
13008                completion_menu_entries(&menu),
13009                &["let"],
13010                "With no digits in the completion query, no digits should be in the word completions"
13011            );
13012        } else {
13013            panic!("expected completion menu to be open");
13014        }
13015        editor.cancel(&Cancel, window, cx);
13016    });
13017
13018    cx.set_state(indoc! {"13019        0_usize
13020        let
13021        3
13022        33.35f32
13023    "});
13024    cx.update_editor(|editor, window, cx| {
13025        editor.show_completions(&ShowCompletions::default(), window, cx);
13026    });
13027    cx.executor().run_until_parked();
13028    cx.condition(|editor, _| editor.context_menu_visible())
13029        .await;
13030    cx.update_editor(|editor, _, _| {
13031        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13032        {
13033            assert_eq!(completion_menu_entries(&menu), &["33", "35f32"], "The digit is in the completion query, \
13034                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
13035        } else {
13036            panic!("expected completion menu to be open");
13037        }
13038    });
13039}
13040
13041fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
13042    let position = || lsp::Position {
13043        line: params.text_document_position.position.line,
13044        character: params.text_document_position.position.character,
13045    };
13046    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13047        range: lsp::Range {
13048            start: position(),
13049            end: position(),
13050        },
13051        new_text: text.to_string(),
13052    }))
13053}
13054
13055#[gpui::test]
13056async fn test_multiline_completion(cx: &mut TestAppContext) {
13057    init_test(cx, |_| {});
13058
13059    let fs = FakeFs::new(cx.executor());
13060    fs.insert_tree(
13061        path!("/a"),
13062        json!({
13063            "main.ts": "a",
13064        }),
13065    )
13066    .await;
13067
13068    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13069    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13070    let typescript_language = Arc::new(Language::new(
13071        LanguageConfig {
13072            name: "TypeScript".into(),
13073            matcher: LanguageMatcher {
13074                path_suffixes: vec!["ts".to_string()],
13075                ..LanguageMatcher::default()
13076            },
13077            line_comments: vec!["// ".into()],
13078            ..LanguageConfig::default()
13079        },
13080        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
13081    ));
13082    language_registry.add(typescript_language.clone());
13083    let mut fake_servers = language_registry.register_fake_lsp(
13084        "TypeScript",
13085        FakeLspAdapter {
13086            capabilities: lsp::ServerCapabilities {
13087                completion_provider: Some(lsp::CompletionOptions {
13088                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13089                    ..lsp::CompletionOptions::default()
13090                }),
13091                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13092                ..lsp::ServerCapabilities::default()
13093            },
13094            // Emulate vtsls label generation
13095            label_for_completion: Some(Box::new(|item, _| {
13096                let text = if let Some(description) = item
13097                    .label_details
13098                    .as_ref()
13099                    .and_then(|label_details| label_details.description.as_ref())
13100                {
13101                    format!("{} {}", item.label, description)
13102                } else if let Some(detail) = &item.detail {
13103                    format!("{} {}", item.label, detail)
13104                } else {
13105                    item.label.clone()
13106                };
13107                let len = text.len();
13108                Some(language::CodeLabel {
13109                    text,
13110                    runs: Vec::new(),
13111                    filter_range: 0..len,
13112                })
13113            })),
13114            ..FakeLspAdapter::default()
13115        },
13116    );
13117    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13118    let cx = &mut VisualTestContext::from_window(*workspace, cx);
13119    let worktree_id = workspace
13120        .update(cx, |workspace, _window, cx| {
13121            workspace.project().update(cx, |project, cx| {
13122                project.worktrees(cx).next().unwrap().read(cx).id()
13123            })
13124        })
13125        .unwrap();
13126    let _buffer = project
13127        .update(cx, |project, cx| {
13128            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
13129        })
13130        .await
13131        .unwrap();
13132    let editor = workspace
13133        .update(cx, |workspace, window, cx| {
13134            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
13135        })
13136        .unwrap()
13137        .await
13138        .unwrap()
13139        .downcast::<Editor>()
13140        .unwrap();
13141    let fake_server = fake_servers.next().await.unwrap();
13142
13143    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
13144    let multiline_label_2 = "a\nb\nc\n";
13145    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
13146    let multiline_description = "d\ne\nf\n";
13147    let multiline_detail_2 = "g\nh\ni\n";
13148
13149    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
13150        move |params, _| async move {
13151            Ok(Some(lsp::CompletionResponse::Array(vec![
13152                lsp::CompletionItem {
13153                    label: multiline_label.to_string(),
13154                    text_edit: gen_text_edit(&params, "new_text_1"),
13155                    ..lsp::CompletionItem::default()
13156                },
13157                lsp::CompletionItem {
13158                    label: "single line label 1".to_string(),
13159                    detail: Some(multiline_detail.to_string()),
13160                    text_edit: gen_text_edit(&params, "new_text_2"),
13161                    ..lsp::CompletionItem::default()
13162                },
13163                lsp::CompletionItem {
13164                    label: "single line label 2".to_string(),
13165                    label_details: Some(lsp::CompletionItemLabelDetails {
13166                        description: Some(multiline_description.to_string()),
13167                        detail: None,
13168                    }),
13169                    text_edit: gen_text_edit(&params, "new_text_2"),
13170                    ..lsp::CompletionItem::default()
13171                },
13172                lsp::CompletionItem {
13173                    label: multiline_label_2.to_string(),
13174                    detail: Some(multiline_detail_2.to_string()),
13175                    text_edit: gen_text_edit(&params, "new_text_3"),
13176                    ..lsp::CompletionItem::default()
13177                },
13178                lsp::CompletionItem {
13179                    label: "Label with many     spaces and \t but without newlines".to_string(),
13180                    detail: Some(
13181                        "Details with many     spaces and \t but without newlines".to_string(),
13182                    ),
13183                    text_edit: gen_text_edit(&params, "new_text_4"),
13184                    ..lsp::CompletionItem::default()
13185                },
13186            ])))
13187        },
13188    );
13189
13190    editor.update_in(cx, |editor, window, cx| {
13191        cx.focus_self(window);
13192        editor.move_to_end(&MoveToEnd, window, cx);
13193        editor.handle_input(".", window, cx);
13194    });
13195    cx.run_until_parked();
13196    completion_handle.next().await.unwrap();
13197
13198    editor.update(cx, |editor, _| {
13199        assert!(editor.context_menu_visible());
13200        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13201        {
13202            let completion_labels = menu
13203                .completions
13204                .borrow()
13205                .iter()
13206                .map(|c| c.label.text.clone())
13207                .collect::<Vec<_>>();
13208            assert_eq!(
13209                completion_labels,
13210                &[
13211                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
13212                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
13213                    "single line label 2 d e f ",
13214                    "a b c g h i ",
13215                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
13216                ],
13217                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
13218            );
13219
13220            for completion in menu
13221                .completions
13222                .borrow()
13223                .iter() {
13224                    assert_eq!(
13225                        completion.label.filter_range,
13226                        0..completion.label.text.len(),
13227                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
13228                    );
13229                }
13230        } else {
13231            panic!("expected completion menu to be open");
13232        }
13233    });
13234}
13235
13236#[gpui::test]
13237async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
13238    init_test(cx, |_| {});
13239    let mut cx = EditorLspTestContext::new_rust(
13240        lsp::ServerCapabilities {
13241            completion_provider: Some(lsp::CompletionOptions {
13242                trigger_characters: Some(vec![".".to_string()]),
13243                ..Default::default()
13244            }),
13245            ..Default::default()
13246        },
13247        cx,
13248    )
13249    .await;
13250    cx.lsp
13251        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13252            Ok(Some(lsp::CompletionResponse::Array(vec![
13253                lsp::CompletionItem {
13254                    label: "first".into(),
13255                    ..Default::default()
13256                },
13257                lsp::CompletionItem {
13258                    label: "last".into(),
13259                    ..Default::default()
13260                },
13261            ])))
13262        });
13263    cx.set_state("variableˇ");
13264    cx.simulate_keystroke(".");
13265    cx.executor().run_until_parked();
13266
13267    cx.update_editor(|editor, _, _| {
13268        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13269        {
13270            assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
13271        } else {
13272            panic!("expected completion menu to be open");
13273        }
13274    });
13275
13276    cx.update_editor(|editor, window, cx| {
13277        editor.move_page_down(&MovePageDown::default(), window, cx);
13278        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13279        {
13280            assert!(
13281                menu.selected_item == 1,
13282                "expected PageDown to select the last item from the context menu"
13283            );
13284        } else {
13285            panic!("expected completion menu to stay open after PageDown");
13286        }
13287    });
13288
13289    cx.update_editor(|editor, window, cx| {
13290        editor.move_page_up(&MovePageUp::default(), window, cx);
13291        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13292        {
13293            assert!(
13294                menu.selected_item == 0,
13295                "expected PageUp to select the first item from the context menu"
13296            );
13297        } else {
13298            panic!("expected completion menu to stay open after PageUp");
13299        }
13300    });
13301}
13302
13303#[gpui::test]
13304async fn test_as_is_completions(cx: &mut TestAppContext) {
13305    init_test(cx, |_| {});
13306    let mut cx = EditorLspTestContext::new_rust(
13307        lsp::ServerCapabilities {
13308            completion_provider: Some(lsp::CompletionOptions {
13309                ..Default::default()
13310            }),
13311            ..Default::default()
13312        },
13313        cx,
13314    )
13315    .await;
13316    cx.lsp
13317        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13318            Ok(Some(lsp::CompletionResponse::Array(vec![
13319                lsp::CompletionItem {
13320                    label: "unsafe".into(),
13321                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13322                        range: lsp::Range {
13323                            start: lsp::Position {
13324                                line: 1,
13325                                character: 2,
13326                            },
13327                            end: lsp::Position {
13328                                line: 1,
13329                                character: 3,
13330                            },
13331                        },
13332                        new_text: "unsafe".to_string(),
13333                    })),
13334                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
13335                    ..Default::default()
13336                },
13337            ])))
13338        });
13339    cx.set_state("fn a() {}\n");
13340    cx.executor().run_until_parked();
13341    cx.update_editor(|editor, window, cx| {
13342        editor.show_completions(
13343            &ShowCompletions {
13344                trigger: Some("\n".into()),
13345            },
13346            window,
13347            cx,
13348        );
13349    });
13350    cx.executor().run_until_parked();
13351
13352    cx.update_editor(|editor, window, cx| {
13353        editor.confirm_completion(&Default::default(), window, cx)
13354    });
13355    cx.executor().run_until_parked();
13356    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
13357}
13358
13359#[gpui::test]
13360async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
13361    init_test(cx, |_| {});
13362    let language =
13363        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
13364    let mut cx = EditorLspTestContext::new(
13365        language,
13366        lsp::ServerCapabilities {
13367            completion_provider: Some(lsp::CompletionOptions {
13368                ..lsp::CompletionOptions::default()
13369            }),
13370            ..lsp::ServerCapabilities::default()
13371        },
13372        cx,
13373    )
13374    .await;
13375
13376    cx.set_state(
13377        "#ifndef BAR_H
13378#define BAR_H
13379
13380#include <stdbool.h>
13381
13382int fn_branch(bool do_branch1, bool do_branch2);
13383
13384#endif // BAR_H
13385ˇ",
13386    );
13387    cx.executor().run_until_parked();
13388    cx.update_editor(|editor, window, cx| {
13389        editor.handle_input("#", window, cx);
13390    });
13391    cx.executor().run_until_parked();
13392    cx.update_editor(|editor, window, cx| {
13393        editor.handle_input("i", window, cx);
13394    });
13395    cx.executor().run_until_parked();
13396    cx.update_editor(|editor, window, cx| {
13397        editor.handle_input("n", window, cx);
13398    });
13399    cx.executor().run_until_parked();
13400    cx.assert_editor_state(
13401        "#ifndef BAR_H
13402#define BAR_H
13403
13404#include <stdbool.h>
13405
13406int fn_branch(bool do_branch1, bool do_branch2);
13407
13408#endif // BAR_H
13409#inˇ",
13410    );
13411
13412    cx.lsp
13413        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13414            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
13415                is_incomplete: false,
13416                item_defaults: None,
13417                items: vec![lsp::CompletionItem {
13418                    kind: Some(lsp::CompletionItemKind::SNIPPET),
13419                    label_details: Some(lsp::CompletionItemLabelDetails {
13420                        detail: Some("header".to_string()),
13421                        description: None,
13422                    }),
13423                    label: " include".to_string(),
13424                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13425                        range: lsp::Range {
13426                            start: lsp::Position {
13427                                line: 8,
13428                                character: 1,
13429                            },
13430                            end: lsp::Position {
13431                                line: 8,
13432                                character: 1,
13433                            },
13434                        },
13435                        new_text: "include \"$0\"".to_string(),
13436                    })),
13437                    sort_text: Some("40b67681include".to_string()),
13438                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
13439                    filter_text: Some("include".to_string()),
13440                    insert_text: Some("include \"$0\"".to_string()),
13441                    ..lsp::CompletionItem::default()
13442                }],
13443            })))
13444        });
13445    cx.update_editor(|editor, window, cx| {
13446        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13447    });
13448    cx.executor().run_until_parked();
13449    cx.update_editor(|editor, window, cx| {
13450        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
13451    });
13452    cx.executor().run_until_parked();
13453    cx.assert_editor_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#include \"ˇ\"",
13463    );
13464
13465    cx.lsp
13466        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13467            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
13468                is_incomplete: true,
13469                item_defaults: None,
13470                items: vec![lsp::CompletionItem {
13471                    kind: Some(lsp::CompletionItemKind::FILE),
13472                    label: "AGL/".to_string(),
13473                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13474                        range: lsp::Range {
13475                            start: lsp::Position {
13476                                line: 8,
13477                                character: 10,
13478                            },
13479                            end: lsp::Position {
13480                                line: 8,
13481                                character: 11,
13482                            },
13483                        },
13484                        new_text: "AGL/".to_string(),
13485                    })),
13486                    sort_text: Some("40b67681AGL/".to_string()),
13487                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
13488                    filter_text: Some("AGL/".to_string()),
13489                    insert_text: Some("AGL/".to_string()),
13490                    ..lsp::CompletionItem::default()
13491                }],
13492            })))
13493        });
13494    cx.update_editor(|editor, window, cx| {
13495        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13496    });
13497    cx.executor().run_until_parked();
13498    cx.update_editor(|editor, window, cx| {
13499        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
13500    });
13501    cx.executor().run_until_parked();
13502    cx.assert_editor_state(
13503        r##"#ifndef BAR_H
13504#define BAR_H
13505
13506#include <stdbool.h>
13507
13508int fn_branch(bool do_branch1, bool do_branch2);
13509
13510#endif // BAR_H
13511#include "AGL/ˇ"##,
13512    );
13513
13514    cx.update_editor(|editor, window, cx| {
13515        editor.handle_input("\"", window, cx);
13516    });
13517    cx.executor().run_until_parked();
13518    cx.assert_editor_state(
13519        r##"#ifndef BAR_H
13520#define BAR_H
13521
13522#include <stdbool.h>
13523
13524int fn_branch(bool do_branch1, bool do_branch2);
13525
13526#endif // BAR_H
13527#include "AGL/"ˇ"##,
13528    );
13529}
13530
13531#[gpui::test]
13532async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
13533    init_test(cx, |_| {});
13534
13535    let mut cx = EditorLspTestContext::new_rust(
13536        lsp::ServerCapabilities {
13537            completion_provider: Some(lsp::CompletionOptions {
13538                trigger_characters: Some(vec![".".to_string()]),
13539                resolve_provider: Some(true),
13540                ..Default::default()
13541            }),
13542            ..Default::default()
13543        },
13544        cx,
13545    )
13546    .await;
13547
13548    cx.set_state("fn main() { let a = 2ˇ; }");
13549    cx.simulate_keystroke(".");
13550    let completion_item = lsp::CompletionItem {
13551        label: "Some".into(),
13552        kind: Some(lsp::CompletionItemKind::SNIPPET),
13553        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
13554        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
13555            kind: lsp::MarkupKind::Markdown,
13556            value: "```rust\nSome(2)\n```".to_string(),
13557        })),
13558        deprecated: Some(false),
13559        sort_text: Some("Some".to_string()),
13560        filter_text: Some("Some".to_string()),
13561        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
13562        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13563            range: lsp::Range {
13564                start: lsp::Position {
13565                    line: 0,
13566                    character: 22,
13567                },
13568                end: lsp::Position {
13569                    line: 0,
13570                    character: 22,
13571                },
13572            },
13573            new_text: "Some(2)".to_string(),
13574        })),
13575        additional_text_edits: Some(vec![lsp::TextEdit {
13576            range: lsp::Range {
13577                start: lsp::Position {
13578                    line: 0,
13579                    character: 20,
13580                },
13581                end: lsp::Position {
13582                    line: 0,
13583                    character: 22,
13584                },
13585            },
13586            new_text: "".to_string(),
13587        }]),
13588        ..Default::default()
13589    };
13590
13591    let closure_completion_item = completion_item.clone();
13592    let counter = Arc::new(AtomicUsize::new(0));
13593    let counter_clone = counter.clone();
13594    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
13595        let task_completion_item = closure_completion_item.clone();
13596        counter_clone.fetch_add(1, atomic::Ordering::Release);
13597        async move {
13598            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
13599                is_incomplete: true,
13600                item_defaults: None,
13601                items: vec![task_completion_item],
13602            })))
13603        }
13604    });
13605
13606    cx.condition(|editor, _| editor.context_menu_visible())
13607        .await;
13608    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
13609    assert!(request.next().await.is_some());
13610    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13611
13612    cx.simulate_keystrokes("S o m");
13613    cx.condition(|editor, _| editor.context_menu_visible())
13614        .await;
13615    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
13616    assert!(request.next().await.is_some());
13617    assert!(request.next().await.is_some());
13618    assert!(request.next().await.is_some());
13619    request.close();
13620    assert!(request.next().await.is_none());
13621    assert_eq!(
13622        counter.load(atomic::Ordering::Acquire),
13623        4,
13624        "With the completions menu open, only one LSP request should happen per input"
13625    );
13626}
13627
13628#[gpui::test]
13629async fn test_toggle_comment(cx: &mut TestAppContext) {
13630    init_test(cx, |_| {});
13631    let mut cx = EditorTestContext::new(cx).await;
13632    let language = Arc::new(Language::new(
13633        LanguageConfig {
13634            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
13635            ..Default::default()
13636        },
13637        Some(tree_sitter_rust::LANGUAGE.into()),
13638    ));
13639    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
13640
13641    // If multiple selections intersect a line, the line is only toggled once.
13642    cx.set_state(indoc! {"
13643        fn a() {
13644            «//b();
13645            ˇ»// «c();
13646            //ˇ»  d();
13647        }
13648    "});
13649
13650    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13651
13652    cx.assert_editor_state(indoc! {"
13653        fn a() {
13654            «b();
13655            c();
13656            ˇ» d();
13657        }
13658    "});
13659
13660    // The comment prefix is inserted at the same column for every line in a
13661    // selection.
13662    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13663
13664    cx.assert_editor_state(indoc! {"
13665        fn a() {
13666            // «b();
13667            // c();
13668            ˇ»//  d();
13669        }
13670    "});
13671
13672    // If a selection ends at the beginning of a line, that line is not toggled.
13673    cx.set_selections_state(indoc! {"
13674        fn a() {
13675            // b();
13676            «// c();
13677        ˇ»    //  d();
13678        }
13679    "});
13680
13681    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13682
13683    cx.assert_editor_state(indoc! {"
13684        fn a() {
13685            // b();
13686            «c();
13687        ˇ»    //  d();
13688        }
13689    "});
13690
13691    // If a selection span a single line and is empty, the line is toggled.
13692    cx.set_state(indoc! {"
13693        fn a() {
13694            a();
13695            b();
13696        ˇ
13697        }
13698    "});
13699
13700    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13701
13702    cx.assert_editor_state(indoc! {"
13703        fn a() {
13704            a();
13705            b();
13706        //•ˇ
13707        }
13708    "});
13709
13710    // If a selection span multiple lines, empty lines are not toggled.
13711    cx.set_state(indoc! {"
13712        fn a() {
13713            «a();
13714
13715            c();ˇ»
13716        }
13717    "});
13718
13719    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13720
13721    cx.assert_editor_state(indoc! {"
13722        fn a() {
13723            // «a();
13724
13725            // c();ˇ»
13726        }
13727    "});
13728
13729    // If a selection includes multiple comment prefixes, all lines are uncommented.
13730    cx.set_state(indoc! {"
13731        fn a() {
13732            «// a();
13733            /// b();
13734            //! c();ˇ»
13735        }
13736    "});
13737
13738    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13739
13740    cx.assert_editor_state(indoc! {"
13741        fn a() {
13742            «a();
13743            b();
13744            c();ˇ»
13745        }
13746    "});
13747}
13748
13749#[gpui::test]
13750async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
13751    init_test(cx, |_| {});
13752    let mut cx = EditorTestContext::new(cx).await;
13753    let language = Arc::new(Language::new(
13754        LanguageConfig {
13755            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
13756            ..Default::default()
13757        },
13758        Some(tree_sitter_rust::LANGUAGE.into()),
13759    ));
13760    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
13761
13762    let toggle_comments = &ToggleComments {
13763        advance_downwards: false,
13764        ignore_indent: true,
13765    };
13766
13767    // If multiple selections intersect a line, the line is only toggled once.
13768    cx.set_state(indoc! {"
13769        fn a() {
13770        //    «b();
13771        //    c();
13772        //    ˇ» d();
13773        }
13774    "});
13775
13776    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13777
13778    cx.assert_editor_state(indoc! {"
13779        fn a() {
13780            «b();
13781            c();
13782            ˇ» d();
13783        }
13784    "});
13785
13786    // The comment prefix is inserted at the beginning of each line
13787    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13788
13789    cx.assert_editor_state(indoc! {"
13790        fn a() {
13791        //    «b();
13792        //    c();
13793        //    ˇ» d();
13794        }
13795    "});
13796
13797    // If a selection ends at the beginning of a line, that line is not toggled.
13798    cx.set_selections_state(indoc! {"
13799        fn a() {
13800        //    b();
13801        //    «c();
13802        ˇ»//     d();
13803        }
13804    "});
13805
13806    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13807
13808    cx.assert_editor_state(indoc! {"
13809        fn a() {
13810        //    b();
13811            «c();
13812        ˇ»//     d();
13813        }
13814    "});
13815
13816    // If a selection span a single line and is empty, the line is toggled.
13817    cx.set_state(indoc! {"
13818        fn a() {
13819            a();
13820            b();
13821        ˇ
13822        }
13823    "});
13824
13825    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13826
13827    cx.assert_editor_state(indoc! {"
13828        fn a() {
13829            a();
13830            b();
13831        //ˇ
13832        }
13833    "});
13834
13835    // If a selection span multiple lines, empty lines are not toggled.
13836    cx.set_state(indoc! {"
13837        fn a() {
13838            «a();
13839
13840            c();ˇ»
13841        }
13842    "});
13843
13844    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13845
13846    cx.assert_editor_state(indoc! {"
13847        fn a() {
13848        //    «a();
13849
13850        //    c();ˇ»
13851        }
13852    "});
13853
13854    // If a selection includes multiple comment prefixes, all lines are uncommented.
13855    cx.set_state(indoc! {"
13856        fn a() {
13857        //    «a();
13858        ///    b();
13859        //!    c();ˇ»
13860        }
13861    "});
13862
13863    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13864
13865    cx.assert_editor_state(indoc! {"
13866        fn a() {
13867            «a();
13868            b();
13869            c();ˇ»
13870        }
13871    "});
13872}
13873
13874#[gpui::test]
13875async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
13876    init_test(cx, |_| {});
13877
13878    let language = Arc::new(Language::new(
13879        LanguageConfig {
13880            line_comments: vec!["// ".into()],
13881            ..Default::default()
13882        },
13883        Some(tree_sitter_rust::LANGUAGE.into()),
13884    ));
13885
13886    let mut cx = EditorTestContext::new(cx).await;
13887
13888    cx.language_registry().add(language.clone());
13889    cx.update_buffer(|buffer, cx| {
13890        buffer.set_language(Some(language), cx);
13891    });
13892
13893    let toggle_comments = &ToggleComments {
13894        advance_downwards: true,
13895        ignore_indent: false,
13896    };
13897
13898    // Single cursor on one line -> advance
13899    // Cursor moves horizontally 3 characters as well on non-blank line
13900    cx.set_state(indoc!(
13901        "fn a() {
13902             ˇdog();
13903             cat();
13904        }"
13905    ));
13906    cx.update_editor(|editor, window, cx| {
13907        editor.toggle_comments(toggle_comments, window, cx);
13908    });
13909    cx.assert_editor_state(indoc!(
13910        "fn a() {
13911             // dog();
13912             catˇ();
13913        }"
13914    ));
13915
13916    // Single selection on one line -> don't advance
13917    cx.set_state(indoc!(
13918        "fn a() {
13919             «dog()ˇ»;
13920             cat();
13921        }"
13922    ));
13923    cx.update_editor(|editor, window, cx| {
13924        editor.toggle_comments(toggle_comments, window, cx);
13925    });
13926    cx.assert_editor_state(indoc!(
13927        "fn a() {
13928             // «dog()ˇ»;
13929             cat();
13930        }"
13931    ));
13932
13933    // Multiple cursors on one line -> advance
13934    cx.set_state(indoc!(
13935        "fn a() {
13936             ˇdˇog();
13937             cat();
13938        }"
13939    ));
13940    cx.update_editor(|editor, window, cx| {
13941        editor.toggle_comments(toggle_comments, window, cx);
13942    });
13943    cx.assert_editor_state(indoc!(
13944        "fn a() {
13945             // dog();
13946             catˇ(ˇ);
13947        }"
13948    ));
13949
13950    // Multiple cursors on one line, with selection -> don't advance
13951    cx.set_state(indoc!(
13952        "fn a() {
13953             ˇdˇog«()ˇ»;
13954             cat();
13955        }"
13956    ));
13957    cx.update_editor(|editor, window, cx| {
13958        editor.toggle_comments(toggle_comments, window, cx);
13959    });
13960    cx.assert_editor_state(indoc!(
13961        "fn a() {
13962             // ˇdˇog«()ˇ»;
13963             cat();
13964        }"
13965    ));
13966
13967    // Single cursor on one line -> advance
13968    // Cursor moves to column 0 on blank line
13969    cx.set_state(indoc!(
13970        "fn a() {
13971             ˇdog();
13972
13973             cat();
13974        }"
13975    ));
13976    cx.update_editor(|editor, window, cx| {
13977        editor.toggle_comments(toggle_comments, window, cx);
13978    });
13979    cx.assert_editor_state(indoc!(
13980        "fn a() {
13981             // dog();
13982        ˇ
13983             cat();
13984        }"
13985    ));
13986
13987    // Single cursor on one line -> advance
13988    // Cursor starts and ends at column 0
13989    cx.set_state(indoc!(
13990        "fn a() {
13991         ˇ    dog();
13992             cat();
13993        }"
13994    ));
13995    cx.update_editor(|editor, window, cx| {
13996        editor.toggle_comments(toggle_comments, window, cx);
13997    });
13998    cx.assert_editor_state(indoc!(
13999        "fn a() {
14000             // dog();
14001         ˇ    cat();
14002        }"
14003    ));
14004}
14005
14006#[gpui::test]
14007async fn test_toggle_block_comment(cx: &mut TestAppContext) {
14008    init_test(cx, |_| {});
14009
14010    let mut cx = EditorTestContext::new(cx).await;
14011
14012    let html_language = Arc::new(
14013        Language::new(
14014            LanguageConfig {
14015                name: "HTML".into(),
14016                block_comment: Some(BlockCommentConfig {
14017                    start: "<!-- ".into(),
14018                    prefix: "".into(),
14019                    end: " -->".into(),
14020                    tab_size: 0,
14021                }),
14022                ..Default::default()
14023            },
14024            Some(tree_sitter_html::LANGUAGE.into()),
14025        )
14026        .with_injection_query(
14027            r#"
14028            (script_element
14029                (raw_text) @injection.content
14030                (#set! injection.language "javascript"))
14031            "#,
14032        )
14033        .unwrap(),
14034    );
14035
14036    let javascript_language = Arc::new(Language::new(
14037        LanguageConfig {
14038            name: "JavaScript".into(),
14039            line_comments: vec!["// ".into()],
14040            ..Default::default()
14041        },
14042        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
14043    ));
14044
14045    cx.language_registry().add(html_language.clone());
14046    cx.language_registry().add(javascript_language.clone());
14047    cx.update_buffer(|buffer, cx| {
14048        buffer.set_language(Some(html_language), cx);
14049    });
14050
14051    // Toggle comments for empty selections
14052    cx.set_state(
14053        &r#"
14054            <p>A</p>ˇ
14055            <p>B</p>ˇ
14056            <p>C</p>ˇ
14057        "#
14058        .unindent(),
14059    );
14060    cx.update_editor(|editor, window, cx| {
14061        editor.toggle_comments(&ToggleComments::default(), window, cx)
14062    });
14063    cx.assert_editor_state(
14064        &r#"
14065            <!-- <p>A</p>ˇ -->
14066            <!-- <p>B</p>ˇ -->
14067            <!-- <p>C</p>ˇ -->
14068        "#
14069        .unindent(),
14070    );
14071    cx.update_editor(|editor, window, cx| {
14072        editor.toggle_comments(&ToggleComments::default(), window, cx)
14073    });
14074    cx.assert_editor_state(
14075        &r#"
14076            <p>A</p>ˇ
14077            <p>B</p>ˇ
14078            <p>C</p>ˇ
14079        "#
14080        .unindent(),
14081    );
14082
14083    // Toggle comments for mixture of empty and non-empty selections, where
14084    // multiple selections occupy a given line.
14085    cx.set_state(
14086        &r#"
14087            <p>A«</p>
14088            <p>ˇ»B</p>ˇ
14089            <p>C«</p>
14090            <p>ˇ»D</p>ˇ
14091        "#
14092        .unindent(),
14093    );
14094
14095    cx.update_editor(|editor, window, cx| {
14096        editor.toggle_comments(&ToggleComments::default(), window, cx)
14097    });
14098    cx.assert_editor_state(
14099        &r#"
14100            <!-- <p>A«</p>
14101            <p>ˇ»B</p>ˇ -->
14102            <!-- <p>C«</p>
14103            <p>ˇ»D</p>ˇ -->
14104        "#
14105        .unindent(),
14106    );
14107    cx.update_editor(|editor, window, cx| {
14108        editor.toggle_comments(&ToggleComments::default(), window, cx)
14109    });
14110    cx.assert_editor_state(
14111        &r#"
14112            <p>A«</p>
14113            <p>ˇ»B</p>ˇ
14114            <p>C«</p>
14115            <p>ˇ»D</p>ˇ
14116        "#
14117        .unindent(),
14118    );
14119
14120    // Toggle comments when different languages are active for different
14121    // selections.
14122    cx.set_state(
14123        &r#"
14124            ˇ<script>
14125                ˇvar x = new Y();
14126            ˇ</script>
14127        "#
14128        .unindent(),
14129    );
14130    cx.executor().run_until_parked();
14131    cx.update_editor(|editor, window, cx| {
14132        editor.toggle_comments(&ToggleComments::default(), window, cx)
14133    });
14134    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
14135    // Uncommenting and commenting from this position brings in even more wrong artifacts.
14136    cx.assert_editor_state(
14137        &r#"
14138            <!-- ˇ<script> -->
14139                // ˇvar x = new Y();
14140            <!-- ˇ</script> -->
14141        "#
14142        .unindent(),
14143    );
14144}
14145
14146#[gpui::test]
14147fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
14148    init_test(cx, |_| {});
14149
14150    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
14151    let multibuffer = cx.new(|cx| {
14152        let mut multibuffer = MultiBuffer::new(ReadWrite);
14153        multibuffer.push_excerpts(
14154            buffer.clone(),
14155            [
14156                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
14157                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
14158            ],
14159            cx,
14160        );
14161        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
14162        multibuffer
14163    });
14164
14165    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
14166    editor.update_in(cx, |editor, window, cx| {
14167        assert_eq!(editor.text(cx), "aaaa\nbbbb");
14168        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14169            s.select_ranges([
14170                Point::new(0, 0)..Point::new(0, 0),
14171                Point::new(1, 0)..Point::new(1, 0),
14172            ])
14173        });
14174
14175        editor.handle_input("X", window, cx);
14176        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
14177        assert_eq!(
14178            editor.selections.ranges(cx),
14179            [
14180                Point::new(0, 1)..Point::new(0, 1),
14181                Point::new(1, 1)..Point::new(1, 1),
14182            ]
14183        );
14184
14185        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
14186        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14187            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
14188        });
14189        editor.backspace(&Default::default(), window, cx);
14190        assert_eq!(editor.text(cx), "Xa\nbbb");
14191        assert_eq!(
14192            editor.selections.ranges(cx),
14193            [Point::new(1, 0)..Point::new(1, 0)]
14194        );
14195
14196        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14197            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
14198        });
14199        editor.backspace(&Default::default(), window, cx);
14200        assert_eq!(editor.text(cx), "X\nbb");
14201        assert_eq!(
14202            editor.selections.ranges(cx),
14203            [Point::new(0, 1)..Point::new(0, 1)]
14204        );
14205    });
14206}
14207
14208#[gpui::test]
14209fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
14210    init_test(cx, |_| {});
14211
14212    let markers = vec![('[', ']').into(), ('(', ')').into()];
14213    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
14214        indoc! {"
14215            [aaaa
14216            (bbbb]
14217            cccc)",
14218        },
14219        markers.clone(),
14220    );
14221    let excerpt_ranges = markers.into_iter().map(|marker| {
14222        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
14223        ExcerptRange::new(context.clone())
14224    });
14225    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
14226    let multibuffer = cx.new(|cx| {
14227        let mut multibuffer = MultiBuffer::new(ReadWrite);
14228        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
14229        multibuffer
14230    });
14231
14232    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
14233    editor.update_in(cx, |editor, window, cx| {
14234        let (expected_text, selection_ranges) = marked_text_ranges(
14235            indoc! {"
14236                aaaa
14237                bˇbbb
14238                bˇbbˇb
14239                cccc"
14240            },
14241            true,
14242        );
14243        assert_eq!(editor.text(cx), expected_text);
14244        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14245            s.select_ranges(selection_ranges)
14246        });
14247
14248        editor.handle_input("X", window, cx);
14249
14250        let (expected_text, expected_selections) = marked_text_ranges(
14251            indoc! {"
14252                aaaa
14253                bXˇbbXb
14254                bXˇbbXˇb
14255                cccc"
14256            },
14257            false,
14258        );
14259        assert_eq!(editor.text(cx), expected_text);
14260        assert_eq!(editor.selections.ranges(cx), expected_selections);
14261
14262        editor.newline(&Newline, window, cx);
14263        let (expected_text, expected_selections) = marked_text_ranges(
14264            indoc! {"
14265                aaaa
14266                bX
14267                ˇbbX
14268                b
14269                bX
14270                ˇbbX
14271                ˇb
14272                cccc"
14273            },
14274            false,
14275        );
14276        assert_eq!(editor.text(cx), expected_text);
14277        assert_eq!(editor.selections.ranges(cx), expected_selections);
14278    });
14279}
14280
14281#[gpui::test]
14282fn test_refresh_selections(cx: &mut TestAppContext) {
14283    init_test(cx, |_| {});
14284
14285    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
14286    let mut excerpt1_id = None;
14287    let multibuffer = cx.new(|cx| {
14288        let mut multibuffer = MultiBuffer::new(ReadWrite);
14289        excerpt1_id = multibuffer
14290            .push_excerpts(
14291                buffer.clone(),
14292                [
14293                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
14294                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
14295                ],
14296                cx,
14297            )
14298            .into_iter()
14299            .next();
14300        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
14301        multibuffer
14302    });
14303
14304    let editor = cx.add_window(|window, cx| {
14305        let mut editor = build_editor(multibuffer.clone(), window, cx);
14306        let snapshot = editor.snapshot(window, cx);
14307        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14308            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
14309        });
14310        editor.begin_selection(
14311            Point::new(2, 1).to_display_point(&snapshot),
14312            true,
14313            1,
14314            window,
14315            cx,
14316        );
14317        assert_eq!(
14318            editor.selections.ranges(cx),
14319            [
14320                Point::new(1, 3)..Point::new(1, 3),
14321                Point::new(2, 1)..Point::new(2, 1),
14322            ]
14323        );
14324        editor
14325    });
14326
14327    // Refreshing selections is a no-op when excerpts haven't changed.
14328    _ = editor.update(cx, |editor, window, cx| {
14329        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14330        assert_eq!(
14331            editor.selections.ranges(cx),
14332            [
14333                Point::new(1, 3)..Point::new(1, 3),
14334                Point::new(2, 1)..Point::new(2, 1),
14335            ]
14336        );
14337    });
14338
14339    multibuffer.update(cx, |multibuffer, cx| {
14340        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
14341    });
14342    _ = editor.update(cx, |editor, window, cx| {
14343        // Removing an excerpt causes the first selection to become degenerate.
14344        assert_eq!(
14345            editor.selections.ranges(cx),
14346            [
14347                Point::new(0, 0)..Point::new(0, 0),
14348                Point::new(0, 1)..Point::new(0, 1)
14349            ]
14350        );
14351
14352        // Refreshing selections will relocate the first selection to the original buffer
14353        // location.
14354        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14355        assert_eq!(
14356            editor.selections.ranges(cx),
14357            [
14358                Point::new(0, 1)..Point::new(0, 1),
14359                Point::new(0, 3)..Point::new(0, 3)
14360            ]
14361        );
14362        assert!(editor.selections.pending_anchor().is_some());
14363    });
14364}
14365
14366#[gpui::test]
14367fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
14368    init_test(cx, |_| {});
14369
14370    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
14371    let mut excerpt1_id = None;
14372    let multibuffer = cx.new(|cx| {
14373        let mut multibuffer = MultiBuffer::new(ReadWrite);
14374        excerpt1_id = multibuffer
14375            .push_excerpts(
14376                buffer.clone(),
14377                [
14378                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
14379                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
14380                ],
14381                cx,
14382            )
14383            .into_iter()
14384            .next();
14385        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
14386        multibuffer
14387    });
14388
14389    let editor = cx.add_window(|window, cx| {
14390        let mut editor = build_editor(multibuffer.clone(), window, cx);
14391        let snapshot = editor.snapshot(window, cx);
14392        editor.begin_selection(
14393            Point::new(1, 3).to_display_point(&snapshot),
14394            false,
14395            1,
14396            window,
14397            cx,
14398        );
14399        assert_eq!(
14400            editor.selections.ranges(cx),
14401            [Point::new(1, 3)..Point::new(1, 3)]
14402        );
14403        editor
14404    });
14405
14406    multibuffer.update(cx, |multibuffer, cx| {
14407        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
14408    });
14409    _ = editor.update(cx, |editor, window, cx| {
14410        assert_eq!(
14411            editor.selections.ranges(cx),
14412            [Point::new(0, 0)..Point::new(0, 0)]
14413        );
14414
14415        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
14416        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14417        assert_eq!(
14418            editor.selections.ranges(cx),
14419            [Point::new(0, 3)..Point::new(0, 3)]
14420        );
14421        assert!(editor.selections.pending_anchor().is_some());
14422    });
14423}
14424
14425#[gpui::test]
14426async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
14427    init_test(cx, |_| {});
14428
14429    let language = Arc::new(
14430        Language::new(
14431            LanguageConfig {
14432                brackets: BracketPairConfig {
14433                    pairs: vec![
14434                        BracketPair {
14435                            start: "{".to_string(),
14436                            end: "}".to_string(),
14437                            close: true,
14438                            surround: true,
14439                            newline: true,
14440                        },
14441                        BracketPair {
14442                            start: "/* ".to_string(),
14443                            end: " */".to_string(),
14444                            close: true,
14445                            surround: true,
14446                            newline: true,
14447                        },
14448                    ],
14449                    ..Default::default()
14450                },
14451                ..Default::default()
14452            },
14453            Some(tree_sitter_rust::LANGUAGE.into()),
14454        )
14455        .with_indents_query("")
14456        .unwrap(),
14457    );
14458
14459    let text = concat!(
14460        "{   }\n",     //
14461        "  x\n",       //
14462        "  /*   */\n", //
14463        "x\n",         //
14464        "{{} }\n",     //
14465    );
14466
14467    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
14468    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
14469    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
14470    editor
14471        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
14472        .await;
14473
14474    editor.update_in(cx, |editor, window, cx| {
14475        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14476            s.select_display_ranges([
14477                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
14478                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
14479                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
14480            ])
14481        });
14482        editor.newline(&Newline, window, cx);
14483
14484        assert_eq!(
14485            editor.buffer().read(cx).read(cx).text(),
14486            concat!(
14487                "{ \n",    // Suppress rustfmt
14488                "\n",      //
14489                "}\n",     //
14490                "  x\n",   //
14491                "  /* \n", //
14492                "  \n",    //
14493                "  */\n",  //
14494                "x\n",     //
14495                "{{} \n",  //
14496                "}\n",     //
14497            )
14498        );
14499    });
14500}
14501
14502#[gpui::test]
14503fn test_highlighted_ranges(cx: &mut TestAppContext) {
14504    init_test(cx, |_| {});
14505
14506    let editor = cx.add_window(|window, cx| {
14507        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
14508        build_editor(buffer.clone(), window, cx)
14509    });
14510
14511    _ = editor.update(cx, |editor, window, cx| {
14512        struct Type1;
14513        struct Type2;
14514
14515        let buffer = editor.buffer.read(cx).snapshot(cx);
14516
14517        let anchor_range =
14518            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
14519
14520        editor.highlight_background::<Type1>(
14521            &[
14522                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
14523                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
14524                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
14525                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
14526            ],
14527            |_| Hsla::red(),
14528            cx,
14529        );
14530        editor.highlight_background::<Type2>(
14531            &[
14532                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
14533                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
14534                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
14535                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
14536            ],
14537            |_| Hsla::green(),
14538            cx,
14539        );
14540
14541        let snapshot = editor.snapshot(window, cx);
14542        let mut highlighted_ranges = editor.background_highlights_in_range(
14543            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
14544            &snapshot,
14545            cx.theme(),
14546        );
14547        // Enforce a consistent ordering based on color without relying on the ordering of the
14548        // highlight's `TypeId` which is non-executor.
14549        highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
14550        assert_eq!(
14551            highlighted_ranges,
14552            &[
14553                (
14554                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
14555                    Hsla::red(),
14556                ),
14557                (
14558                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
14559                    Hsla::red(),
14560                ),
14561                (
14562                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
14563                    Hsla::green(),
14564                ),
14565                (
14566                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
14567                    Hsla::green(),
14568                ),
14569            ]
14570        );
14571        assert_eq!(
14572            editor.background_highlights_in_range(
14573                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
14574                &snapshot,
14575                cx.theme(),
14576            ),
14577            &[(
14578                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
14579                Hsla::red(),
14580            )]
14581        );
14582    });
14583}
14584
14585#[gpui::test]
14586async fn test_following(cx: &mut TestAppContext) {
14587    init_test(cx, |_| {});
14588
14589    let fs = FakeFs::new(cx.executor());
14590    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
14591
14592    let buffer = project.update(cx, |project, cx| {
14593        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
14594        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
14595    });
14596    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
14597    let follower = cx.update(|cx| {
14598        cx.open_window(
14599            WindowOptions {
14600                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
14601                    gpui::Point::new(px(0.), px(0.)),
14602                    gpui::Point::new(px(10.), px(80.)),
14603                ))),
14604                ..Default::default()
14605            },
14606            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
14607        )
14608        .unwrap()
14609    });
14610
14611    let is_still_following = Rc::new(RefCell::new(true));
14612    let follower_edit_event_count = Rc::new(RefCell::new(0));
14613    let pending_update = Rc::new(RefCell::new(None));
14614    let leader_entity = leader.root(cx).unwrap();
14615    let follower_entity = follower.root(cx).unwrap();
14616    _ = follower.update(cx, {
14617        let update = pending_update.clone();
14618        let is_still_following = is_still_following.clone();
14619        let follower_edit_event_count = follower_edit_event_count.clone();
14620        |_, window, cx| {
14621            cx.subscribe_in(
14622                &leader_entity,
14623                window,
14624                move |_, leader, event, window, cx| {
14625                    leader.read(cx).add_event_to_update_proto(
14626                        event,
14627                        &mut update.borrow_mut(),
14628                        window,
14629                        cx,
14630                    );
14631                },
14632            )
14633            .detach();
14634
14635            cx.subscribe_in(
14636                &follower_entity,
14637                window,
14638                move |_, _, event: &EditorEvent, _window, _cx| {
14639                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
14640                        *is_still_following.borrow_mut() = false;
14641                    }
14642
14643                    if let EditorEvent::BufferEdited = event {
14644                        *follower_edit_event_count.borrow_mut() += 1;
14645                    }
14646                },
14647            )
14648            .detach();
14649        }
14650    });
14651
14652    // Update the selections only
14653    _ = leader.update(cx, |leader, window, cx| {
14654        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14655            s.select_ranges([1..1])
14656        });
14657    });
14658    follower
14659        .update(cx, |follower, window, cx| {
14660            follower.apply_update_proto(
14661                &project,
14662                pending_update.borrow_mut().take().unwrap(),
14663                window,
14664                cx,
14665            )
14666        })
14667        .unwrap()
14668        .await
14669        .unwrap();
14670    _ = follower.update(cx, |follower, _, cx| {
14671        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
14672    });
14673    assert!(*is_still_following.borrow());
14674    assert_eq!(*follower_edit_event_count.borrow(), 0);
14675
14676    // Update the scroll position only
14677    _ = leader.update(cx, |leader, window, cx| {
14678        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
14679    });
14680    follower
14681        .update(cx, |follower, window, cx| {
14682            follower.apply_update_proto(
14683                &project,
14684                pending_update.borrow_mut().take().unwrap(),
14685                window,
14686                cx,
14687            )
14688        })
14689        .unwrap()
14690        .await
14691        .unwrap();
14692    assert_eq!(
14693        follower
14694            .update(cx, |follower, _, cx| follower.scroll_position(cx))
14695            .unwrap(),
14696        gpui::Point::new(1.5, 3.5)
14697    );
14698    assert!(*is_still_following.borrow());
14699    assert_eq!(*follower_edit_event_count.borrow(), 0);
14700
14701    // Update the selections and scroll position. The follower's scroll position is updated
14702    // via autoscroll, not via the leader's exact scroll position.
14703    _ = leader.update(cx, |leader, window, cx| {
14704        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14705            s.select_ranges([0..0])
14706        });
14707        leader.request_autoscroll(Autoscroll::newest(), cx);
14708        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
14709    });
14710    follower
14711        .update(cx, |follower, window, cx| {
14712            follower.apply_update_proto(
14713                &project,
14714                pending_update.borrow_mut().take().unwrap(),
14715                window,
14716                cx,
14717            )
14718        })
14719        .unwrap()
14720        .await
14721        .unwrap();
14722    _ = follower.update(cx, |follower, _, cx| {
14723        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
14724        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
14725    });
14726    assert!(*is_still_following.borrow());
14727
14728    // Creating a pending selection that precedes another selection
14729    _ = leader.update(cx, |leader, window, cx| {
14730        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14731            s.select_ranges([1..1])
14732        });
14733        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
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![0..0, 1..1]);
14749    });
14750    assert!(*is_still_following.borrow());
14751
14752    // Extend the pending selection so that it surrounds another selection
14753    _ = leader.update(cx, |leader, window, cx| {
14754        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
14755    });
14756    follower
14757        .update(cx, |follower, window, cx| {
14758            follower.apply_update_proto(
14759                &project,
14760                pending_update.borrow_mut().take().unwrap(),
14761                window,
14762                cx,
14763            )
14764        })
14765        .unwrap()
14766        .await
14767        .unwrap();
14768    _ = follower.update(cx, |follower, _, cx| {
14769        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
14770    });
14771
14772    // Scrolling locally breaks the follow
14773    _ = follower.update(cx, |follower, window, cx| {
14774        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
14775        follower.set_scroll_anchor(
14776            ScrollAnchor {
14777                anchor: top_anchor,
14778                offset: gpui::Point::new(0.0, 0.5),
14779            },
14780            window,
14781            cx,
14782        );
14783    });
14784    assert!(!(*is_still_following.borrow()));
14785}
14786
14787#[gpui::test]
14788async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
14789    init_test(cx, |_| {});
14790
14791    let fs = FakeFs::new(cx.executor());
14792    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
14793    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14794    let pane = workspace
14795        .update(cx, |workspace, _, _| workspace.active_pane().clone())
14796        .unwrap();
14797
14798    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14799
14800    let leader = pane.update_in(cx, |_, window, cx| {
14801        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
14802        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
14803    });
14804
14805    // Start following the editor when it has no excerpts.
14806    let mut state_message =
14807        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
14808    let workspace_entity = workspace.root(cx).unwrap();
14809    let follower_1 = cx
14810        .update_window(*workspace.deref(), |_, window, cx| {
14811            Editor::from_state_proto(
14812                workspace_entity,
14813                ViewId {
14814                    creator: CollaboratorId::PeerId(PeerId::default()),
14815                    id: 0,
14816                },
14817                &mut state_message,
14818                window,
14819                cx,
14820            )
14821        })
14822        .unwrap()
14823        .unwrap()
14824        .await
14825        .unwrap();
14826
14827    let update_message = Rc::new(RefCell::new(None));
14828    follower_1.update_in(cx, {
14829        let update = update_message.clone();
14830        |_, window, cx| {
14831            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
14832                leader.read(cx).add_event_to_update_proto(
14833                    event,
14834                    &mut update.borrow_mut(),
14835                    window,
14836                    cx,
14837                );
14838            })
14839            .detach();
14840        }
14841    });
14842
14843    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
14844        (
14845            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
14846            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
14847        )
14848    });
14849
14850    // Insert some excerpts.
14851    leader.update(cx, |leader, cx| {
14852        leader.buffer.update(cx, |multibuffer, cx| {
14853            multibuffer.set_excerpts_for_path(
14854                PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
14855                buffer_1.clone(),
14856                vec![
14857                    Point::row_range(0..3),
14858                    Point::row_range(1..6),
14859                    Point::row_range(12..15),
14860                ],
14861                0,
14862                cx,
14863            );
14864            multibuffer.set_excerpts_for_path(
14865                PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
14866                buffer_2.clone(),
14867                vec![Point::row_range(0..6), Point::row_range(8..12)],
14868                0,
14869                cx,
14870            );
14871        });
14872    });
14873
14874    // Apply the update of adding the excerpts.
14875    follower_1
14876        .update_in(cx, |follower, window, cx| {
14877            follower.apply_update_proto(
14878                &project,
14879                update_message.borrow().clone().unwrap(),
14880                window,
14881                cx,
14882            )
14883        })
14884        .await
14885        .unwrap();
14886    assert_eq!(
14887        follower_1.update(cx, |editor, cx| editor.text(cx)),
14888        leader.update(cx, |editor, cx| editor.text(cx))
14889    );
14890    update_message.borrow_mut().take();
14891
14892    // Start following separately after it already has excerpts.
14893    let mut state_message =
14894        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
14895    let workspace_entity = workspace.root(cx).unwrap();
14896    let follower_2 = cx
14897        .update_window(*workspace.deref(), |_, window, cx| {
14898            Editor::from_state_proto(
14899                workspace_entity,
14900                ViewId {
14901                    creator: CollaboratorId::PeerId(PeerId::default()),
14902                    id: 0,
14903                },
14904                &mut state_message,
14905                window,
14906                cx,
14907            )
14908        })
14909        .unwrap()
14910        .unwrap()
14911        .await
14912        .unwrap();
14913    assert_eq!(
14914        follower_2.update(cx, |editor, cx| editor.text(cx)),
14915        leader.update(cx, |editor, cx| editor.text(cx))
14916    );
14917
14918    // Remove some excerpts.
14919    leader.update(cx, |leader, cx| {
14920        leader.buffer.update(cx, |multibuffer, cx| {
14921            let excerpt_ids = multibuffer.excerpt_ids();
14922            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
14923            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
14924        });
14925    });
14926
14927    // Apply the update of removing the excerpts.
14928    follower_1
14929        .update_in(cx, |follower, window, cx| {
14930            follower.apply_update_proto(
14931                &project,
14932                update_message.borrow().clone().unwrap(),
14933                window,
14934                cx,
14935            )
14936        })
14937        .await
14938        .unwrap();
14939    follower_2
14940        .update_in(cx, |follower, window, cx| {
14941            follower.apply_update_proto(
14942                &project,
14943                update_message.borrow().clone().unwrap(),
14944                window,
14945                cx,
14946            )
14947        })
14948        .await
14949        .unwrap();
14950    update_message.borrow_mut().take();
14951    assert_eq!(
14952        follower_1.update(cx, |editor, cx| editor.text(cx)),
14953        leader.update(cx, |editor, cx| editor.text(cx))
14954    );
14955}
14956
14957#[gpui::test]
14958async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14959    init_test(cx, |_| {});
14960
14961    let mut cx = EditorTestContext::new(cx).await;
14962    let lsp_store =
14963        cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
14964
14965    cx.set_state(indoc! {"
14966        ˇfn func(abc def: i32) -> u32 {
14967        }
14968    "});
14969
14970    cx.update(|_, cx| {
14971        lsp_store.update(cx, |lsp_store, cx| {
14972            lsp_store
14973                .update_diagnostics(
14974                    LanguageServerId(0),
14975                    lsp::PublishDiagnosticsParams {
14976                        uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
14977                        version: None,
14978                        diagnostics: vec![
14979                            lsp::Diagnostic {
14980                                range: lsp::Range::new(
14981                                    lsp::Position::new(0, 11),
14982                                    lsp::Position::new(0, 12),
14983                                ),
14984                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14985                                ..Default::default()
14986                            },
14987                            lsp::Diagnostic {
14988                                range: lsp::Range::new(
14989                                    lsp::Position::new(0, 12),
14990                                    lsp::Position::new(0, 15),
14991                                ),
14992                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14993                                ..Default::default()
14994                            },
14995                            lsp::Diagnostic {
14996                                range: lsp::Range::new(
14997                                    lsp::Position::new(0, 25),
14998                                    lsp::Position::new(0, 28),
14999                                ),
15000                                severity: Some(lsp::DiagnosticSeverity::ERROR),
15001                                ..Default::default()
15002                            },
15003                        ],
15004                    },
15005                    None,
15006                    DiagnosticSourceKind::Pushed,
15007                    &[],
15008                    cx,
15009                )
15010                .unwrap()
15011        });
15012    });
15013
15014    executor.run_until_parked();
15015
15016    cx.update_editor(|editor, window, cx| {
15017        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
15018    });
15019
15020    cx.assert_editor_state(indoc! {"
15021        fn func(abc def: i32) -> ˇu32 {
15022        }
15023    "});
15024
15025    cx.update_editor(|editor, window, cx| {
15026        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
15027    });
15028
15029    cx.assert_editor_state(indoc! {"
15030        fn func(abc ˇdef: i32) -> u32 {
15031        }
15032    "});
15033
15034    cx.update_editor(|editor, window, cx| {
15035        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
15036    });
15037
15038    cx.assert_editor_state(indoc! {"
15039        fn func(abcˇ def: i32) -> u32 {
15040        }
15041    "});
15042
15043    cx.update_editor(|editor, window, cx| {
15044        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
15045    });
15046
15047    cx.assert_editor_state(indoc! {"
15048        fn func(abc def: i32) -> ˇu32 {
15049        }
15050    "});
15051}
15052
15053#[gpui::test]
15054async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
15055    init_test(cx, |_| {});
15056
15057    let mut cx = EditorTestContext::new(cx).await;
15058
15059    let diff_base = r#"
15060        use some::mod;
15061
15062        const A: u32 = 42;
15063
15064        fn main() {
15065            println!("hello");
15066
15067            println!("world");
15068        }
15069        "#
15070    .unindent();
15071
15072    // Edits are modified, removed, modified, added
15073    cx.set_state(
15074        &r#"
15075        use some::modified;
15076
15077        ˇ
15078        fn main() {
15079            println!("hello there");
15080
15081            println!("around the");
15082            println!("world");
15083        }
15084        "#
15085        .unindent(),
15086    );
15087
15088    cx.set_head_text(&diff_base);
15089    executor.run_until_parked();
15090
15091    cx.update_editor(|editor, window, cx| {
15092        //Wrap around the bottom of the buffer
15093        for _ in 0..3 {
15094            editor.go_to_next_hunk(&GoToHunk, window, cx);
15095        }
15096    });
15097
15098    cx.assert_editor_state(
15099        &r#"
15100        ˇuse some::modified;
15101
15102
15103        fn main() {
15104            println!("hello there");
15105
15106            println!("around the");
15107            println!("world");
15108        }
15109        "#
15110        .unindent(),
15111    );
15112
15113    cx.update_editor(|editor, window, cx| {
15114        //Wrap around the top of the buffer
15115        for _ in 0..2 {
15116            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
15117        }
15118    });
15119
15120    cx.assert_editor_state(
15121        &r#"
15122        use some::modified;
15123
15124
15125        fn main() {
15126        ˇ    println!("hello there");
15127
15128            println!("around the");
15129            println!("world");
15130        }
15131        "#
15132        .unindent(),
15133    );
15134
15135    cx.update_editor(|editor, window, cx| {
15136        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
15137    });
15138
15139    cx.assert_editor_state(
15140        &r#"
15141        use some::modified;
15142
15143        ˇ
15144        fn main() {
15145            println!("hello there");
15146
15147            println!("around the");
15148            println!("world");
15149        }
15150        "#
15151        .unindent(),
15152    );
15153
15154    cx.update_editor(|editor, window, cx| {
15155        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
15156    });
15157
15158    cx.assert_editor_state(
15159        &r#"
15160        ˇuse some::modified;
15161
15162
15163        fn main() {
15164            println!("hello there");
15165
15166            println!("around the");
15167            println!("world");
15168        }
15169        "#
15170        .unindent(),
15171    );
15172
15173    cx.update_editor(|editor, window, cx| {
15174        for _ in 0..2 {
15175            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
15176        }
15177    });
15178
15179    cx.assert_editor_state(
15180        &r#"
15181        use some::modified;
15182
15183
15184        fn main() {
15185        ˇ    println!("hello there");
15186
15187            println!("around the");
15188            println!("world");
15189        }
15190        "#
15191        .unindent(),
15192    );
15193
15194    cx.update_editor(|editor, window, cx| {
15195        editor.fold(&Fold, window, cx);
15196    });
15197
15198    cx.update_editor(|editor, window, cx| {
15199        editor.go_to_next_hunk(&GoToHunk, window, cx);
15200    });
15201
15202    cx.assert_editor_state(
15203        &r#"
15204        ˇuse some::modified;
15205
15206
15207        fn main() {
15208            println!("hello there");
15209
15210            println!("around the");
15211            println!("world");
15212        }
15213        "#
15214        .unindent(),
15215    );
15216}
15217
15218#[test]
15219fn test_split_words() {
15220    fn split(text: &str) -> Vec<&str> {
15221        split_words(text).collect()
15222    }
15223
15224    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
15225    assert_eq!(split("hello_world"), &["hello_", "world"]);
15226    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
15227    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
15228    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
15229    assert_eq!(split("helloworld"), &["helloworld"]);
15230
15231    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
15232}
15233
15234#[gpui::test]
15235async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
15236    init_test(cx, |_| {});
15237
15238    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
15239    let mut assert = |before, after| {
15240        let _state_context = cx.set_state(before);
15241        cx.run_until_parked();
15242        cx.update_editor(|editor, window, cx| {
15243            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
15244        });
15245        cx.run_until_parked();
15246        cx.assert_editor_state(after);
15247    };
15248
15249    // Outside bracket jumps to outside of matching bracket
15250    assert("console.logˇ(var);", "console.log(var)ˇ;");
15251    assert("console.log(var)ˇ;", "console.logˇ(var);");
15252
15253    // Inside bracket jumps to inside of matching bracket
15254    assert("console.log(ˇvar);", "console.log(varˇ);");
15255    assert("console.log(varˇ);", "console.log(ˇvar);");
15256
15257    // When outside a bracket and inside, favor jumping to the inside bracket
15258    assert(
15259        "console.log('foo', [1, 2, 3]ˇ);",
15260        "console.log(ˇ'foo', [1, 2, 3]);",
15261    );
15262    assert(
15263        "console.log(ˇ'foo', [1, 2, 3]);",
15264        "console.log('foo', [1, 2, 3]ˇ);",
15265    );
15266
15267    // Bias forward if two options are equally likely
15268    assert(
15269        "let result = curried_fun()ˇ();",
15270        "let result = curried_fun()()ˇ;",
15271    );
15272
15273    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
15274    assert(
15275        indoc! {"
15276            function test() {
15277                console.log('test')ˇ
15278            }"},
15279        indoc! {"
15280            function test() {
15281                console.logˇ('test')
15282            }"},
15283    );
15284}
15285
15286#[gpui::test]
15287async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
15288    init_test(cx, |_| {});
15289
15290    let fs = FakeFs::new(cx.executor());
15291    fs.insert_tree(
15292        path!("/a"),
15293        json!({
15294            "main.rs": "fn main() { let a = 5; }",
15295            "other.rs": "// Test file",
15296        }),
15297    )
15298    .await;
15299    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15300
15301    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15302    language_registry.add(Arc::new(Language::new(
15303        LanguageConfig {
15304            name: "Rust".into(),
15305            matcher: LanguageMatcher {
15306                path_suffixes: vec!["rs".to_string()],
15307                ..Default::default()
15308            },
15309            brackets: BracketPairConfig {
15310                pairs: vec![BracketPair {
15311                    start: "{".to_string(),
15312                    end: "}".to_string(),
15313                    close: true,
15314                    surround: true,
15315                    newline: true,
15316                }],
15317                disabled_scopes_by_bracket_ix: Vec::new(),
15318            },
15319            ..Default::default()
15320        },
15321        Some(tree_sitter_rust::LANGUAGE.into()),
15322    )));
15323    let mut fake_servers = language_registry.register_fake_lsp(
15324        "Rust",
15325        FakeLspAdapter {
15326            capabilities: lsp::ServerCapabilities {
15327                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
15328                    first_trigger_character: "{".to_string(),
15329                    more_trigger_character: None,
15330                }),
15331                ..Default::default()
15332            },
15333            ..Default::default()
15334        },
15335    );
15336
15337    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15338
15339    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15340
15341    let worktree_id = workspace
15342        .update(cx, |workspace, _, cx| {
15343            workspace.project().update(cx, |project, cx| {
15344                project.worktrees(cx).next().unwrap().read(cx).id()
15345            })
15346        })
15347        .unwrap();
15348
15349    let buffer = project
15350        .update(cx, |project, cx| {
15351            project.open_local_buffer(path!("/a/main.rs"), cx)
15352        })
15353        .await
15354        .unwrap();
15355    let editor_handle = workspace
15356        .update(cx, |workspace, window, cx| {
15357            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
15358        })
15359        .unwrap()
15360        .await
15361        .unwrap()
15362        .downcast::<Editor>()
15363        .unwrap();
15364
15365    cx.executor().start_waiting();
15366    let fake_server = fake_servers.next().await.unwrap();
15367
15368    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
15369        |params, _| async move {
15370            assert_eq!(
15371                params.text_document_position.text_document.uri,
15372                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
15373            );
15374            assert_eq!(
15375                params.text_document_position.position,
15376                lsp::Position::new(0, 21),
15377            );
15378
15379            Ok(Some(vec![lsp::TextEdit {
15380                new_text: "]".to_string(),
15381                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15382            }]))
15383        },
15384    );
15385
15386    editor_handle.update_in(cx, |editor, window, cx| {
15387        window.focus(&editor.focus_handle(cx));
15388        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15389            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
15390        });
15391        editor.handle_input("{", window, cx);
15392    });
15393
15394    cx.executor().run_until_parked();
15395
15396    buffer.update(cx, |buffer, _| {
15397        assert_eq!(
15398            buffer.text(),
15399            "fn main() { let a = {5}; }",
15400            "No extra braces from on type formatting should appear in the buffer"
15401        )
15402    });
15403}
15404
15405#[gpui::test(iterations = 20, seeds(31))]
15406async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
15407    init_test(cx, |_| {});
15408
15409    let mut cx = EditorLspTestContext::new_rust(
15410        lsp::ServerCapabilities {
15411            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
15412                first_trigger_character: ".".to_string(),
15413                more_trigger_character: None,
15414            }),
15415            ..Default::default()
15416        },
15417        cx,
15418    )
15419    .await;
15420
15421    cx.update_buffer(|buffer, _| {
15422        // This causes autoindent to be async.
15423        buffer.set_sync_parse_timeout(Duration::ZERO)
15424    });
15425
15426    cx.set_state("fn c() {\n    d()ˇ\n}\n");
15427    cx.simulate_keystroke("\n");
15428    cx.run_until_parked();
15429
15430    let buffer_cloned =
15431        cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap().clone());
15432    let mut request =
15433        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
15434            let buffer_cloned = buffer_cloned.clone();
15435            async move {
15436                buffer_cloned.update(&mut cx, |buffer, _| {
15437                    assert_eq!(
15438                        buffer.text(),
15439                        "fn c() {\n    d()\n        .\n}\n",
15440                        "OnTypeFormatting should triggered after autoindent applied"
15441                    )
15442                })?;
15443
15444                Ok(Some(vec![]))
15445            }
15446        });
15447
15448    cx.simulate_keystroke(".");
15449    cx.run_until_parked();
15450
15451    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
15452    assert!(request.next().await.is_some());
15453    request.close();
15454    assert!(request.next().await.is_none());
15455}
15456
15457#[gpui::test]
15458async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
15459    init_test(cx, |_| {});
15460
15461    let fs = FakeFs::new(cx.executor());
15462    fs.insert_tree(
15463        path!("/a"),
15464        json!({
15465            "main.rs": "fn main() { let a = 5; }",
15466            "other.rs": "// Test file",
15467        }),
15468    )
15469    .await;
15470
15471    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15472
15473    let server_restarts = Arc::new(AtomicUsize::new(0));
15474    let closure_restarts = Arc::clone(&server_restarts);
15475    let language_server_name = "test language server";
15476    let language_name: LanguageName = "Rust".into();
15477
15478    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15479    language_registry.add(Arc::new(Language::new(
15480        LanguageConfig {
15481            name: language_name.clone(),
15482            matcher: LanguageMatcher {
15483                path_suffixes: vec!["rs".to_string()],
15484                ..Default::default()
15485            },
15486            ..Default::default()
15487        },
15488        Some(tree_sitter_rust::LANGUAGE.into()),
15489    )));
15490    let mut fake_servers = language_registry.register_fake_lsp(
15491        "Rust",
15492        FakeLspAdapter {
15493            name: language_server_name,
15494            initialization_options: Some(json!({
15495                "testOptionValue": true
15496            })),
15497            initializer: Some(Box::new(move |fake_server| {
15498                let task_restarts = Arc::clone(&closure_restarts);
15499                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
15500                    task_restarts.fetch_add(1, atomic::Ordering::Release);
15501                    futures::future::ready(Ok(()))
15502                });
15503            })),
15504            ..Default::default()
15505        },
15506    );
15507
15508    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15509    let _buffer = project
15510        .update(cx, |project, cx| {
15511            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
15512        })
15513        .await
15514        .unwrap();
15515    let _fake_server = fake_servers.next().await.unwrap();
15516    update_test_language_settings(cx, |language_settings| {
15517        language_settings.languages.0.insert(
15518            language_name.clone(),
15519            LanguageSettingsContent {
15520                tab_size: NonZeroU32::new(8),
15521                ..Default::default()
15522            },
15523        );
15524    });
15525    cx.executor().run_until_parked();
15526    assert_eq!(
15527        server_restarts.load(atomic::Ordering::Acquire),
15528        0,
15529        "Should not restart LSP server on an unrelated change"
15530    );
15531
15532    update_test_project_settings(cx, |project_settings| {
15533        project_settings.lsp.insert(
15534            "Some other server name".into(),
15535            LspSettings {
15536                binary: None,
15537                settings: None,
15538                initialization_options: Some(json!({
15539                    "some other init value": false
15540                })),
15541                enable_lsp_tasks: false,
15542            },
15543        );
15544    });
15545    cx.executor().run_until_parked();
15546    assert_eq!(
15547        server_restarts.load(atomic::Ordering::Acquire),
15548        0,
15549        "Should not restart LSP server on an unrelated LSP settings change"
15550    );
15551
15552    update_test_project_settings(cx, |project_settings| {
15553        project_settings.lsp.insert(
15554            language_server_name.into(),
15555            LspSettings {
15556                binary: None,
15557                settings: None,
15558                initialization_options: Some(json!({
15559                    "anotherInitValue": false
15560                })),
15561                enable_lsp_tasks: false,
15562            },
15563        );
15564    });
15565    cx.executor().run_until_parked();
15566    assert_eq!(
15567        server_restarts.load(atomic::Ordering::Acquire),
15568        1,
15569        "Should restart LSP server on a related LSP settings change"
15570    );
15571
15572    update_test_project_settings(cx, |project_settings| {
15573        project_settings.lsp.insert(
15574            language_server_name.into(),
15575            LspSettings {
15576                binary: None,
15577                settings: None,
15578                initialization_options: Some(json!({
15579                    "anotherInitValue": false
15580                })),
15581                enable_lsp_tasks: false,
15582            },
15583        );
15584    });
15585    cx.executor().run_until_parked();
15586    assert_eq!(
15587        server_restarts.load(atomic::Ordering::Acquire),
15588        1,
15589        "Should not restart LSP server on a related LSP settings change that is the same"
15590    );
15591
15592    update_test_project_settings(cx, |project_settings| {
15593        project_settings.lsp.insert(
15594            language_server_name.into(),
15595            LspSettings {
15596                binary: None,
15597                settings: None,
15598                initialization_options: None,
15599                enable_lsp_tasks: false,
15600            },
15601        );
15602    });
15603    cx.executor().run_until_parked();
15604    assert_eq!(
15605        server_restarts.load(atomic::Ordering::Acquire),
15606        2,
15607        "Should restart LSP server on another related LSP settings change"
15608    );
15609}
15610
15611#[gpui::test]
15612async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
15613    init_test(cx, |_| {});
15614
15615    let mut cx = EditorLspTestContext::new_rust(
15616        lsp::ServerCapabilities {
15617            completion_provider: Some(lsp::CompletionOptions {
15618                trigger_characters: Some(vec![".".to_string()]),
15619                resolve_provider: Some(true),
15620                ..Default::default()
15621            }),
15622            ..Default::default()
15623        },
15624        cx,
15625    )
15626    .await;
15627
15628    cx.set_state("fn main() { let a = 2ˇ; }");
15629    cx.simulate_keystroke(".");
15630    let completion_item = lsp::CompletionItem {
15631        label: "some".into(),
15632        kind: Some(lsp::CompletionItemKind::SNIPPET),
15633        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15634        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15635            kind: lsp::MarkupKind::Markdown,
15636            value: "```rust\nSome(2)\n```".to_string(),
15637        })),
15638        deprecated: Some(false),
15639        sort_text: Some("fffffff2".to_string()),
15640        filter_text: Some("some".to_string()),
15641        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15642        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15643            range: lsp::Range {
15644                start: lsp::Position {
15645                    line: 0,
15646                    character: 22,
15647                },
15648                end: lsp::Position {
15649                    line: 0,
15650                    character: 22,
15651                },
15652            },
15653            new_text: "Some(2)".to_string(),
15654        })),
15655        additional_text_edits: Some(vec![lsp::TextEdit {
15656            range: lsp::Range {
15657                start: lsp::Position {
15658                    line: 0,
15659                    character: 20,
15660                },
15661                end: lsp::Position {
15662                    line: 0,
15663                    character: 22,
15664                },
15665            },
15666            new_text: "".to_string(),
15667        }]),
15668        ..Default::default()
15669    };
15670
15671    let closure_completion_item = completion_item.clone();
15672    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15673        let task_completion_item = closure_completion_item.clone();
15674        async move {
15675            Ok(Some(lsp::CompletionResponse::Array(vec![
15676                task_completion_item,
15677            ])))
15678        }
15679    });
15680
15681    request.next().await;
15682
15683    cx.condition(|editor, _| editor.context_menu_visible())
15684        .await;
15685    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15686        editor
15687            .confirm_completion(&ConfirmCompletion::default(), window, cx)
15688            .unwrap()
15689    });
15690    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
15691
15692    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
15693        let task_completion_item = completion_item.clone();
15694        async move { Ok(task_completion_item) }
15695    })
15696    .next()
15697    .await
15698    .unwrap();
15699    apply_additional_edits.await.unwrap();
15700    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
15701}
15702
15703#[gpui::test]
15704async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
15705    init_test(cx, |_| {});
15706
15707    let mut cx = EditorLspTestContext::new_rust(
15708        lsp::ServerCapabilities {
15709            completion_provider: Some(lsp::CompletionOptions {
15710                trigger_characters: Some(vec![".".to_string()]),
15711                resolve_provider: Some(true),
15712                ..Default::default()
15713            }),
15714            ..Default::default()
15715        },
15716        cx,
15717    )
15718    .await;
15719
15720    cx.set_state("fn main() { let a = 2ˇ; }");
15721    cx.simulate_keystroke(".");
15722
15723    let item1 = lsp::CompletionItem {
15724        label: "method id()".to_string(),
15725        filter_text: Some("id".to_string()),
15726        detail: None,
15727        documentation: None,
15728        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15729            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15730            new_text: ".id".to_string(),
15731        })),
15732        ..lsp::CompletionItem::default()
15733    };
15734
15735    let item2 = lsp::CompletionItem {
15736        label: "other".to_string(),
15737        filter_text: Some("other".to_string()),
15738        detail: None,
15739        documentation: None,
15740        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15741            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15742            new_text: ".other".to_string(),
15743        })),
15744        ..lsp::CompletionItem::default()
15745    };
15746
15747    let item1 = item1.clone();
15748    cx.set_request_handler::<lsp::request::Completion, _, _>({
15749        let item1 = item1.clone();
15750        move |_, _, _| {
15751            let item1 = item1.clone();
15752            let item2 = item2.clone();
15753            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
15754        }
15755    })
15756    .next()
15757    .await;
15758
15759    cx.condition(|editor, _| editor.context_menu_visible())
15760        .await;
15761    cx.update_editor(|editor, _, _| {
15762        let context_menu = editor.context_menu.borrow_mut();
15763        let context_menu = context_menu
15764            .as_ref()
15765            .expect("Should have the context menu deployed");
15766        match context_menu {
15767            CodeContextMenu::Completions(completions_menu) => {
15768                let completions = completions_menu.completions.borrow_mut();
15769                assert_eq!(
15770                    completions
15771                        .iter()
15772                        .map(|completion| &completion.label.text)
15773                        .collect::<Vec<_>>(),
15774                    vec!["method id()", "other"]
15775                )
15776            }
15777            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15778        }
15779    });
15780
15781    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
15782        let item1 = item1.clone();
15783        move |_, item_to_resolve, _| {
15784            let item1 = item1.clone();
15785            async move {
15786                if item1 == item_to_resolve {
15787                    Ok(lsp::CompletionItem {
15788                        label: "method id()".to_string(),
15789                        filter_text: Some("id".to_string()),
15790                        detail: Some("Now resolved!".to_string()),
15791                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
15792                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15793                            range: lsp::Range::new(
15794                                lsp::Position::new(0, 22),
15795                                lsp::Position::new(0, 22),
15796                            ),
15797                            new_text: ".id".to_string(),
15798                        })),
15799                        ..lsp::CompletionItem::default()
15800                    })
15801                } else {
15802                    Ok(item_to_resolve)
15803                }
15804            }
15805        }
15806    })
15807    .next()
15808    .await
15809    .unwrap();
15810    cx.run_until_parked();
15811
15812    cx.update_editor(|editor, window, cx| {
15813        editor.context_menu_next(&Default::default(), window, cx);
15814    });
15815
15816    cx.update_editor(|editor, _, _| {
15817        let context_menu = editor.context_menu.borrow_mut();
15818        let context_menu = context_menu
15819            .as_ref()
15820            .expect("Should have the context menu deployed");
15821        match context_menu {
15822            CodeContextMenu::Completions(completions_menu) => {
15823                let completions = completions_menu.completions.borrow_mut();
15824                assert_eq!(
15825                    completions
15826                        .iter()
15827                        .map(|completion| &completion.label.text)
15828                        .collect::<Vec<_>>(),
15829                    vec!["method id() Now resolved!", "other"],
15830                    "Should update first completion label, but not second as the filter text did not match."
15831                );
15832            }
15833            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15834        }
15835    });
15836}
15837
15838#[gpui::test]
15839async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
15840    init_test(cx, |_| {});
15841    let mut cx = EditorLspTestContext::new_rust(
15842        lsp::ServerCapabilities {
15843            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
15844            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
15845            completion_provider: Some(lsp::CompletionOptions {
15846                resolve_provider: Some(true),
15847                ..Default::default()
15848            }),
15849            ..Default::default()
15850        },
15851        cx,
15852    )
15853    .await;
15854    cx.set_state(indoc! {"
15855        struct TestStruct {
15856            field: i32
15857        }
15858
15859        fn mainˇ() {
15860            let unused_var = 42;
15861            let test_struct = TestStruct { field: 42 };
15862        }
15863    "});
15864    let symbol_range = cx.lsp_range(indoc! {"
15865        struct TestStruct {
15866            field: i32
15867        }
15868
15869        «fn main»() {
15870            let unused_var = 42;
15871            let test_struct = TestStruct { field: 42 };
15872        }
15873    "});
15874    let mut hover_requests =
15875        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
15876            Ok(Some(lsp::Hover {
15877                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
15878                    kind: lsp::MarkupKind::Markdown,
15879                    value: "Function documentation".to_string(),
15880                }),
15881                range: Some(symbol_range),
15882            }))
15883        });
15884
15885    // Case 1: Test that code action menu hide hover popover
15886    cx.dispatch_action(Hover);
15887    hover_requests.next().await;
15888    cx.condition(|editor, _| editor.hover_state.visible()).await;
15889    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
15890        move |_, _, _| async move {
15891            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
15892                lsp::CodeAction {
15893                    title: "Remove unused variable".to_string(),
15894                    kind: Some(CodeActionKind::QUICKFIX),
15895                    edit: Some(lsp::WorkspaceEdit {
15896                        changes: Some(
15897                            [(
15898                                lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
15899                                vec![lsp::TextEdit {
15900                                    range: lsp::Range::new(
15901                                        lsp::Position::new(5, 4),
15902                                        lsp::Position::new(5, 27),
15903                                    ),
15904                                    new_text: "".to_string(),
15905                                }],
15906                            )]
15907                            .into_iter()
15908                            .collect(),
15909                        ),
15910                        ..Default::default()
15911                    }),
15912                    ..Default::default()
15913                },
15914            )]))
15915        },
15916    );
15917    cx.update_editor(|editor, window, cx| {
15918        editor.toggle_code_actions(
15919            &ToggleCodeActions {
15920                deployed_from: None,
15921                quick_launch: false,
15922            },
15923            window,
15924            cx,
15925        );
15926    });
15927    code_action_requests.next().await;
15928    cx.run_until_parked();
15929    cx.condition(|editor, _| editor.context_menu_visible())
15930        .await;
15931    cx.update_editor(|editor, _, _| {
15932        assert!(
15933            !editor.hover_state.visible(),
15934            "Hover popover should be hidden when code action menu is shown"
15935        );
15936        // Hide code actions
15937        editor.context_menu.take();
15938    });
15939
15940    // Case 2: Test that code completions hide hover popover
15941    cx.dispatch_action(Hover);
15942    hover_requests.next().await;
15943    cx.condition(|editor, _| editor.hover_state.visible()).await;
15944    let counter = Arc::new(AtomicUsize::new(0));
15945    let mut completion_requests =
15946        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15947            let counter = counter.clone();
15948            async move {
15949                counter.fetch_add(1, atomic::Ordering::Release);
15950                Ok(Some(lsp::CompletionResponse::Array(vec![
15951                    lsp::CompletionItem {
15952                        label: "main".into(),
15953                        kind: Some(lsp::CompletionItemKind::FUNCTION),
15954                        detail: Some("() -> ()".to_string()),
15955                        ..Default::default()
15956                    },
15957                    lsp::CompletionItem {
15958                        label: "TestStruct".into(),
15959                        kind: Some(lsp::CompletionItemKind::STRUCT),
15960                        detail: Some("struct TestStruct".to_string()),
15961                        ..Default::default()
15962                    },
15963                ])))
15964            }
15965        });
15966    cx.update_editor(|editor, window, cx| {
15967        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15968    });
15969    completion_requests.next().await;
15970    cx.condition(|editor, _| editor.context_menu_visible())
15971        .await;
15972    cx.update_editor(|editor, _, _| {
15973        assert!(
15974            !editor.hover_state.visible(),
15975            "Hover popover should be hidden when completion menu is shown"
15976        );
15977    });
15978}
15979
15980#[gpui::test]
15981async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
15982    init_test(cx, |_| {});
15983
15984    let mut cx = EditorLspTestContext::new_rust(
15985        lsp::ServerCapabilities {
15986            completion_provider: Some(lsp::CompletionOptions {
15987                trigger_characters: Some(vec![".".to_string()]),
15988                resolve_provider: Some(true),
15989                ..Default::default()
15990            }),
15991            ..Default::default()
15992        },
15993        cx,
15994    )
15995    .await;
15996
15997    cx.set_state("fn main() { let a = 2ˇ; }");
15998    cx.simulate_keystroke(".");
15999
16000    let unresolved_item_1 = lsp::CompletionItem {
16001        label: "id".to_string(),
16002        filter_text: Some("id".to_string()),
16003        detail: None,
16004        documentation: None,
16005        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16006            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16007            new_text: ".id".to_string(),
16008        })),
16009        ..lsp::CompletionItem::default()
16010    };
16011    let resolved_item_1 = lsp::CompletionItem {
16012        additional_text_edits: Some(vec![lsp::TextEdit {
16013            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
16014            new_text: "!!".to_string(),
16015        }]),
16016        ..unresolved_item_1.clone()
16017    };
16018    let unresolved_item_2 = lsp::CompletionItem {
16019        label: "other".to_string(),
16020        filter_text: Some("other".to_string()),
16021        detail: None,
16022        documentation: None,
16023        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16024            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16025            new_text: ".other".to_string(),
16026        })),
16027        ..lsp::CompletionItem::default()
16028    };
16029    let resolved_item_2 = lsp::CompletionItem {
16030        additional_text_edits: Some(vec![lsp::TextEdit {
16031            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
16032            new_text: "??".to_string(),
16033        }]),
16034        ..unresolved_item_2.clone()
16035    };
16036
16037    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
16038    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
16039    cx.lsp
16040        .server
16041        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
16042            let unresolved_item_1 = unresolved_item_1.clone();
16043            let resolved_item_1 = resolved_item_1.clone();
16044            let unresolved_item_2 = unresolved_item_2.clone();
16045            let resolved_item_2 = resolved_item_2.clone();
16046            let resolve_requests_1 = resolve_requests_1.clone();
16047            let resolve_requests_2 = resolve_requests_2.clone();
16048            move |unresolved_request, _| {
16049                let unresolved_item_1 = unresolved_item_1.clone();
16050                let resolved_item_1 = resolved_item_1.clone();
16051                let unresolved_item_2 = unresolved_item_2.clone();
16052                let resolved_item_2 = resolved_item_2.clone();
16053                let resolve_requests_1 = resolve_requests_1.clone();
16054                let resolve_requests_2 = resolve_requests_2.clone();
16055                async move {
16056                    if unresolved_request == unresolved_item_1 {
16057                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
16058                        Ok(resolved_item_1.clone())
16059                    } else if unresolved_request == unresolved_item_2 {
16060                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
16061                        Ok(resolved_item_2.clone())
16062                    } else {
16063                        panic!("Unexpected completion item {unresolved_request:?}")
16064                    }
16065                }
16066            }
16067        })
16068        .detach();
16069
16070    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16071        let unresolved_item_1 = unresolved_item_1.clone();
16072        let unresolved_item_2 = unresolved_item_2.clone();
16073        async move {
16074            Ok(Some(lsp::CompletionResponse::Array(vec![
16075                unresolved_item_1,
16076                unresolved_item_2,
16077            ])))
16078        }
16079    })
16080    .next()
16081    .await;
16082
16083    cx.condition(|editor, _| editor.context_menu_visible())
16084        .await;
16085    cx.update_editor(|editor, _, _| {
16086        let context_menu = editor.context_menu.borrow_mut();
16087        let context_menu = context_menu
16088            .as_ref()
16089            .expect("Should have the context menu deployed");
16090        match context_menu {
16091            CodeContextMenu::Completions(completions_menu) => {
16092                let completions = completions_menu.completions.borrow_mut();
16093                assert_eq!(
16094                    completions
16095                        .iter()
16096                        .map(|completion| &completion.label.text)
16097                        .collect::<Vec<_>>(),
16098                    vec!["id", "other"]
16099                )
16100            }
16101            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
16102        }
16103    });
16104    cx.run_until_parked();
16105
16106    cx.update_editor(|editor, window, cx| {
16107        editor.context_menu_next(&ContextMenuNext, window, cx);
16108    });
16109    cx.run_until_parked();
16110    cx.update_editor(|editor, window, cx| {
16111        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
16112    });
16113    cx.run_until_parked();
16114    cx.update_editor(|editor, window, cx| {
16115        editor.context_menu_next(&ContextMenuNext, window, cx);
16116    });
16117    cx.run_until_parked();
16118    cx.update_editor(|editor, window, cx| {
16119        editor
16120            .compose_completion(&ComposeCompletion::default(), window, cx)
16121            .expect("No task returned")
16122    })
16123    .await
16124    .expect("Completion failed");
16125    cx.run_until_parked();
16126
16127    cx.update_editor(|editor, _, cx| {
16128        assert_eq!(
16129            resolve_requests_1.load(atomic::Ordering::Acquire),
16130            1,
16131            "Should always resolve once despite multiple selections"
16132        );
16133        assert_eq!(
16134            resolve_requests_2.load(atomic::Ordering::Acquire),
16135            1,
16136            "Should always resolve once after multiple selections and applying the completion"
16137        );
16138        assert_eq!(
16139            editor.text(cx),
16140            "fn main() { let a = ??.other; }",
16141            "Should use resolved data when applying the completion"
16142        );
16143    });
16144}
16145
16146#[gpui::test]
16147async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
16148    init_test(cx, |_| {});
16149
16150    let item_0 = lsp::CompletionItem {
16151        label: "abs".into(),
16152        insert_text: Some("abs".into()),
16153        data: Some(json!({ "very": "special"})),
16154        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
16155        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
16156            lsp::InsertReplaceEdit {
16157                new_text: "abs".to_string(),
16158                insert: lsp::Range::default(),
16159                replace: lsp::Range::default(),
16160            },
16161        )),
16162        ..lsp::CompletionItem::default()
16163    };
16164    let items = iter::once(item_0.clone())
16165        .chain((11..51).map(|i| lsp::CompletionItem {
16166            label: format!("item_{}", i),
16167            insert_text: Some(format!("item_{}", i)),
16168            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
16169            ..lsp::CompletionItem::default()
16170        }))
16171        .collect::<Vec<_>>();
16172
16173    let default_commit_characters = vec!["?".to_string()];
16174    let default_data = json!({ "default": "data"});
16175    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
16176    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
16177    let default_edit_range = lsp::Range {
16178        start: lsp::Position {
16179            line: 0,
16180            character: 5,
16181        },
16182        end: lsp::Position {
16183            line: 0,
16184            character: 5,
16185        },
16186    };
16187
16188    let mut cx = EditorLspTestContext::new_rust(
16189        lsp::ServerCapabilities {
16190            completion_provider: Some(lsp::CompletionOptions {
16191                trigger_characters: Some(vec![".".to_string()]),
16192                resolve_provider: Some(true),
16193                ..Default::default()
16194            }),
16195            ..Default::default()
16196        },
16197        cx,
16198    )
16199    .await;
16200
16201    cx.set_state("fn main() { let a = 2ˇ; }");
16202    cx.simulate_keystroke(".");
16203
16204    let completion_data = default_data.clone();
16205    let completion_characters = default_commit_characters.clone();
16206    let completion_items = items.clone();
16207    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16208        let default_data = completion_data.clone();
16209        let default_commit_characters = completion_characters.clone();
16210        let items = completion_items.clone();
16211        async move {
16212            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16213                items,
16214                item_defaults: Some(lsp::CompletionListItemDefaults {
16215                    data: Some(default_data.clone()),
16216                    commit_characters: Some(default_commit_characters.clone()),
16217                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
16218                        default_edit_range,
16219                    )),
16220                    insert_text_format: Some(default_insert_text_format),
16221                    insert_text_mode: Some(default_insert_text_mode),
16222                }),
16223                ..lsp::CompletionList::default()
16224            })))
16225        }
16226    })
16227    .next()
16228    .await;
16229
16230    let resolved_items = Arc::new(Mutex::new(Vec::new()));
16231    cx.lsp
16232        .server
16233        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
16234            let closure_resolved_items = resolved_items.clone();
16235            move |item_to_resolve, _| {
16236                let closure_resolved_items = closure_resolved_items.clone();
16237                async move {
16238                    closure_resolved_items.lock().push(item_to_resolve.clone());
16239                    Ok(item_to_resolve)
16240                }
16241            }
16242        })
16243        .detach();
16244
16245    cx.condition(|editor, _| editor.context_menu_visible())
16246        .await;
16247    cx.run_until_parked();
16248    cx.update_editor(|editor, _, _| {
16249        let menu = editor.context_menu.borrow_mut();
16250        match menu.as_ref().expect("should have the completions menu") {
16251            CodeContextMenu::Completions(completions_menu) => {
16252                assert_eq!(
16253                    completions_menu
16254                        .entries
16255                        .borrow()
16256                        .iter()
16257                        .map(|mat| mat.string.clone())
16258                        .collect::<Vec<String>>(),
16259                    items
16260                        .iter()
16261                        .map(|completion| completion.label.clone())
16262                        .collect::<Vec<String>>()
16263                );
16264            }
16265            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
16266        }
16267    });
16268    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
16269    // with 4 from the end.
16270    assert_eq!(
16271        *resolved_items.lock(),
16272        [&items[0..16], &items[items.len() - 4..items.len()]]
16273            .concat()
16274            .iter()
16275            .cloned()
16276            .map(|mut item| {
16277                if item.data.is_none() {
16278                    item.data = Some(default_data.clone());
16279                }
16280                item
16281            })
16282            .collect::<Vec<lsp::CompletionItem>>(),
16283        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
16284    );
16285    resolved_items.lock().clear();
16286
16287    cx.update_editor(|editor, window, cx| {
16288        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
16289    });
16290    cx.run_until_parked();
16291    // Completions that have already been resolved are skipped.
16292    assert_eq!(
16293        *resolved_items.lock(),
16294        items[items.len() - 17..items.len() - 4]
16295            .iter()
16296            .cloned()
16297            .map(|mut item| {
16298                if item.data.is_none() {
16299                    item.data = Some(default_data.clone());
16300                }
16301                item
16302            })
16303            .collect::<Vec<lsp::CompletionItem>>()
16304    );
16305    resolved_items.lock().clear();
16306}
16307
16308#[gpui::test]
16309async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
16310    init_test(cx, |_| {});
16311
16312    let mut cx = EditorLspTestContext::new(
16313        Language::new(
16314            LanguageConfig {
16315                matcher: LanguageMatcher {
16316                    path_suffixes: vec!["jsx".into()],
16317                    ..Default::default()
16318                },
16319                overrides: [(
16320                    "element".into(),
16321                    LanguageConfigOverride {
16322                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
16323                        ..Default::default()
16324                    },
16325                )]
16326                .into_iter()
16327                .collect(),
16328                ..Default::default()
16329            },
16330            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
16331        )
16332        .with_override_query("(jsx_self_closing_element) @element")
16333        .unwrap(),
16334        lsp::ServerCapabilities {
16335            completion_provider: Some(lsp::CompletionOptions {
16336                trigger_characters: Some(vec![":".to_string()]),
16337                ..Default::default()
16338            }),
16339            ..Default::default()
16340        },
16341        cx,
16342    )
16343    .await;
16344
16345    cx.lsp
16346        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16347            Ok(Some(lsp::CompletionResponse::Array(vec![
16348                lsp::CompletionItem {
16349                    label: "bg-blue".into(),
16350                    ..Default::default()
16351                },
16352                lsp::CompletionItem {
16353                    label: "bg-red".into(),
16354                    ..Default::default()
16355                },
16356                lsp::CompletionItem {
16357                    label: "bg-yellow".into(),
16358                    ..Default::default()
16359                },
16360            ])))
16361        });
16362
16363    cx.set_state(r#"<p class="bgˇ" />"#);
16364
16365    // Trigger completion when typing a dash, because the dash is an extra
16366    // word character in the 'element' scope, which contains the cursor.
16367    cx.simulate_keystroke("-");
16368    cx.executor().run_until_parked();
16369    cx.update_editor(|editor, _, _| {
16370        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16371        {
16372            assert_eq!(
16373                completion_menu_entries(&menu),
16374                &["bg-blue", "bg-red", "bg-yellow"]
16375            );
16376        } else {
16377            panic!("expected completion menu to be open");
16378        }
16379    });
16380
16381    cx.simulate_keystroke("l");
16382    cx.executor().run_until_parked();
16383    cx.update_editor(|editor, _, _| {
16384        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16385        {
16386            assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
16387        } else {
16388            panic!("expected completion menu to be open");
16389        }
16390    });
16391
16392    // When filtering completions, consider the character after the '-' to
16393    // be the start of a subword.
16394    cx.set_state(r#"<p class="yelˇ" />"#);
16395    cx.simulate_keystroke("l");
16396    cx.executor().run_until_parked();
16397    cx.update_editor(|editor, _, _| {
16398        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16399        {
16400            assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
16401        } else {
16402            panic!("expected completion menu to be open");
16403        }
16404    });
16405}
16406
16407fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
16408    let entries = menu.entries.borrow();
16409    entries.iter().map(|mat| mat.string.clone()).collect()
16410}
16411
16412#[gpui::test]
16413async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
16414    init_test(cx, |settings| {
16415        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
16416            Formatter::Prettier,
16417        )))
16418    });
16419
16420    let fs = FakeFs::new(cx.executor());
16421    fs.insert_file(path!("/file.ts"), Default::default()).await;
16422
16423    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
16424    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16425
16426    language_registry.add(Arc::new(Language::new(
16427        LanguageConfig {
16428            name: "TypeScript".into(),
16429            matcher: LanguageMatcher {
16430                path_suffixes: vec!["ts".to_string()],
16431                ..Default::default()
16432            },
16433            ..Default::default()
16434        },
16435        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
16436    )));
16437    update_test_language_settings(cx, |settings| {
16438        settings.defaults.prettier = Some(PrettierSettings {
16439            allowed: true,
16440            ..PrettierSettings::default()
16441        });
16442    });
16443
16444    let test_plugin = "test_plugin";
16445    let _ = language_registry.register_fake_lsp(
16446        "TypeScript",
16447        FakeLspAdapter {
16448            prettier_plugins: vec![test_plugin],
16449            ..Default::default()
16450        },
16451    );
16452
16453    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
16454    let buffer = project
16455        .update(cx, |project, cx| {
16456            project.open_local_buffer(path!("/file.ts"), cx)
16457        })
16458        .await
16459        .unwrap();
16460
16461    let buffer_text = "one\ntwo\nthree\n";
16462    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16463    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16464    editor.update_in(cx, |editor, window, cx| {
16465        editor.set_text(buffer_text, window, cx)
16466    });
16467
16468    editor
16469        .update_in(cx, |editor, window, cx| {
16470            editor.perform_format(
16471                project.clone(),
16472                FormatTrigger::Manual,
16473                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
16474                window,
16475                cx,
16476            )
16477        })
16478        .unwrap()
16479        .await;
16480    assert_eq!(
16481        editor.update(cx, |editor, cx| editor.text(cx)),
16482        buffer_text.to_string() + prettier_format_suffix,
16483        "Test prettier formatting was not applied to the original buffer text",
16484    );
16485
16486    update_test_language_settings(cx, |settings| {
16487        settings.defaults.formatter = Some(SelectedFormatter::Auto)
16488    });
16489    let format = editor.update_in(cx, |editor, window, cx| {
16490        editor.perform_format(
16491            project.clone(),
16492            FormatTrigger::Manual,
16493            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
16494            window,
16495            cx,
16496        )
16497    });
16498    format.await.unwrap();
16499    assert_eq!(
16500        editor.update(cx, |editor, cx| editor.text(cx)),
16501        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
16502        "Autoformatting (via test prettier) was not applied to the original buffer text",
16503    );
16504}
16505
16506#[gpui::test]
16507async fn test_addition_reverts(cx: &mut TestAppContext) {
16508    init_test(cx, |_| {});
16509    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16510    let base_text = indoc! {r#"
16511        struct Row;
16512        struct Row1;
16513        struct Row2;
16514
16515        struct Row4;
16516        struct Row5;
16517        struct Row6;
16518
16519        struct Row8;
16520        struct Row9;
16521        struct Row10;"#};
16522
16523    // When addition hunks are not adjacent to carets, no hunk revert is performed
16524    assert_hunk_revert(
16525        indoc! {r#"struct Row;
16526                   struct Row1;
16527                   struct Row1.1;
16528                   struct Row1.2;
16529                   struct Row2;ˇ
16530
16531                   struct Row4;
16532                   struct Row5;
16533                   struct Row6;
16534
16535                   struct Row8;
16536                   ˇstruct Row9;
16537                   struct Row9.1;
16538                   struct Row9.2;
16539                   struct Row9.3;
16540                   struct Row10;"#},
16541        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
16542        indoc! {r#"struct Row;
16543                   struct Row1;
16544                   struct Row1.1;
16545                   struct Row1.2;
16546                   struct Row2;ˇ
16547
16548                   struct Row4;
16549                   struct Row5;
16550                   struct Row6;
16551
16552                   struct Row8;
16553                   ˇstruct Row9;
16554                   struct Row9.1;
16555                   struct Row9.2;
16556                   struct Row9.3;
16557                   struct Row10;"#},
16558        base_text,
16559        &mut cx,
16560    );
16561    // Same for selections
16562    assert_hunk_revert(
16563        indoc! {r#"struct Row;
16564                   struct Row1;
16565                   struct Row2;
16566                   struct Row2.1;
16567                   struct Row2.2;
16568                   «ˇ
16569                   struct Row4;
16570                   struct» Row5;
16571                   «struct Row6;
16572                   ˇ»
16573                   struct Row9.1;
16574                   struct Row9.2;
16575                   struct Row9.3;
16576                   struct Row8;
16577                   struct Row9;
16578                   struct Row10;"#},
16579        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
16580        indoc! {r#"struct Row;
16581                   struct Row1;
16582                   struct Row2;
16583                   struct Row2.1;
16584                   struct Row2.2;
16585                   «ˇ
16586                   struct Row4;
16587                   struct» Row5;
16588                   «struct Row6;
16589                   ˇ»
16590                   struct Row9.1;
16591                   struct Row9.2;
16592                   struct Row9.3;
16593                   struct Row8;
16594                   struct Row9;
16595                   struct Row10;"#},
16596        base_text,
16597        &mut cx,
16598    );
16599
16600    // When carets and selections intersect the addition hunks, those are reverted.
16601    // Adjacent carets got merged.
16602    assert_hunk_revert(
16603        indoc! {r#"struct Row;
16604                   ˇ// something on the top
16605                   struct Row1;
16606                   struct Row2;
16607                   struct Roˇw3.1;
16608                   struct Row2.2;
16609                   struct Row2.3;ˇ
16610
16611                   struct Row4;
16612                   struct ˇRow5.1;
16613                   struct Row5.2;
16614                   struct «Rowˇ»5.3;
16615                   struct Row5;
16616                   struct Row6;
16617                   ˇ
16618                   struct Row9.1;
16619                   struct «Rowˇ»9.2;
16620                   struct «ˇRow»9.3;
16621                   struct Row8;
16622                   struct Row9;
16623                   «ˇ// something on bottom»
16624                   struct Row10;"#},
16625        vec![
16626            DiffHunkStatusKind::Added,
16627            DiffHunkStatusKind::Added,
16628            DiffHunkStatusKind::Added,
16629            DiffHunkStatusKind::Added,
16630            DiffHunkStatusKind::Added,
16631        ],
16632        indoc! {r#"struct Row;
16633                   ˇstruct Row1;
16634                   struct Row2;
16635                   ˇ
16636                   struct Row4;
16637                   ˇstruct Row5;
16638                   struct Row6;
16639                   ˇ
16640                   ˇstruct Row8;
16641                   struct Row9;
16642                   ˇstruct Row10;"#},
16643        base_text,
16644        &mut cx,
16645    );
16646}
16647
16648#[gpui::test]
16649async fn test_modification_reverts(cx: &mut TestAppContext) {
16650    init_test(cx, |_| {});
16651    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16652    let base_text = indoc! {r#"
16653        struct Row;
16654        struct Row1;
16655        struct Row2;
16656
16657        struct Row4;
16658        struct Row5;
16659        struct Row6;
16660
16661        struct Row8;
16662        struct Row9;
16663        struct Row10;"#};
16664
16665    // Modification hunks behave the same as the addition ones.
16666    assert_hunk_revert(
16667        indoc! {r#"struct Row;
16668                   struct Row1;
16669                   struct Row33;
16670                   ˇ
16671                   struct Row4;
16672                   struct Row5;
16673                   struct Row6;
16674                   ˇ
16675                   struct Row99;
16676                   struct Row9;
16677                   struct Row10;"#},
16678        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
16679        indoc! {r#"struct Row;
16680                   struct Row1;
16681                   struct Row33;
16682                   ˇ
16683                   struct Row4;
16684                   struct Row5;
16685                   struct Row6;
16686                   ˇ
16687                   struct Row99;
16688                   struct Row9;
16689                   struct Row10;"#},
16690        base_text,
16691        &mut cx,
16692    );
16693    assert_hunk_revert(
16694        indoc! {r#"struct Row;
16695                   struct Row1;
16696                   struct Row33;
16697                   «ˇ
16698                   struct Row4;
16699                   struct» Row5;
16700                   «struct Row6;
16701                   ˇ»
16702                   struct Row99;
16703                   struct Row9;
16704                   struct Row10;"#},
16705        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
16706        indoc! {r#"struct Row;
16707                   struct Row1;
16708                   struct Row33;
16709                   «ˇ
16710                   struct Row4;
16711                   struct» Row5;
16712                   «struct Row6;
16713                   ˇ»
16714                   struct Row99;
16715                   struct Row9;
16716                   struct Row10;"#},
16717        base_text,
16718        &mut cx,
16719    );
16720
16721    assert_hunk_revert(
16722        indoc! {r#"ˇstruct Row1.1;
16723                   struct Row1;
16724                   «ˇstr»uct Row22;
16725
16726                   struct ˇRow44;
16727                   struct Row5;
16728                   struct «Rˇ»ow66;ˇ
16729
16730                   «struˇ»ct Row88;
16731                   struct Row9;
16732                   struct Row1011;ˇ"#},
16733        vec![
16734            DiffHunkStatusKind::Modified,
16735            DiffHunkStatusKind::Modified,
16736            DiffHunkStatusKind::Modified,
16737            DiffHunkStatusKind::Modified,
16738            DiffHunkStatusKind::Modified,
16739            DiffHunkStatusKind::Modified,
16740        ],
16741        indoc! {r#"struct Row;
16742                   ˇstruct Row1;
16743                   struct Row2;
16744                   ˇ
16745                   struct Row4;
16746                   ˇstruct Row5;
16747                   struct Row6;
16748                   ˇ
16749                   struct Row8;
16750                   ˇstruct Row9;
16751                   struct Row10;ˇ"#},
16752        base_text,
16753        &mut cx,
16754    );
16755}
16756
16757#[gpui::test]
16758async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
16759    init_test(cx, |_| {});
16760    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16761    let base_text = indoc! {r#"
16762        one
16763
16764        two
16765        three
16766        "#};
16767
16768    cx.set_head_text(base_text);
16769    cx.set_state("\nˇ\n");
16770    cx.executor().run_until_parked();
16771    cx.update_editor(|editor, _window, cx| {
16772        editor.expand_selected_diff_hunks(cx);
16773    });
16774    cx.executor().run_until_parked();
16775    cx.update_editor(|editor, window, cx| {
16776        editor.backspace(&Default::default(), window, cx);
16777    });
16778    cx.run_until_parked();
16779    cx.assert_state_with_diff(
16780        indoc! {r#"
16781
16782        - two
16783        - threeˇ
16784        +
16785        "#}
16786        .to_string(),
16787    );
16788}
16789
16790#[gpui::test]
16791async fn test_deletion_reverts(cx: &mut TestAppContext) {
16792    init_test(cx, |_| {});
16793    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16794    let base_text = indoc! {r#"struct Row;
16795struct Row1;
16796struct Row2;
16797
16798struct Row4;
16799struct Row5;
16800struct Row6;
16801
16802struct Row8;
16803struct Row9;
16804struct Row10;"#};
16805
16806    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
16807    assert_hunk_revert(
16808        indoc! {r#"struct Row;
16809                   struct Row2;
16810
16811                   ˇstruct Row4;
16812                   struct Row5;
16813                   struct Row6;
16814                   ˇ
16815                   struct Row8;
16816                   struct Row10;"#},
16817        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16818        indoc! {r#"struct Row;
16819                   struct Row2;
16820
16821                   ˇstruct Row4;
16822                   struct Row5;
16823                   struct Row6;
16824                   ˇ
16825                   struct Row8;
16826                   struct Row10;"#},
16827        base_text,
16828        &mut cx,
16829    );
16830    assert_hunk_revert(
16831        indoc! {r#"struct Row;
16832                   struct Row2;
16833
16834                   «ˇstruct Row4;
16835                   struct» Row5;
16836                   «struct Row6;
16837                   ˇ»
16838                   struct Row8;
16839                   struct Row10;"#},
16840        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16841        indoc! {r#"struct Row;
16842                   struct Row2;
16843
16844                   «ˇstruct Row4;
16845                   struct» Row5;
16846                   «struct Row6;
16847                   ˇ»
16848                   struct Row8;
16849                   struct Row10;"#},
16850        base_text,
16851        &mut cx,
16852    );
16853
16854    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
16855    assert_hunk_revert(
16856        indoc! {r#"struct Row;
16857                   ˇstruct Row2;
16858
16859                   struct Row4;
16860                   struct Row5;
16861                   struct Row6;
16862
16863                   struct Row8;ˇ
16864                   struct Row10;"#},
16865        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16866        indoc! {r#"struct Row;
16867                   struct Row1;
16868                   ˇstruct Row2;
16869
16870                   struct Row4;
16871                   struct Row5;
16872                   struct Row6;
16873
16874                   struct Row8;ˇ
16875                   struct Row9;
16876                   struct Row10;"#},
16877        base_text,
16878        &mut cx,
16879    );
16880    assert_hunk_revert(
16881        indoc! {r#"struct Row;
16882                   struct Row2«ˇ;
16883                   struct Row4;
16884                   struct» Row5;
16885                   «struct Row6;
16886
16887                   struct Row8;ˇ»
16888                   struct Row10;"#},
16889        vec![
16890            DiffHunkStatusKind::Deleted,
16891            DiffHunkStatusKind::Deleted,
16892            DiffHunkStatusKind::Deleted,
16893        ],
16894        indoc! {r#"struct Row;
16895                   struct Row1;
16896                   struct Row2«ˇ;
16897
16898                   struct Row4;
16899                   struct» Row5;
16900                   «struct Row6;
16901
16902                   struct Row8;ˇ»
16903                   struct Row9;
16904                   struct Row10;"#},
16905        base_text,
16906        &mut cx,
16907    );
16908}
16909
16910#[gpui::test]
16911async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
16912    init_test(cx, |_| {});
16913
16914    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
16915    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
16916    let base_text_3 =
16917        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
16918
16919    let text_1 = edit_first_char_of_every_line(base_text_1);
16920    let text_2 = edit_first_char_of_every_line(base_text_2);
16921    let text_3 = edit_first_char_of_every_line(base_text_3);
16922
16923    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
16924    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
16925    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
16926
16927    let multibuffer = cx.new(|cx| {
16928        let mut multibuffer = MultiBuffer::new(ReadWrite);
16929        multibuffer.push_excerpts(
16930            buffer_1.clone(),
16931            [
16932                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16933                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16934                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16935            ],
16936            cx,
16937        );
16938        multibuffer.push_excerpts(
16939            buffer_2.clone(),
16940            [
16941                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16942                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16943                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16944            ],
16945            cx,
16946        );
16947        multibuffer.push_excerpts(
16948            buffer_3.clone(),
16949            [
16950                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16951                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16952                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16953            ],
16954            cx,
16955        );
16956        multibuffer
16957    });
16958
16959    let fs = FakeFs::new(cx.executor());
16960    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
16961    let (editor, cx) = cx
16962        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
16963    editor.update_in(cx, |editor, _window, cx| {
16964        for (buffer, diff_base) in [
16965            (buffer_1.clone(), base_text_1),
16966            (buffer_2.clone(), base_text_2),
16967            (buffer_3.clone(), base_text_3),
16968        ] {
16969            let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
16970            editor
16971                .buffer
16972                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
16973        }
16974    });
16975    cx.executor().run_until_parked();
16976
16977    editor.update_in(cx, |editor, window, cx| {
16978        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}");
16979        editor.select_all(&SelectAll, window, cx);
16980        editor.git_restore(&Default::default(), window, cx);
16981    });
16982    cx.executor().run_until_parked();
16983
16984    // When all ranges are selected, all buffer hunks are reverted.
16985    editor.update(cx, |editor, cx| {
16986        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");
16987    });
16988    buffer_1.update(cx, |buffer, _| {
16989        assert_eq!(buffer.text(), base_text_1);
16990    });
16991    buffer_2.update(cx, |buffer, _| {
16992        assert_eq!(buffer.text(), base_text_2);
16993    });
16994    buffer_3.update(cx, |buffer, _| {
16995        assert_eq!(buffer.text(), base_text_3);
16996    });
16997
16998    editor.update_in(cx, |editor, window, cx| {
16999        editor.undo(&Default::default(), window, cx);
17000    });
17001
17002    editor.update_in(cx, |editor, window, cx| {
17003        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17004            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
17005        });
17006        editor.git_restore(&Default::default(), window, cx);
17007    });
17008
17009    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
17010    // but not affect buffer_2 and its related excerpts.
17011    editor.update(cx, |editor, cx| {
17012        assert_eq!(
17013            editor.text(cx),
17014            "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}"
17015        );
17016    });
17017    buffer_1.update(cx, |buffer, _| {
17018        assert_eq!(buffer.text(), base_text_1);
17019    });
17020    buffer_2.update(cx, |buffer, _| {
17021        assert_eq!(
17022            buffer.text(),
17023            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
17024        );
17025    });
17026    buffer_3.update(cx, |buffer, _| {
17027        assert_eq!(
17028            buffer.text(),
17029            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
17030        );
17031    });
17032
17033    fn edit_first_char_of_every_line(text: &str) -> String {
17034        text.split('\n')
17035            .map(|line| format!("X{}", &line[1..]))
17036            .collect::<Vec<_>>()
17037            .join("\n")
17038    }
17039}
17040
17041#[gpui::test]
17042async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
17043    init_test(cx, |_| {});
17044
17045    let cols = 4;
17046    let rows = 10;
17047    let sample_text_1 = sample_text(rows, cols, 'a');
17048    assert_eq!(
17049        sample_text_1,
17050        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
17051    );
17052    let sample_text_2 = sample_text(rows, cols, 'l');
17053    assert_eq!(
17054        sample_text_2,
17055        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
17056    );
17057    let sample_text_3 = sample_text(rows, cols, 'v');
17058    assert_eq!(
17059        sample_text_3,
17060        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
17061    );
17062
17063    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
17064    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
17065    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
17066
17067    let multi_buffer = cx.new(|cx| {
17068        let mut multibuffer = MultiBuffer::new(ReadWrite);
17069        multibuffer.push_excerpts(
17070            buffer_1.clone(),
17071            [
17072                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17073                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17074                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17075            ],
17076            cx,
17077        );
17078        multibuffer.push_excerpts(
17079            buffer_2.clone(),
17080            [
17081                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17082                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17083                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17084            ],
17085            cx,
17086        );
17087        multibuffer.push_excerpts(
17088            buffer_3.clone(),
17089            [
17090                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17091                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17092                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17093            ],
17094            cx,
17095        );
17096        multibuffer
17097    });
17098
17099    let fs = FakeFs::new(cx.executor());
17100    fs.insert_tree(
17101        "/a",
17102        json!({
17103            "main.rs": sample_text_1,
17104            "other.rs": sample_text_2,
17105            "lib.rs": sample_text_3,
17106        }),
17107    )
17108    .await;
17109    let project = Project::test(fs, ["/a".as_ref()], cx).await;
17110    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17111    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17112    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
17113        Editor::new(
17114            EditorMode::full(),
17115            multi_buffer,
17116            Some(project.clone()),
17117            window,
17118            cx,
17119        )
17120    });
17121    let multibuffer_item_id = workspace
17122        .update(cx, |workspace, window, cx| {
17123            assert!(
17124                workspace.active_item(cx).is_none(),
17125                "active item should be None before the first item is added"
17126            );
17127            workspace.add_item_to_active_pane(
17128                Box::new(multi_buffer_editor.clone()),
17129                None,
17130                true,
17131                window,
17132                cx,
17133            );
17134            let active_item = workspace
17135                .active_item(cx)
17136                .expect("should have an active item after adding the multi buffer");
17137            assert!(
17138                !active_item.is_singleton(cx),
17139                "A multi buffer was expected to active after adding"
17140            );
17141            active_item.item_id()
17142        })
17143        .unwrap();
17144    cx.executor().run_until_parked();
17145
17146    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17147        editor.change_selections(
17148            SelectionEffects::scroll(Autoscroll::Next),
17149            window,
17150            cx,
17151            |s| s.select_ranges(Some(1..2)),
17152        );
17153        editor.open_excerpts(&OpenExcerpts, window, cx);
17154    });
17155    cx.executor().run_until_parked();
17156    let first_item_id = workspace
17157        .update(cx, |workspace, window, cx| {
17158            let active_item = workspace
17159                .active_item(cx)
17160                .expect("should have an active item after navigating into the 1st buffer");
17161            let first_item_id = active_item.item_id();
17162            assert_ne!(
17163                first_item_id, multibuffer_item_id,
17164                "Should navigate into the 1st buffer and activate it"
17165            );
17166            assert!(
17167                active_item.is_singleton(cx),
17168                "New active item should be a singleton buffer"
17169            );
17170            assert_eq!(
17171                active_item
17172                    .act_as::<Editor>(cx)
17173                    .expect("should have navigated into an editor for the 1st buffer")
17174                    .read(cx)
17175                    .text(cx),
17176                sample_text_1
17177            );
17178
17179            workspace
17180                .go_back(workspace.active_pane().downgrade(), window, cx)
17181                .detach_and_log_err(cx);
17182
17183            first_item_id
17184        })
17185        .unwrap();
17186    cx.executor().run_until_parked();
17187    workspace
17188        .update(cx, |workspace, _, cx| {
17189            let active_item = workspace
17190                .active_item(cx)
17191                .expect("should have an active item after navigating back");
17192            assert_eq!(
17193                active_item.item_id(),
17194                multibuffer_item_id,
17195                "Should navigate back to the multi buffer"
17196            );
17197            assert!(!active_item.is_singleton(cx));
17198        })
17199        .unwrap();
17200
17201    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17202        editor.change_selections(
17203            SelectionEffects::scroll(Autoscroll::Next),
17204            window,
17205            cx,
17206            |s| s.select_ranges(Some(39..40)),
17207        );
17208        editor.open_excerpts(&OpenExcerpts, window, cx);
17209    });
17210    cx.executor().run_until_parked();
17211    let second_item_id = workspace
17212        .update(cx, |workspace, window, cx| {
17213            let active_item = workspace
17214                .active_item(cx)
17215                .expect("should have an active item after navigating into the 2nd buffer");
17216            let second_item_id = active_item.item_id();
17217            assert_ne!(
17218                second_item_id, multibuffer_item_id,
17219                "Should navigate away from the multibuffer"
17220            );
17221            assert_ne!(
17222                second_item_id, first_item_id,
17223                "Should navigate into the 2nd buffer and activate it"
17224            );
17225            assert!(
17226                active_item.is_singleton(cx),
17227                "New active item should be a singleton buffer"
17228            );
17229            assert_eq!(
17230                active_item
17231                    .act_as::<Editor>(cx)
17232                    .expect("should have navigated into an editor")
17233                    .read(cx)
17234                    .text(cx),
17235                sample_text_2
17236            );
17237
17238            workspace
17239                .go_back(workspace.active_pane().downgrade(), window, cx)
17240                .detach_and_log_err(cx);
17241
17242            second_item_id
17243        })
17244        .unwrap();
17245    cx.executor().run_until_parked();
17246    workspace
17247        .update(cx, |workspace, _, cx| {
17248            let active_item = workspace
17249                .active_item(cx)
17250                .expect("should have an active item after navigating back from the 2nd buffer");
17251            assert_eq!(
17252                active_item.item_id(),
17253                multibuffer_item_id,
17254                "Should navigate back from the 2nd buffer to the multi buffer"
17255            );
17256            assert!(!active_item.is_singleton(cx));
17257        })
17258        .unwrap();
17259
17260    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17261        editor.change_selections(
17262            SelectionEffects::scroll(Autoscroll::Next),
17263            window,
17264            cx,
17265            |s| s.select_ranges(Some(70..70)),
17266        );
17267        editor.open_excerpts(&OpenExcerpts, window, cx);
17268    });
17269    cx.executor().run_until_parked();
17270    workspace
17271        .update(cx, |workspace, window, cx| {
17272            let active_item = workspace
17273                .active_item(cx)
17274                .expect("should have an active item after navigating into the 3rd buffer");
17275            let third_item_id = active_item.item_id();
17276            assert_ne!(
17277                third_item_id, multibuffer_item_id,
17278                "Should navigate into the 3rd buffer and activate it"
17279            );
17280            assert_ne!(third_item_id, first_item_id);
17281            assert_ne!(third_item_id, second_item_id);
17282            assert!(
17283                active_item.is_singleton(cx),
17284                "New active item should be a singleton buffer"
17285            );
17286            assert_eq!(
17287                active_item
17288                    .act_as::<Editor>(cx)
17289                    .expect("should have navigated into an editor")
17290                    .read(cx)
17291                    .text(cx),
17292                sample_text_3
17293            );
17294
17295            workspace
17296                .go_back(workspace.active_pane().downgrade(), window, cx)
17297                .detach_and_log_err(cx);
17298        })
17299        .unwrap();
17300    cx.executor().run_until_parked();
17301    workspace
17302        .update(cx, |workspace, _, cx| {
17303            let active_item = workspace
17304                .active_item(cx)
17305                .expect("should have an active item after navigating back from the 3rd buffer");
17306            assert_eq!(
17307                active_item.item_id(),
17308                multibuffer_item_id,
17309                "Should navigate back from the 3rd buffer to the multi buffer"
17310            );
17311            assert!(!active_item.is_singleton(cx));
17312        })
17313        .unwrap();
17314}
17315
17316#[gpui::test]
17317async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17318    init_test(cx, |_| {});
17319
17320    let mut cx = EditorTestContext::new(cx).await;
17321
17322    let diff_base = r#"
17323        use some::mod;
17324
17325        const A: u32 = 42;
17326
17327        fn main() {
17328            println!("hello");
17329
17330            println!("world");
17331        }
17332        "#
17333    .unindent();
17334
17335    cx.set_state(
17336        &r#"
17337        use some::modified;
17338
17339        ˇ
17340        fn main() {
17341            println!("hello there");
17342
17343            println!("around the");
17344            println!("world");
17345        }
17346        "#
17347        .unindent(),
17348    );
17349
17350    cx.set_head_text(&diff_base);
17351    executor.run_until_parked();
17352
17353    cx.update_editor(|editor, window, cx| {
17354        editor.go_to_next_hunk(&GoToHunk, window, cx);
17355        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17356    });
17357    executor.run_until_parked();
17358    cx.assert_state_with_diff(
17359        r#"
17360          use some::modified;
17361
17362
17363          fn main() {
17364        -     println!("hello");
17365        + ˇ    println!("hello there");
17366
17367              println!("around the");
17368              println!("world");
17369          }
17370        "#
17371        .unindent(),
17372    );
17373
17374    cx.update_editor(|editor, window, cx| {
17375        for _ in 0..2 {
17376            editor.go_to_next_hunk(&GoToHunk, window, cx);
17377            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17378        }
17379    });
17380    executor.run_until_parked();
17381    cx.assert_state_with_diff(
17382        r#"
17383        - use some::mod;
17384        + ˇuse some::modified;
17385
17386
17387          fn main() {
17388        -     println!("hello");
17389        +     println!("hello there");
17390
17391        +     println!("around the");
17392              println!("world");
17393          }
17394        "#
17395        .unindent(),
17396    );
17397
17398    cx.update_editor(|editor, window, cx| {
17399        editor.go_to_next_hunk(&GoToHunk, window, cx);
17400        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17401    });
17402    executor.run_until_parked();
17403    cx.assert_state_with_diff(
17404        r#"
17405        - use some::mod;
17406        + use some::modified;
17407
17408        - const A: u32 = 42;
17409          ˇ
17410          fn main() {
17411        -     println!("hello");
17412        +     println!("hello there");
17413
17414        +     println!("around the");
17415              println!("world");
17416          }
17417        "#
17418        .unindent(),
17419    );
17420
17421    cx.update_editor(|editor, window, cx| {
17422        editor.cancel(&Cancel, window, cx);
17423    });
17424
17425    cx.assert_state_with_diff(
17426        r#"
17427          use some::modified;
17428
17429          ˇ
17430          fn main() {
17431              println!("hello there");
17432
17433              println!("around the");
17434              println!("world");
17435          }
17436        "#
17437        .unindent(),
17438    );
17439}
17440
17441#[gpui::test]
17442async fn test_diff_base_change_with_expanded_diff_hunks(
17443    executor: BackgroundExecutor,
17444    cx: &mut TestAppContext,
17445) {
17446    init_test(cx, |_| {});
17447
17448    let mut cx = EditorTestContext::new(cx).await;
17449
17450    let diff_base = r#"
17451        use some::mod1;
17452        use some::mod2;
17453
17454        const A: u32 = 42;
17455        const B: u32 = 42;
17456        const C: u32 = 42;
17457
17458        fn main() {
17459            println!("hello");
17460
17461            println!("world");
17462        }
17463        "#
17464    .unindent();
17465
17466    cx.set_state(
17467        &r#"
17468        use some::mod2;
17469
17470        const A: u32 = 42;
17471        const C: u32 = 42;
17472
17473        fn main(ˇ) {
17474            //println!("hello");
17475
17476            println!("world");
17477            //
17478            //
17479        }
17480        "#
17481        .unindent(),
17482    );
17483
17484    cx.set_head_text(&diff_base);
17485    executor.run_until_parked();
17486
17487    cx.update_editor(|editor, window, cx| {
17488        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17489    });
17490    executor.run_until_parked();
17491    cx.assert_state_with_diff(
17492        r#"
17493        - use some::mod1;
17494          use some::mod2;
17495
17496          const A: u32 = 42;
17497        - const B: u32 = 42;
17498          const C: u32 = 42;
17499
17500          fn main(ˇ) {
17501        -     println!("hello");
17502        +     //println!("hello");
17503
17504              println!("world");
17505        +     //
17506        +     //
17507          }
17508        "#
17509        .unindent(),
17510    );
17511
17512    cx.set_head_text("new diff base!");
17513    executor.run_until_parked();
17514    cx.assert_state_with_diff(
17515        r#"
17516        - new diff base!
17517        + use some::mod2;
17518        +
17519        + const A: u32 = 42;
17520        + const C: u32 = 42;
17521        +
17522        + fn main(ˇ) {
17523        +     //println!("hello");
17524        +
17525        +     println!("world");
17526        +     //
17527        +     //
17528        + }
17529        "#
17530        .unindent(),
17531    );
17532}
17533
17534#[gpui::test]
17535async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
17536    init_test(cx, |_| {});
17537
17538    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
17539    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
17540    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
17541    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
17542    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
17543    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
17544
17545    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
17546    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
17547    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
17548
17549    let multi_buffer = cx.new(|cx| {
17550        let mut multibuffer = MultiBuffer::new(ReadWrite);
17551        multibuffer.push_excerpts(
17552            buffer_1.clone(),
17553            [
17554                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17555                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17556                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17557            ],
17558            cx,
17559        );
17560        multibuffer.push_excerpts(
17561            buffer_2.clone(),
17562            [
17563                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17564                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17565                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17566            ],
17567            cx,
17568        );
17569        multibuffer.push_excerpts(
17570            buffer_3.clone(),
17571            [
17572                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17573                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17574                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17575            ],
17576            cx,
17577        );
17578        multibuffer
17579    });
17580
17581    let editor =
17582        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
17583    editor
17584        .update(cx, |editor, _window, cx| {
17585            for (buffer, diff_base) in [
17586                (buffer_1.clone(), file_1_old),
17587                (buffer_2.clone(), file_2_old),
17588                (buffer_3.clone(), file_3_old),
17589            ] {
17590                let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
17591                editor
17592                    .buffer
17593                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
17594            }
17595        })
17596        .unwrap();
17597
17598    let mut cx = EditorTestContext::for_editor(editor, cx).await;
17599    cx.run_until_parked();
17600
17601    cx.assert_editor_state(
17602        &"
17603            ˇaaa
17604            ccc
17605            ddd
17606
17607            ggg
17608            hhh
17609
17610
17611            lll
17612            mmm
17613            NNN
17614
17615            qqq
17616            rrr
17617
17618            uuu
17619            111
17620            222
17621            333
17622
17623            666
17624            777
17625
17626            000
17627            !!!"
17628        .unindent(),
17629    );
17630
17631    cx.update_editor(|editor, window, cx| {
17632        editor.select_all(&SelectAll, window, cx);
17633        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17634    });
17635    cx.executor().run_until_parked();
17636
17637    cx.assert_state_with_diff(
17638        "
17639            «aaa
17640          - bbb
17641            ccc
17642            ddd
17643
17644            ggg
17645            hhh
17646
17647
17648            lll
17649            mmm
17650          - nnn
17651          + NNN
17652
17653            qqq
17654            rrr
17655
17656            uuu
17657            111
17658            222
17659            333
17660
17661          + 666
17662            777
17663
17664            000
17665            !!!ˇ»"
17666            .unindent(),
17667    );
17668}
17669
17670#[gpui::test]
17671async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
17672    init_test(cx, |_| {});
17673
17674    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
17675    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
17676
17677    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
17678    let multi_buffer = cx.new(|cx| {
17679        let mut multibuffer = MultiBuffer::new(ReadWrite);
17680        multibuffer.push_excerpts(
17681            buffer.clone(),
17682            [
17683                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
17684                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
17685                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
17686            ],
17687            cx,
17688        );
17689        multibuffer
17690    });
17691
17692    let editor =
17693        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
17694    editor
17695        .update(cx, |editor, _window, cx| {
17696            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
17697            editor
17698                .buffer
17699                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
17700        })
17701        .unwrap();
17702
17703    let mut cx = EditorTestContext::for_editor(editor, cx).await;
17704    cx.run_until_parked();
17705
17706    cx.update_editor(|editor, window, cx| {
17707        editor.expand_all_diff_hunks(&Default::default(), window, cx)
17708    });
17709    cx.executor().run_until_parked();
17710
17711    // When the start of a hunk coincides with the start of its excerpt,
17712    // the hunk is expanded. When the start of a a hunk is earlier than
17713    // the start of its excerpt, the hunk is not expanded.
17714    cx.assert_state_with_diff(
17715        "
17716            ˇaaa
17717          - bbb
17718          + BBB
17719
17720          - ddd
17721          - eee
17722          + DDD
17723          + EEE
17724            fff
17725
17726            iii
17727        "
17728        .unindent(),
17729    );
17730}
17731
17732#[gpui::test]
17733async fn test_edits_around_expanded_insertion_hunks(
17734    executor: BackgroundExecutor,
17735    cx: &mut TestAppContext,
17736) {
17737    init_test(cx, |_| {});
17738
17739    let mut cx = EditorTestContext::new(cx).await;
17740
17741    let diff_base = r#"
17742        use some::mod1;
17743        use some::mod2;
17744
17745        const A: u32 = 42;
17746
17747        fn main() {
17748            println!("hello");
17749
17750            println!("world");
17751        }
17752        "#
17753    .unindent();
17754    executor.run_until_parked();
17755    cx.set_state(
17756        &r#"
17757        use some::mod1;
17758        use some::mod2;
17759
17760        const A: u32 = 42;
17761        const B: u32 = 42;
17762        const C: u32 = 42;
17763        ˇ
17764
17765        fn main() {
17766            println!("hello");
17767
17768            println!("world");
17769        }
17770        "#
17771        .unindent(),
17772    );
17773
17774    cx.set_head_text(&diff_base);
17775    executor.run_until_parked();
17776
17777    cx.update_editor(|editor, window, cx| {
17778        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17779    });
17780    executor.run_until_parked();
17781
17782    cx.assert_state_with_diff(
17783        r#"
17784        use some::mod1;
17785        use some::mod2;
17786
17787        const A: u32 = 42;
17788      + const B: u32 = 42;
17789      + const C: u32 = 42;
17790      + ˇ
17791
17792        fn main() {
17793            println!("hello");
17794
17795            println!("world");
17796        }
17797      "#
17798        .unindent(),
17799    );
17800
17801    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
17802    executor.run_until_parked();
17803
17804    cx.assert_state_with_diff(
17805        r#"
17806        use some::mod1;
17807        use some::mod2;
17808
17809        const A: u32 = 42;
17810      + const B: u32 = 42;
17811      + const C: u32 = 42;
17812      + const D: u32 = 42;
17813      + ˇ
17814
17815        fn main() {
17816            println!("hello");
17817
17818            println!("world");
17819        }
17820      "#
17821        .unindent(),
17822    );
17823
17824    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
17825    executor.run_until_parked();
17826
17827    cx.assert_state_with_diff(
17828        r#"
17829        use some::mod1;
17830        use some::mod2;
17831
17832        const A: u32 = 42;
17833      + const B: u32 = 42;
17834      + const C: u32 = 42;
17835      + const D: u32 = 42;
17836      + const E: u32 = 42;
17837      + ˇ
17838
17839        fn main() {
17840            println!("hello");
17841
17842            println!("world");
17843        }
17844      "#
17845        .unindent(),
17846    );
17847
17848    cx.update_editor(|editor, window, cx| {
17849        editor.delete_line(&DeleteLine, window, cx);
17850    });
17851    executor.run_until_parked();
17852
17853    cx.assert_state_with_diff(
17854        r#"
17855        use some::mod1;
17856        use some::mod2;
17857
17858        const A: u32 = 42;
17859      + const B: u32 = 42;
17860      + const C: u32 = 42;
17861      + const D: u32 = 42;
17862      + const E: u32 = 42;
17863        ˇ
17864        fn main() {
17865            println!("hello");
17866
17867            println!("world");
17868        }
17869      "#
17870        .unindent(),
17871    );
17872
17873    cx.update_editor(|editor, window, cx| {
17874        editor.move_up(&MoveUp, window, cx);
17875        editor.delete_line(&DeleteLine, window, cx);
17876        editor.move_up(&MoveUp, window, cx);
17877        editor.delete_line(&DeleteLine, window, cx);
17878        editor.move_up(&MoveUp, window, cx);
17879        editor.delete_line(&DeleteLine, window, cx);
17880    });
17881    executor.run_until_parked();
17882    cx.assert_state_with_diff(
17883        r#"
17884        use some::mod1;
17885        use some::mod2;
17886
17887        const A: u32 = 42;
17888      + const B: u32 = 42;
17889        ˇ
17890        fn main() {
17891            println!("hello");
17892
17893            println!("world");
17894        }
17895      "#
17896        .unindent(),
17897    );
17898
17899    cx.update_editor(|editor, window, cx| {
17900        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
17901        editor.delete_line(&DeleteLine, window, cx);
17902    });
17903    executor.run_until_parked();
17904    cx.assert_state_with_diff(
17905        r#"
17906        ˇ
17907        fn main() {
17908            println!("hello");
17909
17910            println!("world");
17911        }
17912      "#
17913        .unindent(),
17914    );
17915}
17916
17917#[gpui::test]
17918async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
17919    init_test(cx, |_| {});
17920
17921    let mut cx = EditorTestContext::new(cx).await;
17922    cx.set_head_text(indoc! { "
17923        one
17924        two
17925        three
17926        four
17927        five
17928        "
17929    });
17930    cx.set_state(indoc! { "
17931        one
17932        ˇthree
17933        five
17934    "});
17935    cx.run_until_parked();
17936    cx.update_editor(|editor, window, cx| {
17937        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17938    });
17939    cx.assert_state_with_diff(
17940        indoc! { "
17941        one
17942      - two
17943        ˇthree
17944      - four
17945        five
17946    "}
17947        .to_string(),
17948    );
17949    cx.update_editor(|editor, window, cx| {
17950        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17951    });
17952
17953    cx.assert_state_with_diff(
17954        indoc! { "
17955        one
17956        ˇthree
17957        five
17958    "}
17959        .to_string(),
17960    );
17961
17962    cx.set_state(indoc! { "
17963        one
17964        ˇTWO
17965        three
17966        four
17967        five
17968    "});
17969    cx.run_until_parked();
17970    cx.update_editor(|editor, window, cx| {
17971        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17972    });
17973
17974    cx.assert_state_with_diff(
17975        indoc! { "
17976            one
17977          - two
17978          + ˇTWO
17979            three
17980            four
17981            five
17982        "}
17983        .to_string(),
17984    );
17985    cx.update_editor(|editor, window, cx| {
17986        editor.move_up(&Default::default(), window, cx);
17987        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17988    });
17989    cx.assert_state_with_diff(
17990        indoc! { "
17991            one
17992            ˇTWO
17993            three
17994            four
17995            five
17996        "}
17997        .to_string(),
17998    );
17999}
18000
18001#[gpui::test]
18002async fn test_edits_around_expanded_deletion_hunks(
18003    executor: BackgroundExecutor,
18004    cx: &mut TestAppContext,
18005) {
18006    init_test(cx, |_| {});
18007
18008    let mut cx = EditorTestContext::new(cx).await;
18009
18010    let diff_base = r#"
18011        use some::mod1;
18012        use some::mod2;
18013
18014        const A: u32 = 42;
18015        const B: u32 = 42;
18016        const C: u32 = 42;
18017
18018
18019        fn main() {
18020            println!("hello");
18021
18022            println!("world");
18023        }
18024    "#
18025    .unindent();
18026    executor.run_until_parked();
18027    cx.set_state(
18028        &r#"
18029        use some::mod1;
18030        use some::mod2;
18031
18032        ˇconst B: u32 = 42;
18033        const C: u32 = 42;
18034
18035
18036        fn main() {
18037            println!("hello");
18038
18039            println!("world");
18040        }
18041        "#
18042        .unindent(),
18043    );
18044
18045    cx.set_head_text(&diff_base);
18046    executor.run_until_parked();
18047
18048    cx.update_editor(|editor, window, cx| {
18049        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18050    });
18051    executor.run_until_parked();
18052
18053    cx.assert_state_with_diff(
18054        r#"
18055        use some::mod1;
18056        use some::mod2;
18057
18058      - const A: u32 = 42;
18059        ˇconst B: u32 = 42;
18060        const C: u32 = 42;
18061
18062
18063        fn main() {
18064            println!("hello");
18065
18066            println!("world");
18067        }
18068      "#
18069        .unindent(),
18070    );
18071
18072    cx.update_editor(|editor, window, cx| {
18073        editor.delete_line(&DeleteLine, window, cx);
18074    });
18075    executor.run_until_parked();
18076    cx.assert_state_with_diff(
18077        r#"
18078        use some::mod1;
18079        use some::mod2;
18080
18081      - const A: u32 = 42;
18082      - const B: u32 = 42;
18083        ˇconst C: u32 = 42;
18084
18085
18086        fn main() {
18087            println!("hello");
18088
18089            println!("world");
18090        }
18091      "#
18092        .unindent(),
18093    );
18094
18095    cx.update_editor(|editor, window, cx| {
18096        editor.delete_line(&DeleteLine, window, cx);
18097    });
18098    executor.run_until_parked();
18099    cx.assert_state_with_diff(
18100        r#"
18101        use some::mod1;
18102        use some::mod2;
18103
18104      - const A: u32 = 42;
18105      - const B: u32 = 42;
18106      - const C: u32 = 42;
18107        ˇ
18108
18109        fn main() {
18110            println!("hello");
18111
18112            println!("world");
18113        }
18114      "#
18115        .unindent(),
18116    );
18117
18118    cx.update_editor(|editor, window, cx| {
18119        editor.handle_input("replacement", window, cx);
18120    });
18121    executor.run_until_parked();
18122    cx.assert_state_with_diff(
18123        r#"
18124        use some::mod1;
18125        use some::mod2;
18126
18127      - const A: u32 = 42;
18128      - const B: u32 = 42;
18129      - const C: u32 = 42;
18130      -
18131      + replacementˇ
18132
18133        fn main() {
18134            println!("hello");
18135
18136            println!("world");
18137        }
18138      "#
18139        .unindent(),
18140    );
18141}
18142
18143#[gpui::test]
18144async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18145    init_test(cx, |_| {});
18146
18147    let mut cx = EditorTestContext::new(cx).await;
18148
18149    let base_text = r#"
18150        one
18151        two
18152        three
18153        four
18154        five
18155    "#
18156    .unindent();
18157    executor.run_until_parked();
18158    cx.set_state(
18159        &r#"
18160        one
18161        two
18162        fˇour
18163        five
18164        "#
18165        .unindent(),
18166    );
18167
18168    cx.set_head_text(&base_text);
18169    executor.run_until_parked();
18170
18171    cx.update_editor(|editor, window, cx| {
18172        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18173    });
18174    executor.run_until_parked();
18175
18176    cx.assert_state_with_diff(
18177        r#"
18178          one
18179          two
18180        - three
18181          fˇour
18182          five
18183        "#
18184        .unindent(),
18185    );
18186
18187    cx.update_editor(|editor, window, cx| {
18188        editor.backspace(&Backspace, window, cx);
18189        editor.backspace(&Backspace, window, cx);
18190    });
18191    executor.run_until_parked();
18192    cx.assert_state_with_diff(
18193        r#"
18194          one
18195          two
18196        - threeˇ
18197        - four
18198        + our
18199          five
18200        "#
18201        .unindent(),
18202    );
18203}
18204
18205#[gpui::test]
18206async fn test_edit_after_expanded_modification_hunk(
18207    executor: BackgroundExecutor,
18208    cx: &mut TestAppContext,
18209) {
18210    init_test(cx, |_| {});
18211
18212    let mut cx = EditorTestContext::new(cx).await;
18213
18214    let diff_base = r#"
18215        use some::mod1;
18216        use some::mod2;
18217
18218        const A: u32 = 42;
18219        const B: u32 = 42;
18220        const C: u32 = 42;
18221        const D: u32 = 42;
18222
18223
18224        fn main() {
18225            println!("hello");
18226
18227            println!("world");
18228        }"#
18229    .unindent();
18230
18231    cx.set_state(
18232        &r#"
18233        use some::mod1;
18234        use some::mod2;
18235
18236        const A: u32 = 42;
18237        const B: u32 = 42;
18238        const C: u32 = 43ˇ
18239        const D: u32 = 42;
18240
18241
18242        fn main() {
18243            println!("hello");
18244
18245            println!("world");
18246        }"#
18247        .unindent(),
18248    );
18249
18250    cx.set_head_text(&diff_base);
18251    executor.run_until_parked();
18252    cx.update_editor(|editor, window, cx| {
18253        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18254    });
18255    executor.run_until_parked();
18256
18257    cx.assert_state_with_diff(
18258        r#"
18259        use some::mod1;
18260        use some::mod2;
18261
18262        const A: u32 = 42;
18263        const B: u32 = 42;
18264      - const C: u32 = 42;
18265      + const C: u32 = 43ˇ
18266        const D: u32 = 42;
18267
18268
18269        fn main() {
18270            println!("hello");
18271
18272            println!("world");
18273        }"#
18274        .unindent(),
18275    );
18276
18277    cx.update_editor(|editor, window, cx| {
18278        editor.handle_input("\nnew_line\n", window, cx);
18279    });
18280    executor.run_until_parked();
18281
18282    cx.assert_state_with_diff(
18283        r#"
18284        use some::mod1;
18285        use some::mod2;
18286
18287        const A: u32 = 42;
18288        const B: u32 = 42;
18289      - const C: u32 = 42;
18290      + const C: u32 = 43
18291      + new_line
18292      + ˇ
18293        const D: u32 = 42;
18294
18295
18296        fn main() {
18297            println!("hello");
18298
18299            println!("world");
18300        }"#
18301        .unindent(),
18302    );
18303}
18304
18305#[gpui::test]
18306async fn test_stage_and_unstage_added_file_hunk(
18307    executor: BackgroundExecutor,
18308    cx: &mut TestAppContext,
18309) {
18310    init_test(cx, |_| {});
18311
18312    let mut cx = EditorTestContext::new(cx).await;
18313    cx.update_editor(|editor, _, cx| {
18314        editor.set_expand_all_diff_hunks(cx);
18315    });
18316
18317    let working_copy = r#"
18318            ˇfn main() {
18319                println!("hello, world!");
18320            }
18321        "#
18322    .unindent();
18323
18324    cx.set_state(&working_copy);
18325    executor.run_until_parked();
18326
18327    cx.assert_state_with_diff(
18328        r#"
18329            + ˇfn main() {
18330            +     println!("hello, world!");
18331            + }
18332        "#
18333        .unindent(),
18334    );
18335    cx.assert_index_text(None);
18336
18337    cx.update_editor(|editor, window, cx| {
18338        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18339    });
18340    executor.run_until_parked();
18341    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
18342    cx.assert_state_with_diff(
18343        r#"
18344            + ˇfn main() {
18345            +     println!("hello, world!");
18346            + }
18347        "#
18348        .unindent(),
18349    );
18350
18351    cx.update_editor(|editor, window, cx| {
18352        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18353    });
18354    executor.run_until_parked();
18355    cx.assert_index_text(None);
18356}
18357
18358async fn setup_indent_guides_editor(
18359    text: &str,
18360    cx: &mut TestAppContext,
18361) -> (BufferId, EditorTestContext) {
18362    init_test(cx, |_| {});
18363
18364    let mut cx = EditorTestContext::new(cx).await;
18365
18366    let buffer_id = cx.update_editor(|editor, window, cx| {
18367        editor.set_text(text, window, cx);
18368        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
18369
18370        buffer_ids[0]
18371    });
18372
18373    (buffer_id, cx)
18374}
18375
18376fn assert_indent_guides(
18377    range: Range<u32>,
18378    expected: Vec<IndentGuide>,
18379    active_indices: Option<Vec<usize>>,
18380    cx: &mut EditorTestContext,
18381) {
18382    let indent_guides = cx.update_editor(|editor, window, cx| {
18383        let snapshot = editor.snapshot(window, cx).display_snapshot;
18384        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
18385            editor,
18386            MultiBufferRow(range.start)..MultiBufferRow(range.end),
18387            true,
18388            &snapshot,
18389            cx,
18390        );
18391
18392        indent_guides.sort_by(|a, b| {
18393            a.depth.cmp(&b.depth).then(
18394                a.start_row
18395                    .cmp(&b.start_row)
18396                    .then(a.end_row.cmp(&b.end_row)),
18397            )
18398        });
18399        indent_guides
18400    });
18401
18402    if let Some(expected) = active_indices {
18403        let active_indices = cx.update_editor(|editor, window, cx| {
18404            let snapshot = editor.snapshot(window, cx).display_snapshot;
18405            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
18406        });
18407
18408        assert_eq!(
18409            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
18410            expected,
18411            "Active indent guide indices do not match"
18412        );
18413    }
18414
18415    assert_eq!(indent_guides, expected, "Indent guides do not match");
18416}
18417
18418fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
18419    IndentGuide {
18420        buffer_id,
18421        start_row: MultiBufferRow(start_row),
18422        end_row: MultiBufferRow(end_row),
18423        depth,
18424        tab_size: 4,
18425        settings: IndentGuideSettings {
18426            enabled: true,
18427            line_width: 1,
18428            active_line_width: 1,
18429            ..Default::default()
18430        },
18431    }
18432}
18433
18434#[gpui::test]
18435async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
18436    let (buffer_id, mut cx) = setup_indent_guides_editor(
18437        &"
18438        fn main() {
18439            let a = 1;
18440        }"
18441        .unindent(),
18442        cx,
18443    )
18444    .await;
18445
18446    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
18447}
18448
18449#[gpui::test]
18450async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
18451    let (buffer_id, mut cx) = setup_indent_guides_editor(
18452        &"
18453        fn main() {
18454            let a = 1;
18455            let b = 2;
18456        }"
18457        .unindent(),
18458        cx,
18459    )
18460    .await;
18461
18462    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
18463}
18464
18465#[gpui::test]
18466async fn test_indent_guide_nested(cx: &mut TestAppContext) {
18467    let (buffer_id, mut cx) = setup_indent_guides_editor(
18468        &"
18469        fn main() {
18470            let a = 1;
18471            if a == 3 {
18472                let b = 2;
18473            } else {
18474                let c = 3;
18475            }
18476        }"
18477        .unindent(),
18478        cx,
18479    )
18480    .await;
18481
18482    assert_indent_guides(
18483        0..8,
18484        vec![
18485            indent_guide(buffer_id, 1, 6, 0),
18486            indent_guide(buffer_id, 3, 3, 1),
18487            indent_guide(buffer_id, 5, 5, 1),
18488        ],
18489        None,
18490        &mut cx,
18491    );
18492}
18493
18494#[gpui::test]
18495async fn test_indent_guide_tab(cx: &mut TestAppContext) {
18496    let (buffer_id, mut cx) = setup_indent_guides_editor(
18497        &"
18498        fn main() {
18499            let a = 1;
18500                let b = 2;
18501            let c = 3;
18502        }"
18503        .unindent(),
18504        cx,
18505    )
18506    .await;
18507
18508    assert_indent_guides(
18509        0..5,
18510        vec![
18511            indent_guide(buffer_id, 1, 3, 0),
18512            indent_guide(buffer_id, 2, 2, 1),
18513        ],
18514        None,
18515        &mut cx,
18516    );
18517}
18518
18519#[gpui::test]
18520async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
18521    let (buffer_id, mut cx) = setup_indent_guides_editor(
18522        &"
18523        fn main() {
18524            let a = 1;
18525
18526            let c = 3;
18527        }"
18528        .unindent(),
18529        cx,
18530    )
18531    .await;
18532
18533    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
18534}
18535
18536#[gpui::test]
18537async fn test_indent_guide_complex(cx: &mut TestAppContext) {
18538    let (buffer_id, mut cx) = setup_indent_guides_editor(
18539        &"
18540        fn main() {
18541            let a = 1;
18542
18543            let c = 3;
18544
18545            if a == 3 {
18546                let b = 2;
18547            } else {
18548                let c = 3;
18549            }
18550        }"
18551        .unindent(),
18552        cx,
18553    )
18554    .await;
18555
18556    assert_indent_guides(
18557        0..11,
18558        vec![
18559            indent_guide(buffer_id, 1, 9, 0),
18560            indent_guide(buffer_id, 6, 6, 1),
18561            indent_guide(buffer_id, 8, 8, 1),
18562        ],
18563        None,
18564        &mut cx,
18565    );
18566}
18567
18568#[gpui::test]
18569async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
18570    let (buffer_id, mut cx) = setup_indent_guides_editor(
18571        &"
18572        fn main() {
18573            let a = 1;
18574
18575            let c = 3;
18576
18577            if a == 3 {
18578                let b = 2;
18579            } else {
18580                let c = 3;
18581            }
18582        }"
18583        .unindent(),
18584        cx,
18585    )
18586    .await;
18587
18588    assert_indent_guides(
18589        1..11,
18590        vec![
18591            indent_guide(buffer_id, 1, 9, 0),
18592            indent_guide(buffer_id, 6, 6, 1),
18593            indent_guide(buffer_id, 8, 8, 1),
18594        ],
18595        None,
18596        &mut cx,
18597    );
18598}
18599
18600#[gpui::test]
18601async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
18602    let (buffer_id, mut cx) = setup_indent_guides_editor(
18603        &"
18604        fn main() {
18605            let a = 1;
18606
18607            let c = 3;
18608
18609            if a == 3 {
18610                let b = 2;
18611            } else {
18612                let c = 3;
18613            }
18614        }"
18615        .unindent(),
18616        cx,
18617    )
18618    .await;
18619
18620    assert_indent_guides(
18621        1..10,
18622        vec![
18623            indent_guide(buffer_id, 1, 9, 0),
18624            indent_guide(buffer_id, 6, 6, 1),
18625            indent_guide(buffer_id, 8, 8, 1),
18626        ],
18627        None,
18628        &mut cx,
18629    );
18630}
18631
18632#[gpui::test]
18633async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
18634    let (buffer_id, mut cx) = setup_indent_guides_editor(
18635        &"
18636        fn main() {
18637            if a {
18638                b(
18639                    c,
18640                    d,
18641                )
18642            } else {
18643                e(
18644                    f
18645                )
18646            }
18647        }"
18648        .unindent(),
18649        cx,
18650    )
18651    .await;
18652
18653    assert_indent_guides(
18654        0..11,
18655        vec![
18656            indent_guide(buffer_id, 1, 10, 0),
18657            indent_guide(buffer_id, 2, 5, 1),
18658            indent_guide(buffer_id, 7, 9, 1),
18659            indent_guide(buffer_id, 3, 4, 2),
18660            indent_guide(buffer_id, 8, 8, 2),
18661        ],
18662        None,
18663        &mut cx,
18664    );
18665
18666    cx.update_editor(|editor, window, cx| {
18667        editor.fold_at(MultiBufferRow(2), window, cx);
18668        assert_eq!(
18669            editor.display_text(cx),
18670            "
18671            fn main() {
18672                if a {
18673                    b(⋯
18674                    )
18675                } else {
18676                    e(
18677                        f
18678                    )
18679                }
18680            }"
18681            .unindent()
18682        );
18683    });
18684
18685    assert_indent_guides(
18686        0..11,
18687        vec![
18688            indent_guide(buffer_id, 1, 10, 0),
18689            indent_guide(buffer_id, 2, 5, 1),
18690            indent_guide(buffer_id, 7, 9, 1),
18691            indent_guide(buffer_id, 8, 8, 2),
18692        ],
18693        None,
18694        &mut cx,
18695    );
18696}
18697
18698#[gpui::test]
18699async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
18700    let (buffer_id, mut cx) = setup_indent_guides_editor(
18701        &"
18702        block1
18703            block2
18704                block3
18705                    block4
18706            block2
18707        block1
18708        block1"
18709            .unindent(),
18710        cx,
18711    )
18712    .await;
18713
18714    assert_indent_guides(
18715        1..10,
18716        vec![
18717            indent_guide(buffer_id, 1, 4, 0),
18718            indent_guide(buffer_id, 2, 3, 1),
18719            indent_guide(buffer_id, 3, 3, 2),
18720        ],
18721        None,
18722        &mut cx,
18723    );
18724}
18725
18726#[gpui::test]
18727async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
18728    let (buffer_id, mut cx) = setup_indent_guides_editor(
18729        &"
18730        block1
18731            block2
18732                block3
18733
18734        block1
18735        block1"
18736            .unindent(),
18737        cx,
18738    )
18739    .await;
18740
18741    assert_indent_guides(
18742        0..6,
18743        vec![
18744            indent_guide(buffer_id, 1, 2, 0),
18745            indent_guide(buffer_id, 2, 2, 1),
18746        ],
18747        None,
18748        &mut cx,
18749    );
18750}
18751
18752#[gpui::test]
18753async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
18754    let (buffer_id, mut cx) = setup_indent_guides_editor(
18755        &"
18756        function component() {
18757        \treturn (
18758        \t\t\t
18759        \t\t<div>
18760        \t\t\t<abc></abc>
18761        \t\t</div>
18762        \t)
18763        }"
18764        .unindent(),
18765        cx,
18766    )
18767    .await;
18768
18769    assert_indent_guides(
18770        0..8,
18771        vec![
18772            indent_guide(buffer_id, 1, 6, 0),
18773            indent_guide(buffer_id, 2, 5, 1),
18774            indent_guide(buffer_id, 4, 4, 2),
18775        ],
18776        None,
18777        &mut cx,
18778    );
18779}
18780
18781#[gpui::test]
18782async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
18783    let (buffer_id, mut cx) = setup_indent_guides_editor(
18784        &"
18785        function component() {
18786        \treturn (
18787        \t
18788        \t\t<div>
18789        \t\t\t<abc></abc>
18790        \t\t</div>
18791        \t)
18792        }"
18793        .unindent(),
18794        cx,
18795    )
18796    .await;
18797
18798    assert_indent_guides(
18799        0..8,
18800        vec![
18801            indent_guide(buffer_id, 1, 6, 0),
18802            indent_guide(buffer_id, 2, 5, 1),
18803            indent_guide(buffer_id, 4, 4, 2),
18804        ],
18805        None,
18806        &mut cx,
18807    );
18808}
18809
18810#[gpui::test]
18811async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
18812    let (buffer_id, mut cx) = setup_indent_guides_editor(
18813        &"
18814        block1
18815
18816
18817
18818            block2
18819        "
18820        .unindent(),
18821        cx,
18822    )
18823    .await;
18824
18825    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
18826}
18827
18828#[gpui::test]
18829async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
18830    let (buffer_id, mut cx) = setup_indent_guides_editor(
18831        &"
18832        def a:
18833        \tb = 3
18834        \tif True:
18835        \t\tc = 4
18836        \t\td = 5
18837        \tprint(b)
18838        "
18839        .unindent(),
18840        cx,
18841    )
18842    .await;
18843
18844    assert_indent_guides(
18845        0..6,
18846        vec![
18847            indent_guide(buffer_id, 1, 5, 0),
18848            indent_guide(buffer_id, 3, 4, 1),
18849        ],
18850        None,
18851        &mut cx,
18852    );
18853}
18854
18855#[gpui::test]
18856async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
18857    let (buffer_id, mut cx) = setup_indent_guides_editor(
18858        &"
18859    fn main() {
18860        let a = 1;
18861    }"
18862        .unindent(),
18863        cx,
18864    )
18865    .await;
18866
18867    cx.update_editor(|editor, window, cx| {
18868        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18869            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18870        });
18871    });
18872
18873    assert_indent_guides(
18874        0..3,
18875        vec![indent_guide(buffer_id, 1, 1, 0)],
18876        Some(vec![0]),
18877        &mut cx,
18878    );
18879}
18880
18881#[gpui::test]
18882async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
18883    let (buffer_id, mut cx) = setup_indent_guides_editor(
18884        &"
18885    fn main() {
18886        if 1 == 2 {
18887            let a = 1;
18888        }
18889    }"
18890        .unindent(),
18891        cx,
18892    )
18893    .await;
18894
18895    cx.update_editor(|editor, window, cx| {
18896        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18897            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18898        });
18899    });
18900
18901    assert_indent_guides(
18902        0..4,
18903        vec![
18904            indent_guide(buffer_id, 1, 3, 0),
18905            indent_guide(buffer_id, 2, 2, 1),
18906        ],
18907        Some(vec![1]),
18908        &mut cx,
18909    );
18910
18911    cx.update_editor(|editor, window, cx| {
18912        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18913            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
18914        });
18915    });
18916
18917    assert_indent_guides(
18918        0..4,
18919        vec![
18920            indent_guide(buffer_id, 1, 3, 0),
18921            indent_guide(buffer_id, 2, 2, 1),
18922        ],
18923        Some(vec![1]),
18924        &mut cx,
18925    );
18926
18927    cx.update_editor(|editor, window, cx| {
18928        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18929            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
18930        });
18931    });
18932
18933    assert_indent_guides(
18934        0..4,
18935        vec![
18936            indent_guide(buffer_id, 1, 3, 0),
18937            indent_guide(buffer_id, 2, 2, 1),
18938        ],
18939        Some(vec![0]),
18940        &mut cx,
18941    );
18942}
18943
18944#[gpui::test]
18945async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
18946    let (buffer_id, mut cx) = setup_indent_guides_editor(
18947        &"
18948    fn main() {
18949        let a = 1;
18950
18951        let b = 2;
18952    }"
18953        .unindent(),
18954        cx,
18955    )
18956    .await;
18957
18958    cx.update_editor(|editor, window, cx| {
18959        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18960            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
18961        });
18962    });
18963
18964    assert_indent_guides(
18965        0..5,
18966        vec![indent_guide(buffer_id, 1, 3, 0)],
18967        Some(vec![0]),
18968        &mut cx,
18969    );
18970}
18971
18972#[gpui::test]
18973async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
18974    let (buffer_id, mut cx) = setup_indent_guides_editor(
18975        &"
18976    def m:
18977        a = 1
18978        pass"
18979            .unindent(),
18980        cx,
18981    )
18982    .await;
18983
18984    cx.update_editor(|editor, window, cx| {
18985        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18986            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18987        });
18988    });
18989
18990    assert_indent_guides(
18991        0..3,
18992        vec![indent_guide(buffer_id, 1, 2, 0)],
18993        Some(vec![0]),
18994        &mut cx,
18995    );
18996}
18997
18998#[gpui::test]
18999async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
19000    init_test(cx, |_| {});
19001    let mut cx = EditorTestContext::new(cx).await;
19002    let text = indoc! {
19003        "
19004        impl A {
19005            fn b() {
19006                0;
19007                3;
19008                5;
19009                6;
19010                7;
19011            }
19012        }
19013        "
19014    };
19015    let base_text = indoc! {
19016        "
19017        impl A {
19018            fn b() {
19019                0;
19020                1;
19021                2;
19022                3;
19023                4;
19024            }
19025            fn c() {
19026                5;
19027                6;
19028                7;
19029            }
19030        }
19031        "
19032    };
19033
19034    cx.update_editor(|editor, window, cx| {
19035        editor.set_text(text, window, cx);
19036
19037        editor.buffer().update(cx, |multibuffer, cx| {
19038            let buffer = multibuffer.as_singleton().unwrap();
19039            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
19040
19041            multibuffer.set_all_diff_hunks_expanded(cx);
19042            multibuffer.add_diff(diff, cx);
19043
19044            buffer.read(cx).remote_id()
19045        })
19046    });
19047    cx.run_until_parked();
19048
19049    cx.assert_state_with_diff(
19050        indoc! { "
19051          impl A {
19052              fn b() {
19053                  0;
19054        -         1;
19055        -         2;
19056                  3;
19057        -         4;
19058        -     }
19059        -     fn c() {
19060                  5;
19061                  6;
19062                  7;
19063              }
19064          }
19065          ˇ"
19066        }
19067        .to_string(),
19068    );
19069
19070    let mut actual_guides = cx.update_editor(|editor, window, cx| {
19071        editor
19072            .snapshot(window, cx)
19073            .buffer_snapshot
19074            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
19075            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
19076            .collect::<Vec<_>>()
19077    });
19078    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
19079    assert_eq!(
19080        actual_guides,
19081        vec![
19082            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
19083            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
19084            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
19085        ]
19086    );
19087}
19088
19089#[gpui::test]
19090async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19091    init_test(cx, |_| {});
19092    let mut cx = EditorTestContext::new(cx).await;
19093
19094    let diff_base = r#"
19095        a
19096        b
19097        c
19098        "#
19099    .unindent();
19100
19101    cx.set_state(
19102        &r#"
19103        ˇA
19104        b
19105        C
19106        "#
19107        .unindent(),
19108    );
19109    cx.set_head_text(&diff_base);
19110    cx.update_editor(|editor, window, cx| {
19111        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19112    });
19113    executor.run_until_parked();
19114
19115    let both_hunks_expanded = r#"
19116        - a
19117        + ˇA
19118          b
19119        - c
19120        + C
19121        "#
19122    .unindent();
19123
19124    cx.assert_state_with_diff(both_hunks_expanded.clone());
19125
19126    let hunk_ranges = cx.update_editor(|editor, window, cx| {
19127        let snapshot = editor.snapshot(window, cx);
19128        let hunks = editor
19129            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19130            .collect::<Vec<_>>();
19131        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
19132        let buffer_id = hunks[0].buffer_id;
19133        hunks
19134            .into_iter()
19135            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
19136            .collect::<Vec<_>>()
19137    });
19138    assert_eq!(hunk_ranges.len(), 2);
19139
19140    cx.update_editor(|editor, _, cx| {
19141        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19142    });
19143    executor.run_until_parked();
19144
19145    let second_hunk_expanded = r#"
19146          ˇA
19147          b
19148        - c
19149        + C
19150        "#
19151    .unindent();
19152
19153    cx.assert_state_with_diff(second_hunk_expanded);
19154
19155    cx.update_editor(|editor, _, cx| {
19156        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19157    });
19158    executor.run_until_parked();
19159
19160    cx.assert_state_with_diff(both_hunks_expanded.clone());
19161
19162    cx.update_editor(|editor, _, cx| {
19163        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
19164    });
19165    executor.run_until_parked();
19166
19167    let first_hunk_expanded = r#"
19168        - a
19169        + ˇA
19170          b
19171          C
19172        "#
19173    .unindent();
19174
19175    cx.assert_state_with_diff(first_hunk_expanded);
19176
19177    cx.update_editor(|editor, _, cx| {
19178        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
19179    });
19180    executor.run_until_parked();
19181
19182    cx.assert_state_with_diff(both_hunks_expanded);
19183
19184    cx.set_state(
19185        &r#"
19186        ˇA
19187        b
19188        "#
19189        .unindent(),
19190    );
19191    cx.run_until_parked();
19192
19193    // TODO this cursor position seems bad
19194    cx.assert_state_with_diff(
19195        r#"
19196        - ˇa
19197        + A
19198          b
19199        "#
19200        .unindent(),
19201    );
19202
19203    cx.update_editor(|editor, window, cx| {
19204        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19205    });
19206
19207    cx.assert_state_with_diff(
19208        r#"
19209            - ˇa
19210            + A
19211              b
19212            - c
19213            "#
19214        .unindent(),
19215    );
19216
19217    let hunk_ranges = cx.update_editor(|editor, window, cx| {
19218        let snapshot = editor.snapshot(window, cx);
19219        let hunks = editor
19220            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19221            .collect::<Vec<_>>();
19222        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
19223        let buffer_id = hunks[0].buffer_id;
19224        hunks
19225            .into_iter()
19226            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
19227            .collect::<Vec<_>>()
19228    });
19229    assert_eq!(hunk_ranges.len(), 2);
19230
19231    cx.update_editor(|editor, _, cx| {
19232        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
19233    });
19234    executor.run_until_parked();
19235
19236    cx.assert_state_with_diff(
19237        r#"
19238        - ˇa
19239        + A
19240          b
19241        "#
19242        .unindent(),
19243    );
19244}
19245
19246#[gpui::test]
19247async fn test_toggle_deletion_hunk_at_start_of_file(
19248    executor: BackgroundExecutor,
19249    cx: &mut TestAppContext,
19250) {
19251    init_test(cx, |_| {});
19252    let mut cx = EditorTestContext::new(cx).await;
19253
19254    let diff_base = r#"
19255        a
19256        b
19257        c
19258        "#
19259    .unindent();
19260
19261    cx.set_state(
19262        &r#"
19263        ˇb
19264        c
19265        "#
19266        .unindent(),
19267    );
19268    cx.set_head_text(&diff_base);
19269    cx.update_editor(|editor, window, cx| {
19270        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19271    });
19272    executor.run_until_parked();
19273
19274    let hunk_expanded = r#"
19275        - a
19276          ˇb
19277          c
19278        "#
19279    .unindent();
19280
19281    cx.assert_state_with_diff(hunk_expanded.clone());
19282
19283    let hunk_ranges = cx.update_editor(|editor, window, cx| {
19284        let snapshot = editor.snapshot(window, cx);
19285        let hunks = editor
19286            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19287            .collect::<Vec<_>>();
19288        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
19289        let buffer_id = hunks[0].buffer_id;
19290        hunks
19291            .into_iter()
19292            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
19293            .collect::<Vec<_>>()
19294    });
19295    assert_eq!(hunk_ranges.len(), 1);
19296
19297    cx.update_editor(|editor, _, cx| {
19298        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19299    });
19300    executor.run_until_parked();
19301
19302    let hunk_collapsed = r#"
19303          ˇb
19304          c
19305        "#
19306    .unindent();
19307
19308    cx.assert_state_with_diff(hunk_collapsed);
19309
19310    cx.update_editor(|editor, _, cx| {
19311        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19312    });
19313    executor.run_until_parked();
19314
19315    cx.assert_state_with_diff(hunk_expanded.clone());
19316}
19317
19318#[gpui::test]
19319async fn test_display_diff_hunks(cx: &mut TestAppContext) {
19320    init_test(cx, |_| {});
19321
19322    let fs = FakeFs::new(cx.executor());
19323    fs.insert_tree(
19324        path!("/test"),
19325        json!({
19326            ".git": {},
19327            "file-1": "ONE\n",
19328            "file-2": "TWO\n",
19329            "file-3": "THREE\n",
19330        }),
19331    )
19332    .await;
19333
19334    fs.set_head_for_repo(
19335        path!("/test/.git").as_ref(),
19336        &[
19337            ("file-1".into(), "one\n".into()),
19338            ("file-2".into(), "two\n".into()),
19339            ("file-3".into(), "three\n".into()),
19340        ],
19341        "deadbeef",
19342    );
19343
19344    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
19345    let mut buffers = vec![];
19346    for i in 1..=3 {
19347        let buffer = project
19348            .update(cx, |project, cx| {
19349                let path = format!(path!("/test/file-{}"), i);
19350                project.open_local_buffer(path, cx)
19351            })
19352            .await
19353            .unwrap();
19354        buffers.push(buffer);
19355    }
19356
19357    let multibuffer = cx.new(|cx| {
19358        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
19359        multibuffer.set_all_diff_hunks_expanded(cx);
19360        for buffer in &buffers {
19361            let snapshot = buffer.read(cx).snapshot();
19362            multibuffer.set_excerpts_for_path(
19363                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
19364                buffer.clone(),
19365                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
19366                DEFAULT_MULTIBUFFER_CONTEXT,
19367                cx,
19368            );
19369        }
19370        multibuffer
19371    });
19372
19373    let editor = cx.add_window(|window, cx| {
19374        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
19375    });
19376    cx.run_until_parked();
19377
19378    let snapshot = editor
19379        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19380        .unwrap();
19381    let hunks = snapshot
19382        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
19383        .map(|hunk| match hunk {
19384            DisplayDiffHunk::Unfolded {
19385                display_row_range, ..
19386            } => display_row_range,
19387            DisplayDiffHunk::Folded { .. } => unreachable!(),
19388        })
19389        .collect::<Vec<_>>();
19390    assert_eq!(
19391        hunks,
19392        [
19393            DisplayRow(2)..DisplayRow(4),
19394            DisplayRow(7)..DisplayRow(9),
19395            DisplayRow(12)..DisplayRow(14),
19396        ]
19397    );
19398}
19399
19400#[gpui::test]
19401async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
19402    init_test(cx, |_| {});
19403
19404    let mut cx = EditorTestContext::new(cx).await;
19405    cx.set_head_text(indoc! { "
19406        one
19407        two
19408        three
19409        four
19410        five
19411        "
19412    });
19413    cx.set_index_text(indoc! { "
19414        one
19415        two
19416        three
19417        four
19418        five
19419        "
19420    });
19421    cx.set_state(indoc! {"
19422        one
19423        TWO
19424        ˇTHREE
19425        FOUR
19426        five
19427    "});
19428    cx.run_until_parked();
19429    cx.update_editor(|editor, window, cx| {
19430        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19431    });
19432    cx.run_until_parked();
19433    cx.assert_index_text(Some(indoc! {"
19434        one
19435        TWO
19436        THREE
19437        FOUR
19438        five
19439    "}));
19440    cx.set_state(indoc! { "
19441        one
19442        TWO
19443        ˇTHREE-HUNDRED
19444        FOUR
19445        five
19446    "});
19447    cx.run_until_parked();
19448    cx.update_editor(|editor, window, cx| {
19449        let snapshot = editor.snapshot(window, cx);
19450        let hunks = editor
19451            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19452            .collect::<Vec<_>>();
19453        assert_eq!(hunks.len(), 1);
19454        assert_eq!(
19455            hunks[0].status(),
19456            DiffHunkStatus {
19457                kind: DiffHunkStatusKind::Modified,
19458                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
19459            }
19460        );
19461
19462        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19463    });
19464    cx.run_until_parked();
19465    cx.assert_index_text(Some(indoc! {"
19466        one
19467        TWO
19468        THREE-HUNDRED
19469        FOUR
19470        five
19471    "}));
19472}
19473
19474#[gpui::test]
19475fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
19476    init_test(cx, |_| {});
19477
19478    let editor = cx.add_window(|window, cx| {
19479        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
19480        build_editor(buffer, window, cx)
19481    });
19482
19483    let render_args = Arc::new(Mutex::new(None));
19484    let snapshot = editor
19485        .update(cx, |editor, window, cx| {
19486            let snapshot = editor.buffer().read(cx).snapshot(cx);
19487            let range =
19488                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
19489
19490            struct RenderArgs {
19491                row: MultiBufferRow,
19492                folded: bool,
19493                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
19494            }
19495
19496            let crease = Crease::inline(
19497                range,
19498                FoldPlaceholder::test(),
19499                {
19500                    let toggle_callback = render_args.clone();
19501                    move |row, folded, callback, _window, _cx| {
19502                        *toggle_callback.lock() = Some(RenderArgs {
19503                            row,
19504                            folded,
19505                            callback,
19506                        });
19507                        div()
19508                    }
19509                },
19510                |_row, _folded, _window, _cx| div(),
19511            );
19512
19513            editor.insert_creases(Some(crease), cx);
19514            let snapshot = editor.snapshot(window, cx);
19515            let _div = snapshot.render_crease_toggle(
19516                MultiBufferRow(1),
19517                false,
19518                cx.entity().clone(),
19519                window,
19520                cx,
19521            );
19522            snapshot
19523        })
19524        .unwrap();
19525
19526    let render_args = render_args.lock().take().unwrap();
19527    assert_eq!(render_args.row, MultiBufferRow(1));
19528    assert!(!render_args.folded);
19529    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
19530
19531    cx.update_window(*editor, |_, window, cx| {
19532        (render_args.callback)(true, window, cx)
19533    })
19534    .unwrap();
19535    let snapshot = editor
19536        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19537        .unwrap();
19538    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
19539
19540    cx.update_window(*editor, |_, window, cx| {
19541        (render_args.callback)(false, window, cx)
19542    })
19543    .unwrap();
19544    let snapshot = editor
19545        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19546        .unwrap();
19547    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
19548}
19549
19550#[gpui::test]
19551async fn test_input_text(cx: &mut TestAppContext) {
19552    init_test(cx, |_| {});
19553    let mut cx = EditorTestContext::new(cx).await;
19554
19555    cx.set_state(
19556        &r#"ˇone
19557        two
19558
19559        three
19560        fourˇ
19561        five
19562
19563        siˇx"#
19564            .unindent(),
19565    );
19566
19567    cx.dispatch_action(HandleInput(String::new()));
19568    cx.assert_editor_state(
19569        &r#"ˇone
19570        two
19571
19572        three
19573        fourˇ
19574        five
19575
19576        siˇx"#
19577            .unindent(),
19578    );
19579
19580    cx.dispatch_action(HandleInput("AAAA".to_string()));
19581    cx.assert_editor_state(
19582        &r#"AAAAˇone
19583        two
19584
19585        three
19586        fourAAAAˇ
19587        five
19588
19589        siAAAAˇx"#
19590            .unindent(),
19591    );
19592}
19593
19594#[gpui::test]
19595async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
19596    init_test(cx, |_| {});
19597
19598    let mut cx = EditorTestContext::new(cx).await;
19599    cx.set_state(
19600        r#"let foo = 1;
19601let foo = 2;
19602let foo = 3;
19603let fooˇ = 4;
19604let foo = 5;
19605let foo = 6;
19606let foo = 7;
19607let foo = 8;
19608let foo = 9;
19609let foo = 10;
19610let foo = 11;
19611let foo = 12;
19612let foo = 13;
19613let foo = 14;
19614let foo = 15;"#,
19615    );
19616
19617    cx.update_editor(|e, window, cx| {
19618        assert_eq!(
19619            e.next_scroll_position,
19620            NextScrollCursorCenterTopBottom::Center,
19621            "Default next scroll direction is center",
19622        );
19623
19624        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19625        assert_eq!(
19626            e.next_scroll_position,
19627            NextScrollCursorCenterTopBottom::Top,
19628            "After center, next scroll direction should be top",
19629        );
19630
19631        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19632        assert_eq!(
19633            e.next_scroll_position,
19634            NextScrollCursorCenterTopBottom::Bottom,
19635            "After top, next scroll direction should be bottom",
19636        );
19637
19638        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19639        assert_eq!(
19640            e.next_scroll_position,
19641            NextScrollCursorCenterTopBottom::Center,
19642            "After bottom, scrolling should start over",
19643        );
19644
19645        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19646        assert_eq!(
19647            e.next_scroll_position,
19648            NextScrollCursorCenterTopBottom::Top,
19649            "Scrolling continues if retriggered fast enough"
19650        );
19651    });
19652
19653    cx.executor()
19654        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
19655    cx.executor().run_until_parked();
19656    cx.update_editor(|e, _, _| {
19657        assert_eq!(
19658            e.next_scroll_position,
19659            NextScrollCursorCenterTopBottom::Center,
19660            "If scrolling is not triggered fast enough, it should reset"
19661        );
19662    });
19663}
19664
19665#[gpui::test]
19666async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
19667    init_test(cx, |_| {});
19668    let mut cx = EditorLspTestContext::new_rust(
19669        lsp::ServerCapabilities {
19670            definition_provider: Some(lsp::OneOf::Left(true)),
19671            references_provider: Some(lsp::OneOf::Left(true)),
19672            ..lsp::ServerCapabilities::default()
19673        },
19674        cx,
19675    )
19676    .await;
19677
19678    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
19679        let go_to_definition = cx
19680            .lsp
19681            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
19682                move |params, _| async move {
19683                    if empty_go_to_definition {
19684                        Ok(None)
19685                    } else {
19686                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
19687                            uri: params.text_document_position_params.text_document.uri,
19688                            range: lsp::Range::new(
19689                                lsp::Position::new(4, 3),
19690                                lsp::Position::new(4, 6),
19691                            ),
19692                        })))
19693                    }
19694                },
19695            );
19696        let references = cx
19697            .lsp
19698            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
19699                Ok(Some(vec![lsp::Location {
19700                    uri: params.text_document_position.text_document.uri,
19701                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
19702                }]))
19703            });
19704        (go_to_definition, references)
19705    };
19706
19707    cx.set_state(
19708        &r#"fn one() {
19709            let mut a = ˇtwo();
19710        }
19711
19712        fn two() {}"#
19713            .unindent(),
19714    );
19715    set_up_lsp_handlers(false, &mut cx);
19716    let navigated = cx
19717        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19718        .await
19719        .expect("Failed to navigate to definition");
19720    assert_eq!(
19721        navigated,
19722        Navigated::Yes,
19723        "Should have navigated to definition from the GetDefinition response"
19724    );
19725    cx.assert_editor_state(
19726        &r#"fn one() {
19727            let mut a = two();
19728        }
19729
19730        fn «twoˇ»() {}"#
19731            .unindent(),
19732    );
19733
19734    let editors = cx.update_workspace(|workspace, _, cx| {
19735        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19736    });
19737    cx.update_editor(|_, _, test_editor_cx| {
19738        assert_eq!(
19739            editors.len(),
19740            1,
19741            "Initially, only one, test, editor should be open in the workspace"
19742        );
19743        assert_eq!(
19744            test_editor_cx.entity(),
19745            editors.last().expect("Asserted len is 1").clone()
19746        );
19747    });
19748
19749    set_up_lsp_handlers(true, &mut cx);
19750    let navigated = cx
19751        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19752        .await
19753        .expect("Failed to navigate to lookup references");
19754    assert_eq!(
19755        navigated,
19756        Navigated::Yes,
19757        "Should have navigated to references as a fallback after empty GoToDefinition response"
19758    );
19759    // We should not change the selections in the existing file,
19760    // if opening another milti buffer with the references
19761    cx.assert_editor_state(
19762        &r#"fn one() {
19763            let mut a = two();
19764        }
19765
19766        fn «twoˇ»() {}"#
19767            .unindent(),
19768    );
19769    let editors = cx.update_workspace(|workspace, _, cx| {
19770        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19771    });
19772    cx.update_editor(|_, _, test_editor_cx| {
19773        assert_eq!(
19774            editors.len(),
19775            2,
19776            "After falling back to references search, we open a new editor with the results"
19777        );
19778        let references_fallback_text = editors
19779            .into_iter()
19780            .find(|new_editor| *new_editor != test_editor_cx.entity())
19781            .expect("Should have one non-test editor now")
19782            .read(test_editor_cx)
19783            .text(test_editor_cx);
19784        assert_eq!(
19785            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
19786            "Should use the range from the references response and not the GoToDefinition one"
19787        );
19788    });
19789}
19790
19791#[gpui::test]
19792async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
19793    init_test(cx, |_| {});
19794    cx.update(|cx| {
19795        let mut editor_settings = EditorSettings::get_global(cx).clone();
19796        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
19797        EditorSettings::override_global(editor_settings, cx);
19798    });
19799    let mut cx = EditorLspTestContext::new_rust(
19800        lsp::ServerCapabilities {
19801            definition_provider: Some(lsp::OneOf::Left(true)),
19802            references_provider: Some(lsp::OneOf::Left(true)),
19803            ..lsp::ServerCapabilities::default()
19804        },
19805        cx,
19806    )
19807    .await;
19808    let original_state = r#"fn one() {
19809        let mut a = ˇtwo();
19810    }
19811
19812    fn two() {}"#
19813        .unindent();
19814    cx.set_state(&original_state);
19815
19816    let mut go_to_definition = cx
19817        .lsp
19818        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
19819            move |_, _| async move { Ok(None) },
19820        );
19821    let _references = cx
19822        .lsp
19823        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
19824            panic!("Should not call for references with no go to definition fallback")
19825        });
19826
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    go_to_definition
19832        .next()
19833        .await
19834        .expect("Should have called the go_to_definition handler");
19835
19836    assert_eq!(
19837        navigated,
19838        Navigated::No,
19839        "Should have navigated to references as a fallback after empty GoToDefinition response"
19840    );
19841    cx.assert_editor_state(&original_state);
19842    let editors = cx.update_workspace(|workspace, _, cx| {
19843        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19844    });
19845    cx.update_editor(|_, _, _| {
19846        assert_eq!(
19847            editors.len(),
19848            1,
19849            "After unsuccessful fallback, no other editor should have been opened"
19850        );
19851    });
19852}
19853
19854#[gpui::test]
19855async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
19856    init_test(cx, |_| {});
19857
19858    let language = Arc::new(Language::new(
19859        LanguageConfig::default(),
19860        Some(tree_sitter_rust::LANGUAGE.into()),
19861    ));
19862
19863    let text = r#"
19864        #[cfg(test)]
19865        mod tests() {
19866            #[test]
19867            fn runnable_1() {
19868                let a = 1;
19869            }
19870
19871            #[test]
19872            fn runnable_2() {
19873                let a = 1;
19874                let b = 2;
19875            }
19876        }
19877    "#
19878    .unindent();
19879
19880    let fs = FakeFs::new(cx.executor());
19881    fs.insert_file("/file.rs", Default::default()).await;
19882
19883    let project = Project::test(fs, ["/a".as_ref()], cx).await;
19884    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19885    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19886    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
19887    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
19888
19889    let editor = cx.new_window_entity(|window, cx| {
19890        Editor::new(
19891            EditorMode::full(),
19892            multi_buffer,
19893            Some(project.clone()),
19894            window,
19895            cx,
19896        )
19897    });
19898
19899    editor.update_in(cx, |editor, window, cx| {
19900        let snapshot = editor.buffer().read(cx).snapshot(cx);
19901        editor.tasks.insert(
19902            (buffer.read(cx).remote_id(), 3),
19903            RunnableTasks {
19904                templates: vec![],
19905                offset: snapshot.anchor_before(43),
19906                column: 0,
19907                extra_variables: HashMap::default(),
19908                context_range: BufferOffset(43)..BufferOffset(85),
19909            },
19910        );
19911        editor.tasks.insert(
19912            (buffer.read(cx).remote_id(), 8),
19913            RunnableTasks {
19914                templates: vec![],
19915                offset: snapshot.anchor_before(86),
19916                column: 0,
19917                extra_variables: HashMap::default(),
19918                context_range: BufferOffset(86)..BufferOffset(191),
19919            },
19920        );
19921
19922        // Test finding task when cursor is inside function body
19923        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19924            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
19925        });
19926        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
19927        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
19928
19929        // Test finding task when cursor is on function name
19930        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19931            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
19932        });
19933        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
19934        assert_eq!(row, 8, "Should find task when cursor is on function name");
19935    });
19936}
19937
19938#[gpui::test]
19939async fn test_folding_buffers(cx: &mut TestAppContext) {
19940    init_test(cx, |_| {});
19941
19942    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
19943    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
19944    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
19945
19946    let fs = FakeFs::new(cx.executor());
19947    fs.insert_tree(
19948        path!("/a"),
19949        json!({
19950            "first.rs": sample_text_1,
19951            "second.rs": sample_text_2,
19952            "third.rs": sample_text_3,
19953        }),
19954    )
19955    .await;
19956    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19957    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19958    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19959    let worktree = project.update(cx, |project, cx| {
19960        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19961        assert_eq!(worktrees.len(), 1);
19962        worktrees.pop().unwrap()
19963    });
19964    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19965
19966    let buffer_1 = project
19967        .update(cx, |project, cx| {
19968            project.open_buffer((worktree_id, "first.rs"), cx)
19969        })
19970        .await
19971        .unwrap();
19972    let buffer_2 = project
19973        .update(cx, |project, cx| {
19974            project.open_buffer((worktree_id, "second.rs"), cx)
19975        })
19976        .await
19977        .unwrap();
19978    let buffer_3 = project
19979        .update(cx, |project, cx| {
19980            project.open_buffer((worktree_id, "third.rs"), cx)
19981        })
19982        .await
19983        .unwrap();
19984
19985    let multi_buffer = cx.new(|cx| {
19986        let mut multi_buffer = MultiBuffer::new(ReadWrite);
19987        multi_buffer.push_excerpts(
19988            buffer_1.clone(),
19989            [
19990                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19991                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19992                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19993            ],
19994            cx,
19995        );
19996        multi_buffer.push_excerpts(
19997            buffer_2.clone(),
19998            [
19999                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20000                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20001                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20002            ],
20003            cx,
20004        );
20005        multi_buffer.push_excerpts(
20006            buffer_3.clone(),
20007            [
20008                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20009                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20010                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20011            ],
20012            cx,
20013        );
20014        multi_buffer
20015    });
20016    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20017        Editor::new(
20018            EditorMode::full(),
20019            multi_buffer.clone(),
20020            Some(project.clone()),
20021            window,
20022            cx,
20023        )
20024    });
20025
20026    assert_eq!(
20027        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20028        "\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",
20029    );
20030
20031    multi_buffer_editor.update(cx, |editor, cx| {
20032        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
20033    });
20034    assert_eq!(
20035        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20036        "\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",
20037        "After folding the first buffer, its text should not be displayed"
20038    );
20039
20040    multi_buffer_editor.update(cx, |editor, cx| {
20041        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
20042    });
20043    assert_eq!(
20044        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20045        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
20046        "After folding the second buffer, its text should not be displayed"
20047    );
20048
20049    multi_buffer_editor.update(cx, |editor, cx| {
20050        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
20051    });
20052    assert_eq!(
20053        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20054        "\n\n\n\n\n",
20055        "After folding the third buffer, its text should not be displayed"
20056    );
20057
20058    // Emulate selection inside the fold logic, that should work
20059    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20060        editor
20061            .snapshot(window, cx)
20062            .next_line_boundary(Point::new(0, 4));
20063    });
20064
20065    multi_buffer_editor.update(cx, |editor, cx| {
20066        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
20067    });
20068    assert_eq!(
20069        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20070        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
20071        "After unfolding the second buffer, its text should be displayed"
20072    );
20073
20074    // Typing inside of buffer 1 causes that buffer to be unfolded.
20075    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20076        assert_eq!(
20077            multi_buffer
20078                .read(cx)
20079                .snapshot(cx)
20080                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
20081                .collect::<String>(),
20082            "bbbb"
20083        );
20084        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
20085            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
20086        });
20087        editor.handle_input("B", window, cx);
20088    });
20089
20090    assert_eq!(
20091        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20092        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
20093        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
20094    );
20095
20096    multi_buffer_editor.update(cx, |editor, cx| {
20097        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
20098    });
20099    assert_eq!(
20100        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20101        "\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",
20102        "After unfolding the all buffers, all original text should be displayed"
20103    );
20104}
20105
20106#[gpui::test]
20107async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
20108    init_test(cx, |_| {});
20109
20110    let sample_text_1 = "1111\n2222\n3333".to_string();
20111    let sample_text_2 = "4444\n5555\n6666".to_string();
20112    let sample_text_3 = "7777\n8888\n9999".to_string();
20113
20114    let fs = FakeFs::new(cx.executor());
20115    fs.insert_tree(
20116        path!("/a"),
20117        json!({
20118            "first.rs": sample_text_1,
20119            "second.rs": sample_text_2,
20120            "third.rs": sample_text_3,
20121        }),
20122    )
20123    .await;
20124    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20125    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20126    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20127    let worktree = project.update(cx, |project, cx| {
20128        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
20129        assert_eq!(worktrees.len(), 1);
20130        worktrees.pop().unwrap()
20131    });
20132    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
20133
20134    let buffer_1 = project
20135        .update(cx, |project, cx| {
20136            project.open_buffer((worktree_id, "first.rs"), cx)
20137        })
20138        .await
20139        .unwrap();
20140    let buffer_2 = project
20141        .update(cx, |project, cx| {
20142            project.open_buffer((worktree_id, "second.rs"), cx)
20143        })
20144        .await
20145        .unwrap();
20146    let buffer_3 = project
20147        .update(cx, |project, cx| {
20148            project.open_buffer((worktree_id, "third.rs"), cx)
20149        })
20150        .await
20151        .unwrap();
20152
20153    let multi_buffer = cx.new(|cx| {
20154        let mut multi_buffer = MultiBuffer::new(ReadWrite);
20155        multi_buffer.push_excerpts(
20156            buffer_1.clone(),
20157            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
20158            cx,
20159        );
20160        multi_buffer.push_excerpts(
20161            buffer_2.clone(),
20162            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
20163            cx,
20164        );
20165        multi_buffer.push_excerpts(
20166            buffer_3.clone(),
20167            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
20168            cx,
20169        );
20170        multi_buffer
20171    });
20172
20173    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20174        Editor::new(
20175            EditorMode::full(),
20176            multi_buffer,
20177            Some(project.clone()),
20178            window,
20179            cx,
20180        )
20181    });
20182
20183    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
20184    assert_eq!(
20185        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20186        full_text,
20187    );
20188
20189    multi_buffer_editor.update(cx, |editor, cx| {
20190        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
20191    });
20192    assert_eq!(
20193        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20194        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
20195        "After folding the first buffer, its text should not be displayed"
20196    );
20197
20198    multi_buffer_editor.update(cx, |editor, cx| {
20199        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
20200    });
20201
20202    assert_eq!(
20203        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20204        "\n\n\n\n\n\n7777\n8888\n9999",
20205        "After folding the second buffer, its text should not be displayed"
20206    );
20207
20208    multi_buffer_editor.update(cx, |editor, cx| {
20209        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
20210    });
20211    assert_eq!(
20212        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20213        "\n\n\n\n\n",
20214        "After folding the third buffer, its text should not be displayed"
20215    );
20216
20217    multi_buffer_editor.update(cx, |editor, cx| {
20218        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
20219    });
20220    assert_eq!(
20221        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20222        "\n\n\n\n4444\n5555\n6666\n\n",
20223        "After unfolding the second buffer, its text should be displayed"
20224    );
20225
20226    multi_buffer_editor.update(cx, |editor, cx| {
20227        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
20228    });
20229    assert_eq!(
20230        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20231        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
20232        "After unfolding the first buffer, its text should be displayed"
20233    );
20234
20235    multi_buffer_editor.update(cx, |editor, cx| {
20236        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
20237    });
20238    assert_eq!(
20239        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20240        full_text,
20241        "After unfolding all buffers, all original text should be displayed"
20242    );
20243}
20244
20245#[gpui::test]
20246async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
20247    init_test(cx, |_| {});
20248
20249    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
20250
20251    let fs = FakeFs::new(cx.executor());
20252    fs.insert_tree(
20253        path!("/a"),
20254        json!({
20255            "main.rs": sample_text,
20256        }),
20257    )
20258    .await;
20259    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20260    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20261    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20262    let worktree = project.update(cx, |project, cx| {
20263        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
20264        assert_eq!(worktrees.len(), 1);
20265        worktrees.pop().unwrap()
20266    });
20267    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
20268
20269    let buffer_1 = project
20270        .update(cx, |project, cx| {
20271            project.open_buffer((worktree_id, "main.rs"), cx)
20272        })
20273        .await
20274        .unwrap();
20275
20276    let multi_buffer = cx.new(|cx| {
20277        let mut multi_buffer = MultiBuffer::new(ReadWrite);
20278        multi_buffer.push_excerpts(
20279            buffer_1.clone(),
20280            [ExcerptRange::new(
20281                Point::new(0, 0)
20282                    ..Point::new(
20283                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
20284                        0,
20285                    ),
20286            )],
20287            cx,
20288        );
20289        multi_buffer
20290    });
20291    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20292        Editor::new(
20293            EditorMode::full(),
20294            multi_buffer,
20295            Some(project.clone()),
20296            window,
20297            cx,
20298        )
20299    });
20300
20301    let selection_range = Point::new(1, 0)..Point::new(2, 0);
20302    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20303        enum TestHighlight {}
20304        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
20305        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
20306        editor.highlight_text::<TestHighlight>(
20307            vec![highlight_range.clone()],
20308            HighlightStyle::color(Hsla::green()),
20309            cx,
20310        );
20311        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20312            s.select_ranges(Some(highlight_range))
20313        });
20314    });
20315
20316    let full_text = format!("\n\n{sample_text}");
20317    assert_eq!(
20318        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20319        full_text,
20320    );
20321}
20322
20323#[gpui::test]
20324async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
20325    init_test(cx, |_| {});
20326    cx.update(|cx| {
20327        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
20328            "keymaps/default-linux.json",
20329            cx,
20330        )
20331        .unwrap();
20332        cx.bind_keys(default_key_bindings);
20333    });
20334
20335    let (editor, cx) = cx.add_window_view(|window, cx| {
20336        let multi_buffer = MultiBuffer::build_multi(
20337            [
20338                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
20339                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
20340                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
20341                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
20342            ],
20343            cx,
20344        );
20345        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
20346
20347        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
20348        // fold all but the second buffer, so that we test navigating between two
20349        // adjacent folded buffers, as well as folded buffers at the start and
20350        // end the multibuffer
20351        editor.fold_buffer(buffer_ids[0], cx);
20352        editor.fold_buffer(buffer_ids[2], cx);
20353        editor.fold_buffer(buffer_ids[3], cx);
20354
20355        editor
20356    });
20357    cx.simulate_resize(size(px(1000.), px(1000.)));
20358
20359    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
20360    cx.assert_excerpts_with_selections(indoc! {"
20361        [EXCERPT]
20362        ˇ[FOLDED]
20363        [EXCERPT]
20364        a1
20365        b1
20366        [EXCERPT]
20367        [FOLDED]
20368        [EXCERPT]
20369        [FOLDED]
20370        "
20371    });
20372    cx.simulate_keystroke("down");
20373    cx.assert_excerpts_with_selections(indoc! {"
20374        [EXCERPT]
20375        [FOLDED]
20376        [EXCERPT]
20377        ˇa1
20378        b1
20379        [EXCERPT]
20380        [FOLDED]
20381        [EXCERPT]
20382        [FOLDED]
20383        "
20384    });
20385    cx.simulate_keystroke("down");
20386    cx.assert_excerpts_with_selections(indoc! {"
20387        [EXCERPT]
20388        [FOLDED]
20389        [EXCERPT]
20390        a1
20391        ˇb1
20392        [EXCERPT]
20393        [FOLDED]
20394        [EXCERPT]
20395        [FOLDED]
20396        "
20397    });
20398    cx.simulate_keystroke("down");
20399    cx.assert_excerpts_with_selections(indoc! {"
20400        [EXCERPT]
20401        [FOLDED]
20402        [EXCERPT]
20403        a1
20404        b1
20405        ˇ[EXCERPT]
20406        [FOLDED]
20407        [EXCERPT]
20408        [FOLDED]
20409        "
20410    });
20411    cx.simulate_keystroke("down");
20412    cx.assert_excerpts_with_selections(indoc! {"
20413        [EXCERPT]
20414        [FOLDED]
20415        [EXCERPT]
20416        a1
20417        b1
20418        [EXCERPT]
20419        ˇ[FOLDED]
20420        [EXCERPT]
20421        [FOLDED]
20422        "
20423    });
20424    for _ in 0..5 {
20425        cx.simulate_keystroke("down");
20426        cx.assert_excerpts_with_selections(indoc! {"
20427            [EXCERPT]
20428            [FOLDED]
20429            [EXCERPT]
20430            a1
20431            b1
20432            [EXCERPT]
20433            [FOLDED]
20434            [EXCERPT]
20435            ˇ[FOLDED]
20436            "
20437        });
20438    }
20439
20440    cx.simulate_keystroke("up");
20441    cx.assert_excerpts_with_selections(indoc! {"
20442        [EXCERPT]
20443        [FOLDED]
20444        [EXCERPT]
20445        a1
20446        b1
20447        [EXCERPT]
20448        ˇ[FOLDED]
20449        [EXCERPT]
20450        [FOLDED]
20451        "
20452    });
20453    cx.simulate_keystroke("up");
20454    cx.assert_excerpts_with_selections(indoc! {"
20455        [EXCERPT]
20456        [FOLDED]
20457        [EXCERPT]
20458        a1
20459        b1
20460        ˇ[EXCERPT]
20461        [FOLDED]
20462        [EXCERPT]
20463        [FOLDED]
20464        "
20465    });
20466    cx.simulate_keystroke("up");
20467    cx.assert_excerpts_with_selections(indoc! {"
20468        [EXCERPT]
20469        [FOLDED]
20470        [EXCERPT]
20471        a1
20472        ˇb1
20473        [EXCERPT]
20474        [FOLDED]
20475        [EXCERPT]
20476        [FOLDED]
20477        "
20478    });
20479    cx.simulate_keystroke("up");
20480    cx.assert_excerpts_with_selections(indoc! {"
20481        [EXCERPT]
20482        [FOLDED]
20483        [EXCERPT]
20484        ˇa1
20485        b1
20486        [EXCERPT]
20487        [FOLDED]
20488        [EXCERPT]
20489        [FOLDED]
20490        "
20491    });
20492    for _ in 0..5 {
20493        cx.simulate_keystroke("up");
20494        cx.assert_excerpts_with_selections(indoc! {"
20495            [EXCERPT]
20496            ˇ[FOLDED]
20497            [EXCERPT]
20498            a1
20499            b1
20500            [EXCERPT]
20501            [FOLDED]
20502            [EXCERPT]
20503            [FOLDED]
20504            "
20505        });
20506    }
20507}
20508
20509#[gpui::test]
20510async fn test_inline_completion_text(cx: &mut TestAppContext) {
20511    init_test(cx, |_| {});
20512
20513    // Simple insertion
20514    assert_highlighted_edits(
20515        "Hello, world!",
20516        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
20517        true,
20518        cx,
20519        |highlighted_edits, cx| {
20520            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
20521            assert_eq!(highlighted_edits.highlights.len(), 1);
20522            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
20523            assert_eq!(
20524                highlighted_edits.highlights[0].1.background_color,
20525                Some(cx.theme().status().created_background)
20526            );
20527        },
20528    )
20529    .await;
20530
20531    // Replacement
20532    assert_highlighted_edits(
20533        "This is a test.",
20534        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
20535        false,
20536        cx,
20537        |highlighted_edits, cx| {
20538            assert_eq!(highlighted_edits.text, "That is a test.");
20539            assert_eq!(highlighted_edits.highlights.len(), 1);
20540            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
20541            assert_eq!(
20542                highlighted_edits.highlights[0].1.background_color,
20543                Some(cx.theme().status().created_background)
20544            );
20545        },
20546    )
20547    .await;
20548
20549    // Multiple edits
20550    assert_highlighted_edits(
20551        "Hello, world!",
20552        vec![
20553            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
20554            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
20555        ],
20556        false,
20557        cx,
20558        |highlighted_edits, cx| {
20559            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
20560            assert_eq!(highlighted_edits.highlights.len(), 2);
20561            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
20562            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
20563            assert_eq!(
20564                highlighted_edits.highlights[0].1.background_color,
20565                Some(cx.theme().status().created_background)
20566            );
20567            assert_eq!(
20568                highlighted_edits.highlights[1].1.background_color,
20569                Some(cx.theme().status().created_background)
20570            );
20571        },
20572    )
20573    .await;
20574
20575    // Multiple lines with edits
20576    assert_highlighted_edits(
20577        "First line\nSecond line\nThird line\nFourth line",
20578        vec![
20579            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
20580            (
20581                Point::new(2, 0)..Point::new(2, 10),
20582                "New third line".to_string(),
20583            ),
20584            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
20585        ],
20586        false,
20587        cx,
20588        |highlighted_edits, cx| {
20589            assert_eq!(
20590                highlighted_edits.text,
20591                "Second modified\nNew third line\nFourth updated line"
20592            );
20593            assert_eq!(highlighted_edits.highlights.len(), 3);
20594            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
20595            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
20596            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
20597            for highlight in &highlighted_edits.highlights {
20598                assert_eq!(
20599                    highlight.1.background_color,
20600                    Some(cx.theme().status().created_background)
20601                );
20602            }
20603        },
20604    )
20605    .await;
20606}
20607
20608#[gpui::test]
20609async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
20610    init_test(cx, |_| {});
20611
20612    // Deletion
20613    assert_highlighted_edits(
20614        "Hello, world!",
20615        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
20616        true,
20617        cx,
20618        |highlighted_edits, cx| {
20619            assert_eq!(highlighted_edits.text, "Hello, world!");
20620            assert_eq!(highlighted_edits.highlights.len(), 1);
20621            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
20622            assert_eq!(
20623                highlighted_edits.highlights[0].1.background_color,
20624                Some(cx.theme().status().deleted_background)
20625            );
20626        },
20627    )
20628    .await;
20629
20630    // Insertion
20631    assert_highlighted_edits(
20632        "Hello, world!",
20633        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
20634        true,
20635        cx,
20636        |highlighted_edits, cx| {
20637            assert_eq!(highlighted_edits.highlights.len(), 1);
20638            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
20639            assert_eq!(
20640                highlighted_edits.highlights[0].1.background_color,
20641                Some(cx.theme().status().created_background)
20642            );
20643        },
20644    )
20645    .await;
20646}
20647
20648async fn assert_highlighted_edits(
20649    text: &str,
20650    edits: Vec<(Range<Point>, String)>,
20651    include_deletions: bool,
20652    cx: &mut TestAppContext,
20653    assertion_fn: impl Fn(HighlightedText, &App),
20654) {
20655    let window = cx.add_window(|window, cx| {
20656        let buffer = MultiBuffer::build_simple(text, cx);
20657        Editor::new(EditorMode::full(), buffer, None, window, cx)
20658    });
20659    let cx = &mut VisualTestContext::from_window(*window, cx);
20660
20661    let (buffer, snapshot) = window
20662        .update(cx, |editor, _window, cx| {
20663            (
20664                editor.buffer().clone(),
20665                editor.buffer().read(cx).snapshot(cx),
20666            )
20667        })
20668        .unwrap();
20669
20670    let edits = edits
20671        .into_iter()
20672        .map(|(range, edit)| {
20673            (
20674                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
20675                edit,
20676            )
20677        })
20678        .collect::<Vec<_>>();
20679
20680    let text_anchor_edits = edits
20681        .clone()
20682        .into_iter()
20683        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
20684        .collect::<Vec<_>>();
20685
20686    let edit_preview = window
20687        .update(cx, |_, _window, cx| {
20688            buffer
20689                .read(cx)
20690                .as_singleton()
20691                .unwrap()
20692                .read(cx)
20693                .preview_edits(text_anchor_edits.into(), cx)
20694        })
20695        .unwrap()
20696        .await;
20697
20698    cx.update(|_window, cx| {
20699        let highlighted_edits = inline_completion_edit_text(
20700            &snapshot.as_singleton().unwrap().2,
20701            &edits,
20702            &edit_preview,
20703            include_deletions,
20704            cx,
20705        );
20706        assertion_fn(highlighted_edits, cx)
20707    });
20708}
20709
20710#[track_caller]
20711fn assert_breakpoint(
20712    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
20713    path: &Arc<Path>,
20714    expected: Vec<(u32, Breakpoint)>,
20715) {
20716    if expected.len() == 0usize {
20717        assert!(!breakpoints.contains_key(path), "{}", path.display());
20718    } else {
20719        let mut breakpoint = breakpoints
20720            .get(path)
20721            .unwrap()
20722            .into_iter()
20723            .map(|breakpoint| {
20724                (
20725                    breakpoint.row,
20726                    Breakpoint {
20727                        message: breakpoint.message.clone(),
20728                        state: breakpoint.state,
20729                        condition: breakpoint.condition.clone(),
20730                        hit_condition: breakpoint.hit_condition.clone(),
20731                    },
20732                )
20733            })
20734            .collect::<Vec<_>>();
20735
20736        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
20737
20738        assert_eq!(expected, breakpoint);
20739    }
20740}
20741
20742fn add_log_breakpoint_at_cursor(
20743    editor: &mut Editor,
20744    log_message: &str,
20745    window: &mut Window,
20746    cx: &mut Context<Editor>,
20747) {
20748    let (anchor, bp) = editor
20749        .breakpoints_at_cursors(window, cx)
20750        .first()
20751        .and_then(|(anchor, bp)| {
20752            if let Some(bp) = bp {
20753                Some((*anchor, bp.clone()))
20754            } else {
20755                None
20756            }
20757        })
20758        .unwrap_or_else(|| {
20759            let cursor_position: Point = editor.selections.newest(cx).head();
20760
20761            let breakpoint_position = editor
20762                .snapshot(window, cx)
20763                .display_snapshot
20764                .buffer_snapshot
20765                .anchor_before(Point::new(cursor_position.row, 0));
20766
20767            (breakpoint_position, Breakpoint::new_log(&log_message))
20768        });
20769
20770    editor.edit_breakpoint_at_anchor(
20771        anchor,
20772        bp,
20773        BreakpointEditAction::EditLogMessage(log_message.into()),
20774        cx,
20775    );
20776}
20777
20778#[gpui::test]
20779async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
20780    init_test(cx, |_| {});
20781
20782    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20783    let fs = FakeFs::new(cx.executor());
20784    fs.insert_tree(
20785        path!("/a"),
20786        json!({
20787            "main.rs": sample_text,
20788        }),
20789    )
20790    .await;
20791    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20792    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20793    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20794
20795    let fs = FakeFs::new(cx.executor());
20796    fs.insert_tree(
20797        path!("/a"),
20798        json!({
20799            "main.rs": sample_text,
20800        }),
20801    )
20802    .await;
20803    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20804    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20805    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20806    let worktree_id = workspace
20807        .update(cx, |workspace, _window, cx| {
20808            workspace.project().update(cx, |project, cx| {
20809                project.worktrees(cx).next().unwrap().read(cx).id()
20810            })
20811        })
20812        .unwrap();
20813
20814    let buffer = project
20815        .update(cx, |project, cx| {
20816            project.open_buffer((worktree_id, "main.rs"), cx)
20817        })
20818        .await
20819        .unwrap();
20820
20821    let (editor, cx) = cx.add_window_view(|window, cx| {
20822        Editor::new(
20823            EditorMode::full(),
20824            MultiBuffer::build_from_buffer(buffer, cx),
20825            Some(project.clone()),
20826            window,
20827            cx,
20828        )
20829    });
20830
20831    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20832    let abs_path = project.read_with(cx, |project, cx| {
20833        project
20834            .absolute_path(&project_path, cx)
20835            .map(|path_buf| Arc::from(path_buf.to_owned()))
20836            .unwrap()
20837    });
20838
20839    // assert we can add breakpoint on the first line
20840    editor.update_in(cx, |editor, window, cx| {
20841        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20842        editor.move_to_end(&MoveToEnd, window, cx);
20843        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20844    });
20845
20846    let breakpoints = editor.update(cx, |editor, cx| {
20847        editor
20848            .breakpoint_store()
20849            .as_ref()
20850            .unwrap()
20851            .read(cx)
20852            .all_source_breakpoints(cx)
20853            .clone()
20854    });
20855
20856    assert_eq!(1, breakpoints.len());
20857    assert_breakpoint(
20858        &breakpoints,
20859        &abs_path,
20860        vec![
20861            (0, Breakpoint::new_standard()),
20862            (3, Breakpoint::new_standard()),
20863        ],
20864    );
20865
20866    editor.update_in(cx, |editor, window, cx| {
20867        editor.move_to_beginning(&MoveToBeginning, window, cx);
20868        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20869    });
20870
20871    let breakpoints = editor.update(cx, |editor, cx| {
20872        editor
20873            .breakpoint_store()
20874            .as_ref()
20875            .unwrap()
20876            .read(cx)
20877            .all_source_breakpoints(cx)
20878            .clone()
20879    });
20880
20881    assert_eq!(1, breakpoints.len());
20882    assert_breakpoint(
20883        &breakpoints,
20884        &abs_path,
20885        vec![(3, Breakpoint::new_standard())],
20886    );
20887
20888    editor.update_in(cx, |editor, window, cx| {
20889        editor.move_to_end(&MoveToEnd, window, cx);
20890        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20891    });
20892
20893    let breakpoints = editor.update(cx, |editor, cx| {
20894        editor
20895            .breakpoint_store()
20896            .as_ref()
20897            .unwrap()
20898            .read(cx)
20899            .all_source_breakpoints(cx)
20900            .clone()
20901    });
20902
20903    assert_eq!(0, breakpoints.len());
20904    assert_breakpoint(&breakpoints, &abs_path, vec![]);
20905}
20906
20907#[gpui::test]
20908async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
20909    init_test(cx, |_| {});
20910
20911    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20912
20913    let fs = FakeFs::new(cx.executor());
20914    fs.insert_tree(
20915        path!("/a"),
20916        json!({
20917            "main.rs": sample_text,
20918        }),
20919    )
20920    .await;
20921    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20922    let (workspace, cx) =
20923        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
20924
20925    let worktree_id = workspace.update(cx, |workspace, cx| {
20926        workspace.project().update(cx, |project, cx| {
20927            project.worktrees(cx).next().unwrap().read(cx).id()
20928        })
20929    });
20930
20931    let buffer = project
20932        .update(cx, |project, cx| {
20933            project.open_buffer((worktree_id, "main.rs"), cx)
20934        })
20935        .await
20936        .unwrap();
20937
20938    let (editor, cx) = cx.add_window_view(|window, cx| {
20939        Editor::new(
20940            EditorMode::full(),
20941            MultiBuffer::build_from_buffer(buffer, cx),
20942            Some(project.clone()),
20943            window,
20944            cx,
20945        )
20946    });
20947
20948    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20949    let abs_path = project.read_with(cx, |project, cx| {
20950        project
20951            .absolute_path(&project_path, cx)
20952            .map(|path_buf| Arc::from(path_buf.to_owned()))
20953            .unwrap()
20954    });
20955
20956    editor.update_in(cx, |editor, window, cx| {
20957        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
20958    });
20959
20960    let breakpoints = editor.update(cx, |editor, cx| {
20961        editor
20962            .breakpoint_store()
20963            .as_ref()
20964            .unwrap()
20965            .read(cx)
20966            .all_source_breakpoints(cx)
20967            .clone()
20968    });
20969
20970    assert_breakpoint(
20971        &breakpoints,
20972        &abs_path,
20973        vec![(0, Breakpoint::new_log("hello world"))],
20974    );
20975
20976    // Removing a log message from a log breakpoint should remove it
20977    editor.update_in(cx, |editor, window, cx| {
20978        add_log_breakpoint_at_cursor(editor, "", window, cx);
20979    });
20980
20981    let breakpoints = editor.update(cx, |editor, cx| {
20982        editor
20983            .breakpoint_store()
20984            .as_ref()
20985            .unwrap()
20986            .read(cx)
20987            .all_source_breakpoints(cx)
20988            .clone()
20989    });
20990
20991    assert_breakpoint(&breakpoints, &abs_path, vec![]);
20992
20993    editor.update_in(cx, |editor, window, cx| {
20994        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20995        editor.move_to_end(&MoveToEnd, window, cx);
20996        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20997        // Not adding a log message to a standard breakpoint shouldn't remove it
20998        add_log_breakpoint_at_cursor(editor, "", window, cx);
20999    });
21000
21001    let breakpoints = editor.update(cx, |editor, cx| {
21002        editor
21003            .breakpoint_store()
21004            .as_ref()
21005            .unwrap()
21006            .read(cx)
21007            .all_source_breakpoints(cx)
21008            .clone()
21009    });
21010
21011    assert_breakpoint(
21012        &breakpoints,
21013        &abs_path,
21014        vec![
21015            (0, Breakpoint::new_standard()),
21016            (3, Breakpoint::new_standard()),
21017        ],
21018    );
21019
21020    editor.update_in(cx, |editor, window, cx| {
21021        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
21022    });
21023
21024    let breakpoints = editor.update(cx, |editor, cx| {
21025        editor
21026            .breakpoint_store()
21027            .as_ref()
21028            .unwrap()
21029            .read(cx)
21030            .all_source_breakpoints(cx)
21031            .clone()
21032    });
21033
21034    assert_breakpoint(
21035        &breakpoints,
21036        &abs_path,
21037        vec![
21038            (0, Breakpoint::new_standard()),
21039            (3, Breakpoint::new_log("hello world")),
21040        ],
21041    );
21042
21043    editor.update_in(cx, |editor, window, cx| {
21044        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
21045    });
21046
21047    let breakpoints = editor.update(cx, |editor, cx| {
21048        editor
21049            .breakpoint_store()
21050            .as_ref()
21051            .unwrap()
21052            .read(cx)
21053            .all_source_breakpoints(cx)
21054            .clone()
21055    });
21056
21057    assert_breakpoint(
21058        &breakpoints,
21059        &abs_path,
21060        vec![
21061            (0, Breakpoint::new_standard()),
21062            (3, Breakpoint::new_log("hello Earth!!")),
21063        ],
21064    );
21065}
21066
21067/// This also tests that Editor::breakpoint_at_cursor_head is working properly
21068/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
21069/// or when breakpoints were placed out of order. This tests for a regression too
21070#[gpui::test]
21071async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
21072    init_test(cx, |_| {});
21073
21074    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
21075    let fs = FakeFs::new(cx.executor());
21076    fs.insert_tree(
21077        path!("/a"),
21078        json!({
21079            "main.rs": sample_text,
21080        }),
21081    )
21082    .await;
21083    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21084    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21085    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21086
21087    let fs = FakeFs::new(cx.executor());
21088    fs.insert_tree(
21089        path!("/a"),
21090        json!({
21091            "main.rs": sample_text,
21092        }),
21093    )
21094    .await;
21095    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21096    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21097    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21098    let worktree_id = workspace
21099        .update(cx, |workspace, _window, cx| {
21100            workspace.project().update(cx, |project, cx| {
21101                project.worktrees(cx).next().unwrap().read(cx).id()
21102            })
21103        })
21104        .unwrap();
21105
21106    let buffer = project
21107        .update(cx, |project, cx| {
21108            project.open_buffer((worktree_id, "main.rs"), cx)
21109        })
21110        .await
21111        .unwrap();
21112
21113    let (editor, cx) = cx.add_window_view(|window, cx| {
21114        Editor::new(
21115            EditorMode::full(),
21116            MultiBuffer::build_from_buffer(buffer, cx),
21117            Some(project.clone()),
21118            window,
21119            cx,
21120        )
21121    });
21122
21123    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
21124    let abs_path = project.read_with(cx, |project, cx| {
21125        project
21126            .absolute_path(&project_path, cx)
21127            .map(|path_buf| Arc::from(path_buf.to_owned()))
21128            .unwrap()
21129    });
21130
21131    // assert we can add breakpoint on the first line
21132    editor.update_in(cx, |editor, window, cx| {
21133        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21134        editor.move_to_end(&MoveToEnd, window, cx);
21135        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21136        editor.move_up(&MoveUp, window, cx);
21137        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21138    });
21139
21140    let breakpoints = editor.update(cx, |editor, cx| {
21141        editor
21142            .breakpoint_store()
21143            .as_ref()
21144            .unwrap()
21145            .read(cx)
21146            .all_source_breakpoints(cx)
21147            .clone()
21148    });
21149
21150    assert_eq!(1, breakpoints.len());
21151    assert_breakpoint(
21152        &breakpoints,
21153        &abs_path,
21154        vec![
21155            (0, Breakpoint::new_standard()),
21156            (2, Breakpoint::new_standard()),
21157            (3, Breakpoint::new_standard()),
21158        ],
21159    );
21160
21161    editor.update_in(cx, |editor, window, cx| {
21162        editor.move_to_beginning(&MoveToBeginning, window, cx);
21163        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21164        editor.move_to_end(&MoveToEnd, window, cx);
21165        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21166        // Disabling a breakpoint that doesn't exist should do nothing
21167        editor.move_up(&MoveUp, window, cx);
21168        editor.move_up(&MoveUp, window, cx);
21169        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21170    });
21171
21172    let breakpoints = editor.update(cx, |editor, cx| {
21173        editor
21174            .breakpoint_store()
21175            .as_ref()
21176            .unwrap()
21177            .read(cx)
21178            .all_source_breakpoints(cx)
21179            .clone()
21180    });
21181
21182    let disable_breakpoint = {
21183        let mut bp = Breakpoint::new_standard();
21184        bp.state = BreakpointState::Disabled;
21185        bp
21186    };
21187
21188    assert_eq!(1, breakpoints.len());
21189    assert_breakpoint(
21190        &breakpoints,
21191        &abs_path,
21192        vec![
21193            (0, disable_breakpoint.clone()),
21194            (2, Breakpoint::new_standard()),
21195            (3, disable_breakpoint.clone()),
21196        ],
21197    );
21198
21199    editor.update_in(cx, |editor, window, cx| {
21200        editor.move_to_beginning(&MoveToBeginning, window, cx);
21201        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
21202        editor.move_to_end(&MoveToEnd, window, cx);
21203        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
21204        editor.move_up(&MoveUp, window, cx);
21205        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21206    });
21207
21208    let breakpoints = editor.update(cx, |editor, cx| {
21209        editor
21210            .breakpoint_store()
21211            .as_ref()
21212            .unwrap()
21213            .read(cx)
21214            .all_source_breakpoints(cx)
21215            .clone()
21216    });
21217
21218    assert_eq!(1, breakpoints.len());
21219    assert_breakpoint(
21220        &breakpoints,
21221        &abs_path,
21222        vec![
21223            (0, Breakpoint::new_standard()),
21224            (2, disable_breakpoint),
21225            (3, Breakpoint::new_standard()),
21226        ],
21227    );
21228}
21229
21230#[gpui::test]
21231async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
21232    init_test(cx, |_| {});
21233    let capabilities = lsp::ServerCapabilities {
21234        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
21235            prepare_provider: Some(true),
21236            work_done_progress_options: Default::default(),
21237        })),
21238        ..Default::default()
21239    };
21240    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
21241
21242    cx.set_state(indoc! {"
21243        struct Fˇoo {}
21244    "});
21245
21246    cx.update_editor(|editor, _, cx| {
21247        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
21248        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
21249        editor.highlight_background::<DocumentHighlightRead>(
21250            &[highlight_range],
21251            |theme| theme.colors().editor_document_highlight_read_background,
21252            cx,
21253        );
21254    });
21255
21256    let mut prepare_rename_handler = cx
21257        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
21258            move |_, _, _| async move {
21259                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
21260                    start: lsp::Position {
21261                        line: 0,
21262                        character: 7,
21263                    },
21264                    end: lsp::Position {
21265                        line: 0,
21266                        character: 10,
21267                    },
21268                })))
21269            },
21270        );
21271    let prepare_rename_task = cx
21272        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
21273        .expect("Prepare rename was not started");
21274    prepare_rename_handler.next().await.unwrap();
21275    prepare_rename_task.await.expect("Prepare rename failed");
21276
21277    let mut rename_handler =
21278        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
21279            let edit = lsp::TextEdit {
21280                range: lsp::Range {
21281                    start: lsp::Position {
21282                        line: 0,
21283                        character: 7,
21284                    },
21285                    end: lsp::Position {
21286                        line: 0,
21287                        character: 10,
21288                    },
21289                },
21290                new_text: "FooRenamed".to_string(),
21291            };
21292            Ok(Some(lsp::WorkspaceEdit::new(
21293                // Specify the same edit twice
21294                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
21295            )))
21296        });
21297    let rename_task = cx
21298        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
21299        .expect("Confirm rename was not started");
21300    rename_handler.next().await.unwrap();
21301    rename_task.await.expect("Confirm rename failed");
21302    cx.run_until_parked();
21303
21304    // Despite two edits, only one is actually applied as those are identical
21305    cx.assert_editor_state(indoc! {"
21306        struct FooRenamedˇ {}
21307    "});
21308}
21309
21310#[gpui::test]
21311async fn test_rename_without_prepare(cx: &mut TestAppContext) {
21312    init_test(cx, |_| {});
21313    // These capabilities indicate that the server does not support prepare rename.
21314    let capabilities = lsp::ServerCapabilities {
21315        rename_provider: Some(lsp::OneOf::Left(true)),
21316        ..Default::default()
21317    };
21318    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
21319
21320    cx.set_state(indoc! {"
21321        struct Fˇoo {}
21322    "});
21323
21324    cx.update_editor(|editor, _window, cx| {
21325        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
21326        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
21327        editor.highlight_background::<DocumentHighlightRead>(
21328            &[highlight_range],
21329            |theme| theme.colors().editor_document_highlight_read_background,
21330            cx,
21331        );
21332    });
21333
21334    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
21335        .expect("Prepare rename was not started")
21336        .await
21337        .expect("Prepare rename failed");
21338
21339    let mut rename_handler =
21340        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
21341            let edit = lsp::TextEdit {
21342                range: lsp::Range {
21343                    start: lsp::Position {
21344                        line: 0,
21345                        character: 7,
21346                    },
21347                    end: lsp::Position {
21348                        line: 0,
21349                        character: 10,
21350                    },
21351                },
21352                new_text: "FooRenamed".to_string(),
21353            };
21354            Ok(Some(lsp::WorkspaceEdit::new(
21355                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
21356            )))
21357        });
21358    let rename_task = cx
21359        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
21360        .expect("Confirm rename was not started");
21361    rename_handler.next().await.unwrap();
21362    rename_task.await.expect("Confirm rename failed");
21363    cx.run_until_parked();
21364
21365    // Correct range is renamed, as `surrounding_word` is used to find it.
21366    cx.assert_editor_state(indoc! {"
21367        struct FooRenamedˇ {}
21368    "});
21369}
21370
21371#[gpui::test]
21372async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
21373    init_test(cx, |_| {});
21374    let mut cx = EditorTestContext::new(cx).await;
21375
21376    let language = Arc::new(
21377        Language::new(
21378            LanguageConfig::default(),
21379            Some(tree_sitter_html::LANGUAGE.into()),
21380        )
21381        .with_brackets_query(
21382            r#"
21383            ("<" @open "/>" @close)
21384            ("</" @open ">" @close)
21385            ("<" @open ">" @close)
21386            ("\"" @open "\"" @close)
21387            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
21388        "#,
21389        )
21390        .unwrap(),
21391    );
21392    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
21393
21394    cx.set_state(indoc! {"
21395        <span>ˇ</span>
21396    "});
21397    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21398    cx.assert_editor_state(indoc! {"
21399        <span>
21400        ˇ
21401        </span>
21402    "});
21403
21404    cx.set_state(indoc! {"
21405        <span><span></span>ˇ</span>
21406    "});
21407    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21408    cx.assert_editor_state(indoc! {"
21409        <span><span></span>
21410        ˇ</span>
21411    "});
21412
21413    cx.set_state(indoc! {"
21414        <span>ˇ
21415        </span>
21416    "});
21417    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21418    cx.assert_editor_state(indoc! {"
21419        <span>
21420        ˇ
21421        </span>
21422    "});
21423}
21424
21425#[gpui::test(iterations = 10)]
21426async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
21427    init_test(cx, |_| {});
21428
21429    let fs = FakeFs::new(cx.executor());
21430    fs.insert_tree(
21431        path!("/dir"),
21432        json!({
21433            "a.ts": "a",
21434        }),
21435    )
21436    .await;
21437
21438    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
21439    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21440    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21441
21442    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21443    language_registry.add(Arc::new(Language::new(
21444        LanguageConfig {
21445            name: "TypeScript".into(),
21446            matcher: LanguageMatcher {
21447                path_suffixes: vec!["ts".to_string()],
21448                ..Default::default()
21449            },
21450            ..Default::default()
21451        },
21452        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
21453    )));
21454    let mut fake_language_servers = language_registry.register_fake_lsp(
21455        "TypeScript",
21456        FakeLspAdapter {
21457            capabilities: lsp::ServerCapabilities {
21458                code_lens_provider: Some(lsp::CodeLensOptions {
21459                    resolve_provider: Some(true),
21460                }),
21461                execute_command_provider: Some(lsp::ExecuteCommandOptions {
21462                    commands: vec!["_the/command".to_string()],
21463                    ..lsp::ExecuteCommandOptions::default()
21464                }),
21465                ..lsp::ServerCapabilities::default()
21466            },
21467            ..FakeLspAdapter::default()
21468        },
21469    );
21470
21471    let editor = workspace
21472        .update(cx, |workspace, window, cx| {
21473            workspace.open_abs_path(
21474                PathBuf::from(path!("/dir/a.ts")),
21475                OpenOptions::default(),
21476                window,
21477                cx,
21478            )
21479        })
21480        .unwrap()
21481        .await
21482        .unwrap()
21483        .downcast::<Editor>()
21484        .unwrap();
21485    cx.executor().run_until_parked();
21486
21487    let fake_server = fake_language_servers.next().await.unwrap();
21488
21489    let buffer = editor.update(cx, |editor, cx| {
21490        editor
21491            .buffer()
21492            .read(cx)
21493            .as_singleton()
21494            .expect("have opened a single file by path")
21495    });
21496
21497    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
21498    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
21499    drop(buffer_snapshot);
21500    let actions = cx
21501        .update_window(*workspace, |_, window, cx| {
21502            project.code_actions(&buffer, anchor..anchor, window, cx)
21503        })
21504        .unwrap();
21505
21506    fake_server
21507        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
21508            Ok(Some(vec![
21509                lsp::CodeLens {
21510                    range: lsp::Range::default(),
21511                    command: Some(lsp::Command {
21512                        title: "Code lens command".to_owned(),
21513                        command: "_the/command".to_owned(),
21514                        arguments: None,
21515                    }),
21516                    data: None,
21517                },
21518                lsp::CodeLens {
21519                    range: lsp::Range::default(),
21520                    command: Some(lsp::Command {
21521                        title: "Command not in capabilities".to_owned(),
21522                        command: "not in capabilities".to_owned(),
21523                        arguments: None,
21524                    }),
21525                    data: None,
21526                },
21527                lsp::CodeLens {
21528                    range: lsp::Range {
21529                        start: lsp::Position {
21530                            line: 1,
21531                            character: 1,
21532                        },
21533                        end: lsp::Position {
21534                            line: 1,
21535                            character: 1,
21536                        },
21537                    },
21538                    command: Some(lsp::Command {
21539                        title: "Command not in range".to_owned(),
21540                        command: "_the/command".to_owned(),
21541                        arguments: None,
21542                    }),
21543                    data: None,
21544                },
21545            ]))
21546        })
21547        .next()
21548        .await;
21549
21550    let actions = actions.await.unwrap();
21551    assert_eq!(
21552        actions.len(),
21553        1,
21554        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
21555    );
21556    let action = actions[0].clone();
21557    let apply = project.update(cx, |project, cx| {
21558        project.apply_code_action(buffer.clone(), action, true, cx)
21559    });
21560
21561    // Resolving the code action does not populate its edits. In absence of
21562    // edits, we must execute the given command.
21563    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
21564        |mut lens, _| async move {
21565            let lens_command = lens.command.as_mut().expect("should have a command");
21566            assert_eq!(lens_command.title, "Code lens command");
21567            lens_command.arguments = Some(vec![json!("the-argument")]);
21568            Ok(lens)
21569        },
21570    );
21571
21572    // While executing the command, the language server sends the editor
21573    // a `workspaceEdit` request.
21574    fake_server
21575        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
21576            let fake = fake_server.clone();
21577            move |params, _| {
21578                assert_eq!(params.command, "_the/command");
21579                let fake = fake.clone();
21580                async move {
21581                    fake.server
21582                        .request::<lsp::request::ApplyWorkspaceEdit>(
21583                            lsp::ApplyWorkspaceEditParams {
21584                                label: None,
21585                                edit: lsp::WorkspaceEdit {
21586                                    changes: Some(
21587                                        [(
21588                                            lsp::Url::from_file_path(path!("/dir/a.ts")).unwrap(),
21589                                            vec![lsp::TextEdit {
21590                                                range: lsp::Range::new(
21591                                                    lsp::Position::new(0, 0),
21592                                                    lsp::Position::new(0, 0),
21593                                                ),
21594                                                new_text: "X".into(),
21595                                            }],
21596                                        )]
21597                                        .into_iter()
21598                                        .collect(),
21599                                    ),
21600                                    ..lsp::WorkspaceEdit::default()
21601                                },
21602                            },
21603                        )
21604                        .await
21605                        .into_response()
21606                        .unwrap();
21607                    Ok(Some(json!(null)))
21608                }
21609            }
21610        })
21611        .next()
21612        .await;
21613
21614    // Applying the code lens command returns a project transaction containing the edits
21615    // sent by the language server in its `workspaceEdit` request.
21616    let transaction = apply.await.unwrap();
21617    assert!(transaction.0.contains_key(&buffer));
21618    buffer.update(cx, |buffer, cx| {
21619        assert_eq!(buffer.text(), "Xa");
21620        buffer.undo(cx);
21621        assert_eq!(buffer.text(), "a");
21622    });
21623
21624    let actions_after_edits = cx
21625        .update_window(*workspace, |_, window, cx| {
21626            project.code_actions(&buffer, anchor..anchor, window, cx)
21627        })
21628        .unwrap()
21629        .await
21630        .unwrap();
21631    assert_eq!(
21632        actions, actions_after_edits,
21633        "For the same selection, same code lens actions should be returned"
21634    );
21635
21636    let _responses =
21637        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
21638            panic!("No more code lens requests are expected");
21639        });
21640    editor.update_in(cx, |editor, window, cx| {
21641        editor.select_all(&SelectAll, window, cx);
21642    });
21643    cx.executor().run_until_parked();
21644    let new_actions = cx
21645        .update_window(*workspace, |_, window, cx| {
21646            project.code_actions(&buffer, anchor..anchor, window, cx)
21647        })
21648        .unwrap()
21649        .await
21650        .unwrap();
21651    assert_eq!(
21652        actions, new_actions,
21653        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
21654    );
21655}
21656
21657#[gpui::test]
21658async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
21659    init_test(cx, |_| {});
21660
21661    let fs = FakeFs::new(cx.executor());
21662    let main_text = r#"fn main() {
21663println!("1");
21664println!("2");
21665println!("3");
21666println!("4");
21667println!("5");
21668}"#;
21669    let lib_text = "mod foo {}";
21670    fs.insert_tree(
21671        path!("/a"),
21672        json!({
21673            "lib.rs": lib_text,
21674            "main.rs": main_text,
21675        }),
21676    )
21677    .await;
21678
21679    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21680    let (workspace, cx) =
21681        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21682    let worktree_id = workspace.update(cx, |workspace, cx| {
21683        workspace.project().update(cx, |project, cx| {
21684            project.worktrees(cx).next().unwrap().read(cx).id()
21685        })
21686    });
21687
21688    let expected_ranges = vec![
21689        Point::new(0, 0)..Point::new(0, 0),
21690        Point::new(1, 0)..Point::new(1, 1),
21691        Point::new(2, 0)..Point::new(2, 2),
21692        Point::new(3, 0)..Point::new(3, 3),
21693    ];
21694
21695    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21696    let editor_1 = workspace
21697        .update_in(cx, |workspace, window, cx| {
21698            workspace.open_path(
21699                (worktree_id, "main.rs"),
21700                Some(pane_1.downgrade()),
21701                true,
21702                window,
21703                cx,
21704            )
21705        })
21706        .unwrap()
21707        .await
21708        .downcast::<Editor>()
21709        .unwrap();
21710    pane_1.update(cx, |pane, cx| {
21711        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21712        open_editor.update(cx, |editor, cx| {
21713            assert_eq!(
21714                editor.display_text(cx),
21715                main_text,
21716                "Original main.rs text on initial open",
21717            );
21718            assert_eq!(
21719                editor
21720                    .selections
21721                    .all::<Point>(cx)
21722                    .into_iter()
21723                    .map(|s| s.range())
21724                    .collect::<Vec<_>>(),
21725                vec![Point::zero()..Point::zero()],
21726                "Default selections on initial open",
21727            );
21728        })
21729    });
21730    editor_1.update_in(cx, |editor, window, cx| {
21731        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21732            s.select_ranges(expected_ranges.clone());
21733        });
21734    });
21735
21736    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
21737        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
21738    });
21739    let editor_2 = workspace
21740        .update_in(cx, |workspace, window, cx| {
21741            workspace.open_path(
21742                (worktree_id, "main.rs"),
21743                Some(pane_2.downgrade()),
21744                true,
21745                window,
21746                cx,
21747            )
21748        })
21749        .unwrap()
21750        .await
21751        .downcast::<Editor>()
21752        .unwrap();
21753    pane_2.update(cx, |pane, cx| {
21754        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21755        open_editor.update(cx, |editor, cx| {
21756            assert_eq!(
21757                editor.display_text(cx),
21758                main_text,
21759                "Original main.rs text on initial open in another panel",
21760            );
21761            assert_eq!(
21762                editor
21763                    .selections
21764                    .all::<Point>(cx)
21765                    .into_iter()
21766                    .map(|s| s.range())
21767                    .collect::<Vec<_>>(),
21768                vec![Point::zero()..Point::zero()],
21769                "Default selections on initial open in another panel",
21770            );
21771        })
21772    });
21773
21774    editor_2.update_in(cx, |editor, window, cx| {
21775        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
21776    });
21777
21778    let _other_editor_1 = workspace
21779        .update_in(cx, |workspace, window, cx| {
21780            workspace.open_path(
21781                (worktree_id, "lib.rs"),
21782                Some(pane_1.downgrade()),
21783                true,
21784                window,
21785                cx,
21786            )
21787        })
21788        .unwrap()
21789        .await
21790        .downcast::<Editor>()
21791        .unwrap();
21792    pane_1
21793        .update_in(cx, |pane, window, cx| {
21794            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
21795        })
21796        .await
21797        .unwrap();
21798    drop(editor_1);
21799    pane_1.update(cx, |pane, cx| {
21800        pane.active_item()
21801            .unwrap()
21802            .downcast::<Editor>()
21803            .unwrap()
21804            .update(cx, |editor, cx| {
21805                assert_eq!(
21806                    editor.display_text(cx),
21807                    lib_text,
21808                    "Other file should be open and active",
21809                );
21810            });
21811        assert_eq!(pane.items().count(), 1, "No other editors should be open");
21812    });
21813
21814    let _other_editor_2 = workspace
21815        .update_in(cx, |workspace, window, cx| {
21816            workspace.open_path(
21817                (worktree_id, "lib.rs"),
21818                Some(pane_2.downgrade()),
21819                true,
21820                window,
21821                cx,
21822            )
21823        })
21824        .unwrap()
21825        .await
21826        .downcast::<Editor>()
21827        .unwrap();
21828    pane_2
21829        .update_in(cx, |pane, window, cx| {
21830            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
21831        })
21832        .await
21833        .unwrap();
21834    drop(editor_2);
21835    pane_2.update(cx, |pane, cx| {
21836        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21837        open_editor.update(cx, |editor, cx| {
21838            assert_eq!(
21839                editor.display_text(cx),
21840                lib_text,
21841                "Other file should be open and active in another panel too",
21842            );
21843        });
21844        assert_eq!(
21845            pane.items().count(),
21846            1,
21847            "No other editors should be open in another pane",
21848        );
21849    });
21850
21851    let _editor_1_reopened = workspace
21852        .update_in(cx, |workspace, window, cx| {
21853            workspace.open_path(
21854                (worktree_id, "main.rs"),
21855                Some(pane_1.downgrade()),
21856                true,
21857                window,
21858                cx,
21859            )
21860        })
21861        .unwrap()
21862        .await
21863        .downcast::<Editor>()
21864        .unwrap();
21865    let _editor_2_reopened = workspace
21866        .update_in(cx, |workspace, window, cx| {
21867            workspace.open_path(
21868                (worktree_id, "main.rs"),
21869                Some(pane_2.downgrade()),
21870                true,
21871                window,
21872                cx,
21873            )
21874        })
21875        .unwrap()
21876        .await
21877        .downcast::<Editor>()
21878        .unwrap();
21879    pane_1.update(cx, |pane, cx| {
21880        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21881        open_editor.update(cx, |editor, cx| {
21882            assert_eq!(
21883                editor.display_text(cx),
21884                main_text,
21885                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
21886            );
21887            assert_eq!(
21888                editor
21889                    .selections
21890                    .all::<Point>(cx)
21891                    .into_iter()
21892                    .map(|s| s.range())
21893                    .collect::<Vec<_>>(),
21894                expected_ranges,
21895                "Previous editor in the 1st panel had selections and should get them restored on reopen",
21896            );
21897        })
21898    });
21899    pane_2.update(cx, |pane, cx| {
21900        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21901        open_editor.update(cx, |editor, cx| {
21902            assert_eq!(
21903                editor.display_text(cx),
21904                r#"fn main() {
21905⋯rintln!("1");
21906⋯intln!("2");
21907⋯ntln!("3");
21908println!("4");
21909println!("5");
21910}"#,
21911                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
21912            );
21913            assert_eq!(
21914                editor
21915                    .selections
21916                    .all::<Point>(cx)
21917                    .into_iter()
21918                    .map(|s| s.range())
21919                    .collect::<Vec<_>>(),
21920                vec![Point::zero()..Point::zero()],
21921                "Previous editor in the 2nd pane had no selections changed hence should restore none",
21922            );
21923        })
21924    });
21925}
21926
21927#[gpui::test]
21928async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
21929    init_test(cx, |_| {});
21930
21931    let fs = FakeFs::new(cx.executor());
21932    let main_text = r#"fn main() {
21933println!("1");
21934println!("2");
21935println!("3");
21936println!("4");
21937println!("5");
21938}"#;
21939    let lib_text = "mod foo {}";
21940    fs.insert_tree(
21941        path!("/a"),
21942        json!({
21943            "lib.rs": lib_text,
21944            "main.rs": main_text,
21945        }),
21946    )
21947    .await;
21948
21949    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21950    let (workspace, cx) =
21951        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21952    let worktree_id = workspace.update(cx, |workspace, cx| {
21953        workspace.project().update(cx, |project, cx| {
21954            project.worktrees(cx).next().unwrap().read(cx).id()
21955        })
21956    });
21957
21958    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21959    let editor = workspace
21960        .update_in(cx, |workspace, window, cx| {
21961            workspace.open_path(
21962                (worktree_id, "main.rs"),
21963                Some(pane.downgrade()),
21964                true,
21965                window,
21966                cx,
21967            )
21968        })
21969        .unwrap()
21970        .await
21971        .downcast::<Editor>()
21972        .unwrap();
21973    pane.update(cx, |pane, cx| {
21974        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21975        open_editor.update(cx, |editor, cx| {
21976            assert_eq!(
21977                editor.display_text(cx),
21978                main_text,
21979                "Original main.rs text on initial open",
21980            );
21981        })
21982    });
21983    editor.update_in(cx, |editor, window, cx| {
21984        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
21985    });
21986
21987    cx.update_global(|store: &mut SettingsStore, cx| {
21988        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
21989            s.restore_on_file_reopen = Some(false);
21990        });
21991    });
21992    editor.update_in(cx, |editor, window, cx| {
21993        editor.fold_ranges(
21994            vec![
21995                Point::new(1, 0)..Point::new(1, 1),
21996                Point::new(2, 0)..Point::new(2, 2),
21997                Point::new(3, 0)..Point::new(3, 3),
21998            ],
21999            false,
22000            window,
22001            cx,
22002        );
22003    });
22004    pane.update_in(cx, |pane, window, cx| {
22005        pane.close_all_items(&CloseAllItems::default(), window, cx)
22006    })
22007    .await
22008    .unwrap();
22009    pane.update(cx, |pane, _| {
22010        assert!(pane.active_item().is_none());
22011    });
22012    cx.update_global(|store: &mut SettingsStore, cx| {
22013        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
22014            s.restore_on_file_reopen = Some(true);
22015        });
22016    });
22017
22018    let _editor_reopened = workspace
22019        .update_in(cx, |workspace, window, cx| {
22020            workspace.open_path(
22021                (worktree_id, "main.rs"),
22022                Some(pane.downgrade()),
22023                true,
22024                window,
22025                cx,
22026            )
22027        })
22028        .unwrap()
22029        .await
22030        .downcast::<Editor>()
22031        .unwrap();
22032    pane.update(cx, |pane, cx| {
22033        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22034        open_editor.update(cx, |editor, cx| {
22035            assert_eq!(
22036                editor.display_text(cx),
22037                main_text,
22038                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
22039            );
22040        })
22041    });
22042}
22043
22044#[gpui::test]
22045async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
22046    struct EmptyModalView {
22047        focus_handle: gpui::FocusHandle,
22048    }
22049    impl EventEmitter<DismissEvent> for EmptyModalView {}
22050    impl Render for EmptyModalView {
22051        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
22052            div()
22053        }
22054    }
22055    impl Focusable for EmptyModalView {
22056        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
22057            self.focus_handle.clone()
22058        }
22059    }
22060    impl workspace::ModalView for EmptyModalView {}
22061    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
22062        EmptyModalView {
22063            focus_handle: cx.focus_handle(),
22064        }
22065    }
22066
22067    init_test(cx, |_| {});
22068
22069    let fs = FakeFs::new(cx.executor());
22070    let project = Project::test(fs, [], cx).await;
22071    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22072    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
22073    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22074    let editor = cx.new_window_entity(|window, cx| {
22075        Editor::new(
22076            EditorMode::full(),
22077            buffer,
22078            Some(project.clone()),
22079            window,
22080            cx,
22081        )
22082    });
22083    workspace
22084        .update(cx, |workspace, window, cx| {
22085            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
22086        })
22087        .unwrap();
22088    editor.update_in(cx, |editor, window, cx| {
22089        editor.open_context_menu(&OpenContextMenu, window, cx);
22090        assert!(editor.mouse_context_menu.is_some());
22091    });
22092    workspace
22093        .update(cx, |workspace, window, cx| {
22094            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
22095        })
22096        .unwrap();
22097    cx.read(|cx| {
22098        assert!(editor.read(cx).mouse_context_menu.is_none());
22099    });
22100}
22101
22102#[gpui::test]
22103async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
22104    init_test(cx, |_| {});
22105
22106    let fs = FakeFs::new(cx.executor());
22107    fs.insert_file(path!("/file.html"), Default::default())
22108        .await;
22109
22110    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
22111
22112    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22113    let html_language = Arc::new(Language::new(
22114        LanguageConfig {
22115            name: "HTML".into(),
22116            matcher: LanguageMatcher {
22117                path_suffixes: vec!["html".to_string()],
22118                ..LanguageMatcher::default()
22119            },
22120            brackets: BracketPairConfig {
22121                pairs: vec![BracketPair {
22122                    start: "<".into(),
22123                    end: ">".into(),
22124                    close: true,
22125                    ..Default::default()
22126                }],
22127                ..Default::default()
22128            },
22129            ..Default::default()
22130        },
22131        Some(tree_sitter_html::LANGUAGE.into()),
22132    ));
22133    language_registry.add(html_language);
22134    let mut fake_servers = language_registry.register_fake_lsp(
22135        "HTML",
22136        FakeLspAdapter {
22137            capabilities: lsp::ServerCapabilities {
22138                completion_provider: Some(lsp::CompletionOptions {
22139                    resolve_provider: Some(true),
22140                    ..Default::default()
22141                }),
22142                ..Default::default()
22143            },
22144            ..Default::default()
22145        },
22146    );
22147
22148    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22149    let cx = &mut VisualTestContext::from_window(*workspace, cx);
22150
22151    let worktree_id = workspace
22152        .update(cx, |workspace, _window, cx| {
22153            workspace.project().update(cx, |project, cx| {
22154                project.worktrees(cx).next().unwrap().read(cx).id()
22155            })
22156        })
22157        .unwrap();
22158    project
22159        .update(cx, |project, cx| {
22160            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
22161        })
22162        .await
22163        .unwrap();
22164    let editor = workspace
22165        .update(cx, |workspace, window, cx| {
22166            workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
22167        })
22168        .unwrap()
22169        .await
22170        .unwrap()
22171        .downcast::<Editor>()
22172        .unwrap();
22173
22174    let fake_server = fake_servers.next().await.unwrap();
22175    editor.update_in(cx, |editor, window, cx| {
22176        editor.set_text("<ad></ad>", window, cx);
22177        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22178            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
22179        });
22180        let Some((buffer, _)) = editor
22181            .buffer
22182            .read(cx)
22183            .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
22184        else {
22185            panic!("Failed to get buffer for selection position");
22186        };
22187        let buffer = buffer.read(cx);
22188        let buffer_id = buffer.remote_id();
22189        let opening_range =
22190            buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3));
22191        let closing_range =
22192            buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8));
22193        let mut linked_ranges = HashMap::default();
22194        linked_ranges.insert(
22195            buffer_id,
22196            vec![(opening_range.clone(), vec![closing_range.clone()])],
22197        );
22198        editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
22199    });
22200    let mut completion_handle =
22201        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
22202            Ok(Some(lsp::CompletionResponse::Array(vec![
22203                lsp::CompletionItem {
22204                    label: "head".to_string(),
22205                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22206                        lsp::InsertReplaceEdit {
22207                            new_text: "head".to_string(),
22208                            insert: lsp::Range::new(
22209                                lsp::Position::new(0, 1),
22210                                lsp::Position::new(0, 3),
22211                            ),
22212                            replace: lsp::Range::new(
22213                                lsp::Position::new(0, 1),
22214                                lsp::Position::new(0, 3),
22215                            ),
22216                        },
22217                    )),
22218                    ..Default::default()
22219                },
22220            ])))
22221        });
22222    editor.update_in(cx, |editor, window, cx| {
22223        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
22224    });
22225    cx.run_until_parked();
22226    completion_handle.next().await.unwrap();
22227    editor.update(cx, |editor, _| {
22228        assert!(
22229            editor.context_menu_visible(),
22230            "Completion menu should be visible"
22231        );
22232    });
22233    editor.update_in(cx, |editor, window, cx| {
22234        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
22235    });
22236    cx.executor().run_until_parked();
22237    editor.update(cx, |editor, cx| {
22238        assert_eq!(editor.text(cx), "<head></head>");
22239    });
22240}
22241
22242#[gpui::test]
22243async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
22244    init_test(cx, |_| {});
22245
22246    let fs = FakeFs::new(cx.executor());
22247    fs.insert_tree(
22248        path!("/root"),
22249        json!({
22250            "a": {
22251                "main.rs": "fn main() {}",
22252            },
22253            "foo": {
22254                "bar": {
22255                    "external_file.rs": "pub mod external {}",
22256                }
22257            }
22258        }),
22259    )
22260    .await;
22261
22262    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
22263    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22264    language_registry.add(rust_lang());
22265    let _fake_servers = language_registry.register_fake_lsp(
22266        "Rust",
22267        FakeLspAdapter {
22268            ..FakeLspAdapter::default()
22269        },
22270    );
22271    let (workspace, cx) =
22272        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22273    let worktree_id = workspace.update(cx, |workspace, cx| {
22274        workspace.project().update(cx, |project, cx| {
22275            project.worktrees(cx).next().unwrap().read(cx).id()
22276        })
22277    });
22278
22279    let assert_language_servers_count =
22280        |expected: usize, context: &str, cx: &mut VisualTestContext| {
22281            project.update(cx, |project, cx| {
22282                let current = project
22283                    .lsp_store()
22284                    .read(cx)
22285                    .as_local()
22286                    .unwrap()
22287                    .language_servers
22288                    .len();
22289                assert_eq!(expected, current, "{context}");
22290            });
22291        };
22292
22293    assert_language_servers_count(
22294        0,
22295        "No servers should be running before any file is open",
22296        cx,
22297    );
22298    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
22299    let main_editor = workspace
22300        .update_in(cx, |workspace, window, cx| {
22301            workspace.open_path(
22302                (worktree_id, "main.rs"),
22303                Some(pane.downgrade()),
22304                true,
22305                window,
22306                cx,
22307            )
22308        })
22309        .unwrap()
22310        .await
22311        .downcast::<Editor>()
22312        .unwrap();
22313    pane.update(cx, |pane, cx| {
22314        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22315        open_editor.update(cx, |editor, cx| {
22316            assert_eq!(
22317                editor.display_text(cx),
22318                "fn main() {}",
22319                "Original main.rs text on initial open",
22320            );
22321        });
22322        assert_eq!(open_editor, main_editor);
22323    });
22324    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
22325
22326    let external_editor = workspace
22327        .update_in(cx, |workspace, window, cx| {
22328            workspace.open_abs_path(
22329                PathBuf::from("/root/foo/bar/external_file.rs"),
22330                OpenOptions::default(),
22331                window,
22332                cx,
22333            )
22334        })
22335        .await
22336        .expect("opening external file")
22337        .downcast::<Editor>()
22338        .expect("downcasted external file's open element to editor");
22339    pane.update(cx, |pane, cx| {
22340        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22341        open_editor.update(cx, |editor, cx| {
22342            assert_eq!(
22343                editor.display_text(cx),
22344                "pub mod external {}",
22345                "External file is open now",
22346            );
22347        });
22348        assert_eq!(open_editor, external_editor);
22349    });
22350    assert_language_servers_count(
22351        1,
22352        "Second, external, *.rs file should join the existing server",
22353        cx,
22354    );
22355
22356    pane.update_in(cx, |pane, window, cx| {
22357        pane.close_active_item(&CloseActiveItem::default(), window, cx)
22358    })
22359    .await
22360    .unwrap();
22361    pane.update_in(cx, |pane, window, cx| {
22362        pane.navigate_backward(window, cx);
22363    });
22364    cx.run_until_parked();
22365    pane.update(cx, |pane, cx| {
22366        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22367        open_editor.update(cx, |editor, cx| {
22368            assert_eq!(
22369                editor.display_text(cx),
22370                "pub mod external {}",
22371                "External file is open now",
22372            );
22373        });
22374    });
22375    assert_language_servers_count(
22376        1,
22377        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
22378        cx,
22379    );
22380
22381    cx.update(|_, cx| {
22382        workspace::reload(&workspace::Reload::default(), cx);
22383    });
22384    assert_language_servers_count(
22385        1,
22386        "After reloading the worktree with local and external files opened, only one project should be started",
22387        cx,
22388    );
22389}
22390
22391#[gpui::test]
22392async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
22393    init_test(cx, |_| {});
22394
22395    let mut cx = EditorTestContext::new(cx).await;
22396    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22397    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22398
22399    // test cursor move to start of each line on tab
22400    // for `if`, `elif`, `else`, `while`, `with` and `for`
22401    cx.set_state(indoc! {"
22402        def main():
22403        ˇ    for item in items:
22404        ˇ        while item.active:
22405        ˇ            if item.value > 10:
22406        ˇ                continue
22407        ˇ            elif item.value < 0:
22408        ˇ                break
22409        ˇ            else:
22410        ˇ                with item.context() as ctx:
22411        ˇ                    yield count
22412        ˇ        else:
22413        ˇ            log('while else')
22414        ˇ    else:
22415        ˇ        log('for else')
22416    "});
22417    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22418    cx.assert_editor_state(indoc! {"
22419        def main():
22420            ˇfor item in items:
22421                ˇwhile item.active:
22422                    ˇif item.value > 10:
22423                        ˇcontinue
22424                    ˇelif item.value < 0:
22425                        ˇbreak
22426                    ˇelse:
22427                        ˇwith item.context() as ctx:
22428                            ˇyield count
22429                ˇelse:
22430                    ˇlog('while else')
22431            ˇelse:
22432                ˇlog('for else')
22433    "});
22434    // test relative indent is preserved when tab
22435    // for `if`, `elif`, `else`, `while`, `with` and `for`
22436    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22437    cx.assert_editor_state(indoc! {"
22438        def main():
22439                ˇfor item in items:
22440                    ˇwhile item.active:
22441                        ˇif item.value > 10:
22442                            ˇcontinue
22443                        ˇelif item.value < 0:
22444                            ˇbreak
22445                        ˇelse:
22446                            ˇwith item.context() as ctx:
22447                                ˇyield count
22448                    ˇelse:
22449                        ˇlog('while else')
22450                ˇelse:
22451                    ˇlog('for else')
22452    "});
22453
22454    // test cursor move to start of each line on tab
22455    // for `try`, `except`, `else`, `finally`, `match` and `def`
22456    cx.set_state(indoc! {"
22457        def main():
22458        ˇ    try:
22459        ˇ        fetch()
22460        ˇ    except ValueError:
22461        ˇ        handle_error()
22462        ˇ    else:
22463        ˇ        match value:
22464        ˇ            case _:
22465        ˇ    finally:
22466        ˇ        def status():
22467        ˇ            return 0
22468    "});
22469    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22470    cx.assert_editor_state(indoc! {"
22471        def main():
22472            ˇtry:
22473                ˇfetch()
22474            ˇexcept ValueError:
22475                ˇhandle_error()
22476            ˇelse:
22477                ˇmatch value:
22478                    ˇcase _:
22479            ˇfinally:
22480                ˇdef status():
22481                    ˇreturn 0
22482    "});
22483    // test relative indent is preserved when tab
22484    // for `try`, `except`, `else`, `finally`, `match` and `def`
22485    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22486    cx.assert_editor_state(indoc! {"
22487        def main():
22488                ˇtry:
22489                    ˇfetch()
22490                ˇexcept ValueError:
22491                    ˇhandle_error()
22492                ˇelse:
22493                    ˇmatch value:
22494                        ˇcase _:
22495                ˇfinally:
22496                    ˇdef status():
22497                        ˇreturn 0
22498    "});
22499}
22500
22501#[gpui::test]
22502async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
22503    init_test(cx, |_| {});
22504
22505    let mut cx = EditorTestContext::new(cx).await;
22506    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22507    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22508
22509    // test `else` auto outdents when typed inside `if` block
22510    cx.set_state(indoc! {"
22511        def main():
22512            if i == 2:
22513                return
22514                ˇ
22515    "});
22516    cx.update_editor(|editor, window, cx| {
22517        editor.handle_input("else:", window, cx);
22518    });
22519    cx.assert_editor_state(indoc! {"
22520        def main():
22521            if i == 2:
22522                return
22523            else:ˇ
22524    "});
22525
22526    // test `except` auto outdents when typed inside `try` block
22527    cx.set_state(indoc! {"
22528        def main():
22529            try:
22530                i = 2
22531                ˇ
22532    "});
22533    cx.update_editor(|editor, window, cx| {
22534        editor.handle_input("except:", window, cx);
22535    });
22536    cx.assert_editor_state(indoc! {"
22537        def main():
22538            try:
22539                i = 2
22540            except:ˇ
22541    "});
22542
22543    // test `else` auto outdents when typed inside `except` block
22544    cx.set_state(indoc! {"
22545        def main():
22546            try:
22547                i = 2
22548            except:
22549                j = 2
22550                ˇ
22551    "});
22552    cx.update_editor(|editor, window, cx| {
22553        editor.handle_input("else:", window, cx);
22554    });
22555    cx.assert_editor_state(indoc! {"
22556        def main():
22557            try:
22558                i = 2
22559            except:
22560                j = 2
22561            else:ˇ
22562    "});
22563
22564    // test `finally` auto outdents when typed inside `else` block
22565    cx.set_state(indoc! {"
22566        def main():
22567            try:
22568                i = 2
22569            except:
22570                j = 2
22571            else:
22572                k = 2
22573                ˇ
22574    "});
22575    cx.update_editor(|editor, window, cx| {
22576        editor.handle_input("finally:", window, cx);
22577    });
22578    cx.assert_editor_state(indoc! {"
22579        def main():
22580            try:
22581                i = 2
22582            except:
22583                j = 2
22584            else:
22585                k = 2
22586            finally:ˇ
22587    "});
22588
22589    // test `else` does not outdents when typed inside `except` block right after for block
22590    cx.set_state(indoc! {"
22591        def main():
22592            try:
22593                i = 2
22594            except:
22595                for i in range(n):
22596                    pass
22597                ˇ
22598    "});
22599    cx.update_editor(|editor, window, cx| {
22600        editor.handle_input("else:", window, cx);
22601    });
22602    cx.assert_editor_state(indoc! {"
22603        def main():
22604            try:
22605                i = 2
22606            except:
22607                for i in range(n):
22608                    pass
22609                else:ˇ
22610    "});
22611
22612    // test `finally` auto outdents when typed inside `else` block right after for block
22613    cx.set_state(indoc! {"
22614        def main():
22615            try:
22616                i = 2
22617            except:
22618                j = 2
22619            else:
22620                for i in range(n):
22621                    pass
22622                ˇ
22623    "});
22624    cx.update_editor(|editor, window, cx| {
22625        editor.handle_input("finally:", window, cx);
22626    });
22627    cx.assert_editor_state(indoc! {"
22628        def main():
22629            try:
22630                i = 2
22631            except:
22632                j = 2
22633            else:
22634                for i in range(n):
22635                    pass
22636            finally:ˇ
22637    "});
22638
22639    // test `except` outdents to inner "try" block
22640    cx.set_state(indoc! {"
22641        def main():
22642            try:
22643                i = 2
22644                if i == 2:
22645                    try:
22646                        i = 3
22647                        ˇ
22648    "});
22649    cx.update_editor(|editor, window, cx| {
22650        editor.handle_input("except:", window, cx);
22651    });
22652    cx.assert_editor_state(indoc! {"
22653        def main():
22654            try:
22655                i = 2
22656                if i == 2:
22657                    try:
22658                        i = 3
22659                    except:ˇ
22660    "});
22661
22662    // test `except` outdents to outer "try" block
22663    cx.set_state(indoc! {"
22664        def main():
22665            try:
22666                i = 2
22667                if i == 2:
22668                    try:
22669                        i = 3
22670                ˇ
22671    "});
22672    cx.update_editor(|editor, window, cx| {
22673        editor.handle_input("except:", window, cx);
22674    });
22675    cx.assert_editor_state(indoc! {"
22676        def main():
22677            try:
22678                i = 2
22679                if i == 2:
22680                    try:
22681                        i = 3
22682            except:ˇ
22683    "});
22684
22685    // test `else` stays at correct indent when typed after `for` block
22686    cx.set_state(indoc! {"
22687        def main():
22688            for i in range(10):
22689                if i == 3:
22690                    break
22691            ˇ
22692    "});
22693    cx.update_editor(|editor, window, cx| {
22694        editor.handle_input("else:", window, cx);
22695    });
22696    cx.assert_editor_state(indoc! {"
22697        def main():
22698            for i in range(10):
22699                if i == 3:
22700                    break
22701            else:ˇ
22702    "});
22703
22704    // test does not outdent on typing after line with square brackets
22705    cx.set_state(indoc! {"
22706        def f() -> list[str]:
22707            ˇ
22708    "});
22709    cx.update_editor(|editor, window, cx| {
22710        editor.handle_input("a", window, cx);
22711    });
22712    cx.assert_editor_state(indoc! {"
22713        def f() -> list[str]:
2271422715    "});
22716
22717    // test does not outdent on typing : after case keyword
22718    cx.set_state(indoc! {"
22719        match 1:
22720            caseˇ
22721    "});
22722    cx.update_editor(|editor, window, cx| {
22723        editor.handle_input(":", window, cx);
22724    });
22725    cx.assert_editor_state(indoc! {"
22726        match 1:
22727            case:ˇ
22728    "});
22729}
22730
22731#[gpui::test]
22732async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
22733    init_test(cx, |_| {});
22734    update_test_language_settings(cx, |settings| {
22735        settings.defaults.extend_comment_on_newline = Some(false);
22736    });
22737    let mut cx = EditorTestContext::new(cx).await;
22738    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22739    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22740
22741    // test correct indent after newline on comment
22742    cx.set_state(indoc! {"
22743        # COMMENT:ˇ
22744    "});
22745    cx.update_editor(|editor, window, cx| {
22746        editor.newline(&Newline, window, cx);
22747    });
22748    cx.assert_editor_state(indoc! {"
22749        # COMMENT:
22750        ˇ
22751    "});
22752
22753    // test correct indent after newline in brackets
22754    cx.set_state(indoc! {"
22755        {ˇ}
22756    "});
22757    cx.update_editor(|editor, window, cx| {
22758        editor.newline(&Newline, window, cx);
22759    });
22760    cx.run_until_parked();
22761    cx.assert_editor_state(indoc! {"
22762        {
22763            ˇ
22764        }
22765    "});
22766
22767    cx.set_state(indoc! {"
22768        (ˇ)
22769    "});
22770    cx.update_editor(|editor, window, cx| {
22771        editor.newline(&Newline, window, cx);
22772    });
22773    cx.run_until_parked();
22774    cx.assert_editor_state(indoc! {"
22775        (
22776            ˇ
22777        )
22778    "});
22779
22780    // do not indent after empty lists or dictionaries
22781    cx.set_state(indoc! {"
22782        a = []ˇ
22783    "});
22784    cx.update_editor(|editor, window, cx| {
22785        editor.newline(&Newline, window, cx);
22786    });
22787    cx.run_until_parked();
22788    cx.assert_editor_state(indoc! {"
22789        a = []
22790        ˇ
22791    "});
22792}
22793
22794fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
22795    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
22796    point..point
22797}
22798
22799#[track_caller]
22800fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
22801    let (text, ranges) = marked_text_ranges(marked_text, true);
22802    assert_eq!(editor.text(cx), text);
22803    assert_eq!(
22804        editor.selections.ranges(cx),
22805        ranges,
22806        "Assert selections are {}",
22807        marked_text
22808    );
22809}
22810
22811pub fn handle_signature_help_request(
22812    cx: &mut EditorLspTestContext,
22813    mocked_response: lsp::SignatureHelp,
22814) -> impl Future<Output = ()> + use<> {
22815    let mut request =
22816        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
22817            let mocked_response = mocked_response.clone();
22818            async move { Ok(Some(mocked_response)) }
22819        });
22820
22821    async move {
22822        request.next().await;
22823    }
22824}
22825
22826#[track_caller]
22827pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
22828    cx.update_editor(|editor, _, _| {
22829        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
22830            let entries = menu.entries.borrow();
22831            let entries = entries
22832                .iter()
22833                .map(|entry| entry.string.as_str())
22834                .collect::<Vec<_>>();
22835            assert_eq!(entries, expected);
22836        } else {
22837            panic!("Expected completions menu");
22838        }
22839    });
22840}
22841
22842/// Handle completion request passing a marked string specifying where the completion
22843/// should be triggered from using '|' character, what range should be replaced, and what completions
22844/// should be returned using '<' and '>' to delimit the range.
22845///
22846/// Also see `handle_completion_request_with_insert_and_replace`.
22847#[track_caller]
22848pub fn handle_completion_request(
22849    marked_string: &str,
22850    completions: Vec<&'static str>,
22851    is_incomplete: bool,
22852    counter: Arc<AtomicUsize>,
22853    cx: &mut EditorLspTestContext,
22854) -> impl Future<Output = ()> {
22855    let complete_from_marker: TextRangeMarker = '|'.into();
22856    let replace_range_marker: TextRangeMarker = ('<', '>').into();
22857    let (_, mut marked_ranges) = marked_text_ranges_by(
22858        marked_string,
22859        vec![complete_from_marker.clone(), replace_range_marker.clone()],
22860    );
22861
22862    let complete_from_position =
22863        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
22864    let replace_range =
22865        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
22866
22867    let mut request =
22868        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
22869            let completions = completions.clone();
22870            counter.fetch_add(1, atomic::Ordering::Release);
22871            async move {
22872                assert_eq!(params.text_document_position.text_document.uri, url.clone());
22873                assert_eq!(
22874                    params.text_document_position.position,
22875                    complete_from_position
22876                );
22877                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
22878                    is_incomplete: is_incomplete,
22879                    item_defaults: None,
22880                    items: completions
22881                        .iter()
22882                        .map(|completion_text| lsp::CompletionItem {
22883                            label: completion_text.to_string(),
22884                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
22885                                range: replace_range,
22886                                new_text: completion_text.to_string(),
22887                            })),
22888                            ..Default::default()
22889                        })
22890                        .collect(),
22891                })))
22892            }
22893        });
22894
22895    async move {
22896        request.next().await;
22897    }
22898}
22899
22900/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
22901/// given instead, which also contains an `insert` range.
22902///
22903/// This function uses markers to define ranges:
22904/// - `|` marks the cursor position
22905/// - `<>` marks the replace range
22906/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
22907pub fn handle_completion_request_with_insert_and_replace(
22908    cx: &mut EditorLspTestContext,
22909    marked_string: &str,
22910    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
22911    counter: Arc<AtomicUsize>,
22912) -> impl Future<Output = ()> {
22913    let complete_from_marker: TextRangeMarker = '|'.into();
22914    let replace_range_marker: TextRangeMarker = ('<', '>').into();
22915    let insert_range_marker: TextRangeMarker = ('{', '}').into();
22916
22917    let (_, mut marked_ranges) = marked_text_ranges_by(
22918        marked_string,
22919        vec![
22920            complete_from_marker.clone(),
22921            replace_range_marker.clone(),
22922            insert_range_marker.clone(),
22923        ],
22924    );
22925
22926    let complete_from_position =
22927        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
22928    let replace_range =
22929        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
22930
22931    let insert_range = match marked_ranges.remove(&insert_range_marker) {
22932        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
22933        _ => lsp::Range {
22934            start: replace_range.start,
22935            end: complete_from_position,
22936        },
22937    };
22938
22939    let mut request =
22940        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
22941            let completions = completions.clone();
22942            counter.fetch_add(1, atomic::Ordering::Release);
22943            async move {
22944                assert_eq!(params.text_document_position.text_document.uri, url.clone());
22945                assert_eq!(
22946                    params.text_document_position.position, complete_from_position,
22947                    "marker `|` position doesn't match",
22948                );
22949                Ok(Some(lsp::CompletionResponse::Array(
22950                    completions
22951                        .iter()
22952                        .map(|(label, new_text)| lsp::CompletionItem {
22953                            label: label.to_string(),
22954                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22955                                lsp::InsertReplaceEdit {
22956                                    insert: insert_range,
22957                                    replace: replace_range,
22958                                    new_text: new_text.to_string(),
22959                                },
22960                            )),
22961                            ..Default::default()
22962                        })
22963                        .collect(),
22964                )))
22965            }
22966        });
22967
22968    async move {
22969        request.next().await;
22970    }
22971}
22972
22973fn handle_resolve_completion_request(
22974    cx: &mut EditorLspTestContext,
22975    edits: Option<Vec<(&'static str, &'static str)>>,
22976) -> impl Future<Output = ()> {
22977    let edits = edits.map(|edits| {
22978        edits
22979            .iter()
22980            .map(|(marked_string, new_text)| {
22981                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
22982                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
22983                lsp::TextEdit::new(replace_range, new_text.to_string())
22984            })
22985            .collect::<Vec<_>>()
22986    });
22987
22988    let mut request =
22989        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
22990            let edits = edits.clone();
22991            async move {
22992                Ok(lsp::CompletionItem {
22993                    additional_text_edits: edits,
22994                    ..Default::default()
22995                })
22996            }
22997        });
22998
22999    async move {
23000        request.next().await;
23001    }
23002}
23003
23004pub(crate) fn update_test_language_settings(
23005    cx: &mut TestAppContext,
23006    f: impl Fn(&mut AllLanguageSettingsContent),
23007) {
23008    cx.update(|cx| {
23009        SettingsStore::update_global(cx, |store, cx| {
23010            store.update_user_settings::<AllLanguageSettings>(cx, f);
23011        });
23012    });
23013}
23014
23015pub(crate) fn update_test_project_settings(
23016    cx: &mut TestAppContext,
23017    f: impl Fn(&mut ProjectSettings),
23018) {
23019    cx.update(|cx| {
23020        SettingsStore::update_global(cx, |store, cx| {
23021            store.update_user_settings::<ProjectSettings>(cx, f);
23022        });
23023    });
23024}
23025
23026pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
23027    cx.update(|cx| {
23028        assets::Assets.load_test_fonts(cx);
23029        let store = SettingsStore::test(cx);
23030        cx.set_global(store);
23031        theme::init(theme::LoadThemes::JustBase, cx);
23032        release_channel::init(SemanticVersion::default(), cx);
23033        client::init_settings(cx);
23034        language::init(cx);
23035        Project::init_settings(cx);
23036        workspace::init_settings(cx);
23037        crate::init(cx);
23038    });
23039    zlog::init_test();
23040    update_test_language_settings(cx, f);
23041}
23042
23043#[track_caller]
23044fn assert_hunk_revert(
23045    not_reverted_text_with_selections: &str,
23046    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
23047    expected_reverted_text_with_selections: &str,
23048    base_text: &str,
23049    cx: &mut EditorLspTestContext,
23050) {
23051    cx.set_state(not_reverted_text_with_selections);
23052    cx.set_head_text(base_text);
23053    cx.executor().run_until_parked();
23054
23055    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
23056        let snapshot = editor.snapshot(window, cx);
23057        let reverted_hunk_statuses = snapshot
23058            .buffer_snapshot
23059            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
23060            .map(|hunk| hunk.status().kind)
23061            .collect::<Vec<_>>();
23062
23063        editor.git_restore(&Default::default(), window, cx);
23064        reverted_hunk_statuses
23065    });
23066    cx.executor().run_until_parked();
23067    cx.assert_editor_state(expected_reverted_text_with_selections);
23068    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
23069}
23070
23071#[gpui::test(iterations = 10)]
23072async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
23073    init_test(cx, |_| {});
23074
23075    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
23076    let counter = diagnostic_requests.clone();
23077
23078    let fs = FakeFs::new(cx.executor());
23079    fs.insert_tree(
23080        path!("/a"),
23081        json!({
23082            "first.rs": "fn main() { let a = 5; }",
23083            "second.rs": "// Test file",
23084        }),
23085    )
23086    .await;
23087
23088    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23089    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23090    let cx = &mut VisualTestContext::from_window(*workspace, cx);
23091
23092    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23093    language_registry.add(rust_lang());
23094    let mut fake_servers = language_registry.register_fake_lsp(
23095        "Rust",
23096        FakeLspAdapter {
23097            capabilities: lsp::ServerCapabilities {
23098                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
23099                    lsp::DiagnosticOptions {
23100                        identifier: None,
23101                        inter_file_dependencies: true,
23102                        workspace_diagnostics: true,
23103                        work_done_progress_options: Default::default(),
23104                    },
23105                )),
23106                ..Default::default()
23107            },
23108            ..Default::default()
23109        },
23110    );
23111
23112    let editor = workspace
23113        .update(cx, |workspace, window, cx| {
23114            workspace.open_abs_path(
23115                PathBuf::from(path!("/a/first.rs")),
23116                OpenOptions::default(),
23117                window,
23118                cx,
23119            )
23120        })
23121        .unwrap()
23122        .await
23123        .unwrap()
23124        .downcast::<Editor>()
23125        .unwrap();
23126    let fake_server = fake_servers.next().await.unwrap();
23127    let server_id = fake_server.server.server_id();
23128    let mut first_request = fake_server
23129        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
23130            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
23131            let result_id = Some(new_result_id.to_string());
23132            assert_eq!(
23133                params.text_document.uri,
23134                lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
23135            );
23136            async move {
23137                Ok(lsp::DocumentDiagnosticReportResult::Report(
23138                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
23139                        related_documents: None,
23140                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
23141                            items: Vec::new(),
23142                            result_id,
23143                        },
23144                    }),
23145                ))
23146            }
23147        });
23148
23149    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
23150        project.update(cx, |project, cx| {
23151            let buffer_id = editor
23152                .read(cx)
23153                .buffer()
23154                .read(cx)
23155                .as_singleton()
23156                .expect("created a singleton buffer")
23157                .read(cx)
23158                .remote_id();
23159            let buffer_result_id = project
23160                .lsp_store()
23161                .read(cx)
23162                .result_id(server_id, buffer_id, cx);
23163            assert_eq!(expected, buffer_result_id);
23164        });
23165    };
23166
23167    ensure_result_id(None, cx);
23168    cx.executor().advance_clock(Duration::from_millis(60));
23169    cx.executor().run_until_parked();
23170    assert_eq!(
23171        diagnostic_requests.load(atomic::Ordering::Acquire),
23172        1,
23173        "Opening file should trigger diagnostic request"
23174    );
23175    first_request
23176        .next()
23177        .await
23178        .expect("should have sent the first diagnostics pull request");
23179    ensure_result_id(Some("1".to_string()), cx);
23180
23181    // Editing should trigger diagnostics
23182    editor.update_in(cx, |editor, window, cx| {
23183        editor.handle_input("2", window, cx)
23184    });
23185    cx.executor().advance_clock(Duration::from_millis(60));
23186    cx.executor().run_until_parked();
23187    assert_eq!(
23188        diagnostic_requests.load(atomic::Ordering::Acquire),
23189        2,
23190        "Editing should trigger diagnostic request"
23191    );
23192    ensure_result_id(Some("2".to_string()), cx);
23193
23194    // Moving cursor should not trigger diagnostic request
23195    editor.update_in(cx, |editor, window, cx| {
23196        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23197            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
23198        });
23199    });
23200    cx.executor().advance_clock(Duration::from_millis(60));
23201    cx.executor().run_until_parked();
23202    assert_eq!(
23203        diagnostic_requests.load(atomic::Ordering::Acquire),
23204        2,
23205        "Cursor movement should not trigger diagnostic request"
23206    );
23207    ensure_result_id(Some("2".to_string()), cx);
23208    // Multiple rapid edits should be debounced
23209    for _ in 0..5 {
23210        editor.update_in(cx, |editor, window, cx| {
23211            editor.handle_input("x", window, cx)
23212        });
23213    }
23214    cx.executor().advance_clock(Duration::from_millis(60));
23215    cx.executor().run_until_parked();
23216
23217    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
23218    assert!(
23219        final_requests <= 4,
23220        "Multiple rapid edits should be debounced (got {final_requests} requests)",
23221    );
23222    ensure_result_id(Some(final_requests.to_string()), cx);
23223}
23224
23225#[gpui::test]
23226async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
23227    // Regression test for issue #11671
23228    // Previously, adding a cursor after moving multiple cursors would reset
23229    // the cursor count instead of adding to the existing cursors.
23230    init_test(cx, |_| {});
23231    let mut cx = EditorTestContext::new(cx).await;
23232
23233    // Create a simple buffer with cursor at start
23234    cx.set_state(indoc! {"
23235        ˇaaaa
23236        bbbb
23237        cccc
23238        dddd
23239        eeee
23240        ffff
23241        gggg
23242        hhhh"});
23243
23244    // Add 2 cursors below (so we have 3 total)
23245    cx.update_editor(|editor, window, cx| {
23246        editor.add_selection_below(&Default::default(), window, cx);
23247        editor.add_selection_below(&Default::default(), window, cx);
23248    });
23249
23250    // Verify we have 3 cursors
23251    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
23252    assert_eq!(
23253        initial_count, 3,
23254        "Should have 3 cursors after adding 2 below"
23255    );
23256
23257    // Move down one line
23258    cx.update_editor(|editor, window, cx| {
23259        editor.move_down(&MoveDown, window, cx);
23260    });
23261
23262    // Add another cursor below
23263    cx.update_editor(|editor, window, cx| {
23264        editor.add_selection_below(&Default::default(), window, cx);
23265    });
23266
23267    // Should now have 4 cursors (3 original + 1 new)
23268    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
23269    assert_eq!(
23270        final_count, 4,
23271        "Should have 4 cursors after moving and adding another"
23272    );
23273}
23274
23275#[gpui::test(iterations = 10)]
23276async fn test_document_colors(cx: &mut TestAppContext) {
23277    let expected_color = Rgba {
23278        r: 0.33,
23279        g: 0.33,
23280        b: 0.33,
23281        a: 0.33,
23282    };
23283
23284    init_test(cx, |_| {});
23285
23286    let fs = FakeFs::new(cx.executor());
23287    fs.insert_tree(
23288        path!("/a"),
23289        json!({
23290            "first.rs": "fn main() { let a = 5; }",
23291        }),
23292    )
23293    .await;
23294
23295    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23296    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23297    let cx = &mut VisualTestContext::from_window(*workspace, cx);
23298
23299    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23300    language_registry.add(rust_lang());
23301    let mut fake_servers = language_registry.register_fake_lsp(
23302        "Rust",
23303        FakeLspAdapter {
23304            capabilities: lsp::ServerCapabilities {
23305                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
23306                ..lsp::ServerCapabilities::default()
23307            },
23308            name: "rust-analyzer",
23309            ..FakeLspAdapter::default()
23310        },
23311    );
23312    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
23313        "Rust",
23314        FakeLspAdapter {
23315            capabilities: lsp::ServerCapabilities {
23316                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
23317                ..lsp::ServerCapabilities::default()
23318            },
23319            name: "not-rust-analyzer",
23320            ..FakeLspAdapter::default()
23321        },
23322    );
23323
23324    let editor = workspace
23325        .update(cx, |workspace, window, cx| {
23326            workspace.open_abs_path(
23327                PathBuf::from(path!("/a/first.rs")),
23328                OpenOptions::default(),
23329                window,
23330                cx,
23331            )
23332        })
23333        .unwrap()
23334        .await
23335        .unwrap()
23336        .downcast::<Editor>()
23337        .unwrap();
23338    let fake_language_server = fake_servers.next().await.unwrap();
23339    let fake_language_server_without_capabilities =
23340        fake_servers_without_capabilities.next().await.unwrap();
23341    let requests_made = Arc::new(AtomicUsize::new(0));
23342    let closure_requests_made = Arc::clone(&requests_made);
23343    let mut color_request_handle = fake_language_server
23344        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
23345            let requests_made = Arc::clone(&closure_requests_made);
23346            async move {
23347                assert_eq!(
23348                    params.text_document.uri,
23349                    lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
23350                );
23351                requests_made.fetch_add(1, atomic::Ordering::Release);
23352                Ok(vec![
23353                    lsp::ColorInformation {
23354                        range: lsp::Range {
23355                            start: lsp::Position {
23356                                line: 0,
23357                                character: 0,
23358                            },
23359                            end: lsp::Position {
23360                                line: 0,
23361                                character: 1,
23362                            },
23363                        },
23364                        color: lsp::Color {
23365                            red: 0.33,
23366                            green: 0.33,
23367                            blue: 0.33,
23368                            alpha: 0.33,
23369                        },
23370                    },
23371                    lsp::ColorInformation {
23372                        range: lsp::Range {
23373                            start: lsp::Position {
23374                                line: 0,
23375                                character: 0,
23376                            },
23377                            end: lsp::Position {
23378                                line: 0,
23379                                character: 1,
23380                            },
23381                        },
23382                        color: lsp::Color {
23383                            red: 0.33,
23384                            green: 0.33,
23385                            blue: 0.33,
23386                            alpha: 0.33,
23387                        },
23388                    },
23389                ])
23390            }
23391        });
23392
23393    let _handle = fake_language_server_without_capabilities
23394        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
23395            panic!("Should not be called");
23396        });
23397    cx.executor().advance_clock(Duration::from_millis(100));
23398    color_request_handle.next().await.unwrap();
23399    cx.run_until_parked();
23400    assert_eq!(
23401        1,
23402        requests_made.load(atomic::Ordering::Acquire),
23403        "Should query for colors once per editor open"
23404    );
23405    editor.update_in(cx, |editor, _, cx| {
23406        assert_eq!(
23407            vec![expected_color],
23408            extract_color_inlays(editor, cx),
23409            "Should have an initial inlay"
23410        );
23411    });
23412
23413    // opening another file in a split should not influence the LSP query counter
23414    workspace
23415        .update(cx, |workspace, window, cx| {
23416            assert_eq!(
23417                workspace.panes().len(),
23418                1,
23419                "Should have one pane with one editor"
23420            );
23421            workspace.move_item_to_pane_in_direction(
23422                &MoveItemToPaneInDirection {
23423                    direction: SplitDirection::Right,
23424                    focus: false,
23425                    clone: true,
23426                },
23427                window,
23428                cx,
23429            );
23430        })
23431        .unwrap();
23432    cx.run_until_parked();
23433    workspace
23434        .update(cx, |workspace, _, cx| {
23435            let panes = workspace.panes();
23436            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
23437            for pane in panes {
23438                let editor = pane
23439                    .read(cx)
23440                    .active_item()
23441                    .and_then(|item| item.downcast::<Editor>())
23442                    .expect("Should have opened an editor in each split");
23443                let editor_file = editor
23444                    .read(cx)
23445                    .buffer()
23446                    .read(cx)
23447                    .as_singleton()
23448                    .expect("test deals with singleton buffers")
23449                    .read(cx)
23450                    .file()
23451                    .expect("test buffese should have a file")
23452                    .path();
23453                assert_eq!(
23454                    editor_file.as_ref(),
23455                    Path::new("first.rs"),
23456                    "Both editors should be opened for the same file"
23457                )
23458            }
23459        })
23460        .unwrap();
23461
23462    cx.executor().advance_clock(Duration::from_millis(500));
23463    let save = editor.update_in(cx, |editor, window, cx| {
23464        editor.move_to_end(&MoveToEnd, window, cx);
23465        editor.handle_input("dirty", window, cx);
23466        editor.save(
23467            SaveOptions {
23468                format: true,
23469                autosave: true,
23470            },
23471            project.clone(),
23472            window,
23473            cx,
23474        )
23475    });
23476    save.await.unwrap();
23477
23478    color_request_handle.next().await.unwrap();
23479    cx.run_until_parked();
23480    assert_eq!(
23481        3,
23482        requests_made.load(atomic::Ordering::Acquire),
23483        "Should query for colors once per save and once per formatting after save"
23484    );
23485
23486    drop(editor);
23487    let close = workspace
23488        .update(cx, |workspace, window, cx| {
23489            workspace.active_pane().update(cx, |pane, cx| {
23490                pane.close_active_item(&CloseActiveItem::default(), window, cx)
23491            })
23492        })
23493        .unwrap();
23494    close.await.unwrap();
23495    let close = workspace
23496        .update(cx, |workspace, window, cx| {
23497            workspace.active_pane().update(cx, |pane, cx| {
23498                pane.close_active_item(&CloseActiveItem::default(), window, cx)
23499            })
23500        })
23501        .unwrap();
23502    close.await.unwrap();
23503    assert_eq!(
23504        3,
23505        requests_made.load(atomic::Ordering::Acquire),
23506        "After saving and closing all editors, no extra requests should be made"
23507    );
23508    workspace
23509        .update(cx, |workspace, _, cx| {
23510            assert!(
23511                workspace.active_item(cx).is_none(),
23512                "Should close all editors"
23513            )
23514        })
23515        .unwrap();
23516
23517    workspace
23518        .update(cx, |workspace, window, cx| {
23519            workspace.active_pane().update(cx, |pane, cx| {
23520                pane.navigate_backward(window, cx);
23521            })
23522        })
23523        .unwrap();
23524    cx.executor().advance_clock(Duration::from_millis(100));
23525    cx.run_until_parked();
23526    let editor = workspace
23527        .update(cx, |workspace, _, cx| {
23528            workspace
23529                .active_item(cx)
23530                .expect("Should have reopened the editor again after navigating back")
23531                .downcast::<Editor>()
23532                .expect("Should be an editor")
23533        })
23534        .unwrap();
23535    color_request_handle.next().await.unwrap();
23536    assert_eq!(
23537        3,
23538        requests_made.load(atomic::Ordering::Acquire),
23539        "Cache should be reused on buffer close and reopen"
23540    );
23541    editor.update(cx, |editor, cx| {
23542        assert_eq!(
23543            vec![expected_color],
23544            extract_color_inlays(editor, cx),
23545            "Should have an initial inlay"
23546        );
23547    });
23548}
23549
23550#[gpui::test]
23551async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
23552    init_test(cx, |_| {});
23553    let (editor, cx) = cx.add_window_view(Editor::single_line);
23554    editor.update_in(cx, |editor, window, cx| {
23555        editor.set_text("oops\n\nwow\n", window, cx)
23556    });
23557    cx.run_until_parked();
23558    editor.update(cx, |editor, cx| {
23559        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
23560    });
23561    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
23562    cx.run_until_parked();
23563    editor.update(cx, |editor, cx| {
23564        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
23565    });
23566}
23567
23568#[track_caller]
23569fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
23570    editor
23571        .all_inlays(cx)
23572        .into_iter()
23573        .filter_map(|inlay| inlay.get_color())
23574        .map(Rgba::from)
23575        .collect()
23576}