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, CloseInactiveItems, MoveItemToPaneInDirection, NavigationEntry,
   59    OpenOptions, ViewId,
   60    item::{FollowEvent, FollowableItem, Item, ItemHandle, SaveOptions},
   61};
   62
   63#[gpui::test]
   64fn test_edit_events(cx: &mut TestAppContext) {
   65    init_test(cx, |_| {});
   66
   67    let buffer = cx.new(|cx| {
   68        let mut buffer = language::Buffer::local("123456", cx);
   69        buffer.set_group_interval(Duration::from_secs(1));
   70        buffer
   71    });
   72
   73    let events = Rc::new(RefCell::new(Vec::new()));
   74    let editor1 = cx.add_window({
   75        let events = events.clone();
   76        |window, cx| {
   77            let entity = cx.entity().clone();
   78            cx.subscribe_in(
   79                &entity,
   80                window,
   81                move |_, _, event: &EditorEvent, _, _| match event {
   82                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
   83                    EditorEvent::BufferEdited => {
   84                        events.borrow_mut().push(("editor1", "buffer edited"))
   85                    }
   86                    _ => {}
   87                },
   88            )
   89            .detach();
   90            Editor::for_buffer(buffer.clone(), None, window, cx)
   91        }
   92    });
   93
   94    let editor2 = cx.add_window({
   95        let events = events.clone();
   96        |window, cx| {
   97            cx.subscribe_in(
   98                &cx.entity().clone(),
   99                window,
  100                move |_, _, event: &EditorEvent, _, _| match event {
  101                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
  102                    EditorEvent::BufferEdited => {
  103                        events.borrow_mut().push(("editor2", "buffer edited"))
  104                    }
  105                    _ => {}
  106                },
  107            )
  108            .detach();
  109            Editor::for_buffer(buffer.clone(), None, window, cx)
  110        }
  111    });
  112
  113    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  114
  115    // Mutating editor 1 will emit an `Edited` event only for that editor.
  116    _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
  117    assert_eq!(
  118        mem::take(&mut *events.borrow_mut()),
  119        [
  120            ("editor1", "edited"),
  121            ("editor1", "buffer edited"),
  122            ("editor2", "buffer edited"),
  123        ]
  124    );
  125
  126    // Mutating editor 2 will emit an `Edited` event only for that editor.
  127    _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
  128    assert_eq!(
  129        mem::take(&mut *events.borrow_mut()),
  130        [
  131            ("editor2", "edited"),
  132            ("editor1", "buffer edited"),
  133            ("editor2", "buffer edited"),
  134        ]
  135    );
  136
  137    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  138    _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  139    assert_eq!(
  140        mem::take(&mut *events.borrow_mut()),
  141        [
  142            ("editor1", "edited"),
  143            ("editor1", "buffer edited"),
  144            ("editor2", "buffer edited"),
  145        ]
  146    );
  147
  148    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  149    _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  150    assert_eq!(
  151        mem::take(&mut *events.borrow_mut()),
  152        [
  153            ("editor1", "edited"),
  154            ("editor1", "buffer edited"),
  155            ("editor2", "buffer edited"),
  156        ]
  157    );
  158
  159    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  160    _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  161    assert_eq!(
  162        mem::take(&mut *events.borrow_mut()),
  163        [
  164            ("editor2", "edited"),
  165            ("editor1", "buffer edited"),
  166            ("editor2", "buffer edited"),
  167        ]
  168    );
  169
  170    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  171    _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  172    assert_eq!(
  173        mem::take(&mut *events.borrow_mut()),
  174        [
  175            ("editor2", "edited"),
  176            ("editor1", "buffer edited"),
  177            ("editor2", "buffer edited"),
  178        ]
  179    );
  180
  181    // No event is emitted when the mutation is a no-op.
  182    _ = editor2.update(cx, |editor, window, cx| {
  183        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  184            s.select_ranges([0..0])
  185        });
  186
  187        editor.backspace(&Backspace, window, cx);
  188    });
  189    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  190}
  191
  192#[gpui::test]
  193fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  194    init_test(cx, |_| {});
  195
  196    let mut now = Instant::now();
  197    let group_interval = Duration::from_millis(1);
  198    let buffer = cx.new(|cx| {
  199        let mut buf = language::Buffer::local("123456", cx);
  200        buf.set_group_interval(group_interval);
  201        buf
  202    });
  203    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  204    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
  205
  206    _ = editor.update(cx, |editor, window, cx| {
  207        editor.start_transaction_at(now, window, cx);
  208        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  209            s.select_ranges([2..4])
  210        });
  211
  212        editor.insert("cd", window, cx);
  213        editor.end_transaction_at(now, cx);
  214        assert_eq!(editor.text(cx), "12cd56");
  215        assert_eq!(editor.selections.ranges(cx), vec![4..4]);
  216
  217        editor.start_transaction_at(now, window, cx);
  218        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  219            s.select_ranges([4..5])
  220        });
  221        editor.insert("e", window, cx);
  222        editor.end_transaction_at(now, cx);
  223        assert_eq!(editor.text(cx), "12cde6");
  224        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  225
  226        now += group_interval + Duration::from_millis(1);
  227        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  228            s.select_ranges([2..2])
  229        });
  230
  231        // Simulate an edit in another editor
  232        buffer.update(cx, |buffer, cx| {
  233            buffer.start_transaction_at(now, cx);
  234            buffer.edit([(0..1, "a")], None, cx);
  235            buffer.edit([(1..1, "b")], None, cx);
  236            buffer.end_transaction_at(now, cx);
  237        });
  238
  239        assert_eq!(editor.text(cx), "ab2cde6");
  240        assert_eq!(editor.selections.ranges(cx), vec![3..3]);
  241
  242        // Last transaction happened past the group interval in a different editor.
  243        // Undo it individually and don't restore selections.
  244        editor.undo(&Undo, window, cx);
  245        assert_eq!(editor.text(cx), "12cde6");
  246        assert_eq!(editor.selections.ranges(cx), vec![2..2]);
  247
  248        // First two transactions happened within the group interval in this editor.
  249        // Undo them together and restore selections.
  250        editor.undo(&Undo, window, cx);
  251        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  252        assert_eq!(editor.text(cx), "123456");
  253        assert_eq!(editor.selections.ranges(cx), vec![0..0]);
  254
  255        // Redo the first two transactions together.
  256        editor.redo(&Redo, window, cx);
  257        assert_eq!(editor.text(cx), "12cde6");
  258        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  259
  260        // Redo the last transaction on its own.
  261        editor.redo(&Redo, window, cx);
  262        assert_eq!(editor.text(cx), "ab2cde6");
  263        assert_eq!(editor.selections.ranges(cx), vec![6..6]);
  264
  265        // Test empty transactions.
  266        editor.start_transaction_at(now, window, cx);
  267        editor.end_transaction_at(now, cx);
  268        editor.undo(&Undo, window, cx);
  269        assert_eq!(editor.text(cx), "12cde6");
  270    });
  271}
  272
  273#[gpui::test]
  274fn test_ime_composition(cx: &mut TestAppContext) {
  275    init_test(cx, |_| {});
  276
  277    let buffer = cx.new(|cx| {
  278        let mut buffer = language::Buffer::local("abcde", cx);
  279        // Ensure automatic grouping doesn't occur.
  280        buffer.set_group_interval(Duration::ZERO);
  281        buffer
  282    });
  283
  284    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  285    cx.add_window(|window, cx| {
  286        let mut editor = build_editor(buffer.clone(), window, cx);
  287
  288        // Start a new IME composition.
  289        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  290        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
  291        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  292        assert_eq!(editor.text(cx), "äbcde");
  293        assert_eq!(
  294            editor.marked_text_ranges(cx),
  295            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  296        );
  297
  298        // Finalize IME composition.
  299        editor.replace_text_in_range(None, "ā", window, cx);
  300        assert_eq!(editor.text(cx), "ābcde");
  301        assert_eq!(editor.marked_text_ranges(cx), None);
  302
  303        // IME composition edits are grouped and are undone/redone at once.
  304        editor.undo(&Default::default(), window, cx);
  305        assert_eq!(editor.text(cx), "abcde");
  306        assert_eq!(editor.marked_text_ranges(cx), None);
  307        editor.redo(&Default::default(), window, cx);
  308        assert_eq!(editor.text(cx), "ābcde");
  309        assert_eq!(editor.marked_text_ranges(cx), None);
  310
  311        // Start a new IME composition.
  312        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  313        assert_eq!(
  314            editor.marked_text_ranges(cx),
  315            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  316        );
  317
  318        // Undoing during an IME composition cancels it.
  319        editor.undo(&Default::default(), window, cx);
  320        assert_eq!(editor.text(cx), "ābcde");
  321        assert_eq!(editor.marked_text_ranges(cx), None);
  322
  323        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  324        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  325        assert_eq!(editor.text(cx), "ābcdè");
  326        assert_eq!(
  327            editor.marked_text_ranges(cx),
  328            Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
  329        );
  330
  331        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  332        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  333        assert_eq!(editor.text(cx), "ābcdę");
  334        assert_eq!(editor.marked_text_ranges(cx), None);
  335
  336        // Start a new IME composition with multiple cursors.
  337        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  338            s.select_ranges([
  339                OffsetUtf16(1)..OffsetUtf16(1),
  340                OffsetUtf16(3)..OffsetUtf16(3),
  341                OffsetUtf16(5)..OffsetUtf16(5),
  342            ])
  343        });
  344        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  345        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  346        assert_eq!(
  347            editor.marked_text_ranges(cx),
  348            Some(vec![
  349                OffsetUtf16(0)..OffsetUtf16(3),
  350                OffsetUtf16(4)..OffsetUtf16(7),
  351                OffsetUtf16(8)..OffsetUtf16(11)
  352            ])
  353        );
  354
  355        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  356        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  357        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  358        assert_eq!(
  359            editor.marked_text_ranges(cx),
  360            Some(vec![
  361                OffsetUtf16(1)..OffsetUtf16(2),
  362                OffsetUtf16(5)..OffsetUtf16(6),
  363                OffsetUtf16(9)..OffsetUtf16(10)
  364            ])
  365        );
  366
  367        // Finalize IME composition with multiple cursors.
  368        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  369        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  370        assert_eq!(editor.marked_text_ranges(cx), None);
  371
  372        editor
  373    });
  374}
  375
  376#[gpui::test]
  377fn test_selection_with_mouse(cx: &mut TestAppContext) {
  378    init_test(cx, |_| {});
  379
  380    let editor = cx.add_window(|window, cx| {
  381        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  382        build_editor(buffer, window, cx)
  383    });
  384
  385    _ = editor.update(cx, |editor, window, cx| {
  386        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  387    });
  388    assert_eq!(
  389        editor
  390            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  391            .unwrap(),
  392        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  393    );
  394
  395    _ = editor.update(cx, |editor, window, cx| {
  396        editor.update_selection(
  397            DisplayPoint::new(DisplayRow(3), 3),
  398            0,
  399            gpui::Point::<f32>::default(),
  400            window,
  401            cx,
  402        );
  403    });
  404
  405    assert_eq!(
  406        editor
  407            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  408            .unwrap(),
  409        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  410    );
  411
  412    _ = editor.update(cx, |editor, window, cx| {
  413        editor.update_selection(
  414            DisplayPoint::new(DisplayRow(1), 1),
  415            0,
  416            gpui::Point::<f32>::default(),
  417            window,
  418            cx,
  419        );
  420    });
  421
  422    assert_eq!(
  423        editor
  424            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  425            .unwrap(),
  426        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  427    );
  428
  429    _ = editor.update(cx, |editor, window, cx| {
  430        editor.end_selection(window, cx);
  431        editor.update_selection(
  432            DisplayPoint::new(DisplayRow(3), 3),
  433            0,
  434            gpui::Point::<f32>::default(),
  435            window,
  436            cx,
  437        );
  438    });
  439
  440    assert_eq!(
  441        editor
  442            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  443            .unwrap(),
  444        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  445    );
  446
  447    _ = editor.update(cx, |editor, window, cx| {
  448        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  449        editor.update_selection(
  450            DisplayPoint::new(DisplayRow(0), 0),
  451            0,
  452            gpui::Point::<f32>::default(),
  453            window,
  454            cx,
  455        );
  456    });
  457
  458    assert_eq!(
  459        editor
  460            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  461            .unwrap(),
  462        [
  463            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  464            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  465        ]
  466    );
  467
  468    _ = editor.update(cx, |editor, window, cx| {
  469        editor.end_selection(window, cx);
  470    });
  471
  472    assert_eq!(
  473        editor
  474            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  475            .unwrap(),
  476        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  477    );
  478}
  479
  480#[gpui::test]
  481fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  482    init_test(cx, |_| {});
  483
  484    let editor = cx.add_window(|window, cx| {
  485        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  486        build_editor(buffer, window, cx)
  487    });
  488
  489    _ = editor.update(cx, |editor, window, cx| {
  490        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  491    });
  492
  493    _ = editor.update(cx, |editor, window, cx| {
  494        editor.end_selection(window, cx);
  495    });
  496
  497    _ = editor.update(cx, |editor, window, cx| {
  498        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  499    });
  500
  501    _ = editor.update(cx, |editor, window, cx| {
  502        editor.end_selection(window, cx);
  503    });
  504
  505    assert_eq!(
  506        editor
  507            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  508            .unwrap(),
  509        [
  510            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  511            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  512        ]
  513    );
  514
  515    _ = editor.update(cx, |editor, window, cx| {
  516        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  517    });
  518
  519    _ = editor.update(cx, |editor, window, cx| {
  520        editor.end_selection(window, cx);
  521    });
  522
  523    assert_eq!(
  524        editor
  525            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  526            .unwrap(),
  527        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  528    );
  529}
  530
  531#[gpui::test]
  532fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  533    init_test(cx, |_| {});
  534
  535    let editor = cx.add_window(|window, cx| {
  536        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  537        build_editor(buffer, window, cx)
  538    });
  539
  540    _ = editor.update(cx, |editor, window, cx| {
  541        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  542        assert_eq!(
  543            editor.selections.display_ranges(cx),
  544            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  545        );
  546    });
  547
  548    _ = editor.update(cx, |editor, window, cx| {
  549        editor.update_selection(
  550            DisplayPoint::new(DisplayRow(3), 3),
  551            0,
  552            gpui::Point::<f32>::default(),
  553            window,
  554            cx,
  555        );
  556        assert_eq!(
  557            editor.selections.display_ranges(cx),
  558            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  559        );
  560    });
  561
  562    _ = editor.update(cx, |editor, window, cx| {
  563        editor.cancel(&Cancel, window, cx);
  564        editor.update_selection(
  565            DisplayPoint::new(DisplayRow(1), 1),
  566            0,
  567            gpui::Point::<f32>::default(),
  568            window,
  569            cx,
  570        );
  571        assert_eq!(
  572            editor.selections.display_ranges(cx),
  573            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  574        );
  575    });
  576}
  577
  578#[gpui::test]
  579fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  580    init_test(cx, |_| {});
  581
  582    let editor = cx.add_window(|window, cx| {
  583        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  584        build_editor(buffer, window, cx)
  585    });
  586
  587    _ = editor.update(cx, |editor, window, cx| {
  588        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  589        assert_eq!(
  590            editor.selections.display_ranges(cx),
  591            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  592        );
  593
  594        editor.move_down(&Default::default(), window, cx);
  595        assert_eq!(
  596            editor.selections.display_ranges(cx),
  597            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  598        );
  599
  600        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  601        assert_eq!(
  602            editor.selections.display_ranges(cx),
  603            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  604        );
  605
  606        editor.move_up(&Default::default(), window, cx);
  607        assert_eq!(
  608            editor.selections.display_ranges(cx),
  609            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  610        );
  611    });
  612}
  613
  614#[gpui::test]
  615fn test_clone(cx: &mut TestAppContext) {
  616    init_test(cx, |_| {});
  617
  618    let (text, selection_ranges) = marked_text_ranges(
  619        indoc! {"
  620            one
  621            two
  622            threeˇ
  623            four
  624            fiveˇ
  625        "},
  626        true,
  627    );
  628
  629    let editor = cx.add_window(|window, cx| {
  630        let buffer = MultiBuffer::build_simple(&text, cx);
  631        build_editor(buffer, window, cx)
  632    });
  633
  634    _ = editor.update(cx, |editor, window, cx| {
  635        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  636            s.select_ranges(selection_ranges.clone())
  637        });
  638        editor.fold_creases(
  639            vec![
  640                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  641                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  642            ],
  643            true,
  644            window,
  645            cx,
  646        );
  647    });
  648
  649    let cloned_editor = editor
  650        .update(cx, |editor, _, cx| {
  651            cx.open_window(Default::default(), |window, cx| {
  652                cx.new(|cx| editor.clone(window, cx))
  653            })
  654        })
  655        .unwrap()
  656        .unwrap();
  657
  658    let snapshot = editor
  659        .update(cx, |e, window, cx| e.snapshot(window, cx))
  660        .unwrap();
  661    let cloned_snapshot = cloned_editor
  662        .update(cx, |e, window, cx| e.snapshot(window, cx))
  663        .unwrap();
  664
  665    assert_eq!(
  666        cloned_editor
  667            .update(cx, |e, _, cx| e.display_text(cx))
  668            .unwrap(),
  669        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  670    );
  671    assert_eq!(
  672        cloned_snapshot
  673            .folds_in_range(0..text.len())
  674            .collect::<Vec<_>>(),
  675        snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
  676    );
  677    assert_set_eq!(
  678        cloned_editor
  679            .update(cx, |editor, _, cx| editor.selections.ranges::<Point>(cx))
  680            .unwrap(),
  681        editor
  682            .update(cx, |editor, _, cx| editor.selections.ranges(cx))
  683            .unwrap()
  684    );
  685    assert_set_eq!(
  686        cloned_editor
  687            .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
  688            .unwrap(),
  689        editor
  690            .update(cx, |e, _, cx| e.selections.display_ranges(cx))
  691            .unwrap()
  692    );
  693}
  694
  695#[gpui::test]
  696async fn test_navigation_history(cx: &mut TestAppContext) {
  697    init_test(cx, |_| {});
  698
  699    use workspace::item::Item;
  700
  701    let fs = FakeFs::new(cx.executor());
  702    let project = Project::test(fs, [], cx).await;
  703    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  704    let pane = workspace
  705        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  706        .unwrap();
  707
  708    _ = workspace.update(cx, |_v, window, cx| {
  709        cx.new(|cx| {
  710            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  711            let mut editor = build_editor(buffer.clone(), window, cx);
  712            let handle = cx.entity();
  713            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  714
  715            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  716                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  717            }
  718
  719            // Move the cursor a small distance.
  720            // Nothing is added to the navigation history.
  721            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  722                s.select_display_ranges([
  723                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  724                ])
  725            });
  726            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  727                s.select_display_ranges([
  728                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  729                ])
  730            });
  731            assert!(pop_history(&mut editor, cx).is_none());
  732
  733            // Move the cursor a large distance.
  734            // The history can jump back to the previous position.
  735            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  736                s.select_display_ranges([
  737                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  738                ])
  739            });
  740            let nav_entry = pop_history(&mut editor, cx).unwrap();
  741            editor.navigate(nav_entry.data.unwrap(), window, cx);
  742            assert_eq!(nav_entry.item.id(), cx.entity_id());
  743            assert_eq!(
  744                editor.selections.display_ranges(cx),
  745                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  746            );
  747            assert!(pop_history(&mut editor, cx).is_none());
  748
  749            // Move the cursor a small distance via the mouse.
  750            // Nothing is added to the navigation history.
  751            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  752            editor.end_selection(window, cx);
  753            assert_eq!(
  754                editor.selections.display_ranges(cx),
  755                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  756            );
  757            assert!(pop_history(&mut editor, cx).is_none());
  758
  759            // Move the cursor a large distance via the mouse.
  760            // The history can jump back to the previous position.
  761            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  762            editor.end_selection(window, cx);
  763            assert_eq!(
  764                editor.selections.display_ranges(cx),
  765                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  766            );
  767            let nav_entry = pop_history(&mut editor, cx).unwrap();
  768            editor.navigate(nav_entry.data.unwrap(), window, cx);
  769            assert_eq!(nav_entry.item.id(), cx.entity_id());
  770            assert_eq!(
  771                editor.selections.display_ranges(cx),
  772                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  773            );
  774            assert!(pop_history(&mut editor, cx).is_none());
  775
  776            // Set scroll position to check later
  777            editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), window, cx);
  778            let original_scroll_position = editor.scroll_manager.anchor();
  779
  780            // Jump to the end of the document and adjust scroll
  781            editor.move_to_end(&MoveToEnd, window, cx);
  782            editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), window, cx);
  783            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  784
  785            let nav_entry = pop_history(&mut editor, cx).unwrap();
  786            editor.navigate(nav_entry.data.unwrap(), window, cx);
  787            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  788
  789            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  790            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  791            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  792            let invalid_point = Point::new(9999, 0);
  793            editor.navigate(
  794                Box::new(NavigationData {
  795                    cursor_anchor: invalid_anchor,
  796                    cursor_position: invalid_point,
  797                    scroll_anchor: ScrollAnchor {
  798                        anchor: invalid_anchor,
  799                        offset: Default::default(),
  800                    },
  801                    scroll_top_row: invalid_point.row,
  802                }),
  803                window,
  804                cx,
  805            );
  806            assert_eq!(
  807                editor.selections.display_ranges(cx),
  808                &[editor.max_point(cx)..editor.max_point(cx)]
  809            );
  810            assert_eq!(
  811                editor.scroll_position(cx),
  812                gpui::Point::new(0., editor.max_point(cx).row().as_f32())
  813            );
  814
  815            editor
  816        })
  817    });
  818}
  819
  820#[gpui::test]
  821fn test_cancel(cx: &mut TestAppContext) {
  822    init_test(cx, |_| {});
  823
  824    let editor = cx.add_window(|window, cx| {
  825        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  826        build_editor(buffer, window, cx)
  827    });
  828
  829    _ = editor.update(cx, |editor, window, cx| {
  830        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
  831        editor.update_selection(
  832            DisplayPoint::new(DisplayRow(1), 1),
  833            0,
  834            gpui::Point::<f32>::default(),
  835            window,
  836            cx,
  837        );
  838        editor.end_selection(window, cx);
  839
  840        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
  841        editor.update_selection(
  842            DisplayPoint::new(DisplayRow(0), 3),
  843            0,
  844            gpui::Point::<f32>::default(),
  845            window,
  846            cx,
  847        );
  848        editor.end_selection(window, cx);
  849        assert_eq!(
  850            editor.selections.display_ranges(cx),
  851            [
  852                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
  853                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
  854            ]
  855        );
  856    });
  857
  858    _ = editor.update(cx, |editor, window, cx| {
  859        editor.cancel(&Cancel, window, cx);
  860        assert_eq!(
  861            editor.selections.display_ranges(cx),
  862            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
  863        );
  864    });
  865
  866    _ = editor.update(cx, |editor, window, cx| {
  867        editor.cancel(&Cancel, window, cx);
  868        assert_eq!(
  869            editor.selections.display_ranges(cx),
  870            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
  871        );
  872    });
  873}
  874
  875#[gpui::test]
  876fn test_fold_action(cx: &mut TestAppContext) {
  877    init_test(cx, |_| {});
  878
  879    let editor = cx.add_window(|window, cx| {
  880        let buffer = MultiBuffer::build_simple(
  881            &"
  882                impl Foo {
  883                    // Hello!
  884
  885                    fn a() {
  886                        1
  887                    }
  888
  889                    fn b() {
  890                        2
  891                    }
  892
  893                    fn c() {
  894                        3
  895                    }
  896                }
  897            "
  898            .unindent(),
  899            cx,
  900        );
  901        build_editor(buffer.clone(), window, cx)
  902    });
  903
  904    _ = editor.update(cx, |editor, window, cx| {
  905        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  906            s.select_display_ranges([
  907                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
  908            ]);
  909        });
  910        editor.fold(&Fold, window, cx);
  911        assert_eq!(
  912            editor.display_text(cx),
  913            "
  914                impl Foo {
  915                    // Hello!
  916
  917                    fn a() {
  918                        1
  919                    }
  920
  921                    fn b() {⋯
  922                    }
  923
  924                    fn c() {⋯
  925                    }
  926                }
  927            "
  928            .unindent(),
  929        );
  930
  931        editor.fold(&Fold, window, cx);
  932        assert_eq!(
  933            editor.display_text(cx),
  934            "
  935                impl Foo {⋯
  936                }
  937            "
  938            .unindent(),
  939        );
  940
  941        editor.unfold_lines(&UnfoldLines, window, cx);
  942        assert_eq!(
  943            editor.display_text(cx),
  944            "
  945                impl Foo {
  946                    // Hello!
  947
  948                    fn a() {
  949                        1
  950                    }
  951
  952                    fn b() {⋯
  953                    }
  954
  955                    fn c() {⋯
  956                    }
  957                }
  958            "
  959            .unindent(),
  960        );
  961
  962        editor.unfold_lines(&UnfoldLines, window, cx);
  963        assert_eq!(
  964            editor.display_text(cx),
  965            editor.buffer.read(cx).read(cx).text()
  966        );
  967    });
  968}
  969
  970#[gpui::test]
  971fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
  972    init_test(cx, |_| {});
  973
  974    let editor = cx.add_window(|window, cx| {
  975        let buffer = MultiBuffer::build_simple(
  976            &"
  977                class Foo:
  978                    # Hello!
  979
  980                    def a():
  981                        print(1)
  982
  983                    def b():
  984                        print(2)
  985
  986                    def c():
  987                        print(3)
  988            "
  989            .unindent(),
  990            cx,
  991        );
  992        build_editor(buffer.clone(), window, cx)
  993    });
  994
  995    _ = editor.update(cx, |editor, window, cx| {
  996        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  997            s.select_display_ranges([
  998                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
  999            ]);
 1000        });
 1001        editor.fold(&Fold, window, cx);
 1002        assert_eq!(
 1003            editor.display_text(cx),
 1004            "
 1005                class Foo:
 1006                    # Hello!
 1007
 1008                    def a():
 1009                        print(1)
 1010
 1011                    def b():⋯
 1012
 1013                    def c():⋯
 1014            "
 1015            .unindent(),
 1016        );
 1017
 1018        editor.fold(&Fold, window, cx);
 1019        assert_eq!(
 1020            editor.display_text(cx),
 1021            "
 1022                class Foo:⋯
 1023            "
 1024            .unindent(),
 1025        );
 1026
 1027        editor.unfold_lines(&UnfoldLines, window, cx);
 1028        assert_eq!(
 1029            editor.display_text(cx),
 1030            "
 1031                class Foo:
 1032                    # Hello!
 1033
 1034                    def a():
 1035                        print(1)
 1036
 1037                    def b():⋯
 1038
 1039                    def c():⋯
 1040            "
 1041            .unindent(),
 1042        );
 1043
 1044        editor.unfold_lines(&UnfoldLines, window, cx);
 1045        assert_eq!(
 1046            editor.display_text(cx),
 1047            editor.buffer.read(cx).read(cx).text()
 1048        );
 1049    });
 1050}
 1051
 1052#[gpui::test]
 1053fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1054    init_test(cx, |_| {});
 1055
 1056    let editor = cx.add_window(|window, cx| {
 1057        let buffer = MultiBuffer::build_simple(
 1058            &"
 1059                class Foo:
 1060                    # Hello!
 1061
 1062                    def a():
 1063                        print(1)
 1064
 1065                    def b():
 1066                        print(2)
 1067
 1068
 1069                    def c():
 1070                        print(3)
 1071
 1072
 1073            "
 1074            .unindent(),
 1075            cx,
 1076        );
 1077        build_editor(buffer.clone(), window, cx)
 1078    });
 1079
 1080    _ = editor.update(cx, |editor, window, cx| {
 1081        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1082            s.select_display_ranges([
 1083                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1084            ]);
 1085        });
 1086        editor.fold(&Fold, window, cx);
 1087        assert_eq!(
 1088            editor.display_text(cx),
 1089            "
 1090                class Foo:
 1091                    # Hello!
 1092
 1093                    def a():
 1094                        print(1)
 1095
 1096                    def b():⋯
 1097
 1098
 1099                    def c():⋯
 1100
 1101
 1102            "
 1103            .unindent(),
 1104        );
 1105
 1106        editor.fold(&Fold, window, cx);
 1107        assert_eq!(
 1108            editor.display_text(cx),
 1109            "
 1110                class Foo:⋯
 1111
 1112
 1113            "
 1114            .unindent(),
 1115        );
 1116
 1117        editor.unfold_lines(&UnfoldLines, window, cx);
 1118        assert_eq!(
 1119            editor.display_text(cx),
 1120            "
 1121                class Foo:
 1122                    # Hello!
 1123
 1124                    def a():
 1125                        print(1)
 1126
 1127                    def b():⋯
 1128
 1129
 1130                    def c():⋯
 1131
 1132
 1133            "
 1134            .unindent(),
 1135        );
 1136
 1137        editor.unfold_lines(&UnfoldLines, window, cx);
 1138        assert_eq!(
 1139            editor.display_text(cx),
 1140            editor.buffer.read(cx).read(cx).text()
 1141        );
 1142    });
 1143}
 1144
 1145#[gpui::test]
 1146fn test_fold_at_level(cx: &mut TestAppContext) {
 1147    init_test(cx, |_| {});
 1148
 1149    let editor = cx.add_window(|window, cx| {
 1150        let buffer = MultiBuffer::build_simple(
 1151            &"
 1152                class Foo:
 1153                    # Hello!
 1154
 1155                    def a():
 1156                        print(1)
 1157
 1158                    def b():
 1159                        print(2)
 1160
 1161
 1162                class Bar:
 1163                    # World!
 1164
 1165                    def a():
 1166                        print(1)
 1167
 1168                    def b():
 1169                        print(2)
 1170
 1171
 1172            "
 1173            .unindent(),
 1174            cx,
 1175        );
 1176        build_editor(buffer.clone(), window, cx)
 1177    });
 1178
 1179    _ = editor.update(cx, |editor, window, cx| {
 1180        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1181        assert_eq!(
 1182            editor.display_text(cx),
 1183            "
 1184                class Foo:
 1185                    # Hello!
 1186
 1187                    def a():⋯
 1188
 1189                    def b():⋯
 1190
 1191
 1192                class Bar:
 1193                    # World!
 1194
 1195                    def a():⋯
 1196
 1197                    def b():⋯
 1198
 1199
 1200            "
 1201            .unindent(),
 1202        );
 1203
 1204        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1205        assert_eq!(
 1206            editor.display_text(cx),
 1207            "
 1208                class Foo:⋯
 1209
 1210
 1211                class Bar:⋯
 1212
 1213
 1214            "
 1215            .unindent(),
 1216        );
 1217
 1218        editor.unfold_all(&UnfoldAll, window, cx);
 1219        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1220        assert_eq!(
 1221            editor.display_text(cx),
 1222            "
 1223                class Foo:
 1224                    # Hello!
 1225
 1226                    def a():
 1227                        print(1)
 1228
 1229                    def b():
 1230                        print(2)
 1231
 1232
 1233                class Bar:
 1234                    # World!
 1235
 1236                    def a():
 1237                        print(1)
 1238
 1239                    def b():
 1240                        print(2)
 1241
 1242
 1243            "
 1244            .unindent(),
 1245        );
 1246
 1247        assert_eq!(
 1248            editor.display_text(cx),
 1249            editor.buffer.read(cx).read(cx).text()
 1250        );
 1251    });
 1252}
 1253
 1254#[gpui::test]
 1255fn test_move_cursor(cx: &mut TestAppContext) {
 1256    init_test(cx, |_| {});
 1257
 1258    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1259    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1260
 1261    buffer.update(cx, |buffer, cx| {
 1262        buffer.edit(
 1263            vec![
 1264                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1265                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1266            ],
 1267            None,
 1268            cx,
 1269        );
 1270    });
 1271    _ = editor.update(cx, |editor, window, cx| {
 1272        assert_eq!(
 1273            editor.selections.display_ranges(cx),
 1274            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1275        );
 1276
 1277        editor.move_down(&MoveDown, window, cx);
 1278        assert_eq!(
 1279            editor.selections.display_ranges(cx),
 1280            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1281        );
 1282
 1283        editor.move_right(&MoveRight, window, cx);
 1284        assert_eq!(
 1285            editor.selections.display_ranges(cx),
 1286            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1287        );
 1288
 1289        editor.move_left(&MoveLeft, window, cx);
 1290        assert_eq!(
 1291            editor.selections.display_ranges(cx),
 1292            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1293        );
 1294
 1295        editor.move_up(&MoveUp, window, cx);
 1296        assert_eq!(
 1297            editor.selections.display_ranges(cx),
 1298            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1299        );
 1300
 1301        editor.move_to_end(&MoveToEnd, window, cx);
 1302        assert_eq!(
 1303            editor.selections.display_ranges(cx),
 1304            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1305        );
 1306
 1307        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1308        assert_eq!(
 1309            editor.selections.display_ranges(cx),
 1310            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1311        );
 1312
 1313        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1314            s.select_display_ranges([
 1315                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1316            ]);
 1317        });
 1318        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1319        assert_eq!(
 1320            editor.selections.display_ranges(cx),
 1321            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1322        );
 1323
 1324        editor.select_to_end(&SelectToEnd, window, cx);
 1325        assert_eq!(
 1326            editor.selections.display_ranges(cx),
 1327            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1328        );
 1329    });
 1330}
 1331
 1332#[gpui::test]
 1333fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1334    init_test(cx, |_| {});
 1335
 1336    let editor = cx.add_window(|window, cx| {
 1337        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1338        build_editor(buffer.clone(), window, cx)
 1339    });
 1340
 1341    assert_eq!('🟥'.len_utf8(), 4);
 1342    assert_eq!('α'.len_utf8(), 2);
 1343
 1344    _ = editor.update(cx, |editor, window, cx| {
 1345        editor.fold_creases(
 1346            vec![
 1347                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1348                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1349                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1350            ],
 1351            true,
 1352            window,
 1353            cx,
 1354        );
 1355        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1356
 1357        editor.move_right(&MoveRight, window, cx);
 1358        assert_eq!(
 1359            editor.selections.display_ranges(cx),
 1360            &[empty_range(0, "🟥".len())]
 1361        );
 1362        editor.move_right(&MoveRight, window, cx);
 1363        assert_eq!(
 1364            editor.selections.display_ranges(cx),
 1365            &[empty_range(0, "🟥🟧".len())]
 1366        );
 1367        editor.move_right(&MoveRight, window, cx);
 1368        assert_eq!(
 1369            editor.selections.display_ranges(cx),
 1370            &[empty_range(0, "🟥🟧⋯".len())]
 1371        );
 1372
 1373        editor.move_down(&MoveDown, window, cx);
 1374        assert_eq!(
 1375            editor.selections.display_ranges(cx),
 1376            &[empty_range(1, "ab⋯e".len())]
 1377        );
 1378        editor.move_left(&MoveLeft, window, cx);
 1379        assert_eq!(
 1380            editor.selections.display_ranges(cx),
 1381            &[empty_range(1, "ab⋯".len())]
 1382        );
 1383        editor.move_left(&MoveLeft, window, cx);
 1384        assert_eq!(
 1385            editor.selections.display_ranges(cx),
 1386            &[empty_range(1, "ab".len())]
 1387        );
 1388        editor.move_left(&MoveLeft, window, cx);
 1389        assert_eq!(
 1390            editor.selections.display_ranges(cx),
 1391            &[empty_range(1, "a".len())]
 1392        );
 1393
 1394        editor.move_down(&MoveDown, window, cx);
 1395        assert_eq!(
 1396            editor.selections.display_ranges(cx),
 1397            &[empty_range(2, "α".len())]
 1398        );
 1399        editor.move_right(&MoveRight, window, cx);
 1400        assert_eq!(
 1401            editor.selections.display_ranges(cx),
 1402            &[empty_range(2, "αβ".len())]
 1403        );
 1404        editor.move_right(&MoveRight, window, cx);
 1405        assert_eq!(
 1406            editor.selections.display_ranges(cx),
 1407            &[empty_range(2, "αβ⋯".len())]
 1408        );
 1409        editor.move_right(&MoveRight, window, cx);
 1410        assert_eq!(
 1411            editor.selections.display_ranges(cx),
 1412            &[empty_range(2, "αβ⋯ε".len())]
 1413        );
 1414
 1415        editor.move_up(&MoveUp, window, cx);
 1416        assert_eq!(
 1417            editor.selections.display_ranges(cx),
 1418            &[empty_range(1, "ab⋯e".len())]
 1419        );
 1420        editor.move_down(&MoveDown, window, cx);
 1421        assert_eq!(
 1422            editor.selections.display_ranges(cx),
 1423            &[empty_range(2, "αβ⋯ε".len())]
 1424        );
 1425        editor.move_up(&MoveUp, window, cx);
 1426        assert_eq!(
 1427            editor.selections.display_ranges(cx),
 1428            &[empty_range(1, "ab⋯e".len())]
 1429        );
 1430
 1431        editor.move_up(&MoveUp, window, cx);
 1432        assert_eq!(
 1433            editor.selections.display_ranges(cx),
 1434            &[empty_range(0, "🟥🟧".len())]
 1435        );
 1436        editor.move_left(&MoveLeft, window, cx);
 1437        assert_eq!(
 1438            editor.selections.display_ranges(cx),
 1439            &[empty_range(0, "🟥".len())]
 1440        );
 1441        editor.move_left(&MoveLeft, window, cx);
 1442        assert_eq!(
 1443            editor.selections.display_ranges(cx),
 1444            &[empty_range(0, "".len())]
 1445        );
 1446    });
 1447}
 1448
 1449#[gpui::test]
 1450fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1451    init_test(cx, |_| {});
 1452
 1453    let editor = cx.add_window(|window, cx| {
 1454        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1455        build_editor(buffer.clone(), window, cx)
 1456    });
 1457    _ = editor.update(cx, |editor, window, cx| {
 1458        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1459            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1460        });
 1461
 1462        // moving above start of document should move selection to start of document,
 1463        // but the next move down should still be at the original goal_x
 1464        editor.move_up(&MoveUp, window, cx);
 1465        assert_eq!(
 1466            editor.selections.display_ranges(cx),
 1467            &[empty_range(0, "".len())]
 1468        );
 1469
 1470        editor.move_down(&MoveDown, window, cx);
 1471        assert_eq!(
 1472            editor.selections.display_ranges(cx),
 1473            &[empty_range(1, "abcd".len())]
 1474        );
 1475
 1476        editor.move_down(&MoveDown, window, cx);
 1477        assert_eq!(
 1478            editor.selections.display_ranges(cx),
 1479            &[empty_range(2, "αβγ".len())]
 1480        );
 1481
 1482        editor.move_down(&MoveDown, window, cx);
 1483        assert_eq!(
 1484            editor.selections.display_ranges(cx),
 1485            &[empty_range(3, "abcd".len())]
 1486        );
 1487
 1488        editor.move_down(&MoveDown, window, cx);
 1489        assert_eq!(
 1490            editor.selections.display_ranges(cx),
 1491            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1492        );
 1493
 1494        // moving past end of document should not change goal_x
 1495        editor.move_down(&MoveDown, window, cx);
 1496        assert_eq!(
 1497            editor.selections.display_ranges(cx),
 1498            &[empty_range(5, "".len())]
 1499        );
 1500
 1501        editor.move_down(&MoveDown, window, cx);
 1502        assert_eq!(
 1503            editor.selections.display_ranges(cx),
 1504            &[empty_range(5, "".len())]
 1505        );
 1506
 1507        editor.move_up(&MoveUp, window, cx);
 1508        assert_eq!(
 1509            editor.selections.display_ranges(cx),
 1510            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1511        );
 1512
 1513        editor.move_up(&MoveUp, window, cx);
 1514        assert_eq!(
 1515            editor.selections.display_ranges(cx),
 1516            &[empty_range(3, "abcd".len())]
 1517        );
 1518
 1519        editor.move_up(&MoveUp, window, cx);
 1520        assert_eq!(
 1521            editor.selections.display_ranges(cx),
 1522            &[empty_range(2, "αβγ".len())]
 1523        );
 1524    });
 1525}
 1526
 1527#[gpui::test]
 1528fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1529    init_test(cx, |_| {});
 1530    let move_to_beg = MoveToBeginningOfLine {
 1531        stop_at_soft_wraps: true,
 1532        stop_at_indent: true,
 1533    };
 1534
 1535    let delete_to_beg = DeleteToBeginningOfLine {
 1536        stop_at_indent: false,
 1537    };
 1538
 1539    let move_to_end = MoveToEndOfLine {
 1540        stop_at_soft_wraps: true,
 1541    };
 1542
 1543    let editor = cx.add_window(|window, cx| {
 1544        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1545        build_editor(buffer, window, cx)
 1546    });
 1547    _ = editor.update(cx, |editor, window, cx| {
 1548        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1549            s.select_display_ranges([
 1550                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1551                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1552            ]);
 1553        });
 1554    });
 1555
 1556    _ = editor.update(cx, |editor, window, cx| {
 1557        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1558        assert_eq!(
 1559            editor.selections.display_ranges(cx),
 1560            &[
 1561                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1562                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1563            ]
 1564        );
 1565    });
 1566
 1567    _ = editor.update(cx, |editor, window, cx| {
 1568        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1569        assert_eq!(
 1570            editor.selections.display_ranges(cx),
 1571            &[
 1572                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1573                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1574            ]
 1575        );
 1576    });
 1577
 1578    _ = editor.update(cx, |editor, window, cx| {
 1579        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1580        assert_eq!(
 1581            editor.selections.display_ranges(cx),
 1582            &[
 1583                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1584                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1585            ]
 1586        );
 1587    });
 1588
 1589    _ = editor.update(cx, |editor, window, cx| {
 1590        editor.move_to_end_of_line(&move_to_end, window, cx);
 1591        assert_eq!(
 1592            editor.selections.display_ranges(cx),
 1593            &[
 1594                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1595                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1596            ]
 1597        );
 1598    });
 1599
 1600    // Moving to the end of line again is a no-op.
 1601    _ = editor.update(cx, |editor, window, cx| {
 1602        editor.move_to_end_of_line(&move_to_end, window, cx);
 1603        assert_eq!(
 1604            editor.selections.display_ranges(cx),
 1605            &[
 1606                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1607                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1608            ]
 1609        );
 1610    });
 1611
 1612    _ = editor.update(cx, |editor, window, cx| {
 1613        editor.move_left(&MoveLeft, window, cx);
 1614        editor.select_to_beginning_of_line(
 1615            &SelectToBeginningOfLine {
 1616                stop_at_soft_wraps: true,
 1617                stop_at_indent: true,
 1618            },
 1619            window,
 1620            cx,
 1621        );
 1622        assert_eq!(
 1623            editor.selections.display_ranges(cx),
 1624            &[
 1625                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1626                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1627            ]
 1628        );
 1629    });
 1630
 1631    _ = editor.update(cx, |editor, window, cx| {
 1632        editor.select_to_beginning_of_line(
 1633            &SelectToBeginningOfLine {
 1634                stop_at_soft_wraps: true,
 1635                stop_at_indent: true,
 1636            },
 1637            window,
 1638            cx,
 1639        );
 1640        assert_eq!(
 1641            editor.selections.display_ranges(cx),
 1642            &[
 1643                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1644                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1645            ]
 1646        );
 1647    });
 1648
 1649    _ = editor.update(cx, |editor, window, cx| {
 1650        editor.select_to_beginning_of_line(
 1651            &SelectToBeginningOfLine {
 1652                stop_at_soft_wraps: true,
 1653                stop_at_indent: true,
 1654            },
 1655            window,
 1656            cx,
 1657        );
 1658        assert_eq!(
 1659            editor.selections.display_ranges(cx),
 1660            &[
 1661                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1662                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1663            ]
 1664        );
 1665    });
 1666
 1667    _ = editor.update(cx, |editor, window, cx| {
 1668        editor.select_to_end_of_line(
 1669            &SelectToEndOfLine {
 1670                stop_at_soft_wraps: true,
 1671            },
 1672            window,
 1673            cx,
 1674        );
 1675        assert_eq!(
 1676            editor.selections.display_ranges(cx),
 1677            &[
 1678                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1679                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1680            ]
 1681        );
 1682    });
 1683
 1684    _ = editor.update(cx, |editor, window, cx| {
 1685        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1686        assert_eq!(editor.display_text(cx), "ab\n  de");
 1687        assert_eq!(
 1688            editor.selections.display_ranges(cx),
 1689            &[
 1690                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1691                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1692            ]
 1693        );
 1694    });
 1695
 1696    _ = editor.update(cx, |editor, window, cx| {
 1697        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1698        assert_eq!(editor.display_text(cx), "\n");
 1699        assert_eq!(
 1700            editor.selections.display_ranges(cx),
 1701            &[
 1702                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1703                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1704            ]
 1705        );
 1706    });
 1707}
 1708
 1709#[gpui::test]
 1710fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1711    init_test(cx, |_| {});
 1712    let move_to_beg = MoveToBeginningOfLine {
 1713        stop_at_soft_wraps: false,
 1714        stop_at_indent: false,
 1715    };
 1716
 1717    let move_to_end = MoveToEndOfLine {
 1718        stop_at_soft_wraps: false,
 1719    };
 1720
 1721    let editor = cx.add_window(|window, cx| {
 1722        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1723        build_editor(buffer, window, cx)
 1724    });
 1725
 1726    _ = editor.update(cx, |editor, window, cx| {
 1727        editor.set_wrap_width(Some(140.0.into()), cx);
 1728
 1729        // We expect the following lines after wrapping
 1730        // ```
 1731        // thequickbrownfox
 1732        // jumpedoverthelazydo
 1733        // gs
 1734        // ```
 1735        // The final `gs` was soft-wrapped onto a new line.
 1736        assert_eq!(
 1737            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1738            editor.display_text(cx),
 1739        );
 1740
 1741        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1742        // Start the cursor at the `k` on the first line
 1743        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1744            s.select_display_ranges([
 1745                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1746            ]);
 1747        });
 1748
 1749        // Moving to the beginning of the line should put us at the beginning of the line.
 1750        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1751        assert_eq!(
 1752            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1753            editor.selections.display_ranges(cx)
 1754        );
 1755
 1756        // Moving to the end of the line should put us at the end of the line.
 1757        editor.move_to_end_of_line(&move_to_end, window, cx);
 1758        assert_eq!(
 1759            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1760            editor.selections.display_ranges(cx)
 1761        );
 1762
 1763        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1764        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1765        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1766            s.select_display_ranges([
 1767                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1768            ]);
 1769        });
 1770
 1771        // Moving to the beginning of the line should put us at the start of the second line of
 1772        // display text, i.e., the `j`.
 1773        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1774        assert_eq!(
 1775            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1776            editor.selections.display_ranges(cx)
 1777        );
 1778
 1779        // Moving to the beginning of the line again should be a no-op.
 1780        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1781        assert_eq!(
 1782            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1783            editor.selections.display_ranges(cx)
 1784        );
 1785
 1786        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1787        // next display line.
 1788        editor.move_to_end_of_line(&move_to_end, window, cx);
 1789        assert_eq!(
 1790            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1791            editor.selections.display_ranges(cx)
 1792        );
 1793
 1794        // Moving to the end of the line again should be a no-op.
 1795        editor.move_to_end_of_line(&move_to_end, window, cx);
 1796        assert_eq!(
 1797            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1798            editor.selections.display_ranges(cx)
 1799        );
 1800    });
 1801}
 1802
 1803#[gpui::test]
 1804fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1805    init_test(cx, |_| {});
 1806
 1807    let move_to_beg = MoveToBeginningOfLine {
 1808        stop_at_soft_wraps: true,
 1809        stop_at_indent: true,
 1810    };
 1811
 1812    let select_to_beg = SelectToBeginningOfLine {
 1813        stop_at_soft_wraps: true,
 1814        stop_at_indent: true,
 1815    };
 1816
 1817    let delete_to_beg = DeleteToBeginningOfLine {
 1818        stop_at_indent: true,
 1819    };
 1820
 1821    let move_to_end = MoveToEndOfLine {
 1822        stop_at_soft_wraps: false,
 1823    };
 1824
 1825    let editor = cx.add_window(|window, cx| {
 1826        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1827        build_editor(buffer, window, cx)
 1828    });
 1829
 1830    _ = editor.update(cx, |editor, window, cx| {
 1831        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1832            s.select_display_ranges([
 1833                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1834                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1835            ]);
 1836        });
 1837
 1838        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 1839        // and the second cursor at the first non-whitespace character in the line.
 1840        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1841        assert_eq!(
 1842            editor.selections.display_ranges(cx),
 1843            &[
 1844                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1845                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1846            ]
 1847        );
 1848
 1849        // Moving to the beginning of the line again should be a no-op for the first cursor,
 1850        // and should move the second cursor to the beginning of the line.
 1851        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1852        assert_eq!(
 1853            editor.selections.display_ranges(cx),
 1854            &[
 1855                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1856                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1857            ]
 1858        );
 1859
 1860        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 1861        // and should move the second cursor back to the first non-whitespace character in the line.
 1862        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1863        assert_eq!(
 1864            editor.selections.display_ranges(cx),
 1865            &[
 1866                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1867                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1868            ]
 1869        );
 1870
 1871        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 1872        // and to the first non-whitespace character in the line for the second cursor.
 1873        editor.move_to_end_of_line(&move_to_end, window, cx);
 1874        editor.move_left(&MoveLeft, window, cx);
 1875        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1876        assert_eq!(
 1877            editor.selections.display_ranges(cx),
 1878            &[
 1879                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1880                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1881            ]
 1882        );
 1883
 1884        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 1885        // and should select to the beginning of the line for the second cursor.
 1886        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1887        assert_eq!(
 1888            editor.selections.display_ranges(cx),
 1889            &[
 1890                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1891                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1892            ]
 1893        );
 1894
 1895        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 1896        // and should delete to the first non-whitespace character in the line for the second cursor.
 1897        editor.move_to_end_of_line(&move_to_end, window, cx);
 1898        editor.move_left(&MoveLeft, window, cx);
 1899        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1900        assert_eq!(editor.text(cx), "c\n  f");
 1901    });
 1902}
 1903
 1904#[gpui::test]
 1905fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 1906    init_test(cx, |_| {});
 1907
 1908    let editor = cx.add_window(|window, cx| {
 1909        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 1910        build_editor(buffer, window, cx)
 1911    });
 1912    _ = editor.update(cx, |editor, window, cx| {
 1913        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1914            s.select_display_ranges([
 1915                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 1916                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 1917            ])
 1918        });
 1919        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1920        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 1921
 1922        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1923        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 1924
 1925        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1926        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1927
 1928        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1929        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1930
 1931        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1932        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 1933
 1934        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1935        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1936
 1937        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1938        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 1939
 1940        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1941        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1942
 1943        editor.move_right(&MoveRight, window, cx);
 1944        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1945        assert_selection_ranges(
 1946            "use std::«ˇs»tr::{foo, bar}\n«ˇ\n»  {baz.qux()}",
 1947            editor,
 1948            cx,
 1949        );
 1950
 1951        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1952        assert_selection_ranges(
 1953            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 1954            editor,
 1955            cx,
 1956        );
 1957
 1958        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 1959        assert_selection_ranges(
 1960            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 1961            editor,
 1962            cx,
 1963        );
 1964    });
 1965}
 1966
 1967#[gpui::test]
 1968fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 1969    init_test(cx, |_| {});
 1970
 1971    let editor = cx.add_window(|window, cx| {
 1972        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 1973        build_editor(buffer, window, cx)
 1974    });
 1975
 1976    _ = editor.update(cx, |editor, window, cx| {
 1977        editor.set_wrap_width(Some(140.0.into()), cx);
 1978        assert_eq!(
 1979            editor.display_text(cx),
 1980            "use one::{\n    two::three::\n    four::five\n};"
 1981        );
 1982
 1983        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1984            s.select_display_ranges([
 1985                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 1986            ]);
 1987        });
 1988
 1989        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1990        assert_eq!(
 1991            editor.selections.display_ranges(cx),
 1992            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 1993        );
 1994
 1995        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1996        assert_eq!(
 1997            editor.selections.display_ranges(cx),
 1998            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 1999        );
 2000
 2001        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2002        assert_eq!(
 2003            editor.selections.display_ranges(cx),
 2004            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2005        );
 2006
 2007        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2008        assert_eq!(
 2009            editor.selections.display_ranges(cx),
 2010            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2011        );
 2012
 2013        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2014        assert_eq!(
 2015            editor.selections.display_ranges(cx),
 2016            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2017        );
 2018
 2019        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2020        assert_eq!(
 2021            editor.selections.display_ranges(cx),
 2022            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2023        );
 2024    });
 2025}
 2026
 2027#[gpui::test]
 2028async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2029    init_test(cx, |_| {});
 2030    let mut cx = EditorTestContext::new(cx).await;
 2031
 2032    let line_height = cx.editor(|editor, window, _| {
 2033        editor
 2034            .style()
 2035            .unwrap()
 2036            .text
 2037            .line_height_in_pixels(window.rem_size())
 2038    });
 2039    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2040
 2041    cx.set_state(
 2042        &r#"ˇone
 2043        two
 2044
 2045        three
 2046        fourˇ
 2047        five
 2048
 2049        six"#
 2050            .unindent(),
 2051    );
 2052
 2053    cx.update_editor(|editor, window, cx| {
 2054        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2055    });
 2056    cx.assert_editor_state(
 2057        &r#"one
 2058        two
 2059        ˇ
 2060        three
 2061        four
 2062        five
 2063        ˇ
 2064        six"#
 2065            .unindent(),
 2066    );
 2067
 2068    cx.update_editor(|editor, window, cx| {
 2069        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2070    });
 2071    cx.assert_editor_state(
 2072        &r#"one
 2073        two
 2074
 2075        three
 2076        four
 2077        five
 2078        ˇ
 2079        sixˇ"#
 2080            .unindent(),
 2081    );
 2082
 2083    cx.update_editor(|editor, window, cx| {
 2084        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2085    });
 2086    cx.assert_editor_state(
 2087        &r#"one
 2088        two
 2089
 2090        three
 2091        four
 2092        five
 2093
 2094        sixˇ"#
 2095            .unindent(),
 2096    );
 2097
 2098    cx.update_editor(|editor, window, cx| {
 2099        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2100    });
 2101    cx.assert_editor_state(
 2102        &r#"one
 2103        two
 2104
 2105        three
 2106        four
 2107        five
 2108        ˇ
 2109        six"#
 2110            .unindent(),
 2111    );
 2112
 2113    cx.update_editor(|editor, window, cx| {
 2114        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2115    });
 2116    cx.assert_editor_state(
 2117        &r#"one
 2118        two
 2119        ˇ
 2120        three
 2121        four
 2122        five
 2123
 2124        six"#
 2125            .unindent(),
 2126    );
 2127
 2128    cx.update_editor(|editor, window, cx| {
 2129        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2130    });
 2131    cx.assert_editor_state(
 2132        &r#"ˇone
 2133        two
 2134
 2135        three
 2136        four
 2137        five
 2138
 2139        six"#
 2140            .unindent(),
 2141    );
 2142}
 2143
 2144#[gpui::test]
 2145async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2146    init_test(cx, |_| {});
 2147    let mut cx = EditorTestContext::new(cx).await;
 2148    let line_height = cx.editor(|editor, window, _| {
 2149        editor
 2150            .style()
 2151            .unwrap()
 2152            .text
 2153            .line_height_in_pixels(window.rem_size())
 2154    });
 2155    let window = cx.window;
 2156    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2157
 2158    cx.set_state(
 2159        r#"ˇone
 2160        two
 2161        three
 2162        four
 2163        five
 2164        six
 2165        seven
 2166        eight
 2167        nine
 2168        ten
 2169        "#,
 2170    );
 2171
 2172    cx.update_editor(|editor, window, cx| {
 2173        assert_eq!(
 2174            editor.snapshot(window, cx).scroll_position(),
 2175            gpui::Point::new(0., 0.)
 2176        );
 2177        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2178        assert_eq!(
 2179            editor.snapshot(window, cx).scroll_position(),
 2180            gpui::Point::new(0., 3.)
 2181        );
 2182        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2183        assert_eq!(
 2184            editor.snapshot(window, cx).scroll_position(),
 2185            gpui::Point::new(0., 6.)
 2186        );
 2187        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2188        assert_eq!(
 2189            editor.snapshot(window, cx).scroll_position(),
 2190            gpui::Point::new(0., 3.)
 2191        );
 2192
 2193        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2194        assert_eq!(
 2195            editor.snapshot(window, cx).scroll_position(),
 2196            gpui::Point::new(0., 1.)
 2197        );
 2198        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2199        assert_eq!(
 2200            editor.snapshot(window, cx).scroll_position(),
 2201            gpui::Point::new(0., 3.)
 2202        );
 2203    });
 2204}
 2205
 2206#[gpui::test]
 2207async fn test_autoscroll(cx: &mut TestAppContext) {
 2208    init_test(cx, |_| {});
 2209    let mut cx = EditorTestContext::new(cx).await;
 2210
 2211    let line_height = cx.update_editor(|editor, window, cx| {
 2212        editor.set_vertical_scroll_margin(2, cx);
 2213        editor
 2214            .style()
 2215            .unwrap()
 2216            .text
 2217            .line_height_in_pixels(window.rem_size())
 2218    });
 2219    let window = cx.window;
 2220    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2221
 2222    cx.set_state(
 2223        r#"ˇone
 2224            two
 2225            three
 2226            four
 2227            five
 2228            six
 2229            seven
 2230            eight
 2231            nine
 2232            ten
 2233        "#,
 2234    );
 2235    cx.update_editor(|editor, window, cx| {
 2236        assert_eq!(
 2237            editor.snapshot(window, cx).scroll_position(),
 2238            gpui::Point::new(0., 0.0)
 2239        );
 2240    });
 2241
 2242    // Add a cursor below the visible area. Since both cursors cannot fit
 2243    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2244    // allows the vertical scroll margin below that cursor.
 2245    cx.update_editor(|editor, window, cx| {
 2246        editor.change_selections(Default::default(), window, cx, |selections| {
 2247            selections.select_ranges([
 2248                Point::new(0, 0)..Point::new(0, 0),
 2249                Point::new(6, 0)..Point::new(6, 0),
 2250            ]);
 2251        })
 2252    });
 2253    cx.update_editor(|editor, window, cx| {
 2254        assert_eq!(
 2255            editor.snapshot(window, cx).scroll_position(),
 2256            gpui::Point::new(0., 3.0)
 2257        );
 2258    });
 2259
 2260    // Move down. The editor cursor scrolls down to track the newest cursor.
 2261    cx.update_editor(|editor, window, cx| {
 2262        editor.move_down(&Default::default(), window, cx);
 2263    });
 2264    cx.update_editor(|editor, window, cx| {
 2265        assert_eq!(
 2266            editor.snapshot(window, cx).scroll_position(),
 2267            gpui::Point::new(0., 4.0)
 2268        );
 2269    });
 2270
 2271    // Add a cursor above the visible area. Since both cursors fit on screen,
 2272    // the editor scrolls to show both.
 2273    cx.update_editor(|editor, window, cx| {
 2274        editor.change_selections(Default::default(), window, cx, |selections| {
 2275            selections.select_ranges([
 2276                Point::new(1, 0)..Point::new(1, 0),
 2277                Point::new(6, 0)..Point::new(6, 0),
 2278            ]);
 2279        })
 2280    });
 2281    cx.update_editor(|editor, window, cx| {
 2282        assert_eq!(
 2283            editor.snapshot(window, cx).scroll_position(),
 2284            gpui::Point::new(0., 1.0)
 2285        );
 2286    });
 2287}
 2288
 2289#[gpui::test]
 2290async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2291    init_test(cx, |_| {});
 2292    let mut cx = EditorTestContext::new(cx).await;
 2293
 2294    let line_height = cx.editor(|editor, window, _cx| {
 2295        editor
 2296            .style()
 2297            .unwrap()
 2298            .text
 2299            .line_height_in_pixels(window.rem_size())
 2300    });
 2301    let window = cx.window;
 2302    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2303    cx.set_state(
 2304        &r#"
 2305        ˇone
 2306        two
 2307        threeˇ
 2308        four
 2309        five
 2310        six
 2311        seven
 2312        eight
 2313        nine
 2314        ten
 2315        "#
 2316        .unindent(),
 2317    );
 2318
 2319    cx.update_editor(|editor, window, cx| {
 2320        editor.move_page_down(&MovePageDown::default(), window, cx)
 2321    });
 2322    cx.assert_editor_state(
 2323        &r#"
 2324        one
 2325        two
 2326        three
 2327        ˇfour
 2328        five
 2329        sixˇ
 2330        seven
 2331        eight
 2332        nine
 2333        ten
 2334        "#
 2335        .unindent(),
 2336    );
 2337
 2338    cx.update_editor(|editor, window, cx| {
 2339        editor.move_page_down(&MovePageDown::default(), window, cx)
 2340    });
 2341    cx.assert_editor_state(
 2342        &r#"
 2343        one
 2344        two
 2345        three
 2346        four
 2347        five
 2348        six
 2349        ˇseven
 2350        eight
 2351        nineˇ
 2352        ten
 2353        "#
 2354        .unindent(),
 2355    );
 2356
 2357    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2358    cx.assert_editor_state(
 2359        &r#"
 2360        one
 2361        two
 2362        three
 2363        ˇfour
 2364        five
 2365        sixˇ
 2366        seven
 2367        eight
 2368        nine
 2369        ten
 2370        "#
 2371        .unindent(),
 2372    );
 2373
 2374    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2375    cx.assert_editor_state(
 2376        &r#"
 2377        ˇone
 2378        two
 2379        threeˇ
 2380        four
 2381        five
 2382        six
 2383        seven
 2384        eight
 2385        nine
 2386        ten
 2387        "#
 2388        .unindent(),
 2389    );
 2390
 2391    // Test select collapsing
 2392    cx.update_editor(|editor, window, cx| {
 2393        editor.move_page_down(&MovePageDown::default(), window, cx);
 2394        editor.move_page_down(&MovePageDown::default(), window, cx);
 2395        editor.move_page_down(&MovePageDown::default(), window, cx);
 2396    });
 2397    cx.assert_editor_state(
 2398        &r#"
 2399        one
 2400        two
 2401        three
 2402        four
 2403        five
 2404        six
 2405        seven
 2406        eight
 2407        nine
 2408        ˇten
 2409        ˇ"#
 2410        .unindent(),
 2411    );
 2412}
 2413
 2414#[gpui::test]
 2415async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2416    init_test(cx, |_| {});
 2417    let mut cx = EditorTestContext::new(cx).await;
 2418    cx.set_state("one «two threeˇ» four");
 2419    cx.update_editor(|editor, window, cx| {
 2420        editor.delete_to_beginning_of_line(
 2421            &DeleteToBeginningOfLine {
 2422                stop_at_indent: false,
 2423            },
 2424            window,
 2425            cx,
 2426        );
 2427        assert_eq!(editor.text(cx), " four");
 2428    });
 2429}
 2430
 2431#[gpui::test]
 2432fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2433    init_test(cx, |_| {});
 2434
 2435    let editor = cx.add_window(|window, cx| {
 2436        let buffer = MultiBuffer::build_simple("one two three four", cx);
 2437        build_editor(buffer.clone(), window, cx)
 2438    });
 2439
 2440    _ = editor.update(cx, |editor, window, cx| {
 2441        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2442            s.select_display_ranges([
 2443                // an empty selection - the preceding word fragment is deleted
 2444                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2445                // characters selected - they are deleted
 2446                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
 2447            ])
 2448        });
 2449        editor.delete_to_previous_word_start(
 2450            &DeleteToPreviousWordStart {
 2451                ignore_newlines: false,
 2452            },
 2453            window,
 2454            cx,
 2455        );
 2456        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e two te four");
 2457    });
 2458
 2459    _ = editor.update(cx, |editor, window, cx| {
 2460        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2461            s.select_display_ranges([
 2462                // an empty selection - the following word fragment is deleted
 2463                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 2464                // characters selected - they are deleted
 2465                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
 2466            ])
 2467        });
 2468        editor.delete_to_next_word_end(
 2469            &DeleteToNextWordEnd {
 2470                ignore_newlines: false,
 2471            },
 2472            window,
 2473            cx,
 2474        );
 2475        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e t te our");
 2476    });
 2477}
 2478
 2479#[gpui::test]
 2480fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2481    init_test(cx, |_| {});
 2482
 2483    let editor = cx.add_window(|window, cx| {
 2484        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 2485        build_editor(buffer.clone(), window, cx)
 2486    });
 2487    let del_to_prev_word_start = DeleteToPreviousWordStart {
 2488        ignore_newlines: false,
 2489    };
 2490    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 2491        ignore_newlines: true,
 2492    };
 2493
 2494    _ = editor.update(cx, |editor, window, cx| {
 2495        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2496            s.select_display_ranges([
 2497                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 2498            ])
 2499        });
 2500        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2501        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 2502        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2503        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 2504        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2505        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 2506        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2507        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 2508        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2509        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 2510        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2511        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2512    });
 2513}
 2514
 2515#[gpui::test]
 2516fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 2517    init_test(cx, |_| {});
 2518
 2519    let editor = cx.add_window(|window, cx| {
 2520        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 2521        build_editor(buffer.clone(), window, cx)
 2522    });
 2523    let del_to_next_word_end = DeleteToNextWordEnd {
 2524        ignore_newlines: false,
 2525    };
 2526    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 2527        ignore_newlines: true,
 2528    };
 2529
 2530    _ = editor.update(cx, |editor, window, cx| {
 2531        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2532            s.select_display_ranges([
 2533                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 2534            ])
 2535        });
 2536        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2537        assert_eq!(
 2538            editor.buffer.read(cx).read(cx).text(),
 2539            "one\n   two\nthree\n   four"
 2540        );
 2541        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2542        assert_eq!(
 2543            editor.buffer.read(cx).read(cx).text(),
 2544            "\n   two\nthree\n   four"
 2545        );
 2546        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2547        assert_eq!(
 2548            editor.buffer.read(cx).read(cx).text(),
 2549            "two\nthree\n   four"
 2550        );
 2551        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2552        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 2553        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2554        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 2555        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2556        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2557    });
 2558}
 2559
 2560#[gpui::test]
 2561fn test_newline(cx: &mut TestAppContext) {
 2562    init_test(cx, |_| {});
 2563
 2564    let editor = cx.add_window(|window, cx| {
 2565        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 2566        build_editor(buffer.clone(), window, cx)
 2567    });
 2568
 2569    _ = editor.update(cx, |editor, window, cx| {
 2570        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2571            s.select_display_ranges([
 2572                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2573                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2574                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 2575            ])
 2576        });
 2577
 2578        editor.newline(&Newline, window, cx);
 2579        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 2580    });
 2581}
 2582
 2583#[gpui::test]
 2584fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 2585    init_test(cx, |_| {});
 2586
 2587    let editor = cx.add_window(|window, cx| {
 2588        let buffer = MultiBuffer::build_simple(
 2589            "
 2590                a
 2591                b(
 2592                    X
 2593                )
 2594                c(
 2595                    X
 2596                )
 2597            "
 2598            .unindent()
 2599            .as_str(),
 2600            cx,
 2601        );
 2602        let mut editor = build_editor(buffer.clone(), window, cx);
 2603        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2604            s.select_ranges([
 2605                Point::new(2, 4)..Point::new(2, 5),
 2606                Point::new(5, 4)..Point::new(5, 5),
 2607            ])
 2608        });
 2609        editor
 2610    });
 2611
 2612    _ = editor.update(cx, |editor, window, cx| {
 2613        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 2614        editor.buffer.update(cx, |buffer, cx| {
 2615            buffer.edit(
 2616                [
 2617                    (Point::new(1, 2)..Point::new(3, 0), ""),
 2618                    (Point::new(4, 2)..Point::new(6, 0), ""),
 2619                ],
 2620                None,
 2621                cx,
 2622            );
 2623            assert_eq!(
 2624                buffer.read(cx).text(),
 2625                "
 2626                    a
 2627                    b()
 2628                    c()
 2629                "
 2630                .unindent()
 2631            );
 2632        });
 2633        assert_eq!(
 2634            editor.selections.ranges(cx),
 2635            &[
 2636                Point::new(1, 2)..Point::new(1, 2),
 2637                Point::new(2, 2)..Point::new(2, 2),
 2638            ],
 2639        );
 2640
 2641        editor.newline(&Newline, window, cx);
 2642        assert_eq!(
 2643            editor.text(cx),
 2644            "
 2645                a
 2646                b(
 2647                )
 2648                c(
 2649                )
 2650            "
 2651            .unindent()
 2652        );
 2653
 2654        // The selections are moved after the inserted newlines
 2655        assert_eq!(
 2656            editor.selections.ranges(cx),
 2657            &[
 2658                Point::new(2, 0)..Point::new(2, 0),
 2659                Point::new(4, 0)..Point::new(4, 0),
 2660            ],
 2661        );
 2662    });
 2663}
 2664
 2665#[gpui::test]
 2666async fn test_newline_above(cx: &mut TestAppContext) {
 2667    init_test(cx, |settings| {
 2668        settings.defaults.tab_size = NonZeroU32::new(4)
 2669    });
 2670
 2671    let language = Arc::new(
 2672        Language::new(
 2673            LanguageConfig::default(),
 2674            Some(tree_sitter_rust::LANGUAGE.into()),
 2675        )
 2676        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2677        .unwrap(),
 2678    );
 2679
 2680    let mut cx = EditorTestContext::new(cx).await;
 2681    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2682    cx.set_state(indoc! {"
 2683        const a: ˇA = (
 2684 2685                «const_functionˇ»(ˇ),
 2686                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2687 2688        ˇ);ˇ
 2689    "});
 2690
 2691    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 2692    cx.assert_editor_state(indoc! {"
 2693        ˇ
 2694        const a: A = (
 2695            ˇ
 2696            (
 2697                ˇ
 2698                ˇ
 2699                const_function(),
 2700                ˇ
 2701                ˇ
 2702                ˇ
 2703                ˇ
 2704                something_else,
 2705                ˇ
 2706            )
 2707            ˇ
 2708            ˇ
 2709        );
 2710    "});
 2711}
 2712
 2713#[gpui::test]
 2714async fn test_newline_below(cx: &mut TestAppContext) {
 2715    init_test(cx, |settings| {
 2716        settings.defaults.tab_size = NonZeroU32::new(4)
 2717    });
 2718
 2719    let language = Arc::new(
 2720        Language::new(
 2721            LanguageConfig::default(),
 2722            Some(tree_sitter_rust::LANGUAGE.into()),
 2723        )
 2724        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2725        .unwrap(),
 2726    );
 2727
 2728    let mut cx = EditorTestContext::new(cx).await;
 2729    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2730    cx.set_state(indoc! {"
 2731        const a: ˇA = (
 2732 2733                «const_functionˇ»(ˇ),
 2734                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2735 2736        ˇ);ˇ
 2737    "});
 2738
 2739    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 2740    cx.assert_editor_state(indoc! {"
 2741        const a: A = (
 2742            ˇ
 2743            (
 2744                ˇ
 2745                const_function(),
 2746                ˇ
 2747                ˇ
 2748                something_else,
 2749                ˇ
 2750                ˇ
 2751                ˇ
 2752                ˇ
 2753            )
 2754            ˇ
 2755        );
 2756        ˇ
 2757        ˇ
 2758    "});
 2759}
 2760
 2761#[gpui::test]
 2762async fn test_newline_comments(cx: &mut TestAppContext) {
 2763    init_test(cx, |settings| {
 2764        settings.defaults.tab_size = NonZeroU32::new(4)
 2765    });
 2766
 2767    let language = Arc::new(Language::new(
 2768        LanguageConfig {
 2769            line_comments: vec!["// ".into()],
 2770            ..LanguageConfig::default()
 2771        },
 2772        None,
 2773    ));
 2774    {
 2775        let mut cx = EditorTestContext::new(cx).await;
 2776        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2777        cx.set_state(indoc! {"
 2778        // Fooˇ
 2779    "});
 2780
 2781        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2782        cx.assert_editor_state(indoc! {"
 2783        // Foo
 2784        // ˇ
 2785    "});
 2786        // Ensure that we add comment prefix when existing line contains space
 2787        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2788        cx.assert_editor_state(
 2789            indoc! {"
 2790        // Foo
 2791        //s
 2792        // ˇ
 2793    "}
 2794            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2795            .as_str(),
 2796        );
 2797        // Ensure that we add comment prefix when existing line does not contain space
 2798        cx.set_state(indoc! {"
 2799        // Foo
 2800        //ˇ
 2801    "});
 2802        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2803        cx.assert_editor_state(indoc! {"
 2804        // Foo
 2805        //
 2806        // ˇ
 2807    "});
 2808        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 2809        cx.set_state(indoc! {"
 2810        ˇ// Foo
 2811    "});
 2812        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2813        cx.assert_editor_state(indoc! {"
 2814
 2815        ˇ// Foo
 2816    "});
 2817    }
 2818    // Ensure that comment continuations can be disabled.
 2819    update_test_language_settings(cx, |settings| {
 2820        settings.defaults.extend_comment_on_newline = Some(false);
 2821    });
 2822    let mut cx = EditorTestContext::new(cx).await;
 2823    cx.set_state(indoc! {"
 2824        // Fooˇ
 2825    "});
 2826    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2827    cx.assert_editor_state(indoc! {"
 2828        // Foo
 2829        ˇ
 2830    "});
 2831}
 2832
 2833#[gpui::test]
 2834async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 2835    init_test(cx, |settings| {
 2836        settings.defaults.tab_size = NonZeroU32::new(4)
 2837    });
 2838
 2839    let language = Arc::new(Language::new(
 2840        LanguageConfig {
 2841            line_comments: vec!["// ".into(), "/// ".into()],
 2842            ..LanguageConfig::default()
 2843        },
 2844        None,
 2845    ));
 2846    {
 2847        let mut cx = EditorTestContext::new(cx).await;
 2848        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2849        cx.set_state(indoc! {"
 2850        //ˇ
 2851    "});
 2852        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2853        cx.assert_editor_state(indoc! {"
 2854        //
 2855        // ˇ
 2856    "});
 2857
 2858        cx.set_state(indoc! {"
 2859        ///ˇ
 2860    "});
 2861        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2862        cx.assert_editor_state(indoc! {"
 2863        ///
 2864        /// ˇ
 2865    "});
 2866    }
 2867}
 2868
 2869#[gpui::test]
 2870async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 2871    init_test(cx, |settings| {
 2872        settings.defaults.tab_size = NonZeroU32::new(4)
 2873    });
 2874
 2875    let language = Arc::new(
 2876        Language::new(
 2877            LanguageConfig {
 2878                documentation: Some(language::DocumentationConfig {
 2879                    start: "/**".into(),
 2880                    end: "*/".into(),
 2881                    prefix: "* ".into(),
 2882                    tab_size: NonZeroU32::new(1).unwrap(),
 2883                }),
 2884
 2885                ..LanguageConfig::default()
 2886            },
 2887            Some(tree_sitter_rust::LANGUAGE.into()),
 2888        )
 2889        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 2890        .unwrap(),
 2891    );
 2892
 2893    {
 2894        let mut cx = EditorTestContext::new(cx).await;
 2895        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2896        cx.set_state(indoc! {"
 2897        /**ˇ
 2898    "});
 2899
 2900        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2901        cx.assert_editor_state(indoc! {"
 2902        /**
 2903         * ˇ
 2904    "});
 2905        // Ensure that if cursor is before the comment start,
 2906        // we do not actually insert a comment prefix.
 2907        cx.set_state(indoc! {"
 2908        ˇ/**
 2909    "});
 2910        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2911        cx.assert_editor_state(indoc! {"
 2912
 2913        ˇ/**
 2914    "});
 2915        // Ensure that if cursor is between it doesn't add comment prefix.
 2916        cx.set_state(indoc! {"
 2917        /*ˇ*
 2918    "});
 2919        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2920        cx.assert_editor_state(indoc! {"
 2921        /*
 2922        ˇ*
 2923    "});
 2924        // Ensure that if suffix exists on same line after cursor it adds new line.
 2925        cx.set_state(indoc! {"
 2926        /**ˇ*/
 2927    "});
 2928        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2929        cx.assert_editor_state(indoc! {"
 2930        /**
 2931         * ˇ
 2932         */
 2933    "});
 2934        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2935        cx.set_state(indoc! {"
 2936        /**ˇ */
 2937    "});
 2938        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2939        cx.assert_editor_state(indoc! {"
 2940        /**
 2941         * ˇ
 2942         */
 2943    "});
 2944        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2945        cx.set_state(indoc! {"
 2946        /** ˇ*/
 2947    "});
 2948        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2949        cx.assert_editor_state(
 2950            indoc! {"
 2951        /**s
 2952         * ˇ
 2953         */
 2954    "}
 2955            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2956            .as_str(),
 2957        );
 2958        // Ensure that delimiter space is preserved when newline on already
 2959        // spaced delimiter.
 2960        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2961        cx.assert_editor_state(
 2962            indoc! {"
 2963        /**s
 2964         *s
 2965         * ˇ
 2966         */
 2967    "}
 2968            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2969            .as_str(),
 2970        );
 2971        // Ensure that delimiter space is preserved when space is not
 2972        // on existing delimiter.
 2973        cx.set_state(indoc! {"
 2974        /**
 2975 2976         */
 2977    "});
 2978        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2979        cx.assert_editor_state(indoc! {"
 2980        /**
 2981         *
 2982         * ˇ
 2983         */
 2984    "});
 2985        // Ensure that if suffix exists on same line after cursor it
 2986        // doesn't add extra new line if prefix is not on same line.
 2987        cx.set_state(indoc! {"
 2988        /**
 2989        ˇ*/
 2990    "});
 2991        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2992        cx.assert_editor_state(indoc! {"
 2993        /**
 2994
 2995        ˇ*/
 2996    "});
 2997        // Ensure that it detects suffix after existing prefix.
 2998        cx.set_state(indoc! {"
 2999        /**ˇ/
 3000    "});
 3001        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3002        cx.assert_editor_state(indoc! {"
 3003        /**
 3004        ˇ/
 3005    "});
 3006        // Ensure that if suffix exists on same line before
 3007        // cursor it does not add comment prefix.
 3008        cx.set_state(indoc! {"
 3009        /** */ˇ
 3010    "});
 3011        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3012        cx.assert_editor_state(indoc! {"
 3013        /** */
 3014        ˇ
 3015    "});
 3016        // Ensure that if suffix exists on same line before
 3017        // cursor it does not add comment prefix.
 3018        cx.set_state(indoc! {"
 3019        /**
 3020         *
 3021         */ˇ
 3022    "});
 3023        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3024        cx.assert_editor_state(indoc! {"
 3025        /**
 3026         *
 3027         */
 3028         ˇ
 3029    "});
 3030
 3031        // Ensure that inline comment followed by code
 3032        // doesn't add comment prefix on newline
 3033        cx.set_state(indoc! {"
 3034        /** */ textˇ
 3035    "});
 3036        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3037        cx.assert_editor_state(indoc! {"
 3038        /** */ text
 3039        ˇ
 3040    "});
 3041
 3042        // Ensure that text after comment end tag
 3043        // doesn't add comment prefix on newline
 3044        cx.set_state(indoc! {"
 3045        /**
 3046         *
 3047         */ˇtext
 3048    "});
 3049        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3050        cx.assert_editor_state(indoc! {"
 3051        /**
 3052         *
 3053         */
 3054         ˇtext
 3055    "});
 3056
 3057        // Ensure if not comment block it doesn't
 3058        // add comment prefix on newline
 3059        cx.set_state(indoc! {"
 3060        * textˇ
 3061    "});
 3062        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3063        cx.assert_editor_state(indoc! {"
 3064        * text
 3065        ˇ
 3066    "});
 3067    }
 3068    // Ensure that comment continuations can be disabled.
 3069    update_test_language_settings(cx, |settings| {
 3070        settings.defaults.extend_comment_on_newline = Some(false);
 3071    });
 3072    let mut cx = EditorTestContext::new(cx).await;
 3073    cx.set_state(indoc! {"
 3074        /**ˇ
 3075    "});
 3076    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3077    cx.assert_editor_state(indoc! {"
 3078        /**
 3079        ˇ
 3080    "});
 3081}
 3082
 3083#[gpui::test]
 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(("--[[".into(), "]]".into())),
 3093            ..LanguageConfig::default()
 3094        },
 3095        None,
 3096    ));
 3097
 3098    let mut cx = EditorTestContext::new(cx).await;
 3099    cx.update_buffer(|buffer, cx| buffer.set_language(Some(lua_language), cx));
 3100
 3101    // Line with line comment should extend
 3102    cx.set_state(indoc! {"
 3103        --ˇ
 3104    "});
 3105    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3106    cx.assert_editor_state(indoc! {"
 3107        --
 3108        --ˇ
 3109    "});
 3110
 3111    // Line with block comment that matches line comment should not extend
 3112    cx.set_state(indoc! {"
 3113        --[[ˇ
 3114    "});
 3115    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3116    cx.assert_editor_state(indoc! {"
 3117        --[[
 3118        ˇ
 3119    "});
 3120}
 3121
 3122#[gpui::test]
 3123fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3124    init_test(cx, |_| {});
 3125
 3126    let editor = cx.add_window(|window, cx| {
 3127        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3128        let mut editor = build_editor(buffer.clone(), window, cx);
 3129        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3130            s.select_ranges([3..4, 11..12, 19..20])
 3131        });
 3132        editor
 3133    });
 3134
 3135    _ = editor.update(cx, |editor, window, cx| {
 3136        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3137        editor.buffer.update(cx, |buffer, cx| {
 3138            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 3139            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3140        });
 3141        assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
 3142
 3143        editor.insert("Z", window, cx);
 3144        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3145
 3146        // The selections are moved after the inserted characters
 3147        assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
 3148    });
 3149}
 3150
 3151#[gpui::test]
 3152async fn test_tab(cx: &mut TestAppContext) {
 3153    init_test(cx, |settings| {
 3154        settings.defaults.tab_size = NonZeroU32::new(3)
 3155    });
 3156
 3157    let mut cx = EditorTestContext::new(cx).await;
 3158    cx.set_state(indoc! {"
 3159        ˇabˇc
 3160        ˇ🏀ˇ🏀ˇefg
 3161 3162    "});
 3163    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3164    cx.assert_editor_state(indoc! {"
 3165           ˇab ˇc
 3166           ˇ🏀  ˇ🏀  ˇefg
 3167        d  ˇ
 3168    "});
 3169
 3170    cx.set_state(indoc! {"
 3171        a
 3172        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3173    "});
 3174    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3175    cx.assert_editor_state(indoc! {"
 3176        a
 3177           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3178    "});
 3179}
 3180
 3181#[gpui::test]
 3182async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3183    init_test(cx, |_| {});
 3184
 3185    let mut cx = EditorTestContext::new(cx).await;
 3186    let language = Arc::new(
 3187        Language::new(
 3188            LanguageConfig::default(),
 3189            Some(tree_sitter_rust::LANGUAGE.into()),
 3190        )
 3191        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3192        .unwrap(),
 3193    );
 3194    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3195
 3196    // test when all cursors are not at suggested indent
 3197    // then simply move to their suggested indent location
 3198    cx.set_state(indoc! {"
 3199        const a: B = (
 3200            c(
 3201        ˇ
 3202        ˇ    )
 3203        );
 3204    "});
 3205    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3206    cx.assert_editor_state(indoc! {"
 3207        const a: B = (
 3208            c(
 3209                ˇ
 3210            ˇ)
 3211        );
 3212    "});
 3213
 3214    // test cursor already at suggested indent not moving when
 3215    // other cursors are yet to reach their suggested indents
 3216    cx.set_state(indoc! {"
 3217        ˇ
 3218        const a: B = (
 3219            c(
 3220                d(
 3221        ˇ
 3222                )
 3223        ˇ
 3224        ˇ    )
 3225        );
 3226    "});
 3227    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3228    cx.assert_editor_state(indoc! {"
 3229        ˇ
 3230        const a: B = (
 3231            c(
 3232                d(
 3233                    ˇ
 3234                )
 3235                ˇ
 3236            ˇ)
 3237        );
 3238    "});
 3239    // test when all cursors are at suggested indent then tab is inserted
 3240    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3241    cx.assert_editor_state(indoc! {"
 3242            ˇ
 3243        const a: B = (
 3244            c(
 3245                d(
 3246                        ˇ
 3247                )
 3248                    ˇ
 3249                ˇ)
 3250        );
 3251    "});
 3252
 3253    // test when current indent is less than suggested indent,
 3254    // we adjust line to match suggested indent and move cursor to it
 3255    //
 3256    // when no other cursor is at word boundary, all of them should move
 3257    cx.set_state(indoc! {"
 3258        const a: B = (
 3259            c(
 3260                d(
 3261        ˇ
 3262        ˇ   )
 3263        ˇ   )
 3264        );
 3265    "});
 3266    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3267    cx.assert_editor_state(indoc! {"
 3268        const a: B = (
 3269            c(
 3270                d(
 3271                    ˇ
 3272                ˇ)
 3273            ˇ)
 3274        );
 3275    "});
 3276
 3277    // test when current indent is less than suggested indent,
 3278    // we adjust line to match suggested indent and move cursor to it
 3279    //
 3280    // when some other cursor is at word boundary, it should not move
 3281    cx.set_state(indoc! {"
 3282        const a: B = (
 3283            c(
 3284                d(
 3285        ˇ
 3286        ˇ   )
 3287           ˇ)
 3288        );
 3289    "});
 3290    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3291    cx.assert_editor_state(indoc! {"
 3292        const a: B = (
 3293            c(
 3294                d(
 3295                    ˇ
 3296                ˇ)
 3297            ˇ)
 3298        );
 3299    "});
 3300
 3301    // test when current indent is more than suggested indent,
 3302    // we just move cursor to current indent instead of suggested indent
 3303    //
 3304    // when no other cursor is at word boundary, all of them should move
 3305    cx.set_state(indoc! {"
 3306        const a: B = (
 3307            c(
 3308                d(
 3309        ˇ
 3310        ˇ                )
 3311        ˇ   )
 3312        );
 3313    "});
 3314    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3315    cx.assert_editor_state(indoc! {"
 3316        const a: B = (
 3317            c(
 3318                d(
 3319                    ˇ
 3320                        ˇ)
 3321            ˇ)
 3322        );
 3323    "});
 3324    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3325    cx.assert_editor_state(indoc! {"
 3326        const a: B = (
 3327            c(
 3328                d(
 3329                        ˇ
 3330                            ˇ)
 3331                ˇ)
 3332        );
 3333    "});
 3334
 3335    // test when current indent is more than suggested indent,
 3336    // we just move cursor to current indent instead of suggested indent
 3337    //
 3338    // when some other cursor is at word boundary, it doesn't move
 3339    cx.set_state(indoc! {"
 3340        const a: B = (
 3341            c(
 3342                d(
 3343        ˇ
 3344        ˇ                )
 3345            ˇ)
 3346        );
 3347    "});
 3348    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3349    cx.assert_editor_state(indoc! {"
 3350        const a: B = (
 3351            c(
 3352                d(
 3353                    ˇ
 3354                        ˇ)
 3355            ˇ)
 3356        );
 3357    "});
 3358
 3359    // handle auto-indent when there are multiple cursors on the same line
 3360    cx.set_state(indoc! {"
 3361        const a: B = (
 3362            c(
 3363        ˇ    ˇ
 3364        ˇ    )
 3365        );
 3366    "});
 3367    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3368    cx.assert_editor_state(indoc! {"
 3369        const a: B = (
 3370            c(
 3371                ˇ
 3372            ˇ)
 3373        );
 3374    "});
 3375}
 3376
 3377#[gpui::test]
 3378async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 3379    init_test(cx, |settings| {
 3380        settings.defaults.tab_size = NonZeroU32::new(3)
 3381    });
 3382
 3383    let mut cx = EditorTestContext::new(cx).await;
 3384    cx.set_state(indoc! {"
 3385         ˇ
 3386        \t ˇ
 3387        \t  ˇ
 3388        \t   ˇ
 3389         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 3390    "});
 3391
 3392    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3393    cx.assert_editor_state(indoc! {"
 3394           ˇ
 3395        \t   ˇ
 3396        \t   ˇ
 3397        \t      ˇ
 3398         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 3399    "});
 3400}
 3401
 3402#[gpui::test]
 3403async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 3404    init_test(cx, |settings| {
 3405        settings.defaults.tab_size = NonZeroU32::new(4)
 3406    });
 3407
 3408    let language = Arc::new(
 3409        Language::new(
 3410            LanguageConfig::default(),
 3411            Some(tree_sitter_rust::LANGUAGE.into()),
 3412        )
 3413        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 3414        .unwrap(),
 3415    );
 3416
 3417    let mut cx = EditorTestContext::new(cx).await;
 3418    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3419    cx.set_state(indoc! {"
 3420        fn a() {
 3421            if b {
 3422        \t ˇc
 3423            }
 3424        }
 3425    "});
 3426
 3427    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3428    cx.assert_editor_state(indoc! {"
 3429        fn a() {
 3430            if b {
 3431                ˇc
 3432            }
 3433        }
 3434    "});
 3435}
 3436
 3437#[gpui::test]
 3438async fn test_indent_outdent(cx: &mut TestAppContext) {
 3439    init_test(cx, |settings| {
 3440        settings.defaults.tab_size = NonZeroU32::new(4);
 3441    });
 3442
 3443    let mut cx = EditorTestContext::new(cx).await;
 3444
 3445    cx.set_state(indoc! {"
 3446          «oneˇ» «twoˇ»
 3447        three
 3448         four
 3449    "});
 3450    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3451    cx.assert_editor_state(indoc! {"
 3452            «oneˇ» «twoˇ»
 3453        three
 3454         four
 3455    "});
 3456
 3457    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3458    cx.assert_editor_state(indoc! {"
 3459        «oneˇ» «twoˇ»
 3460        three
 3461         four
 3462    "});
 3463
 3464    // select across line ending
 3465    cx.set_state(indoc! {"
 3466        one two
 3467        t«hree
 3468        ˇ» four
 3469    "});
 3470    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3471    cx.assert_editor_state(indoc! {"
 3472        one two
 3473            t«hree
 3474        ˇ» four
 3475    "});
 3476
 3477    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3478    cx.assert_editor_state(indoc! {"
 3479        one two
 3480        t«hree
 3481        ˇ» four
 3482    "});
 3483
 3484    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3485    cx.set_state(indoc! {"
 3486        one two
 3487        ˇthree
 3488            four
 3489    "});
 3490    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3491    cx.assert_editor_state(indoc! {"
 3492        one two
 3493            ˇthree
 3494            four
 3495    "});
 3496
 3497    cx.set_state(indoc! {"
 3498        one two
 3499        ˇ    three
 3500            four
 3501    "});
 3502    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3503    cx.assert_editor_state(indoc! {"
 3504        one two
 3505        ˇthree
 3506            four
 3507    "});
 3508}
 3509
 3510#[gpui::test]
 3511async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3512    // This is a regression test for issue #33761
 3513    init_test(cx, |_| {});
 3514
 3515    let mut cx = EditorTestContext::new(cx).await;
 3516    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3517    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3518
 3519    cx.set_state(
 3520        r#"ˇ#     ingress:
 3521ˇ#         api:
 3522ˇ#             enabled: false
 3523ˇ#             pathType: Prefix
 3524ˇ#           console:
 3525ˇ#               enabled: false
 3526ˇ#               pathType: Prefix
 3527"#,
 3528    );
 3529
 3530    // Press tab to indent all lines
 3531    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3532
 3533    cx.assert_editor_state(
 3534        r#"    ˇ#     ingress:
 3535    ˇ#         api:
 3536    ˇ#             enabled: false
 3537    ˇ#             pathType: Prefix
 3538    ˇ#           console:
 3539    ˇ#               enabled: false
 3540    ˇ#               pathType: Prefix
 3541"#,
 3542    );
 3543}
 3544
 3545#[gpui::test]
 3546async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3547    // This is a test to make sure our fix for issue #33761 didn't break anything
 3548    init_test(cx, |_| {});
 3549
 3550    let mut cx = EditorTestContext::new(cx).await;
 3551    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3552    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3553
 3554    cx.set_state(
 3555        r#"ˇingress:
 3556ˇ  api:
 3557ˇ    enabled: false
 3558ˇ    pathType: Prefix
 3559"#,
 3560    );
 3561
 3562    // Press tab to indent all lines
 3563    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3564
 3565    cx.assert_editor_state(
 3566        r#"ˇingress:
 3567    ˇapi:
 3568        ˇenabled: false
 3569        ˇpathType: Prefix
 3570"#,
 3571    );
 3572}
 3573
 3574#[gpui::test]
 3575async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 3576    init_test(cx, |settings| {
 3577        settings.defaults.hard_tabs = Some(true);
 3578    });
 3579
 3580    let mut cx = EditorTestContext::new(cx).await;
 3581
 3582    // select two ranges on one line
 3583    cx.set_state(indoc! {"
 3584        «oneˇ» «twoˇ»
 3585        three
 3586        four
 3587    "});
 3588    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3589    cx.assert_editor_state(indoc! {"
 3590        \t«oneˇ» «twoˇ»
 3591        three
 3592        four
 3593    "});
 3594    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3595    cx.assert_editor_state(indoc! {"
 3596        \t\t«oneˇ» «twoˇ»
 3597        three
 3598        four
 3599    "});
 3600    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3601    cx.assert_editor_state(indoc! {"
 3602        \t«oneˇ» «twoˇ»
 3603        three
 3604        four
 3605    "});
 3606    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3607    cx.assert_editor_state(indoc! {"
 3608        «oneˇ» «twoˇ»
 3609        three
 3610        four
 3611    "});
 3612
 3613    // select across a line ending
 3614    cx.set_state(indoc! {"
 3615        one two
 3616        t«hree
 3617        ˇ»four
 3618    "});
 3619    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3620    cx.assert_editor_state(indoc! {"
 3621        one two
 3622        \tt«hree
 3623        ˇ»four
 3624    "});
 3625    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3626    cx.assert_editor_state(indoc! {"
 3627        one two
 3628        \t\tt«hree
 3629        ˇ»four
 3630    "});
 3631    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3632    cx.assert_editor_state(indoc! {"
 3633        one two
 3634        \tt«hree
 3635        ˇ»four
 3636    "});
 3637    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3638    cx.assert_editor_state(indoc! {"
 3639        one two
 3640        t«hree
 3641        ˇ»four
 3642    "});
 3643
 3644    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3645    cx.set_state(indoc! {"
 3646        one two
 3647        ˇthree
 3648        four
 3649    "});
 3650    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3651    cx.assert_editor_state(indoc! {"
 3652        one two
 3653        ˇthree
 3654        four
 3655    "});
 3656    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3657    cx.assert_editor_state(indoc! {"
 3658        one two
 3659        \tˇthree
 3660        four
 3661    "});
 3662    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3663    cx.assert_editor_state(indoc! {"
 3664        one two
 3665        ˇthree
 3666        four
 3667    "});
 3668}
 3669
 3670#[gpui::test]
 3671fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 3672    init_test(cx, |settings| {
 3673        settings.languages.0.extend([
 3674            (
 3675                "TOML".into(),
 3676                LanguageSettingsContent {
 3677                    tab_size: NonZeroU32::new(2),
 3678                    ..Default::default()
 3679                },
 3680            ),
 3681            (
 3682                "Rust".into(),
 3683                LanguageSettingsContent {
 3684                    tab_size: NonZeroU32::new(4),
 3685                    ..Default::default()
 3686                },
 3687            ),
 3688        ]);
 3689    });
 3690
 3691    let toml_language = Arc::new(Language::new(
 3692        LanguageConfig {
 3693            name: "TOML".into(),
 3694            ..Default::default()
 3695        },
 3696        None,
 3697    ));
 3698    let rust_language = Arc::new(Language::new(
 3699        LanguageConfig {
 3700            name: "Rust".into(),
 3701            ..Default::default()
 3702        },
 3703        None,
 3704    ));
 3705
 3706    let toml_buffer =
 3707        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 3708    let rust_buffer =
 3709        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 3710    let multibuffer = cx.new(|cx| {
 3711        let mut multibuffer = MultiBuffer::new(ReadWrite);
 3712        multibuffer.push_excerpts(
 3713            toml_buffer.clone(),
 3714            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 3715            cx,
 3716        );
 3717        multibuffer.push_excerpts(
 3718            rust_buffer.clone(),
 3719            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 3720            cx,
 3721        );
 3722        multibuffer
 3723    });
 3724
 3725    cx.add_window(|window, cx| {
 3726        let mut editor = build_editor(multibuffer, window, cx);
 3727
 3728        assert_eq!(
 3729            editor.text(cx),
 3730            indoc! {"
 3731                a = 1
 3732                b = 2
 3733
 3734                const c: usize = 3;
 3735            "}
 3736        );
 3737
 3738        select_ranges(
 3739            &mut editor,
 3740            indoc! {"
 3741                «aˇ» = 1
 3742                b = 2
 3743
 3744                «const c:ˇ» usize = 3;
 3745            "},
 3746            window,
 3747            cx,
 3748        );
 3749
 3750        editor.tab(&Tab, window, cx);
 3751        assert_text_with_selections(
 3752            &mut editor,
 3753            indoc! {"
 3754                  «aˇ» = 1
 3755                b = 2
 3756
 3757                    «const c:ˇ» usize = 3;
 3758            "},
 3759            cx,
 3760        );
 3761        editor.backtab(&Backtab, window, cx);
 3762        assert_text_with_selections(
 3763            &mut editor,
 3764            indoc! {"
 3765                «aˇ» = 1
 3766                b = 2
 3767
 3768                «const c:ˇ» usize = 3;
 3769            "},
 3770            cx,
 3771        );
 3772
 3773        editor
 3774    });
 3775}
 3776
 3777#[gpui::test]
 3778async fn test_backspace(cx: &mut TestAppContext) {
 3779    init_test(cx, |_| {});
 3780
 3781    let mut cx = EditorTestContext::new(cx).await;
 3782
 3783    // Basic backspace
 3784    cx.set_state(indoc! {"
 3785        onˇe two three
 3786        fou«rˇ» five six
 3787        seven «ˇeight nine
 3788        »ten
 3789    "});
 3790    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3791    cx.assert_editor_state(indoc! {"
 3792        oˇe two three
 3793        fouˇ five six
 3794        seven ˇten
 3795    "});
 3796
 3797    // Test backspace inside and around indents
 3798    cx.set_state(indoc! {"
 3799        zero
 3800            ˇone
 3801                ˇtwo
 3802            ˇ ˇ ˇ  three
 3803        ˇ  ˇ  four
 3804    "});
 3805    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3806    cx.assert_editor_state(indoc! {"
 3807        zero
 3808        ˇone
 3809            ˇtwo
 3810        ˇ  threeˇ  four
 3811    "});
 3812}
 3813
 3814#[gpui::test]
 3815async fn test_delete(cx: &mut TestAppContext) {
 3816    init_test(cx, |_| {});
 3817
 3818    let mut cx = EditorTestContext::new(cx).await;
 3819    cx.set_state(indoc! {"
 3820        onˇe two three
 3821        fou«rˇ» five six
 3822        seven «ˇeight nine
 3823        »ten
 3824    "});
 3825    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 3826    cx.assert_editor_state(indoc! {"
 3827        onˇ two three
 3828        fouˇ five six
 3829        seven ˇten
 3830    "});
 3831}
 3832
 3833#[gpui::test]
 3834fn test_delete_line(cx: &mut TestAppContext) {
 3835    init_test(cx, |_| {});
 3836
 3837    let editor = cx.add_window(|window, cx| {
 3838        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3839        build_editor(buffer, window, cx)
 3840    });
 3841    _ = editor.update(cx, |editor, window, cx| {
 3842        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3843            s.select_display_ranges([
 3844                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 3845                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 3846                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 3847            ])
 3848        });
 3849        editor.delete_line(&DeleteLine, window, cx);
 3850        assert_eq!(editor.display_text(cx), "ghi");
 3851        assert_eq!(
 3852            editor.selections.display_ranges(cx),
 3853            vec![
 3854                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 3855                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
 3856            ]
 3857        );
 3858    });
 3859
 3860    let editor = cx.add_window(|window, cx| {
 3861        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3862        build_editor(buffer, window, cx)
 3863    });
 3864    _ = editor.update(cx, |editor, window, cx| {
 3865        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3866            s.select_display_ranges([
 3867                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 3868            ])
 3869        });
 3870        editor.delete_line(&DeleteLine, window, cx);
 3871        assert_eq!(editor.display_text(cx), "ghi\n");
 3872        assert_eq!(
 3873            editor.selections.display_ranges(cx),
 3874            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 3875        );
 3876    });
 3877}
 3878
 3879#[gpui::test]
 3880fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 3881    init_test(cx, |_| {});
 3882
 3883    cx.add_window(|window, cx| {
 3884        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3885        let mut editor = build_editor(buffer.clone(), window, cx);
 3886        let buffer = buffer.read(cx).as_singleton().unwrap();
 3887
 3888        assert_eq!(
 3889            editor.selections.ranges::<Point>(cx),
 3890            &[Point::new(0, 0)..Point::new(0, 0)]
 3891        );
 3892
 3893        // When on single line, replace newline at end by space
 3894        editor.join_lines(&JoinLines, window, cx);
 3895        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3896        assert_eq!(
 3897            editor.selections.ranges::<Point>(cx),
 3898            &[Point::new(0, 3)..Point::new(0, 3)]
 3899        );
 3900
 3901        // When multiple lines are selected, remove newlines that are spanned by the selection
 3902        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3903            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 3904        });
 3905        editor.join_lines(&JoinLines, window, cx);
 3906        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 3907        assert_eq!(
 3908            editor.selections.ranges::<Point>(cx),
 3909            &[Point::new(0, 11)..Point::new(0, 11)]
 3910        );
 3911
 3912        // Undo should be transactional
 3913        editor.undo(&Undo, window, cx);
 3914        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3915        assert_eq!(
 3916            editor.selections.ranges::<Point>(cx),
 3917            &[Point::new(0, 5)..Point::new(2, 2)]
 3918        );
 3919
 3920        // When joining an empty line don't insert a space
 3921        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3922            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 3923        });
 3924        editor.join_lines(&JoinLines, window, cx);
 3925        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 3926        assert_eq!(
 3927            editor.selections.ranges::<Point>(cx),
 3928            [Point::new(2, 3)..Point::new(2, 3)]
 3929        );
 3930
 3931        // We can remove trailing newlines
 3932        editor.join_lines(&JoinLines, window, cx);
 3933        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3934        assert_eq!(
 3935            editor.selections.ranges::<Point>(cx),
 3936            [Point::new(2, 3)..Point::new(2, 3)]
 3937        );
 3938
 3939        // We don't blow up on the last line
 3940        editor.join_lines(&JoinLines, window, cx);
 3941        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3942        assert_eq!(
 3943            editor.selections.ranges::<Point>(cx),
 3944            [Point::new(2, 3)..Point::new(2, 3)]
 3945        );
 3946
 3947        // reset to test indentation
 3948        editor.buffer.update(cx, |buffer, cx| {
 3949            buffer.edit(
 3950                [
 3951                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 3952                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 3953                ],
 3954                None,
 3955                cx,
 3956            )
 3957        });
 3958
 3959        // We remove any leading spaces
 3960        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 3961        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3962            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 3963        });
 3964        editor.join_lines(&JoinLines, window, cx);
 3965        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 3966
 3967        // We don't insert a space for a line containing only spaces
 3968        editor.join_lines(&JoinLines, window, cx);
 3969        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 3970
 3971        // We ignore any leading tabs
 3972        editor.join_lines(&JoinLines, window, cx);
 3973        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 3974
 3975        editor
 3976    });
 3977}
 3978
 3979#[gpui::test]
 3980fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 3981    init_test(cx, |_| {});
 3982
 3983    cx.add_window(|window, cx| {
 3984        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3985        let mut editor = build_editor(buffer.clone(), window, cx);
 3986        let buffer = buffer.read(cx).as_singleton().unwrap();
 3987
 3988        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3989            s.select_ranges([
 3990                Point::new(0, 2)..Point::new(1, 1),
 3991                Point::new(1, 2)..Point::new(1, 2),
 3992                Point::new(3, 1)..Point::new(3, 2),
 3993            ])
 3994        });
 3995
 3996        editor.join_lines(&JoinLines, window, cx);
 3997        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 3998
 3999        assert_eq!(
 4000            editor.selections.ranges::<Point>(cx),
 4001            [
 4002                Point::new(0, 7)..Point::new(0, 7),
 4003                Point::new(1, 3)..Point::new(1, 3)
 4004            ]
 4005        );
 4006        editor
 4007    });
 4008}
 4009
 4010#[gpui::test]
 4011async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 4012    init_test(cx, |_| {});
 4013
 4014    let mut cx = EditorTestContext::new(cx).await;
 4015
 4016    let diff_base = r#"
 4017        Line 0
 4018        Line 1
 4019        Line 2
 4020        Line 3
 4021        "#
 4022    .unindent();
 4023
 4024    cx.set_state(
 4025        &r#"
 4026        ˇLine 0
 4027        Line 1
 4028        Line 2
 4029        Line 3
 4030        "#
 4031        .unindent(),
 4032    );
 4033
 4034    cx.set_head_text(&diff_base);
 4035    executor.run_until_parked();
 4036
 4037    // Join lines
 4038    cx.update_editor(|editor, window, cx| {
 4039        editor.join_lines(&JoinLines, window, cx);
 4040    });
 4041    executor.run_until_parked();
 4042
 4043    cx.assert_editor_state(
 4044        &r#"
 4045        Line 0ˇ Line 1
 4046        Line 2
 4047        Line 3
 4048        "#
 4049        .unindent(),
 4050    );
 4051    // Join again
 4052    cx.update_editor(|editor, window, cx| {
 4053        editor.join_lines(&JoinLines, window, cx);
 4054    });
 4055    executor.run_until_parked();
 4056
 4057    cx.assert_editor_state(
 4058        &r#"
 4059        Line 0 Line 1ˇ Line 2
 4060        Line 3
 4061        "#
 4062        .unindent(),
 4063    );
 4064}
 4065
 4066#[gpui::test]
 4067async fn test_custom_newlines_cause_no_false_positive_diffs(
 4068    executor: BackgroundExecutor,
 4069    cx: &mut TestAppContext,
 4070) {
 4071    init_test(cx, |_| {});
 4072    let mut cx = EditorTestContext::new(cx).await;
 4073    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 4074    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 4075    executor.run_until_parked();
 4076
 4077    cx.update_editor(|editor, window, cx| {
 4078        let snapshot = editor.snapshot(window, cx);
 4079        assert_eq!(
 4080            snapshot
 4081                .buffer_snapshot
 4082                .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
 4083                .collect::<Vec<_>>(),
 4084            Vec::new(),
 4085            "Should not have any diffs for files with custom newlines"
 4086        );
 4087    });
 4088}
 4089
 4090#[gpui::test]
 4091async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 4092    init_test(cx, |_| {});
 4093
 4094    let mut cx = EditorTestContext::new(cx).await;
 4095
 4096    // Test sort_lines_case_insensitive()
 4097    cx.set_state(indoc! {"
 4098        «z
 4099        y
 4100        x
 4101        Z
 4102        Y
 4103        Xˇ»
 4104    "});
 4105    cx.update_editor(|e, window, cx| {
 4106        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4107    });
 4108    cx.assert_editor_state(indoc! {"
 4109        «x
 4110        X
 4111        y
 4112        Y
 4113        z
 4114        Zˇ»
 4115    "});
 4116
 4117    // Test sort_lines_by_length()
 4118    //
 4119    // Demonstrates:
 4120    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 4121    // - sort is stable
 4122    cx.set_state(indoc! {"
 4123        «123
 4124        æ
 4125        12
 4126 4127        1
 4128        æˇ»
 4129    "});
 4130    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 4131    cx.assert_editor_state(indoc! {"
 4132        «æ
 4133 4134        1
 4135        æ
 4136        12
 4137        123ˇ»
 4138    "});
 4139
 4140    // Test reverse_lines()
 4141    cx.set_state(indoc! {"
 4142        «5
 4143        4
 4144        3
 4145        2
 4146        1ˇ»
 4147    "});
 4148    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4149    cx.assert_editor_state(indoc! {"
 4150        «1
 4151        2
 4152        3
 4153        4
 4154        5ˇ»
 4155    "});
 4156
 4157    // Skip testing shuffle_line()
 4158
 4159    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4160    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4161
 4162    // Don't manipulate when cursor is on single line, but expand the selection
 4163    cx.set_state(indoc! {"
 4164        ddˇdd
 4165        ccc
 4166        bb
 4167        a
 4168    "});
 4169    cx.update_editor(|e, window, cx| {
 4170        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4171    });
 4172    cx.assert_editor_state(indoc! {"
 4173        «ddddˇ»
 4174        ccc
 4175        bb
 4176        a
 4177    "});
 4178
 4179    // Basic manipulate case
 4180    // Start selection moves to column 0
 4181    // End of selection shrinks to fit shorter line
 4182    cx.set_state(indoc! {"
 4183        dd«d
 4184        ccc
 4185        bb
 4186        aaaaaˇ»
 4187    "});
 4188    cx.update_editor(|e, window, cx| {
 4189        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4190    });
 4191    cx.assert_editor_state(indoc! {"
 4192        «aaaaa
 4193        bb
 4194        ccc
 4195        dddˇ»
 4196    "});
 4197
 4198    // Manipulate case with newlines
 4199    cx.set_state(indoc! {"
 4200        dd«d
 4201        ccc
 4202
 4203        bb
 4204        aaaaa
 4205
 4206        ˇ»
 4207    "});
 4208    cx.update_editor(|e, window, cx| {
 4209        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4210    });
 4211    cx.assert_editor_state(indoc! {"
 4212        «
 4213
 4214        aaaaa
 4215        bb
 4216        ccc
 4217        dddˇ»
 4218
 4219    "});
 4220
 4221    // Adding new line
 4222    cx.set_state(indoc! {"
 4223        aa«a
 4224        bbˇ»b
 4225    "});
 4226    cx.update_editor(|e, window, cx| {
 4227        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4228    });
 4229    cx.assert_editor_state(indoc! {"
 4230        «aaa
 4231        bbb
 4232        added_lineˇ»
 4233    "});
 4234
 4235    // Removing line
 4236    cx.set_state(indoc! {"
 4237        aa«a
 4238        bbbˇ»
 4239    "});
 4240    cx.update_editor(|e, window, cx| {
 4241        e.manipulate_immutable_lines(window, cx, |lines| {
 4242            lines.pop();
 4243        })
 4244    });
 4245    cx.assert_editor_state(indoc! {"
 4246        «aaaˇ»
 4247    "});
 4248
 4249    // Removing all lines
 4250    cx.set_state(indoc! {"
 4251        aa«a
 4252        bbbˇ»
 4253    "});
 4254    cx.update_editor(|e, window, cx| {
 4255        e.manipulate_immutable_lines(window, cx, |lines| {
 4256            lines.drain(..);
 4257        })
 4258    });
 4259    cx.assert_editor_state(indoc! {"
 4260        ˇ
 4261    "});
 4262}
 4263
 4264#[gpui::test]
 4265async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4266    init_test(cx, |_| {});
 4267
 4268    let mut cx = EditorTestContext::new(cx).await;
 4269
 4270    // Consider continuous selection as single selection
 4271    cx.set_state(indoc! {"
 4272        Aaa«aa
 4273        cˇ»c«c
 4274        bb
 4275        aaaˇ»aa
 4276    "});
 4277    cx.update_editor(|e, window, cx| {
 4278        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4279    });
 4280    cx.assert_editor_state(indoc! {"
 4281        «Aaaaa
 4282        ccc
 4283        bb
 4284        aaaaaˇ»
 4285    "});
 4286
 4287    cx.set_state(indoc! {"
 4288        Aaa«aa
 4289        cˇ»c«c
 4290        bb
 4291        aaaˇ»aa
 4292    "});
 4293    cx.update_editor(|e, window, cx| {
 4294        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4295    });
 4296    cx.assert_editor_state(indoc! {"
 4297        «Aaaaa
 4298        ccc
 4299        bbˇ»
 4300    "});
 4301
 4302    // Consider non continuous selection as distinct dedup operations
 4303    cx.set_state(indoc! {"
 4304        «aaaaa
 4305        bb
 4306        aaaaa
 4307        aaaaaˇ»
 4308
 4309        aaa«aaˇ»
 4310    "});
 4311    cx.update_editor(|e, window, cx| {
 4312        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4313    });
 4314    cx.assert_editor_state(indoc! {"
 4315        «aaaaa
 4316        bbˇ»
 4317
 4318        «aaaaaˇ»
 4319    "});
 4320}
 4321
 4322#[gpui::test]
 4323async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4324    init_test(cx, |_| {});
 4325
 4326    let mut cx = EditorTestContext::new(cx).await;
 4327
 4328    cx.set_state(indoc! {"
 4329        «Aaa
 4330        aAa
 4331        Aaaˇ»
 4332    "});
 4333    cx.update_editor(|e, window, cx| {
 4334        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4335    });
 4336    cx.assert_editor_state(indoc! {"
 4337        «Aaa
 4338        aAaˇ»
 4339    "});
 4340
 4341    cx.set_state(indoc! {"
 4342        «Aaa
 4343        aAa
 4344        aaAˇ»
 4345    "});
 4346    cx.update_editor(|e, window, cx| {
 4347        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4348    });
 4349    cx.assert_editor_state(indoc! {"
 4350        «Aaaˇ»
 4351    "});
 4352}
 4353
 4354#[gpui::test]
 4355async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 4356    init_test(cx, |_| {});
 4357
 4358    let mut cx = EditorTestContext::new(cx).await;
 4359
 4360    // Manipulate with multiple selections on a single line
 4361    cx.set_state(indoc! {"
 4362        dd«dd
 4363        cˇ»c«c
 4364        bb
 4365        aaaˇ»aa
 4366    "});
 4367    cx.update_editor(|e, window, cx| {
 4368        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4369    });
 4370    cx.assert_editor_state(indoc! {"
 4371        «aaaaa
 4372        bb
 4373        ccc
 4374        ddddˇ»
 4375    "});
 4376
 4377    // Manipulate with multiple disjoin selections
 4378    cx.set_state(indoc! {"
 4379 4380        4
 4381        3
 4382        2
 4383        1ˇ»
 4384
 4385        dd«dd
 4386        ccc
 4387        bb
 4388        aaaˇ»aa
 4389    "});
 4390    cx.update_editor(|e, window, cx| {
 4391        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4392    });
 4393    cx.assert_editor_state(indoc! {"
 4394        «1
 4395        2
 4396        3
 4397        4
 4398        5ˇ»
 4399
 4400        «aaaaa
 4401        bb
 4402        ccc
 4403        ddddˇ»
 4404    "});
 4405
 4406    // Adding lines on each selection
 4407    cx.set_state(indoc! {"
 4408 4409        1ˇ»
 4410
 4411        bb«bb
 4412        aaaˇ»aa
 4413    "});
 4414    cx.update_editor(|e, window, cx| {
 4415        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 4416    });
 4417    cx.assert_editor_state(indoc! {"
 4418        «2
 4419        1
 4420        added lineˇ»
 4421
 4422        «bbbb
 4423        aaaaa
 4424        added lineˇ»
 4425    "});
 4426
 4427    // Removing lines on each selection
 4428    cx.set_state(indoc! {"
 4429 4430        1ˇ»
 4431
 4432        bb«bb
 4433        aaaˇ»aa
 4434    "});
 4435    cx.update_editor(|e, window, cx| {
 4436        e.manipulate_immutable_lines(window, cx, |lines| {
 4437            lines.pop();
 4438        })
 4439    });
 4440    cx.assert_editor_state(indoc! {"
 4441        «2ˇ»
 4442
 4443        «bbbbˇ»
 4444    "});
 4445}
 4446
 4447#[gpui::test]
 4448async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 4449    init_test(cx, |settings| {
 4450        settings.defaults.tab_size = NonZeroU32::new(3)
 4451    });
 4452
 4453    let mut cx = EditorTestContext::new(cx).await;
 4454
 4455    // MULTI SELECTION
 4456    // Ln.1 "«" tests empty lines
 4457    // Ln.9 tests just leading whitespace
 4458    cx.set_state(indoc! {"
 4459        «
 4460        abc                 // No indentationˇ»
 4461        «\tabc              // 1 tabˇ»
 4462        \t\tabc «      ˇ»   // 2 tabs
 4463        \t ab«c             // Tab followed by space
 4464         \tabc              // Space followed by tab (3 spaces should be the result)
 4465        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4466           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 4467        \t
 4468        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4469    "});
 4470    cx.update_editor(|e, window, cx| {
 4471        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4472    });
 4473    cx.assert_editor_state(
 4474        indoc! {"
 4475            «
 4476            abc                 // No indentation
 4477               abc              // 1 tab
 4478                  abc          // 2 tabs
 4479                abc             // Tab followed by space
 4480               abc              // Space followed by tab (3 spaces should be the result)
 4481                           abc   // Mixed indentation (tab conversion depends on the column)
 4482               abc         // Already space indented
 4483               ·
 4484               abc\tdef          // Only the leading tab is manipulatedˇ»
 4485        "}
 4486        .replace("·", "")
 4487        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4488    );
 4489
 4490    // Test on just a few lines, the others should remain unchanged
 4491    // Only lines (3, 5, 10, 11) should change
 4492    cx.set_state(
 4493        indoc! {"
 4494            ·
 4495            abc                 // No indentation
 4496            \tabcˇ               // 1 tab
 4497            \t\tabc             // 2 tabs
 4498            \t abcˇ              // Tab followed by space
 4499             \tabc              // Space followed by tab (3 spaces should be the result)
 4500            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4501               abc              // Already space indented
 4502            «\t
 4503            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4504        "}
 4505        .replace("·", "")
 4506        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4507    );
 4508    cx.update_editor(|e, window, cx| {
 4509        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4510    });
 4511    cx.assert_editor_state(
 4512        indoc! {"
 4513            ·
 4514            abc                 // No indentation
 4515            «   abc               // 1 tabˇ»
 4516            \t\tabc             // 2 tabs
 4517            «    abc              // Tab followed by spaceˇ»
 4518             \tabc              // Space followed by tab (3 spaces should be the result)
 4519            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4520               abc              // Already space indented
 4521            «   ·
 4522               abc\tdef          // Only the leading tab is manipulatedˇ»
 4523        "}
 4524        .replace("·", "")
 4525        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4526    );
 4527
 4528    // SINGLE SELECTION
 4529    // Ln.1 "«" tests empty lines
 4530    // Ln.9 tests just leading whitespace
 4531    cx.set_state(indoc! {"
 4532        «
 4533        abc                 // No indentation
 4534        \tabc               // 1 tab
 4535        \t\tabc             // 2 tabs
 4536        \t abc              // Tab followed by space
 4537         \tabc              // Space followed by tab (3 spaces should be the result)
 4538        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4539           abc              // Already space indented
 4540        \t
 4541        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4542    "});
 4543    cx.update_editor(|e, window, cx| {
 4544        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4545    });
 4546    cx.assert_editor_state(
 4547        indoc! {"
 4548            «
 4549            abc                 // No indentation
 4550               abc               // 1 tab
 4551                  abc             // 2 tabs
 4552                abc              // Tab followed by space
 4553               abc              // Space followed by tab (3 spaces should be the result)
 4554                           abc   // Mixed indentation (tab conversion depends on the column)
 4555               abc              // Already space indented
 4556               ·
 4557               abc\tdef          // Only the leading tab is manipulatedˇ»
 4558        "}
 4559        .replace("·", "")
 4560        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4561    );
 4562}
 4563
 4564#[gpui::test]
 4565async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 4566    init_test(cx, |settings| {
 4567        settings.defaults.tab_size = NonZeroU32::new(3)
 4568    });
 4569
 4570    let mut cx = EditorTestContext::new(cx).await;
 4571
 4572    // MULTI SELECTION
 4573    // Ln.1 "«" tests empty lines
 4574    // Ln.11 tests just leading whitespace
 4575    cx.set_state(indoc! {"
 4576        «
 4577        abˇ»ˇc                 // No indentation
 4578         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 4579          abc  «             // 2 spaces (< 3 so dont convert)
 4580           abc              // 3 spaces (convert)
 4581             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 4582        «\tˇ»\t«\tˇ»abc           // Already tab indented
 4583        «\t abc              // Tab followed by space
 4584         \tabc              // Space followed by tab (should be consumed due to tab)
 4585        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4586           \tˇ»  «\t
 4587           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 4588    "});
 4589    cx.update_editor(|e, window, cx| {
 4590        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4591    });
 4592    cx.assert_editor_state(indoc! {"
 4593        «
 4594        abc                 // No indentation
 4595         abc                // 1 space (< 3 so dont convert)
 4596          abc               // 2 spaces (< 3 so dont convert)
 4597        \tabc              // 3 spaces (convert)
 4598        \t  abc            // 5 spaces (1 tab + 2 spaces)
 4599        \t\t\tabc           // Already tab indented
 4600        \t abc              // Tab followed by space
 4601        \tabc              // Space followed by tab (should be consumed due to tab)
 4602        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4603        \t\t\t
 4604        \tabc   \t         // Only the leading spaces should be convertedˇ»
 4605    "});
 4606
 4607    // Test on just a few lines, the other should remain unchanged
 4608    // Only lines (4, 8, 11, 12) should change
 4609    cx.set_state(
 4610        indoc! {"
 4611            ·
 4612            abc                 // No indentation
 4613             abc                // 1 space (< 3 so dont convert)
 4614              abc               // 2 spaces (< 3 so dont convert)
 4615            «   abc              // 3 spaces (convert)ˇ»
 4616                 abc            // 5 spaces (1 tab + 2 spaces)
 4617            \t\t\tabc           // Already tab indented
 4618            \t abc              // Tab followed by space
 4619             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 4620               \t\t  \tabc      // Mixed indentation
 4621            \t \t  \t   \tabc   // Mixed indentation
 4622               \t  \tˇ
 4623            «   abc   \t         // Only the leading spaces should be convertedˇ»
 4624        "}
 4625        .replace("·", "")
 4626        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4627    );
 4628    cx.update_editor(|e, window, cx| {
 4629        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4630    });
 4631    cx.assert_editor_state(
 4632        indoc! {"
 4633            ·
 4634            abc                 // No indentation
 4635             abc                // 1 space (< 3 so dont convert)
 4636              abc               // 2 spaces (< 3 so dont convert)
 4637            «\tabc              // 3 spaces (convert)ˇ»
 4638                 abc            // 5 spaces (1 tab + 2 spaces)
 4639            \t\t\tabc           // Already tab indented
 4640            \t abc              // Tab followed by space
 4641            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 4642               \t\t  \tabc      // Mixed indentation
 4643            \t \t  \t   \tabc   // Mixed indentation
 4644            «\t\t\t
 4645            \tabc   \t         // Only the leading spaces should be convertedˇ»
 4646        "}
 4647        .replace("·", "")
 4648        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4649    );
 4650
 4651    // SINGLE SELECTION
 4652    // Ln.1 "«" tests empty lines
 4653    // Ln.11 tests just leading whitespace
 4654    cx.set_state(indoc! {"
 4655        «
 4656        abc                 // No indentation
 4657         abc                // 1 space (< 3 so dont convert)
 4658          abc               // 2 spaces (< 3 so dont convert)
 4659           abc              // 3 spaces (convert)
 4660             abc            // 5 spaces (1 tab + 2 spaces)
 4661        \t\t\tabc           // Already tab indented
 4662        \t abc              // Tab followed by space
 4663         \tabc              // Space followed by tab (should be consumed due to tab)
 4664        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4665           \t  \t
 4666           abc   \t         // Only the leading spaces should be convertedˇ»
 4667    "});
 4668    cx.update_editor(|e, window, cx| {
 4669        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4670    });
 4671    cx.assert_editor_state(indoc! {"
 4672        «
 4673        abc                 // No indentation
 4674         abc                // 1 space (< 3 so dont convert)
 4675          abc               // 2 spaces (< 3 so dont convert)
 4676        \tabc              // 3 spaces (convert)
 4677        \t  abc            // 5 spaces (1 tab + 2 spaces)
 4678        \t\t\tabc           // Already tab indented
 4679        \t abc              // Tab followed by space
 4680        \tabc              // Space followed by tab (should be consumed due to tab)
 4681        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4682        \t\t\t
 4683        \tabc   \t         // Only the leading spaces should be convertedˇ»
 4684    "});
 4685}
 4686
 4687#[gpui::test]
 4688async fn test_toggle_case(cx: &mut TestAppContext) {
 4689    init_test(cx, |_| {});
 4690
 4691    let mut cx = EditorTestContext::new(cx).await;
 4692
 4693    // If all lower case -> upper case
 4694    cx.set_state(indoc! {"
 4695        «hello worldˇ»
 4696    "});
 4697    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4698    cx.assert_editor_state(indoc! {"
 4699        «HELLO WORLDˇ»
 4700    "});
 4701
 4702    // If all upper case -> lower case
 4703    cx.set_state(indoc! {"
 4704        «HELLO WORLDˇ»
 4705    "});
 4706    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4707    cx.assert_editor_state(indoc! {"
 4708        «hello worldˇ»
 4709    "});
 4710
 4711    // If any upper case characters are identified -> lower case
 4712    // This matches JetBrains IDEs
 4713    cx.set_state(indoc! {"
 4714        «hEllo worldˇ»
 4715    "});
 4716    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4717    cx.assert_editor_state(indoc! {"
 4718        «hello worldˇ»
 4719    "});
 4720}
 4721
 4722#[gpui::test]
 4723async fn test_manipulate_text(cx: &mut TestAppContext) {
 4724    init_test(cx, |_| {});
 4725
 4726    let mut cx = EditorTestContext::new(cx).await;
 4727
 4728    // Test convert_to_upper_case()
 4729    cx.set_state(indoc! {"
 4730        «hello worldˇ»
 4731    "});
 4732    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4733    cx.assert_editor_state(indoc! {"
 4734        «HELLO WORLDˇ»
 4735    "});
 4736
 4737    // Test convert_to_lower_case()
 4738    cx.set_state(indoc! {"
 4739        «HELLO WORLDˇ»
 4740    "});
 4741    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 4742    cx.assert_editor_state(indoc! {"
 4743        «hello worldˇ»
 4744    "});
 4745
 4746    // Test multiple line, single selection case
 4747    cx.set_state(indoc! {"
 4748        «The quick brown
 4749        fox jumps over
 4750        the lazy dogˇ»
 4751    "});
 4752    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 4753    cx.assert_editor_state(indoc! {"
 4754        «The Quick Brown
 4755        Fox Jumps Over
 4756        The Lazy Dogˇ»
 4757    "});
 4758
 4759    // Test multiple line, single selection case
 4760    cx.set_state(indoc! {"
 4761        «The quick brown
 4762        fox jumps over
 4763        the lazy dogˇ»
 4764    "});
 4765    cx.update_editor(|e, window, cx| {
 4766        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 4767    });
 4768    cx.assert_editor_state(indoc! {"
 4769        «TheQuickBrown
 4770        FoxJumpsOver
 4771        TheLazyDogˇ»
 4772    "});
 4773
 4774    // From here on out, test more complex cases of manipulate_text()
 4775
 4776    // Test no selection case - should affect words cursors are in
 4777    // Cursor at beginning, middle, and end of word
 4778    cx.set_state(indoc! {"
 4779        ˇhello big beauˇtiful worldˇ
 4780    "});
 4781    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4782    cx.assert_editor_state(indoc! {"
 4783        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 4784    "});
 4785
 4786    // Test multiple selections on a single line and across multiple lines
 4787    cx.set_state(indoc! {"
 4788        «Theˇ» quick «brown
 4789        foxˇ» jumps «overˇ»
 4790        the «lazyˇ» dog
 4791    "});
 4792    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4793    cx.assert_editor_state(indoc! {"
 4794        «THEˇ» quick «BROWN
 4795        FOXˇ» jumps «OVERˇ»
 4796        the «LAZYˇ» dog
 4797    "});
 4798
 4799    // Test case where text length grows
 4800    cx.set_state(indoc! {"
 4801        «tschüߡ»
 4802    "});
 4803    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4804    cx.assert_editor_state(indoc! {"
 4805        «TSCHÜSSˇ»
 4806    "});
 4807
 4808    // Test to make sure we don't crash when text shrinks
 4809    cx.set_state(indoc! {"
 4810        aaa_bbbˇ
 4811    "});
 4812    cx.update_editor(|e, window, cx| {
 4813        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4814    });
 4815    cx.assert_editor_state(indoc! {"
 4816        «aaaBbbˇ»
 4817    "});
 4818
 4819    // Test to make sure we all aware of the fact that each word can grow and shrink
 4820    // Final selections should be aware of this fact
 4821    cx.set_state(indoc! {"
 4822        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 4823    "});
 4824    cx.update_editor(|e, window, cx| {
 4825        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4826    });
 4827    cx.assert_editor_state(indoc! {"
 4828        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 4829    "});
 4830
 4831    cx.set_state(indoc! {"
 4832        «hElLo, WoRld!ˇ»
 4833    "});
 4834    cx.update_editor(|e, window, cx| {
 4835        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 4836    });
 4837    cx.assert_editor_state(indoc! {"
 4838        «HeLlO, wOrLD!ˇ»
 4839    "});
 4840}
 4841
 4842#[gpui::test]
 4843fn test_duplicate_line(cx: &mut TestAppContext) {
 4844    init_test(cx, |_| {});
 4845
 4846    let editor = cx.add_window(|window, cx| {
 4847        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4848        build_editor(buffer, window, cx)
 4849    });
 4850    _ = editor.update(cx, |editor, window, cx| {
 4851        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4852            s.select_display_ranges([
 4853                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4854                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4855                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4856                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4857            ])
 4858        });
 4859        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4860        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4861        assert_eq!(
 4862            editor.selections.display_ranges(cx),
 4863            vec![
 4864                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4865                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 4866                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4867                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4868            ]
 4869        );
 4870    });
 4871
 4872    let editor = cx.add_window(|window, cx| {
 4873        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4874        build_editor(buffer, window, cx)
 4875    });
 4876    _ = editor.update(cx, |editor, window, cx| {
 4877        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4878            s.select_display_ranges([
 4879                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4880                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4881            ])
 4882        });
 4883        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4884        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4885        assert_eq!(
 4886            editor.selections.display_ranges(cx),
 4887            vec![
 4888                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 4889                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 4890            ]
 4891        );
 4892    });
 4893
 4894    // With `move_upwards` the selections stay in place, except for
 4895    // the lines inserted above them
 4896    let editor = cx.add_window(|window, cx| {
 4897        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4898        build_editor(buffer, window, cx)
 4899    });
 4900    _ = editor.update(cx, |editor, window, cx| {
 4901        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4902            s.select_display_ranges([
 4903                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4904                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4905                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4906                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4907            ])
 4908        });
 4909        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4910        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4911        assert_eq!(
 4912            editor.selections.display_ranges(cx),
 4913            vec![
 4914                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4915                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4916                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 4917                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4918            ]
 4919        );
 4920    });
 4921
 4922    let editor = cx.add_window(|window, cx| {
 4923        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4924        build_editor(buffer, window, cx)
 4925    });
 4926    _ = editor.update(cx, |editor, window, cx| {
 4927        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4928            s.select_display_ranges([
 4929                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4930                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4931            ])
 4932        });
 4933        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4934        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4935        assert_eq!(
 4936            editor.selections.display_ranges(cx),
 4937            vec![
 4938                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4939                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4940            ]
 4941        );
 4942    });
 4943
 4944    let editor = cx.add_window(|window, cx| {
 4945        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4946        build_editor(buffer, window, cx)
 4947    });
 4948    _ = editor.update(cx, |editor, window, cx| {
 4949        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4950            s.select_display_ranges([
 4951                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4952                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4953            ])
 4954        });
 4955        editor.duplicate_selection(&DuplicateSelection, window, cx);
 4956        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 4957        assert_eq!(
 4958            editor.selections.display_ranges(cx),
 4959            vec![
 4960                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4961                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 4962            ]
 4963        );
 4964    });
 4965}
 4966
 4967#[gpui::test]
 4968fn test_move_line_up_down(cx: &mut TestAppContext) {
 4969    init_test(cx, |_| {});
 4970
 4971    let editor = cx.add_window(|window, cx| {
 4972        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 4973        build_editor(buffer, window, cx)
 4974    });
 4975    _ = editor.update(cx, |editor, window, cx| {
 4976        editor.fold_creases(
 4977            vec![
 4978                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 4979                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 4980                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 4981            ],
 4982            true,
 4983            window,
 4984            cx,
 4985        );
 4986        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4987            s.select_display_ranges([
 4988                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4989                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4990                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4991                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 4992            ])
 4993        });
 4994        assert_eq!(
 4995            editor.display_text(cx),
 4996            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 4997        );
 4998
 4999        editor.move_line_up(&MoveLineUp, window, cx);
 5000        assert_eq!(
 5001            editor.display_text(cx),
 5002            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 5003        );
 5004        assert_eq!(
 5005            editor.selections.display_ranges(cx),
 5006            vec![
 5007                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5008                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5009                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5010                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5011            ]
 5012        );
 5013    });
 5014
 5015    _ = editor.update(cx, |editor, window, cx| {
 5016        editor.move_line_down(&MoveLineDown, window, cx);
 5017        assert_eq!(
 5018            editor.display_text(cx),
 5019            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 5020        );
 5021        assert_eq!(
 5022            editor.selections.display_ranges(cx),
 5023            vec![
 5024                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5025                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5026                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5027                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5028            ]
 5029        );
 5030    });
 5031
 5032    _ = editor.update(cx, |editor, window, cx| {
 5033        editor.move_line_down(&MoveLineDown, window, cx);
 5034        assert_eq!(
 5035            editor.display_text(cx),
 5036            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 5037        );
 5038        assert_eq!(
 5039            editor.selections.display_ranges(cx),
 5040            vec![
 5041                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5042                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5043                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5044                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5045            ]
 5046        );
 5047    });
 5048
 5049    _ = editor.update(cx, |editor, window, cx| {
 5050        editor.move_line_up(&MoveLineUp, window, cx);
 5051        assert_eq!(
 5052            editor.display_text(cx),
 5053            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 5054        );
 5055        assert_eq!(
 5056            editor.selections.display_ranges(cx),
 5057            vec![
 5058                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5059                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5060                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5061                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5062            ]
 5063        );
 5064    });
 5065}
 5066
 5067#[gpui::test]
 5068fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 5069    init_test(cx, |_| {});
 5070
 5071    let editor = cx.add_window(|window, cx| {
 5072        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5073        build_editor(buffer, window, cx)
 5074    });
 5075    _ = editor.update(cx, |editor, window, cx| {
 5076        let snapshot = editor.buffer.read(cx).snapshot(cx);
 5077        editor.insert_blocks(
 5078            [BlockProperties {
 5079                style: BlockStyle::Fixed,
 5080                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 5081                height: Some(1),
 5082                render: Arc::new(|_| div().into_any()),
 5083                priority: 0,
 5084                render_in_minimap: true,
 5085            }],
 5086            Some(Autoscroll::fit()),
 5087            cx,
 5088        );
 5089        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5090            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 5091        });
 5092        editor.move_line_down(&MoveLineDown, window, cx);
 5093    });
 5094}
 5095
 5096#[gpui::test]
 5097async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 5098    init_test(cx, |_| {});
 5099
 5100    let mut cx = EditorTestContext::new(cx).await;
 5101    cx.set_state(
 5102        &"
 5103            ˇzero
 5104            one
 5105            two
 5106            three
 5107            four
 5108            five
 5109        "
 5110        .unindent(),
 5111    );
 5112
 5113    // Create a four-line block that replaces three lines of text.
 5114    cx.update_editor(|editor, window, cx| {
 5115        let snapshot = editor.snapshot(window, cx);
 5116        let snapshot = &snapshot.buffer_snapshot;
 5117        let placement = BlockPlacement::Replace(
 5118            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 5119        );
 5120        editor.insert_blocks(
 5121            [BlockProperties {
 5122                placement,
 5123                height: Some(4),
 5124                style: BlockStyle::Sticky,
 5125                render: Arc::new(|_| gpui::div().into_any_element()),
 5126                priority: 0,
 5127                render_in_minimap: true,
 5128            }],
 5129            None,
 5130            cx,
 5131        );
 5132    });
 5133
 5134    // Move down so that the cursor touches the block.
 5135    cx.update_editor(|editor, window, cx| {
 5136        editor.move_down(&Default::default(), window, cx);
 5137    });
 5138    cx.assert_editor_state(
 5139        &"
 5140            zero
 5141            «one
 5142            two
 5143            threeˇ»
 5144            four
 5145            five
 5146        "
 5147        .unindent(),
 5148    );
 5149
 5150    // Move down past the block.
 5151    cx.update_editor(|editor, window, cx| {
 5152        editor.move_down(&Default::default(), window, cx);
 5153    });
 5154    cx.assert_editor_state(
 5155        &"
 5156            zero
 5157            one
 5158            two
 5159            three
 5160            ˇfour
 5161            five
 5162        "
 5163        .unindent(),
 5164    );
 5165}
 5166
 5167#[gpui::test]
 5168fn test_transpose(cx: &mut TestAppContext) {
 5169    init_test(cx, |_| {});
 5170
 5171    _ = cx.add_window(|window, cx| {
 5172        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 5173        editor.set_style(EditorStyle::default(), window, cx);
 5174        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5175            s.select_ranges([1..1])
 5176        });
 5177        editor.transpose(&Default::default(), window, cx);
 5178        assert_eq!(editor.text(cx), "bac");
 5179        assert_eq!(editor.selections.ranges(cx), [2..2]);
 5180
 5181        editor.transpose(&Default::default(), window, cx);
 5182        assert_eq!(editor.text(cx), "bca");
 5183        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5184
 5185        editor.transpose(&Default::default(), window, cx);
 5186        assert_eq!(editor.text(cx), "bac");
 5187        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5188
 5189        editor
 5190    });
 5191
 5192    _ = cx.add_window(|window, cx| {
 5193        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5194        editor.set_style(EditorStyle::default(), window, cx);
 5195        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5196            s.select_ranges([3..3])
 5197        });
 5198        editor.transpose(&Default::default(), window, cx);
 5199        assert_eq!(editor.text(cx), "acb\nde");
 5200        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5201
 5202        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5203            s.select_ranges([4..4])
 5204        });
 5205        editor.transpose(&Default::default(), window, cx);
 5206        assert_eq!(editor.text(cx), "acbd\ne");
 5207        assert_eq!(editor.selections.ranges(cx), [5..5]);
 5208
 5209        editor.transpose(&Default::default(), window, cx);
 5210        assert_eq!(editor.text(cx), "acbde\n");
 5211        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5212
 5213        editor.transpose(&Default::default(), window, cx);
 5214        assert_eq!(editor.text(cx), "acbd\ne");
 5215        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5216
 5217        editor
 5218    });
 5219
 5220    _ = cx.add_window(|window, cx| {
 5221        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5222        editor.set_style(EditorStyle::default(), window, cx);
 5223        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5224            s.select_ranges([1..1, 2..2, 4..4])
 5225        });
 5226        editor.transpose(&Default::default(), window, cx);
 5227        assert_eq!(editor.text(cx), "bacd\ne");
 5228        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 5229
 5230        editor.transpose(&Default::default(), window, cx);
 5231        assert_eq!(editor.text(cx), "bcade\n");
 5232        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 5233
 5234        editor.transpose(&Default::default(), window, cx);
 5235        assert_eq!(editor.text(cx), "bcda\ne");
 5236        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5237
 5238        editor.transpose(&Default::default(), window, cx);
 5239        assert_eq!(editor.text(cx), "bcade\n");
 5240        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5241
 5242        editor.transpose(&Default::default(), window, cx);
 5243        assert_eq!(editor.text(cx), "bcaed\n");
 5244        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 5245
 5246        editor
 5247    });
 5248
 5249    _ = cx.add_window(|window, cx| {
 5250        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 5251        editor.set_style(EditorStyle::default(), window, cx);
 5252        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5253            s.select_ranges([4..4])
 5254        });
 5255        editor.transpose(&Default::default(), window, cx);
 5256        assert_eq!(editor.text(cx), "🏀🍐✋");
 5257        assert_eq!(editor.selections.ranges(cx), [8..8]);
 5258
 5259        editor.transpose(&Default::default(), window, cx);
 5260        assert_eq!(editor.text(cx), "🏀✋🍐");
 5261        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5262
 5263        editor.transpose(&Default::default(), window, cx);
 5264        assert_eq!(editor.text(cx), "🏀🍐✋");
 5265        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5266
 5267        editor
 5268    });
 5269}
 5270
 5271#[gpui::test]
 5272async fn test_rewrap(cx: &mut TestAppContext) {
 5273    init_test(cx, |settings| {
 5274        settings.languages.0.extend([
 5275            (
 5276                "Markdown".into(),
 5277                LanguageSettingsContent {
 5278                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5279                    preferred_line_length: Some(40),
 5280                    ..Default::default()
 5281                },
 5282            ),
 5283            (
 5284                "Plain Text".into(),
 5285                LanguageSettingsContent {
 5286                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5287                    preferred_line_length: Some(40),
 5288                    ..Default::default()
 5289                },
 5290            ),
 5291            (
 5292                "C++".into(),
 5293                LanguageSettingsContent {
 5294                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5295                    preferred_line_length: Some(40),
 5296                    ..Default::default()
 5297                },
 5298            ),
 5299            (
 5300                "Python".into(),
 5301                LanguageSettingsContent {
 5302                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5303                    preferred_line_length: Some(40),
 5304                    ..Default::default()
 5305                },
 5306            ),
 5307            (
 5308                "Rust".into(),
 5309                LanguageSettingsContent {
 5310                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5311                    preferred_line_length: Some(40),
 5312                    ..Default::default()
 5313                },
 5314            ),
 5315        ])
 5316    });
 5317
 5318    let mut cx = EditorTestContext::new(cx).await;
 5319
 5320    let cpp_language = Arc::new(Language::new(
 5321        LanguageConfig {
 5322            name: "C++".into(),
 5323            line_comments: vec!["// ".into()],
 5324            ..LanguageConfig::default()
 5325        },
 5326        None,
 5327    ));
 5328    let python_language = Arc::new(Language::new(
 5329        LanguageConfig {
 5330            name: "Python".into(),
 5331            line_comments: vec!["# ".into()],
 5332            ..LanguageConfig::default()
 5333        },
 5334        None,
 5335    ));
 5336    let markdown_language = Arc::new(Language::new(
 5337        LanguageConfig {
 5338            name: "Markdown".into(),
 5339            rewrap_prefixes: vec![
 5340                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 5341                regex::Regex::new("[-*+]\\s+").unwrap(),
 5342            ],
 5343            ..LanguageConfig::default()
 5344        },
 5345        None,
 5346    ));
 5347    let rust_language = Arc::new(Language::new(
 5348        LanguageConfig {
 5349            name: "Rust".into(),
 5350            line_comments: vec!["// ".into(), "/// ".into()],
 5351            ..LanguageConfig::default()
 5352        },
 5353        Some(tree_sitter_rust::LANGUAGE.into()),
 5354    ));
 5355
 5356    let plaintext_language = Arc::new(Language::new(
 5357        LanguageConfig {
 5358            name: "Plain Text".into(),
 5359            ..LanguageConfig::default()
 5360        },
 5361        None,
 5362    ));
 5363
 5364    // Test basic rewrapping of a long line with a cursor
 5365    assert_rewrap(
 5366        indoc! {"
 5367            // ˇThis is a long comment that needs to be wrapped.
 5368        "},
 5369        indoc! {"
 5370            // ˇThis is a long comment that needs to
 5371            // be wrapped.
 5372        "},
 5373        cpp_language.clone(),
 5374        &mut cx,
 5375    );
 5376
 5377    // Test rewrapping a full selection
 5378    assert_rewrap(
 5379        indoc! {"
 5380            «// This selected long comment needs to be wrapped.ˇ»"
 5381        },
 5382        indoc! {"
 5383            «// This selected long comment needs to
 5384            // be wrapped.ˇ»"
 5385        },
 5386        cpp_language.clone(),
 5387        &mut cx,
 5388    );
 5389
 5390    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 5391    assert_rewrap(
 5392        indoc! {"
 5393            // ˇThis is the first line.
 5394            // Thisˇ is the second line.
 5395            // This is the thirdˇ line, all part of one paragraph.
 5396         "},
 5397        indoc! {"
 5398            // ˇThis is the first line. Thisˇ is the
 5399            // second line. This is the thirdˇ line,
 5400            // all part of one paragraph.
 5401         "},
 5402        cpp_language.clone(),
 5403        &mut cx,
 5404    );
 5405
 5406    // Test multiple cursors in different paragraphs trigger separate rewraps
 5407    assert_rewrap(
 5408        indoc! {"
 5409            // ˇThis is the first paragraph, first line.
 5410            // ˇThis is the first paragraph, second line.
 5411
 5412            // ˇThis is the second paragraph, first line.
 5413            // ˇThis is the second paragraph, second line.
 5414        "},
 5415        indoc! {"
 5416            // ˇThis is the first paragraph, first
 5417            // line. ˇThis is the first paragraph,
 5418            // second line.
 5419
 5420            // ˇThis is the second paragraph, first
 5421            // line. ˇThis is the second paragraph,
 5422            // second line.
 5423        "},
 5424        cpp_language.clone(),
 5425        &mut cx,
 5426    );
 5427
 5428    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 5429    assert_rewrap(
 5430        indoc! {"
 5431            «// A regular long long comment to be wrapped.
 5432            /// A documentation long comment to be wrapped.ˇ»
 5433          "},
 5434        indoc! {"
 5435            «// A regular long long comment to be
 5436            // wrapped.
 5437            /// A documentation long comment to be
 5438            /// wrapped.ˇ»
 5439          "},
 5440        rust_language.clone(),
 5441        &mut cx,
 5442    );
 5443
 5444    // Test that change in indentation level trigger seperate rewraps
 5445    assert_rewrap(
 5446        indoc! {"
 5447            fn foo() {
 5448                «// This is a long comment at the base indent.
 5449                    // This is a long comment at the next indent.ˇ»
 5450            }
 5451        "},
 5452        indoc! {"
 5453            fn foo() {
 5454                «// This is a long comment at the
 5455                // base indent.
 5456                    // This is a long comment at the
 5457                    // next indent.ˇ»
 5458            }
 5459        "},
 5460        rust_language.clone(),
 5461        &mut cx,
 5462    );
 5463
 5464    // Test that different comment prefix characters (e.g., '#') are handled correctly
 5465    assert_rewrap(
 5466        indoc! {"
 5467            # ˇThis is a long comment using a pound sign.
 5468        "},
 5469        indoc! {"
 5470            # ˇThis is a long comment using a pound
 5471            # sign.
 5472        "},
 5473        python_language.clone(),
 5474        &mut cx,
 5475    );
 5476
 5477    // Test rewrapping only affects comments, not code even when selected
 5478    assert_rewrap(
 5479        indoc! {"
 5480            «/// This doc comment is long and should be wrapped.
 5481            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 5482        "},
 5483        indoc! {"
 5484            «/// This doc comment is long and should
 5485            /// be wrapped.
 5486            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 5487        "},
 5488        rust_language.clone(),
 5489        &mut cx,
 5490    );
 5491
 5492    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 5493    assert_rewrap(
 5494        indoc! {"
 5495            # Header
 5496
 5497            A long long long line of markdown text to wrap.ˇ
 5498         "},
 5499        indoc! {"
 5500            # Header
 5501
 5502            A long long long line of markdown text
 5503            to wrap.ˇ
 5504         "},
 5505        markdown_language.clone(),
 5506        &mut cx,
 5507    );
 5508
 5509    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 5510    assert_rewrap(
 5511        indoc! {"
 5512            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 5513            2. This is a numbered list item that is very long and needs to be wrapped properly.
 5514            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 5515        "},
 5516        indoc! {"
 5517            «1. This is a numbered list item that is
 5518               very long and needs to be wrapped
 5519               properly.
 5520            2. This is a numbered list item that is
 5521               very long and needs to be wrapped
 5522               properly.
 5523            - This is an unordered list item that is
 5524              also very long and should not merge
 5525              with the numbered item.ˇ»
 5526        "},
 5527        markdown_language.clone(),
 5528        &mut cx,
 5529    );
 5530
 5531    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 5532    assert_rewrap(
 5533        indoc! {"
 5534            «1. This is a numbered list item that is
 5535            very long and needs to be wrapped
 5536            properly.
 5537            2. This is a numbered list item that is
 5538            very long and needs to be wrapped
 5539            properly.
 5540            - This is an unordered list item that is
 5541            also very long and should not merge with
 5542            the numbered item.ˇ»
 5543        "},
 5544        indoc! {"
 5545            «1. This is a numbered list item that is
 5546               very long and needs to be wrapped
 5547               properly.
 5548            2. This is a numbered list item that is
 5549               very long and needs to be wrapped
 5550               properly.
 5551            - This is an unordered list item that is
 5552              also very long and should not merge
 5553              with the numbered item.ˇ»
 5554        "},
 5555        markdown_language.clone(),
 5556        &mut cx,
 5557    );
 5558
 5559    // Test that rewrapping maintain indents even when they already exists.
 5560    assert_rewrap(
 5561        indoc! {"
 5562            «1. This is a numbered list
 5563               item that is very long and needs to be wrapped properly.
 5564            2. This is a numbered list
 5565               item that is very long and needs to be wrapped properly.
 5566            - This is an unordered list item that is also very long and
 5567              should not merge with the numbered item.ˇ»
 5568        "},
 5569        indoc! {"
 5570            «1. This is a numbered list item that is
 5571               very long and needs to be wrapped
 5572               properly.
 5573            2. This is a numbered list item that is
 5574               very long and needs to be wrapped
 5575               properly.
 5576            - This is an unordered list item that is
 5577              also very long and should not merge
 5578              with the numbered item.ˇ»
 5579        "},
 5580        markdown_language.clone(),
 5581        &mut cx,
 5582    );
 5583
 5584    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 5585    assert_rewrap(
 5586        indoc! {"
 5587            ˇThis is a very long line of plain text that will be wrapped.
 5588        "},
 5589        indoc! {"
 5590            ˇThis is a very long line of plain text
 5591            that will be wrapped.
 5592        "},
 5593        plaintext_language.clone(),
 5594        &mut cx,
 5595    );
 5596
 5597    // Test that non-commented code acts as a paragraph boundary within a selection
 5598    assert_rewrap(
 5599        indoc! {"
 5600               «// This is the first long comment block to be wrapped.
 5601               fn my_func(a: u32);
 5602               // This is the second long comment block to be wrapped.ˇ»
 5603           "},
 5604        indoc! {"
 5605               «// This is the first long comment block
 5606               // to be wrapped.
 5607               fn my_func(a: u32);
 5608               // This is the second long comment block
 5609               // to be wrapped.ˇ»
 5610           "},
 5611        rust_language.clone(),
 5612        &mut cx,
 5613    );
 5614
 5615    // Test rewrapping multiple selections, including ones with blank lines or tabs
 5616    assert_rewrap(
 5617        indoc! {"
 5618            «ˇThis is a very long line that will be wrapped.
 5619
 5620            This is another paragraph in the same selection.»
 5621
 5622            «\tThis is a very long indented line that will be wrapped.ˇ»
 5623         "},
 5624        indoc! {"
 5625            «ˇThis is a very long line that will be
 5626            wrapped.
 5627
 5628            This is another paragraph in the same
 5629            selection.»
 5630
 5631            «\tThis is a very long indented line
 5632            \tthat will be wrapped.ˇ»
 5633         "},
 5634        plaintext_language.clone(),
 5635        &mut cx,
 5636    );
 5637
 5638    // Test that an empty comment line acts as a paragraph boundary
 5639    assert_rewrap(
 5640        indoc! {"
 5641            // ˇThis is a long comment that will be wrapped.
 5642            //
 5643            // And this is another long comment that will also be wrapped.ˇ
 5644         "},
 5645        indoc! {"
 5646            // ˇThis is a long comment that will be
 5647            // wrapped.
 5648            //
 5649            // And this is another long comment that
 5650            // will also be wrapped.ˇ
 5651         "},
 5652        cpp_language,
 5653        &mut cx,
 5654    );
 5655
 5656    #[track_caller]
 5657    fn assert_rewrap(
 5658        unwrapped_text: &str,
 5659        wrapped_text: &str,
 5660        language: Arc<Language>,
 5661        cx: &mut EditorTestContext,
 5662    ) {
 5663        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 5664        cx.set_state(unwrapped_text);
 5665        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 5666        cx.assert_editor_state(wrapped_text);
 5667    }
 5668}
 5669
 5670#[gpui::test]
 5671async fn test_hard_wrap(cx: &mut TestAppContext) {
 5672    init_test(cx, |_| {});
 5673    let mut cx = EditorTestContext::new(cx).await;
 5674
 5675    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 5676    cx.update_editor(|editor, _, cx| {
 5677        editor.set_hard_wrap(Some(14), cx);
 5678    });
 5679
 5680    cx.set_state(indoc!(
 5681        "
 5682        one two three ˇ
 5683        "
 5684    ));
 5685    cx.simulate_input("four");
 5686    cx.run_until_parked();
 5687
 5688    cx.assert_editor_state(indoc!(
 5689        "
 5690        one two three
 5691        fourˇ
 5692        "
 5693    ));
 5694
 5695    cx.update_editor(|editor, window, cx| {
 5696        editor.newline(&Default::default(), window, cx);
 5697    });
 5698    cx.run_until_parked();
 5699    cx.assert_editor_state(indoc!(
 5700        "
 5701        one two three
 5702        four
 5703        ˇ
 5704        "
 5705    ));
 5706
 5707    cx.simulate_input("five");
 5708    cx.run_until_parked();
 5709    cx.assert_editor_state(indoc!(
 5710        "
 5711        one two three
 5712        four
 5713        fiveˇ
 5714        "
 5715    ));
 5716
 5717    cx.update_editor(|editor, window, cx| {
 5718        editor.newline(&Default::default(), window, cx);
 5719    });
 5720    cx.run_until_parked();
 5721    cx.simulate_input("# ");
 5722    cx.run_until_parked();
 5723    cx.assert_editor_state(indoc!(
 5724        "
 5725        one two three
 5726        four
 5727        five
 5728        # ˇ
 5729        "
 5730    ));
 5731
 5732    cx.update_editor(|editor, window, cx| {
 5733        editor.newline(&Default::default(), window, cx);
 5734    });
 5735    cx.run_until_parked();
 5736    cx.assert_editor_state(indoc!(
 5737        "
 5738        one two three
 5739        four
 5740        five
 5741        #\x20
 5742 5743        "
 5744    ));
 5745
 5746    cx.simulate_input(" 6");
 5747    cx.run_until_parked();
 5748    cx.assert_editor_state(indoc!(
 5749        "
 5750        one two three
 5751        four
 5752        five
 5753        #
 5754        # 6ˇ
 5755        "
 5756    ));
 5757}
 5758
 5759#[gpui::test]
 5760async fn test_clipboard(cx: &mut TestAppContext) {
 5761    init_test(cx, |_| {});
 5762
 5763    let mut cx = EditorTestContext::new(cx).await;
 5764
 5765    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 5766    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5767    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 5768
 5769    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 5770    cx.set_state("two ˇfour ˇsix ˇ");
 5771    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5772    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 5773
 5774    // Paste again but with only two cursors. Since the number of cursors doesn't
 5775    // match the number of slices in the clipboard, the entire clipboard text
 5776    // is pasted at each cursor.
 5777    cx.set_state("ˇtwo one✅ four three six five ˇ");
 5778    cx.update_editor(|e, window, cx| {
 5779        e.handle_input("( ", window, cx);
 5780        e.paste(&Paste, window, cx);
 5781        e.handle_input(") ", window, cx);
 5782    });
 5783    cx.assert_editor_state(
 5784        &([
 5785            "( one✅ ",
 5786            "three ",
 5787            "five ) ˇtwo one✅ four three six five ( one✅ ",
 5788            "three ",
 5789            "five ) ˇ",
 5790        ]
 5791        .join("\n")),
 5792    );
 5793
 5794    // Cut with three selections, one of which is full-line.
 5795    cx.set_state(indoc! {"
 5796        1«2ˇ»3
 5797        4ˇ567
 5798        «8ˇ»9"});
 5799    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5800    cx.assert_editor_state(indoc! {"
 5801        1ˇ3
 5802        ˇ9"});
 5803
 5804    // Paste with three selections, noticing how the copied selection that was full-line
 5805    // gets inserted before the second cursor.
 5806    cx.set_state(indoc! {"
 5807        1ˇ3
 5808 5809        «oˇ»ne"});
 5810    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5811    cx.assert_editor_state(indoc! {"
 5812        12ˇ3
 5813        4567
 5814 5815        8ˇne"});
 5816
 5817    // Copy with a single cursor only, which writes the whole line into the clipboard.
 5818    cx.set_state(indoc! {"
 5819        The quick brown
 5820        fox juˇmps over
 5821        the lazy dog"});
 5822    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5823    assert_eq!(
 5824        cx.read_from_clipboard()
 5825            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5826        Some("fox jumps over\n".to_string())
 5827    );
 5828
 5829    // Paste with three selections, noticing how the copied full-line selection is inserted
 5830    // before the empty selections but replaces the selection that is non-empty.
 5831    cx.set_state(indoc! {"
 5832        Tˇhe quick brown
 5833        «foˇ»x jumps over
 5834        tˇhe lazy dog"});
 5835    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5836    cx.assert_editor_state(indoc! {"
 5837        fox jumps over
 5838        Tˇhe quick brown
 5839        fox jumps over
 5840        ˇx jumps over
 5841        fox jumps over
 5842        tˇhe lazy dog"});
 5843}
 5844
 5845#[gpui::test]
 5846async fn test_copy_trim(cx: &mut TestAppContext) {
 5847    init_test(cx, |_| {});
 5848
 5849    let mut cx = EditorTestContext::new(cx).await;
 5850    cx.set_state(
 5851        r#"            «for selection in selections.iter() {
 5852            let mut start = selection.start;
 5853            let mut end = selection.end;
 5854            let is_entire_line = selection.is_empty();
 5855            if is_entire_line {
 5856                start = Point::new(start.row, 0);ˇ»
 5857                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5858            }
 5859        "#,
 5860    );
 5861    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5862    assert_eq!(
 5863        cx.read_from_clipboard()
 5864            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5865        Some(
 5866            "for selection in selections.iter() {
 5867            let mut start = selection.start;
 5868            let mut end = selection.end;
 5869            let is_entire_line = selection.is_empty();
 5870            if is_entire_line {
 5871                start = Point::new(start.row, 0);"
 5872                .to_string()
 5873        ),
 5874        "Regular copying preserves all indentation selected",
 5875    );
 5876    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5877    assert_eq!(
 5878        cx.read_from_clipboard()
 5879            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5880        Some(
 5881            "for selection in selections.iter() {
 5882let mut start = selection.start;
 5883let mut end = selection.end;
 5884let is_entire_line = selection.is_empty();
 5885if is_entire_line {
 5886    start = Point::new(start.row, 0);"
 5887                .to_string()
 5888        ),
 5889        "Copying with stripping should strip all leading whitespaces"
 5890    );
 5891
 5892    cx.set_state(
 5893        r#"       «     for selection in selections.iter() {
 5894            let mut start = selection.start;
 5895            let mut end = selection.end;
 5896            let is_entire_line = selection.is_empty();
 5897            if is_entire_line {
 5898                start = Point::new(start.row, 0);ˇ»
 5899                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5900            }
 5901        "#,
 5902    );
 5903    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5904    assert_eq!(
 5905        cx.read_from_clipboard()
 5906            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5907        Some(
 5908            "     for selection in selections.iter() {
 5909            let mut start = selection.start;
 5910            let mut end = selection.end;
 5911            let is_entire_line = selection.is_empty();
 5912            if is_entire_line {
 5913                start = Point::new(start.row, 0);"
 5914                .to_string()
 5915        ),
 5916        "Regular copying preserves all indentation selected",
 5917    );
 5918    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5919    assert_eq!(
 5920        cx.read_from_clipboard()
 5921            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5922        Some(
 5923            "for selection in selections.iter() {
 5924let mut start = selection.start;
 5925let mut end = selection.end;
 5926let is_entire_line = selection.is_empty();
 5927if is_entire_line {
 5928    start = Point::new(start.row, 0);"
 5929                .to_string()
 5930        ),
 5931        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 5932    );
 5933
 5934    cx.set_state(
 5935        r#"       «ˇ     for selection in selections.iter() {
 5936            let mut start = selection.start;
 5937            let mut end = selection.end;
 5938            let is_entire_line = selection.is_empty();
 5939            if is_entire_line {
 5940                start = Point::new(start.row, 0);»
 5941                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5942            }
 5943        "#,
 5944    );
 5945    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5946    assert_eq!(
 5947        cx.read_from_clipboard()
 5948            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5949        Some(
 5950            "     for selection in selections.iter() {
 5951            let mut start = selection.start;
 5952            let mut end = selection.end;
 5953            let is_entire_line = selection.is_empty();
 5954            if is_entire_line {
 5955                start = Point::new(start.row, 0);"
 5956                .to_string()
 5957        ),
 5958        "Regular copying for reverse selection works the same",
 5959    );
 5960    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5961    assert_eq!(
 5962        cx.read_from_clipboard()
 5963            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5964        Some(
 5965            "for selection in selections.iter() {
 5966let mut start = selection.start;
 5967let mut end = selection.end;
 5968let is_entire_line = selection.is_empty();
 5969if is_entire_line {
 5970    start = Point::new(start.row, 0);"
 5971                .to_string()
 5972        ),
 5973        "Copying with stripping for reverse selection works the same"
 5974    );
 5975
 5976    cx.set_state(
 5977        r#"            for selection «in selections.iter() {
 5978            let mut start = selection.start;
 5979            let mut end = selection.end;
 5980            let is_entire_line = selection.is_empty();
 5981            if is_entire_line {
 5982                start = Point::new(start.row, 0);ˇ»
 5983                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5984            }
 5985        "#,
 5986    );
 5987    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5988    assert_eq!(
 5989        cx.read_from_clipboard()
 5990            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5991        Some(
 5992            "in selections.iter() {
 5993            let mut start = selection.start;
 5994            let mut end = selection.end;
 5995            let is_entire_line = selection.is_empty();
 5996            if is_entire_line {
 5997                start = Point::new(start.row, 0);"
 5998                .to_string()
 5999        ),
 6000        "When selecting past the indent, the copying works as usual",
 6001    );
 6002    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6003    assert_eq!(
 6004        cx.read_from_clipboard()
 6005            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6006        Some(
 6007            "in selections.iter() {
 6008            let mut start = selection.start;
 6009            let mut end = selection.end;
 6010            let is_entire_line = selection.is_empty();
 6011            if is_entire_line {
 6012                start = Point::new(start.row, 0);"
 6013                .to_string()
 6014        ),
 6015        "When selecting past the indent, nothing is trimmed"
 6016    );
 6017
 6018    cx.set_state(
 6019        r#"            «for selection in selections.iter() {
 6020            let mut start = selection.start;
 6021
 6022            let mut end = selection.end;
 6023            let is_entire_line = selection.is_empty();
 6024            if is_entire_line {
 6025                start = Point::new(start.row, 0);
 6026ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6027            }
 6028        "#,
 6029    );
 6030    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6031    assert_eq!(
 6032        cx.read_from_clipboard()
 6033            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6034        Some(
 6035            "for selection in selections.iter() {
 6036let mut start = selection.start;
 6037
 6038let mut end = selection.end;
 6039let is_entire_line = selection.is_empty();
 6040if is_entire_line {
 6041    start = Point::new(start.row, 0);
 6042"
 6043            .to_string()
 6044        ),
 6045        "Copying with stripping should ignore empty lines"
 6046    );
 6047}
 6048
 6049#[gpui::test]
 6050async fn test_paste_multiline(cx: &mut TestAppContext) {
 6051    init_test(cx, |_| {});
 6052
 6053    let mut cx = EditorTestContext::new(cx).await;
 6054    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 6055
 6056    // Cut an indented block, without the leading whitespace.
 6057    cx.set_state(indoc! {"
 6058        const a: B = (
 6059            c(),
 6060            «d(
 6061                e,
 6062                f
 6063            )ˇ»
 6064        );
 6065    "});
 6066    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6067    cx.assert_editor_state(indoc! {"
 6068        const a: B = (
 6069            c(),
 6070            ˇ
 6071        );
 6072    "});
 6073
 6074    // Paste it at the same position.
 6075    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6076    cx.assert_editor_state(indoc! {"
 6077        const a: B = (
 6078            c(),
 6079            d(
 6080                e,
 6081                f
 6082 6083        );
 6084    "});
 6085
 6086    // Paste it at a line with a lower indent level.
 6087    cx.set_state(indoc! {"
 6088        ˇ
 6089        const a: B = (
 6090            c(),
 6091        );
 6092    "});
 6093    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6094    cx.assert_editor_state(indoc! {"
 6095        d(
 6096            e,
 6097            f
 6098 6099        const a: B = (
 6100            c(),
 6101        );
 6102    "});
 6103
 6104    // Cut an indented block, with the leading whitespace.
 6105    cx.set_state(indoc! {"
 6106        const a: B = (
 6107            c(),
 6108        «    d(
 6109                e,
 6110                f
 6111            )
 6112        ˇ»);
 6113    "});
 6114    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6115    cx.assert_editor_state(indoc! {"
 6116        const a: B = (
 6117            c(),
 6118        ˇ);
 6119    "});
 6120
 6121    // Paste it at the same position.
 6122    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6123    cx.assert_editor_state(indoc! {"
 6124        const a: B = (
 6125            c(),
 6126            d(
 6127                e,
 6128                f
 6129            )
 6130        ˇ);
 6131    "});
 6132
 6133    // Paste it at a line with a higher indent level.
 6134    cx.set_state(indoc! {"
 6135        const a: B = (
 6136            c(),
 6137            d(
 6138                e,
 6139 6140            )
 6141        );
 6142    "});
 6143    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6144    cx.assert_editor_state(indoc! {"
 6145        const a: B = (
 6146            c(),
 6147            d(
 6148                e,
 6149                f    d(
 6150                    e,
 6151                    f
 6152                )
 6153        ˇ
 6154            )
 6155        );
 6156    "});
 6157
 6158    // Copy an indented block, starting mid-line
 6159    cx.set_state(indoc! {"
 6160        const a: B = (
 6161            c(),
 6162            somethin«g(
 6163                e,
 6164                f
 6165            )ˇ»
 6166        );
 6167    "});
 6168    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6169
 6170    // Paste it on a line with a lower indent level
 6171    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 6172    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6173    cx.assert_editor_state(indoc! {"
 6174        const a: B = (
 6175            c(),
 6176            something(
 6177                e,
 6178                f
 6179            )
 6180        );
 6181        g(
 6182            e,
 6183            f
 6184"});
 6185}
 6186
 6187#[gpui::test]
 6188async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 6189    init_test(cx, |_| {});
 6190
 6191    cx.write_to_clipboard(ClipboardItem::new_string(
 6192        "    d(\n        e\n    );\n".into(),
 6193    ));
 6194
 6195    let mut cx = EditorTestContext::new(cx).await;
 6196    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 6197
 6198    cx.set_state(indoc! {"
 6199        fn a() {
 6200            b();
 6201            if c() {
 6202                ˇ
 6203            }
 6204        }
 6205    "});
 6206
 6207    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6208    cx.assert_editor_state(indoc! {"
 6209        fn a() {
 6210            b();
 6211            if c() {
 6212                d(
 6213                    e
 6214                );
 6215        ˇ
 6216            }
 6217        }
 6218    "});
 6219
 6220    cx.set_state(indoc! {"
 6221        fn a() {
 6222            b();
 6223            ˇ
 6224        }
 6225    "});
 6226
 6227    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6228    cx.assert_editor_state(indoc! {"
 6229        fn a() {
 6230            b();
 6231            d(
 6232                e
 6233            );
 6234        ˇ
 6235        }
 6236    "});
 6237}
 6238
 6239#[gpui::test]
 6240fn test_select_all(cx: &mut TestAppContext) {
 6241    init_test(cx, |_| {});
 6242
 6243    let editor = cx.add_window(|window, cx| {
 6244        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 6245        build_editor(buffer, window, cx)
 6246    });
 6247    _ = editor.update(cx, |editor, window, cx| {
 6248        editor.select_all(&SelectAll, window, cx);
 6249        assert_eq!(
 6250            editor.selections.display_ranges(cx),
 6251            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 6252        );
 6253    });
 6254}
 6255
 6256#[gpui::test]
 6257fn test_select_line(cx: &mut TestAppContext) {
 6258    init_test(cx, |_| {});
 6259
 6260    let editor = cx.add_window(|window, cx| {
 6261        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 6262        build_editor(buffer, window, cx)
 6263    });
 6264    _ = editor.update(cx, |editor, window, cx| {
 6265        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6266            s.select_display_ranges([
 6267                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6268                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6269                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6270                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 6271            ])
 6272        });
 6273        editor.select_line(&SelectLine, window, cx);
 6274        assert_eq!(
 6275            editor.selections.display_ranges(cx),
 6276            vec![
 6277                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 6278                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 6279            ]
 6280        );
 6281    });
 6282
 6283    _ = editor.update(cx, |editor, window, cx| {
 6284        editor.select_line(&SelectLine, window, cx);
 6285        assert_eq!(
 6286            editor.selections.display_ranges(cx),
 6287            vec![
 6288                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 6289                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 6290            ]
 6291        );
 6292    });
 6293
 6294    _ = editor.update(cx, |editor, window, cx| {
 6295        editor.select_line(&SelectLine, window, cx);
 6296        assert_eq!(
 6297            editor.selections.display_ranges(cx),
 6298            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 6299        );
 6300    });
 6301}
 6302
 6303#[gpui::test]
 6304async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 6305    init_test(cx, |_| {});
 6306    let mut cx = EditorTestContext::new(cx).await;
 6307
 6308    #[track_caller]
 6309    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 6310        cx.set_state(initial_state);
 6311        cx.update_editor(|e, window, cx| {
 6312            e.split_selection_into_lines(&SplitSelectionIntoLines, window, cx)
 6313        });
 6314        cx.assert_editor_state(expected_state);
 6315    }
 6316
 6317    // Selection starts and ends at the middle of lines, left-to-right
 6318    test(
 6319        &mut cx,
 6320        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 6321        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 6322    );
 6323    // Same thing, right-to-left
 6324    test(
 6325        &mut cx,
 6326        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 6327        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 6328    );
 6329
 6330    // Whole buffer, left-to-right, last line *doesn't* end with newline
 6331    test(
 6332        &mut cx,
 6333        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 6334        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 6335    );
 6336    // Same thing, right-to-left
 6337    test(
 6338        &mut cx,
 6339        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 6340        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 6341    );
 6342
 6343    // Whole buffer, left-to-right, last line ends with newline
 6344    test(
 6345        &mut cx,
 6346        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 6347        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 6348    );
 6349    // Same thing, right-to-left
 6350    test(
 6351        &mut cx,
 6352        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 6353        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 6354    );
 6355
 6356    // Starts at the end of a line, ends at the start of another
 6357    test(
 6358        &mut cx,
 6359        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 6360        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 6361    );
 6362}
 6363
 6364#[gpui::test]
 6365async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 6366    init_test(cx, |_| {});
 6367
 6368    let editor = cx.add_window(|window, cx| {
 6369        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 6370        build_editor(buffer, window, cx)
 6371    });
 6372
 6373    // setup
 6374    _ = editor.update(cx, |editor, window, cx| {
 6375        editor.fold_creases(
 6376            vec![
 6377                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 6378                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 6379                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 6380            ],
 6381            true,
 6382            window,
 6383            cx,
 6384        );
 6385        assert_eq!(
 6386            editor.display_text(cx),
 6387            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 6388        );
 6389    });
 6390
 6391    _ = editor.update(cx, |editor, window, cx| {
 6392        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6393            s.select_display_ranges([
 6394                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6395                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6396                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6397                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 6398            ])
 6399        });
 6400        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 6401        assert_eq!(
 6402            editor.display_text(cx),
 6403            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 6404        );
 6405    });
 6406    EditorTestContext::for_editor(editor, cx)
 6407        .await
 6408        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 6409
 6410    _ = editor.update(cx, |editor, window, cx| {
 6411        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6412            s.select_display_ranges([
 6413                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 6414            ])
 6415        });
 6416        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 6417        assert_eq!(
 6418            editor.display_text(cx),
 6419            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 6420        );
 6421        assert_eq!(
 6422            editor.selections.display_ranges(cx),
 6423            [
 6424                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 6425                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 6426                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 6427                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 6428                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 6429                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 6430                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 6431            ]
 6432        );
 6433    });
 6434    EditorTestContext::for_editor(editor, cx)
 6435        .await
 6436        .assert_editor_state(
 6437            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 6438        );
 6439}
 6440
 6441#[gpui::test]
 6442async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 6443    init_test(cx, |_| {});
 6444
 6445    let mut cx = EditorTestContext::new(cx).await;
 6446
 6447    cx.set_state(indoc!(
 6448        r#"abc
 6449           defˇghi
 6450
 6451           jk
 6452           nlmo
 6453           "#
 6454    ));
 6455
 6456    cx.update_editor(|editor, window, cx| {
 6457        editor.add_selection_above(&Default::default(), window, cx);
 6458    });
 6459
 6460    cx.assert_editor_state(indoc!(
 6461        r#"abcˇ
 6462           defˇghi
 6463
 6464           jk
 6465           nlmo
 6466           "#
 6467    ));
 6468
 6469    cx.update_editor(|editor, window, cx| {
 6470        editor.add_selection_above(&Default::default(), window, cx);
 6471    });
 6472
 6473    cx.assert_editor_state(indoc!(
 6474        r#"abcˇ
 6475            defˇghi
 6476
 6477            jk
 6478            nlmo
 6479            "#
 6480    ));
 6481
 6482    cx.update_editor(|editor, window, cx| {
 6483        editor.add_selection_below(&Default::default(), window, cx);
 6484    });
 6485
 6486    cx.assert_editor_state(indoc!(
 6487        r#"abc
 6488           defˇghi
 6489
 6490           jk
 6491           nlmo
 6492           "#
 6493    ));
 6494
 6495    cx.update_editor(|editor, window, cx| {
 6496        editor.undo_selection(&Default::default(), window, cx);
 6497    });
 6498
 6499    cx.assert_editor_state(indoc!(
 6500        r#"abcˇ
 6501           defˇghi
 6502
 6503           jk
 6504           nlmo
 6505           "#
 6506    ));
 6507
 6508    cx.update_editor(|editor, window, cx| {
 6509        editor.redo_selection(&Default::default(), window, cx);
 6510    });
 6511
 6512    cx.assert_editor_state(indoc!(
 6513        r#"abc
 6514           defˇghi
 6515
 6516           jk
 6517           nlmo
 6518           "#
 6519    ));
 6520
 6521    cx.update_editor(|editor, window, cx| {
 6522        editor.add_selection_below(&Default::default(), window, cx);
 6523    });
 6524
 6525    cx.assert_editor_state(indoc!(
 6526        r#"abc
 6527           defˇghi
 6528           ˇ
 6529           jk
 6530           nlmo
 6531           "#
 6532    ));
 6533
 6534    cx.update_editor(|editor, window, cx| {
 6535        editor.add_selection_below(&Default::default(), window, cx);
 6536    });
 6537
 6538    cx.assert_editor_state(indoc!(
 6539        r#"abc
 6540           defˇghi
 6541           ˇ
 6542           jkˇ
 6543           nlmo
 6544           "#
 6545    ));
 6546
 6547    cx.update_editor(|editor, window, cx| {
 6548        editor.add_selection_below(&Default::default(), window, cx);
 6549    });
 6550
 6551    cx.assert_editor_state(indoc!(
 6552        r#"abc
 6553           defˇghi
 6554           ˇ
 6555           jkˇ
 6556           nlmˇo
 6557           "#
 6558    ));
 6559
 6560    cx.update_editor(|editor, window, cx| {
 6561        editor.add_selection_below(&Default::default(), window, cx);
 6562    });
 6563
 6564    cx.assert_editor_state(indoc!(
 6565        r#"abc
 6566           defˇghi
 6567           ˇ
 6568           jkˇ
 6569           nlmˇo
 6570           ˇ"#
 6571    ));
 6572
 6573    // change selections
 6574    cx.set_state(indoc!(
 6575        r#"abc
 6576           def«ˇg»hi
 6577
 6578           jk
 6579           nlmo
 6580           "#
 6581    ));
 6582
 6583    cx.update_editor(|editor, window, cx| {
 6584        editor.add_selection_below(&Default::default(), window, cx);
 6585    });
 6586
 6587    cx.assert_editor_state(indoc!(
 6588        r#"abc
 6589           def«ˇg»hi
 6590
 6591           jk
 6592           nlm«ˇo»
 6593           "#
 6594    ));
 6595
 6596    cx.update_editor(|editor, window, cx| {
 6597        editor.add_selection_below(&Default::default(), window, cx);
 6598    });
 6599
 6600    cx.assert_editor_state(indoc!(
 6601        r#"abc
 6602           def«ˇg»hi
 6603
 6604           jk
 6605           nlm«ˇo»
 6606           "#
 6607    ));
 6608
 6609    cx.update_editor(|editor, window, cx| {
 6610        editor.add_selection_above(&Default::default(), window, cx);
 6611    });
 6612
 6613    cx.assert_editor_state(indoc!(
 6614        r#"abc
 6615           def«ˇg»hi
 6616
 6617           jk
 6618           nlmo
 6619           "#
 6620    ));
 6621
 6622    cx.update_editor(|editor, window, cx| {
 6623        editor.add_selection_above(&Default::default(), window, cx);
 6624    });
 6625
 6626    cx.assert_editor_state(indoc!(
 6627        r#"abc
 6628           def«ˇg»hi
 6629
 6630           jk
 6631           nlmo
 6632           "#
 6633    ));
 6634
 6635    // Change selections again
 6636    cx.set_state(indoc!(
 6637        r#"a«bc
 6638           defgˇ»hi
 6639
 6640           jk
 6641           nlmo
 6642           "#
 6643    ));
 6644
 6645    cx.update_editor(|editor, window, cx| {
 6646        editor.add_selection_below(&Default::default(), window, cx);
 6647    });
 6648
 6649    cx.assert_editor_state(indoc!(
 6650        r#"a«bcˇ»
 6651           d«efgˇ»hi
 6652
 6653           j«kˇ»
 6654           nlmo
 6655           "#
 6656    ));
 6657
 6658    cx.update_editor(|editor, window, cx| {
 6659        editor.add_selection_below(&Default::default(), window, cx);
 6660    });
 6661    cx.assert_editor_state(indoc!(
 6662        r#"a«bcˇ»
 6663           d«efgˇ»hi
 6664
 6665           j«kˇ»
 6666           n«lmoˇ»
 6667           "#
 6668    ));
 6669    cx.update_editor(|editor, window, cx| {
 6670        editor.add_selection_above(&Default::default(), window, cx);
 6671    });
 6672
 6673    cx.assert_editor_state(indoc!(
 6674        r#"a«bcˇ»
 6675           d«efgˇ»hi
 6676
 6677           j«kˇ»
 6678           nlmo
 6679           "#
 6680    ));
 6681
 6682    // Change selections again
 6683    cx.set_state(indoc!(
 6684        r#"abc
 6685           d«ˇefghi
 6686
 6687           jk
 6688           nlm»o
 6689           "#
 6690    ));
 6691
 6692    cx.update_editor(|editor, window, cx| {
 6693        editor.add_selection_above(&Default::default(), window, cx);
 6694    });
 6695
 6696    cx.assert_editor_state(indoc!(
 6697        r#"a«ˇbc»
 6698           d«ˇef»ghi
 6699
 6700           j«ˇk»
 6701           n«ˇlm»o
 6702           "#
 6703    ));
 6704
 6705    cx.update_editor(|editor, window, cx| {
 6706        editor.add_selection_below(&Default::default(), window, cx);
 6707    });
 6708
 6709    cx.assert_editor_state(indoc!(
 6710        r#"abc
 6711           d«ˇef»ghi
 6712
 6713           j«ˇk»
 6714           n«ˇlm»o
 6715           "#
 6716    ));
 6717}
 6718
 6719#[gpui::test]
 6720async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 6721    init_test(cx, |_| {});
 6722    let mut cx = EditorTestContext::new(cx).await;
 6723
 6724    cx.set_state(indoc!(
 6725        r#"line onˇe
 6726           liˇne two
 6727           line three
 6728           line four"#
 6729    ));
 6730
 6731    cx.update_editor(|editor, window, cx| {
 6732        editor.add_selection_below(&Default::default(), window, cx);
 6733    });
 6734
 6735    // test multiple cursors expand in the same direction
 6736    cx.assert_editor_state(indoc!(
 6737        r#"line onˇe
 6738           liˇne twˇo
 6739           liˇne three
 6740           line four"#
 6741    ));
 6742
 6743    cx.update_editor(|editor, window, cx| {
 6744        editor.add_selection_below(&Default::default(), window, cx);
 6745    });
 6746
 6747    cx.update_editor(|editor, window, cx| {
 6748        editor.add_selection_below(&Default::default(), window, cx);
 6749    });
 6750
 6751    // test multiple cursors expand below overflow
 6752    cx.assert_editor_state(indoc!(
 6753        r#"line onˇe
 6754           liˇne twˇo
 6755           liˇne thˇree
 6756           liˇne foˇur"#
 6757    ));
 6758
 6759    cx.update_editor(|editor, window, cx| {
 6760        editor.add_selection_above(&Default::default(), window, cx);
 6761    });
 6762
 6763    // test multiple cursors retrieves back correctly
 6764    cx.assert_editor_state(indoc!(
 6765        r#"line onˇe
 6766           liˇne twˇo
 6767           liˇne thˇree
 6768           line four"#
 6769    ));
 6770
 6771    cx.update_editor(|editor, window, cx| {
 6772        editor.add_selection_above(&Default::default(), window, cx);
 6773    });
 6774
 6775    cx.update_editor(|editor, window, cx| {
 6776        editor.add_selection_above(&Default::default(), window, cx);
 6777    });
 6778
 6779    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 6780    cx.assert_editor_state(indoc!(
 6781        r#"liˇne onˇe
 6782           liˇne two
 6783           line three
 6784           line four"#
 6785    ));
 6786
 6787    cx.update_editor(|editor, window, cx| {
 6788        editor.undo_selection(&Default::default(), window, cx);
 6789    });
 6790
 6791    // test undo
 6792    cx.assert_editor_state(indoc!(
 6793        r#"line onˇe
 6794           liˇne twˇo
 6795           line three
 6796           line four"#
 6797    ));
 6798
 6799    cx.update_editor(|editor, window, cx| {
 6800        editor.redo_selection(&Default::default(), window, cx);
 6801    });
 6802
 6803    // test redo
 6804    cx.assert_editor_state(indoc!(
 6805        r#"liˇne onˇe
 6806           liˇne two
 6807           line three
 6808           line four"#
 6809    ));
 6810
 6811    cx.set_state(indoc!(
 6812        r#"abcd
 6813           ef«ghˇ»
 6814           ijkl
 6815           «mˇ»nop"#
 6816    ));
 6817
 6818    cx.update_editor(|editor, window, cx| {
 6819        editor.add_selection_above(&Default::default(), window, cx);
 6820    });
 6821
 6822    // test multiple selections expand in the same direction
 6823    cx.assert_editor_state(indoc!(
 6824        r#"ab«cdˇ»
 6825           ef«ghˇ»
 6826           «iˇ»jkl
 6827           «mˇ»nop"#
 6828    ));
 6829
 6830    cx.update_editor(|editor, window, cx| {
 6831        editor.add_selection_above(&Default::default(), window, cx);
 6832    });
 6833
 6834    // test multiple selection upward overflow
 6835    cx.assert_editor_state(indoc!(
 6836        r#"ab«cdˇ»
 6837           «eˇ»f«ghˇ»
 6838           «iˇ»jkl
 6839           «mˇ»nop"#
 6840    ));
 6841
 6842    cx.update_editor(|editor, window, cx| {
 6843        editor.add_selection_below(&Default::default(), window, cx);
 6844    });
 6845
 6846    // test multiple selection retrieves back correctly
 6847    cx.assert_editor_state(indoc!(
 6848        r#"abcd
 6849           ef«ghˇ»
 6850           «iˇ»jkl
 6851           «mˇ»nop"#
 6852    ));
 6853
 6854    cx.update_editor(|editor, window, cx| {
 6855        editor.add_selection_below(&Default::default(), window, cx);
 6856    });
 6857
 6858    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 6859    cx.assert_editor_state(indoc!(
 6860        r#"abcd
 6861           ef«ghˇ»
 6862           ij«klˇ»
 6863           «mˇ»nop"#
 6864    ));
 6865
 6866    cx.update_editor(|editor, window, cx| {
 6867        editor.undo_selection(&Default::default(), window, cx);
 6868    });
 6869
 6870    // test undo
 6871    cx.assert_editor_state(indoc!(
 6872        r#"abcd
 6873           ef«ghˇ»
 6874           «iˇ»jkl
 6875           «mˇ»nop"#
 6876    ));
 6877
 6878    cx.update_editor(|editor, window, cx| {
 6879        editor.redo_selection(&Default::default(), window, cx);
 6880    });
 6881
 6882    // test redo
 6883    cx.assert_editor_state(indoc!(
 6884        r#"abcd
 6885           ef«ghˇ»
 6886           ij«klˇ»
 6887           «mˇ»nop"#
 6888    ));
 6889}
 6890
 6891#[gpui::test]
 6892async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 6893    init_test(cx, |_| {});
 6894    let mut cx = EditorTestContext::new(cx).await;
 6895
 6896    cx.set_state(indoc!(
 6897        r#"line onˇe
 6898           liˇne two
 6899           line three
 6900           line four"#
 6901    ));
 6902
 6903    cx.update_editor(|editor, window, cx| {
 6904        editor.add_selection_below(&Default::default(), window, cx);
 6905        editor.add_selection_below(&Default::default(), window, cx);
 6906        editor.add_selection_below(&Default::default(), window, cx);
 6907    });
 6908
 6909    // initial state with two multi cursor groups
 6910    cx.assert_editor_state(indoc!(
 6911        r#"line onˇe
 6912           liˇne twˇo
 6913           liˇne thˇree
 6914           liˇne foˇur"#
 6915    ));
 6916
 6917    // add single cursor in middle - simulate opt click
 6918    cx.update_editor(|editor, window, cx| {
 6919        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 6920        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 6921        editor.end_selection(window, cx);
 6922    });
 6923
 6924    cx.assert_editor_state(indoc!(
 6925        r#"line onˇe
 6926           liˇne twˇo
 6927           liˇneˇ thˇree
 6928           liˇne foˇur"#
 6929    ));
 6930
 6931    cx.update_editor(|editor, window, cx| {
 6932        editor.add_selection_above(&Default::default(), window, cx);
 6933    });
 6934
 6935    // test new added selection expands above and existing selection shrinks
 6936    cx.assert_editor_state(indoc!(
 6937        r#"line onˇe
 6938           liˇneˇ twˇo
 6939           liˇneˇ thˇree
 6940           line four"#
 6941    ));
 6942
 6943    cx.update_editor(|editor, window, cx| {
 6944        editor.add_selection_above(&Default::default(), window, cx);
 6945    });
 6946
 6947    // test new added selection expands above and existing selection shrinks
 6948    cx.assert_editor_state(indoc!(
 6949        r#"lineˇ onˇe
 6950           liˇneˇ twˇo
 6951           lineˇ three
 6952           line four"#
 6953    ));
 6954
 6955    // intial state with two selection groups
 6956    cx.set_state(indoc!(
 6957        r#"abcd
 6958           ef«ghˇ»
 6959           ijkl
 6960           «mˇ»nop"#
 6961    ));
 6962
 6963    cx.update_editor(|editor, window, cx| {
 6964        editor.add_selection_above(&Default::default(), window, cx);
 6965        editor.add_selection_above(&Default::default(), window, cx);
 6966    });
 6967
 6968    cx.assert_editor_state(indoc!(
 6969        r#"ab«cdˇ»
 6970           «eˇ»f«ghˇ»
 6971           «iˇ»jkl
 6972           «mˇ»nop"#
 6973    ));
 6974
 6975    // add single selection in middle - simulate opt drag
 6976    cx.update_editor(|editor, window, cx| {
 6977        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 6978        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 6979        editor.update_selection(
 6980            DisplayPoint::new(DisplayRow(2), 4),
 6981            0,
 6982            gpui::Point::<f32>::default(),
 6983            window,
 6984            cx,
 6985        );
 6986        editor.end_selection(window, cx);
 6987    });
 6988
 6989    cx.assert_editor_state(indoc!(
 6990        r#"ab«cdˇ»
 6991           «eˇ»f«ghˇ»
 6992           «iˇ»jk«lˇ»
 6993           «mˇ»nop"#
 6994    ));
 6995
 6996    cx.update_editor(|editor, window, cx| {
 6997        editor.add_selection_below(&Default::default(), window, cx);
 6998    });
 6999
 7000    // test new added selection expands below, others shrinks from above
 7001    cx.assert_editor_state(indoc!(
 7002        r#"abcd
 7003           ef«ghˇ»
 7004           «iˇ»jk«lˇ»
 7005           «mˇ»no«pˇ»"#
 7006    ));
 7007}
 7008
 7009#[gpui::test]
 7010async fn test_select_next(cx: &mut TestAppContext) {
 7011    init_test(cx, |_| {});
 7012
 7013    let mut cx = EditorTestContext::new(cx).await;
 7014    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7015
 7016    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7017        .unwrap();
 7018    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7019
 7020    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7021        .unwrap();
 7022    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 7023
 7024    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7025    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7026
 7027    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7028    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 7029
 7030    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7031        .unwrap();
 7032    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7033
 7034    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7035        .unwrap();
 7036    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7037
 7038    // Test selection direction should be preserved
 7039    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 7040
 7041    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7042        .unwrap();
 7043    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 7044}
 7045
 7046#[gpui::test]
 7047async fn test_select_all_matches(cx: &mut TestAppContext) {
 7048    init_test(cx, |_| {});
 7049
 7050    let mut cx = EditorTestContext::new(cx).await;
 7051
 7052    // Test caret-only selections
 7053    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7054    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7055        .unwrap();
 7056    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7057
 7058    // Test left-to-right selections
 7059    cx.set_state("abc\n«abcˇ»\nabc");
 7060    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7061        .unwrap();
 7062    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 7063
 7064    // Test right-to-left selections
 7065    cx.set_state("abc\n«ˇabc»\nabc");
 7066    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7067        .unwrap();
 7068    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 7069
 7070    // Test selecting whitespace with caret selection
 7071    cx.set_state("abc\nˇ   abc\nabc");
 7072    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7073        .unwrap();
 7074    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 7075
 7076    // Test selecting whitespace with left-to-right selection
 7077    cx.set_state("abc\n«ˇ  »abc\nabc");
 7078    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7079        .unwrap();
 7080    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 7081
 7082    // Test no matches with right-to-left selection
 7083    cx.set_state("abc\n«  ˇ»abc\nabc");
 7084    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7085        .unwrap();
 7086    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 7087
 7088    // Test with a single word and clip_at_line_ends=true (#29823)
 7089    cx.set_state("aˇbc");
 7090    cx.update_editor(|e, window, cx| {
 7091        e.set_clip_at_line_ends(true, cx);
 7092        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 7093        e.set_clip_at_line_ends(false, cx);
 7094    });
 7095    cx.assert_editor_state("«abcˇ»");
 7096}
 7097
 7098#[gpui::test]
 7099async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 7100    init_test(cx, |_| {});
 7101
 7102    let mut cx = EditorTestContext::new(cx).await;
 7103
 7104    let large_body_1 = "\nd".repeat(200);
 7105    let large_body_2 = "\ne".repeat(200);
 7106
 7107    cx.set_state(&format!(
 7108        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 7109    ));
 7110    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 7111        let scroll_position = editor.scroll_position(cx);
 7112        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 7113        scroll_position
 7114    });
 7115
 7116    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7117        .unwrap();
 7118    cx.assert_editor_state(&format!(
 7119        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 7120    ));
 7121    let scroll_position_after_selection =
 7122        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 7123    assert_eq!(
 7124        initial_scroll_position, scroll_position_after_selection,
 7125        "Scroll position should not change after selecting all matches"
 7126    );
 7127}
 7128
 7129#[gpui::test]
 7130async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 7131    init_test(cx, |_| {});
 7132
 7133    let mut cx = EditorLspTestContext::new_rust(
 7134        lsp::ServerCapabilities {
 7135            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 7136            ..Default::default()
 7137        },
 7138        cx,
 7139    )
 7140    .await;
 7141
 7142    cx.set_state(indoc! {"
 7143        line 1
 7144        line 2
 7145        linˇe 3
 7146        line 4
 7147        line 5
 7148    "});
 7149
 7150    // Make an edit
 7151    cx.update_editor(|editor, window, cx| {
 7152        editor.handle_input("X", window, cx);
 7153    });
 7154
 7155    // Move cursor to a different position
 7156    cx.update_editor(|editor, window, cx| {
 7157        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7158            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 7159        });
 7160    });
 7161
 7162    cx.assert_editor_state(indoc! {"
 7163        line 1
 7164        line 2
 7165        linXe 3
 7166        line 4
 7167        liˇne 5
 7168    "});
 7169
 7170    cx.lsp
 7171        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 7172            Ok(Some(vec![lsp::TextEdit::new(
 7173                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 7174                "PREFIX ".to_string(),
 7175            )]))
 7176        });
 7177
 7178    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 7179        .unwrap()
 7180        .await
 7181        .unwrap();
 7182
 7183    cx.assert_editor_state(indoc! {"
 7184        PREFIX line 1
 7185        line 2
 7186        linXe 3
 7187        line 4
 7188        liˇne 5
 7189    "});
 7190
 7191    // Undo formatting
 7192    cx.update_editor(|editor, window, cx| {
 7193        editor.undo(&Default::default(), window, cx);
 7194    });
 7195
 7196    // Verify cursor moved back to position after edit
 7197    cx.assert_editor_state(indoc! {"
 7198        line 1
 7199        line 2
 7200        linXˇe 3
 7201        line 4
 7202        line 5
 7203    "});
 7204}
 7205
 7206#[gpui::test]
 7207async fn test_undo_inline_completion_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 7208    init_test(cx, |_| {});
 7209
 7210    let mut cx = EditorTestContext::new(cx).await;
 7211
 7212    let provider = cx.new(|_| FakeInlineCompletionProvider::default());
 7213    cx.update_editor(|editor, window, cx| {
 7214        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 7215    });
 7216
 7217    cx.set_state(indoc! {"
 7218        line 1
 7219        line 2
 7220        linˇe 3
 7221        line 4
 7222        line 5
 7223        line 6
 7224        line 7
 7225        line 8
 7226        line 9
 7227        line 10
 7228    "});
 7229
 7230    let snapshot = cx.buffer_snapshot();
 7231    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 7232
 7233    cx.update(|_, cx| {
 7234        provider.update(cx, |provider, _| {
 7235            provider.set_inline_completion(Some(inline_completion::InlineCompletion {
 7236                id: None,
 7237                edits: vec![(edit_position..edit_position, "X".into())],
 7238                edit_preview: None,
 7239            }))
 7240        })
 7241    });
 7242
 7243    cx.update_editor(|editor, window, cx| editor.update_visible_inline_completion(window, cx));
 7244    cx.update_editor(|editor, window, cx| {
 7245        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 7246    });
 7247
 7248    cx.assert_editor_state(indoc! {"
 7249        line 1
 7250        line 2
 7251        lineXˇ 3
 7252        line 4
 7253        line 5
 7254        line 6
 7255        line 7
 7256        line 8
 7257        line 9
 7258        line 10
 7259    "});
 7260
 7261    cx.update_editor(|editor, window, cx| {
 7262        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7263            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 7264        });
 7265    });
 7266
 7267    cx.assert_editor_state(indoc! {"
 7268        line 1
 7269        line 2
 7270        lineX 3
 7271        line 4
 7272        line 5
 7273        line 6
 7274        line 7
 7275        line 8
 7276        line 9
 7277        liˇne 10
 7278    "});
 7279
 7280    cx.update_editor(|editor, window, cx| {
 7281        editor.undo(&Default::default(), window, cx);
 7282    });
 7283
 7284    cx.assert_editor_state(indoc! {"
 7285        line 1
 7286        line 2
 7287        lineˇ 3
 7288        line 4
 7289        line 5
 7290        line 6
 7291        line 7
 7292        line 8
 7293        line 9
 7294        line 10
 7295    "});
 7296}
 7297
 7298#[gpui::test]
 7299async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 7300    init_test(cx, |_| {});
 7301
 7302    let mut cx = EditorTestContext::new(cx).await;
 7303    cx.set_state(
 7304        r#"let foo = 2;
 7305lˇet foo = 2;
 7306let fooˇ = 2;
 7307let foo = 2;
 7308let foo = ˇ2;"#,
 7309    );
 7310
 7311    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7312        .unwrap();
 7313    cx.assert_editor_state(
 7314        r#"let foo = 2;
 7315«letˇ» foo = 2;
 7316let «fooˇ» = 2;
 7317let foo = 2;
 7318let foo = «2ˇ»;"#,
 7319    );
 7320
 7321    // noop for multiple selections with different contents
 7322    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7323        .unwrap();
 7324    cx.assert_editor_state(
 7325        r#"let foo = 2;
 7326«letˇ» foo = 2;
 7327let «fooˇ» = 2;
 7328let foo = 2;
 7329let foo = «2ˇ»;"#,
 7330    );
 7331
 7332    // Test last selection direction should be preserved
 7333    cx.set_state(
 7334        r#"let foo = 2;
 7335let foo = 2;
 7336let «fooˇ» = 2;
 7337let «ˇfoo» = 2;
 7338let foo = 2;"#,
 7339    );
 7340
 7341    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7342        .unwrap();
 7343    cx.assert_editor_state(
 7344        r#"let foo = 2;
 7345let foo = 2;
 7346let «fooˇ» = 2;
 7347let «ˇfoo» = 2;
 7348let «ˇfoo» = 2;"#,
 7349    );
 7350}
 7351
 7352#[gpui::test]
 7353async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 7354    init_test(cx, |_| {});
 7355
 7356    let mut cx =
 7357        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 7358
 7359    cx.assert_editor_state(indoc! {"
 7360        ˇbbb
 7361        ccc
 7362
 7363        bbb
 7364        ccc
 7365        "});
 7366    cx.dispatch_action(SelectPrevious::default());
 7367    cx.assert_editor_state(indoc! {"
 7368                «bbbˇ»
 7369                ccc
 7370
 7371                bbb
 7372                ccc
 7373                "});
 7374    cx.dispatch_action(SelectPrevious::default());
 7375    cx.assert_editor_state(indoc! {"
 7376                «bbbˇ»
 7377                ccc
 7378
 7379                «bbbˇ»
 7380                ccc
 7381                "});
 7382}
 7383
 7384#[gpui::test]
 7385async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 7386    init_test(cx, |_| {});
 7387
 7388    let mut cx = EditorTestContext::new(cx).await;
 7389    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7390
 7391    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7392        .unwrap();
 7393    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7394
 7395    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7396        .unwrap();
 7397    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 7398
 7399    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7400    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7401
 7402    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7403    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 7404
 7405    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7406        .unwrap();
 7407    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 7408
 7409    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7410        .unwrap();
 7411    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7412}
 7413
 7414#[gpui::test]
 7415async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 7416    init_test(cx, |_| {});
 7417
 7418    let mut cx = EditorTestContext::new(cx).await;
 7419    cx.set_state("");
 7420
 7421    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7422        .unwrap();
 7423    cx.assert_editor_state("«aˇ»");
 7424    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7425        .unwrap();
 7426    cx.assert_editor_state("«aˇ»");
 7427}
 7428
 7429#[gpui::test]
 7430async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 7431    init_test(cx, |_| {});
 7432
 7433    let mut cx = EditorTestContext::new(cx).await;
 7434    cx.set_state(
 7435        r#"let foo = 2;
 7436lˇet foo = 2;
 7437let fooˇ = 2;
 7438let foo = 2;
 7439let foo = ˇ2;"#,
 7440    );
 7441
 7442    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7443        .unwrap();
 7444    cx.assert_editor_state(
 7445        r#"let foo = 2;
 7446«letˇ» foo = 2;
 7447let «fooˇ» = 2;
 7448let foo = 2;
 7449let foo = «2ˇ»;"#,
 7450    );
 7451
 7452    // noop for multiple selections with different contents
 7453    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7454        .unwrap();
 7455    cx.assert_editor_state(
 7456        r#"let foo = 2;
 7457«letˇ» foo = 2;
 7458let «fooˇ» = 2;
 7459let foo = 2;
 7460let foo = «2ˇ»;"#,
 7461    );
 7462}
 7463
 7464#[gpui::test]
 7465async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 7466    init_test(cx, |_| {});
 7467
 7468    let mut cx = EditorTestContext::new(cx).await;
 7469    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 7470
 7471    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7472        .unwrap();
 7473    // selection direction is preserved
 7474    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 7475
 7476    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7477        .unwrap();
 7478    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7479
 7480    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7481    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 7482
 7483    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7484    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7485
 7486    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7487        .unwrap();
 7488    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 7489
 7490    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7491        .unwrap();
 7492    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 7493}
 7494
 7495#[gpui::test]
 7496async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 7497    init_test(cx, |_| {});
 7498
 7499    let language = Arc::new(Language::new(
 7500        LanguageConfig::default(),
 7501        Some(tree_sitter_rust::LANGUAGE.into()),
 7502    ));
 7503
 7504    let text = r#"
 7505        use mod1::mod2::{mod3, mod4};
 7506
 7507        fn fn_1(param1: bool, param2: &str) {
 7508            let var1 = "text";
 7509        }
 7510    "#
 7511    .unindent();
 7512
 7513    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7514    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7515    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7516
 7517    editor
 7518        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7519        .await;
 7520
 7521    editor.update_in(cx, |editor, window, cx| {
 7522        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7523            s.select_display_ranges([
 7524                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 7525                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 7526                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 7527            ]);
 7528        });
 7529        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7530    });
 7531    editor.update(cx, |editor, cx| {
 7532        assert_text_with_selections(
 7533            editor,
 7534            indoc! {r#"
 7535                use mod1::mod2::{mod3, «mod4ˇ»};
 7536
 7537                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7538                    let var1 = "«ˇtext»";
 7539                }
 7540            "#},
 7541            cx,
 7542        );
 7543    });
 7544
 7545    editor.update_in(cx, |editor, window, cx| {
 7546        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7547    });
 7548    editor.update(cx, |editor, cx| {
 7549        assert_text_with_selections(
 7550            editor,
 7551            indoc! {r#"
 7552                use mod1::mod2::«{mod3, mod4}ˇ»;
 7553
 7554                «ˇfn fn_1(param1: bool, param2: &str) {
 7555                    let var1 = "text";
 7556 7557            "#},
 7558            cx,
 7559        );
 7560    });
 7561
 7562    editor.update_in(cx, |editor, window, cx| {
 7563        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7564    });
 7565    assert_eq!(
 7566        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7567        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7568    );
 7569
 7570    // Trying to expand the selected syntax node one more time has no effect.
 7571    editor.update_in(cx, |editor, window, cx| {
 7572        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7573    });
 7574    assert_eq!(
 7575        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7576        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7577    );
 7578
 7579    editor.update_in(cx, |editor, window, cx| {
 7580        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7581    });
 7582    editor.update(cx, |editor, cx| {
 7583        assert_text_with_selections(
 7584            editor,
 7585            indoc! {r#"
 7586                use mod1::mod2::«{mod3, mod4}ˇ»;
 7587
 7588                «ˇfn fn_1(param1: bool, param2: &str) {
 7589                    let var1 = "text";
 7590 7591            "#},
 7592            cx,
 7593        );
 7594    });
 7595
 7596    editor.update_in(cx, |editor, window, cx| {
 7597        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7598    });
 7599    editor.update(cx, |editor, cx| {
 7600        assert_text_with_selections(
 7601            editor,
 7602            indoc! {r#"
 7603                use mod1::mod2::{mod3, «mod4ˇ»};
 7604
 7605                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7606                    let var1 = "«ˇtext»";
 7607                }
 7608            "#},
 7609            cx,
 7610        );
 7611    });
 7612
 7613    editor.update_in(cx, |editor, window, cx| {
 7614        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7615    });
 7616    editor.update(cx, |editor, cx| {
 7617        assert_text_with_selections(
 7618            editor,
 7619            indoc! {r#"
 7620                use mod1::mod2::{mod3, mo«ˇ»d4};
 7621
 7622                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7623                    let var1 = "te«ˇ»xt";
 7624                }
 7625            "#},
 7626            cx,
 7627        );
 7628    });
 7629
 7630    // Trying to shrink the selected syntax node one more time has no effect.
 7631    editor.update_in(cx, |editor, window, cx| {
 7632        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7633    });
 7634    editor.update_in(cx, |editor, _, cx| {
 7635        assert_text_with_selections(
 7636            editor,
 7637            indoc! {r#"
 7638                use mod1::mod2::{mod3, mo«ˇ»d4};
 7639
 7640                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7641                    let var1 = "te«ˇ»xt";
 7642                }
 7643            "#},
 7644            cx,
 7645        );
 7646    });
 7647
 7648    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 7649    // a fold.
 7650    editor.update_in(cx, |editor, window, cx| {
 7651        editor.fold_creases(
 7652            vec![
 7653                Crease::simple(
 7654                    Point::new(0, 21)..Point::new(0, 24),
 7655                    FoldPlaceholder::test(),
 7656                ),
 7657                Crease::simple(
 7658                    Point::new(3, 20)..Point::new(3, 22),
 7659                    FoldPlaceholder::test(),
 7660                ),
 7661            ],
 7662            true,
 7663            window,
 7664            cx,
 7665        );
 7666        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7667    });
 7668    editor.update(cx, |editor, cx| {
 7669        assert_text_with_selections(
 7670            editor,
 7671            indoc! {r#"
 7672                use mod1::mod2::«{mod3, mod4}ˇ»;
 7673
 7674                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7675                    let var1 = "«ˇtext»";
 7676                }
 7677            "#},
 7678            cx,
 7679        );
 7680    });
 7681}
 7682
 7683#[gpui::test]
 7684async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 7685    init_test(cx, |_| {});
 7686
 7687    let language = Arc::new(Language::new(
 7688        LanguageConfig::default(),
 7689        Some(tree_sitter_rust::LANGUAGE.into()),
 7690    ));
 7691
 7692    let text = "let a = 2;";
 7693
 7694    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7695    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7696    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7697
 7698    editor
 7699        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7700        .await;
 7701
 7702    // Test case 1: Cursor at end of word
 7703    editor.update_in(cx, |editor, window, cx| {
 7704        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7705            s.select_display_ranges([
 7706                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 7707            ]);
 7708        });
 7709    });
 7710    editor.update(cx, |editor, cx| {
 7711        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 7712    });
 7713    editor.update_in(cx, |editor, window, cx| {
 7714        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7715    });
 7716    editor.update(cx, |editor, cx| {
 7717        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 7718    });
 7719    editor.update_in(cx, |editor, window, cx| {
 7720        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7721    });
 7722    editor.update(cx, |editor, cx| {
 7723        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7724    });
 7725
 7726    // Test case 2: Cursor at end of statement
 7727    editor.update_in(cx, |editor, window, cx| {
 7728        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7729            s.select_display_ranges([
 7730                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 7731            ]);
 7732        });
 7733    });
 7734    editor.update(cx, |editor, cx| {
 7735        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 7736    });
 7737    editor.update_in(cx, |editor, window, cx| {
 7738        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7739    });
 7740    editor.update(cx, |editor, cx| {
 7741        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7742    });
 7743}
 7744
 7745#[gpui::test]
 7746async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 7747    init_test(cx, |_| {});
 7748
 7749    let language = Arc::new(Language::new(
 7750        LanguageConfig::default(),
 7751        Some(tree_sitter_rust::LANGUAGE.into()),
 7752    ));
 7753
 7754    let text = r#"
 7755        use mod1::mod2::{mod3, mod4};
 7756
 7757        fn fn_1(param1: bool, param2: &str) {
 7758            let var1 = "hello world";
 7759        }
 7760    "#
 7761    .unindent();
 7762
 7763    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7764    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7765    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7766
 7767    editor
 7768        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7769        .await;
 7770
 7771    // Test 1: Cursor on a letter of a string word
 7772    editor.update_in(cx, |editor, window, cx| {
 7773        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7774            s.select_display_ranges([
 7775                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 7776            ]);
 7777        });
 7778    });
 7779    editor.update_in(cx, |editor, window, cx| {
 7780        assert_text_with_selections(
 7781            editor,
 7782            indoc! {r#"
 7783                use mod1::mod2::{mod3, mod4};
 7784
 7785                fn fn_1(param1: bool, param2: &str) {
 7786                    let var1 = "hˇello world";
 7787                }
 7788            "#},
 7789            cx,
 7790        );
 7791        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7792        assert_text_with_selections(
 7793            editor,
 7794            indoc! {r#"
 7795                use mod1::mod2::{mod3, mod4};
 7796
 7797                fn fn_1(param1: bool, param2: &str) {
 7798                    let var1 = "«ˇhello» world";
 7799                }
 7800            "#},
 7801            cx,
 7802        );
 7803    });
 7804
 7805    // Test 2: Partial selection within a word
 7806    editor.update_in(cx, |editor, window, cx| {
 7807        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7808            s.select_display_ranges([
 7809                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 7810            ]);
 7811        });
 7812    });
 7813    editor.update_in(cx, |editor, window, cx| {
 7814        assert_text_with_selections(
 7815            editor,
 7816            indoc! {r#"
 7817                use mod1::mod2::{mod3, mod4};
 7818
 7819                fn fn_1(param1: bool, param2: &str) {
 7820                    let var1 = "h«elˇ»lo world";
 7821                }
 7822            "#},
 7823            cx,
 7824        );
 7825        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7826        assert_text_with_selections(
 7827            editor,
 7828            indoc! {r#"
 7829                use mod1::mod2::{mod3, mod4};
 7830
 7831                fn fn_1(param1: bool, param2: &str) {
 7832                    let var1 = "«ˇhello» world";
 7833                }
 7834            "#},
 7835            cx,
 7836        );
 7837    });
 7838
 7839    // Test 3: Complete word already selected
 7840    editor.update_in(cx, |editor, window, cx| {
 7841        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7842            s.select_display_ranges([
 7843                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 7844            ]);
 7845        });
 7846    });
 7847    editor.update_in(cx, |editor, window, cx| {
 7848        assert_text_with_selections(
 7849            editor,
 7850            indoc! {r#"
 7851                use mod1::mod2::{mod3, mod4};
 7852
 7853                fn fn_1(param1: bool, param2: &str) {
 7854                    let var1 = "«helloˇ» world";
 7855                }
 7856            "#},
 7857            cx,
 7858        );
 7859        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7860        assert_text_with_selections(
 7861            editor,
 7862            indoc! {r#"
 7863                use mod1::mod2::{mod3, mod4};
 7864
 7865                fn fn_1(param1: bool, param2: &str) {
 7866                    let var1 = "«hello worldˇ»";
 7867                }
 7868            "#},
 7869            cx,
 7870        );
 7871    });
 7872
 7873    // Test 4: Selection spanning across words
 7874    editor.update_in(cx, |editor, window, cx| {
 7875        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7876            s.select_display_ranges([
 7877                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 7878            ]);
 7879        });
 7880    });
 7881    editor.update_in(cx, |editor, window, cx| {
 7882        assert_text_with_selections(
 7883            editor,
 7884            indoc! {r#"
 7885                use mod1::mod2::{mod3, mod4};
 7886
 7887                fn fn_1(param1: bool, param2: &str) {
 7888                    let var1 = "hel«lo woˇ»rld";
 7889                }
 7890            "#},
 7891            cx,
 7892        );
 7893        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7894        assert_text_with_selections(
 7895            editor,
 7896            indoc! {r#"
 7897                use mod1::mod2::{mod3, mod4};
 7898
 7899                fn fn_1(param1: bool, param2: &str) {
 7900                    let var1 = "«ˇhello world»";
 7901                }
 7902            "#},
 7903            cx,
 7904        );
 7905    });
 7906
 7907    // Test 5: Expansion beyond string
 7908    editor.update_in(cx, |editor, window, cx| {
 7909        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7910        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7911        assert_text_with_selections(
 7912            editor,
 7913            indoc! {r#"
 7914                use mod1::mod2::{mod3, mod4};
 7915
 7916                fn fn_1(param1: bool, param2: &str) {
 7917                    «ˇlet var1 = "hello world";»
 7918                }
 7919            "#},
 7920            cx,
 7921        );
 7922    });
 7923}
 7924
 7925#[gpui::test]
 7926async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 7927    init_test(cx, |_| {});
 7928
 7929    let base_text = r#"
 7930        impl A {
 7931            // this is an uncommitted comment
 7932
 7933            fn b() {
 7934                c();
 7935            }
 7936
 7937            // this is another uncommitted comment
 7938
 7939            fn d() {
 7940                // e
 7941                // f
 7942            }
 7943        }
 7944
 7945        fn g() {
 7946            // h
 7947        }
 7948    "#
 7949    .unindent();
 7950
 7951    let text = r#"
 7952        ˇimpl A {
 7953
 7954            fn b() {
 7955                c();
 7956            }
 7957
 7958            fn d() {
 7959                // e
 7960                // f
 7961            }
 7962        }
 7963
 7964        fn g() {
 7965            // h
 7966        }
 7967    "#
 7968    .unindent();
 7969
 7970    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 7971    cx.set_state(&text);
 7972    cx.set_head_text(&base_text);
 7973    cx.update_editor(|editor, window, cx| {
 7974        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 7975    });
 7976
 7977    cx.assert_state_with_diff(
 7978        "
 7979        ˇimpl A {
 7980      -     // this is an uncommitted comment
 7981
 7982            fn b() {
 7983                c();
 7984            }
 7985
 7986      -     // this is another uncommitted comment
 7987      -
 7988            fn d() {
 7989                // e
 7990                // f
 7991            }
 7992        }
 7993
 7994        fn g() {
 7995            // h
 7996        }
 7997    "
 7998        .unindent(),
 7999    );
 8000
 8001    let expected_display_text = "
 8002        impl A {
 8003            // this is an uncommitted comment
 8004
 8005            fn b() {
 8006 8007            }
 8008
 8009            // this is another uncommitted comment
 8010
 8011            fn d() {
 8012 8013            }
 8014        }
 8015
 8016        fn g() {
 8017 8018        }
 8019        "
 8020    .unindent();
 8021
 8022    cx.update_editor(|editor, window, cx| {
 8023        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 8024        assert_eq!(editor.display_text(cx), expected_display_text);
 8025    });
 8026}
 8027
 8028#[gpui::test]
 8029async fn test_autoindent(cx: &mut TestAppContext) {
 8030    init_test(cx, |_| {});
 8031
 8032    let language = Arc::new(
 8033        Language::new(
 8034            LanguageConfig {
 8035                brackets: BracketPairConfig {
 8036                    pairs: vec![
 8037                        BracketPair {
 8038                            start: "{".to_string(),
 8039                            end: "}".to_string(),
 8040                            close: false,
 8041                            surround: false,
 8042                            newline: true,
 8043                        },
 8044                        BracketPair {
 8045                            start: "(".to_string(),
 8046                            end: ")".to_string(),
 8047                            close: false,
 8048                            surround: false,
 8049                            newline: true,
 8050                        },
 8051                    ],
 8052                    ..Default::default()
 8053                },
 8054                ..Default::default()
 8055            },
 8056            Some(tree_sitter_rust::LANGUAGE.into()),
 8057        )
 8058        .with_indents_query(
 8059            r#"
 8060                (_ "(" ")" @end) @indent
 8061                (_ "{" "}" @end) @indent
 8062            "#,
 8063        )
 8064        .unwrap(),
 8065    );
 8066
 8067    let text = "fn a() {}";
 8068
 8069    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8070    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8071    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8072    editor
 8073        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8074        .await;
 8075
 8076    editor.update_in(cx, |editor, window, cx| {
 8077        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8078            s.select_ranges([5..5, 8..8, 9..9])
 8079        });
 8080        editor.newline(&Newline, window, cx);
 8081        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 8082        assert_eq!(
 8083            editor.selections.ranges(cx),
 8084            &[
 8085                Point::new(1, 4)..Point::new(1, 4),
 8086                Point::new(3, 4)..Point::new(3, 4),
 8087                Point::new(5, 0)..Point::new(5, 0)
 8088            ]
 8089        );
 8090    });
 8091}
 8092
 8093#[gpui::test]
 8094async fn test_autoindent_selections(cx: &mut TestAppContext) {
 8095    init_test(cx, |_| {});
 8096
 8097    {
 8098        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 8099        cx.set_state(indoc! {"
 8100            impl A {
 8101
 8102                fn b() {}
 8103
 8104            «fn c() {
 8105
 8106            }ˇ»
 8107            }
 8108        "});
 8109
 8110        cx.update_editor(|editor, window, cx| {
 8111            editor.autoindent(&Default::default(), window, cx);
 8112        });
 8113
 8114        cx.assert_editor_state(indoc! {"
 8115            impl A {
 8116
 8117                fn b() {}
 8118
 8119                «fn c() {
 8120
 8121                }ˇ»
 8122            }
 8123        "});
 8124    }
 8125
 8126    {
 8127        let mut cx = EditorTestContext::new_multibuffer(
 8128            cx,
 8129            [indoc! { "
 8130                impl A {
 8131                «
 8132                // a
 8133                fn b(){}
 8134                »
 8135                «
 8136                    }
 8137                    fn c(){}
 8138                »
 8139            "}],
 8140        );
 8141
 8142        let buffer = cx.update_editor(|editor, _, cx| {
 8143            let buffer = editor.buffer().update(cx, |buffer, _| {
 8144                buffer.all_buffers().iter().next().unwrap().clone()
 8145            });
 8146            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 8147            buffer
 8148        });
 8149
 8150        cx.run_until_parked();
 8151        cx.update_editor(|editor, window, cx| {
 8152            editor.select_all(&Default::default(), window, cx);
 8153            editor.autoindent(&Default::default(), window, cx)
 8154        });
 8155        cx.run_until_parked();
 8156
 8157        cx.update(|_, cx| {
 8158            assert_eq!(
 8159                buffer.read(cx).text(),
 8160                indoc! { "
 8161                    impl A {
 8162
 8163                        // a
 8164                        fn b(){}
 8165
 8166
 8167                    }
 8168                    fn c(){}
 8169
 8170                " }
 8171            )
 8172        });
 8173    }
 8174}
 8175
 8176#[gpui::test]
 8177async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 8178    init_test(cx, |_| {});
 8179
 8180    let mut cx = EditorTestContext::new(cx).await;
 8181
 8182    let language = Arc::new(Language::new(
 8183        LanguageConfig {
 8184            brackets: BracketPairConfig {
 8185                pairs: vec![
 8186                    BracketPair {
 8187                        start: "{".to_string(),
 8188                        end: "}".to_string(),
 8189                        close: true,
 8190                        surround: true,
 8191                        newline: true,
 8192                    },
 8193                    BracketPair {
 8194                        start: "(".to_string(),
 8195                        end: ")".to_string(),
 8196                        close: true,
 8197                        surround: true,
 8198                        newline: true,
 8199                    },
 8200                    BracketPair {
 8201                        start: "/*".to_string(),
 8202                        end: " */".to_string(),
 8203                        close: true,
 8204                        surround: true,
 8205                        newline: true,
 8206                    },
 8207                    BracketPair {
 8208                        start: "[".to_string(),
 8209                        end: "]".to_string(),
 8210                        close: false,
 8211                        surround: false,
 8212                        newline: true,
 8213                    },
 8214                    BracketPair {
 8215                        start: "\"".to_string(),
 8216                        end: "\"".to_string(),
 8217                        close: true,
 8218                        surround: true,
 8219                        newline: false,
 8220                    },
 8221                    BracketPair {
 8222                        start: "<".to_string(),
 8223                        end: ">".to_string(),
 8224                        close: false,
 8225                        surround: true,
 8226                        newline: true,
 8227                    },
 8228                ],
 8229                ..Default::default()
 8230            },
 8231            autoclose_before: "})]".to_string(),
 8232            ..Default::default()
 8233        },
 8234        Some(tree_sitter_rust::LANGUAGE.into()),
 8235    ));
 8236
 8237    cx.language_registry().add(language.clone());
 8238    cx.update_buffer(|buffer, cx| {
 8239        buffer.set_language(Some(language), cx);
 8240    });
 8241
 8242    cx.set_state(
 8243        &r#"
 8244            🏀ˇ
 8245            εˇ
 8246            ❤️ˇ
 8247        "#
 8248        .unindent(),
 8249    );
 8250
 8251    // autoclose multiple nested brackets at multiple cursors
 8252    cx.update_editor(|editor, window, cx| {
 8253        editor.handle_input("{", window, cx);
 8254        editor.handle_input("{", window, cx);
 8255        editor.handle_input("{", window, cx);
 8256    });
 8257    cx.assert_editor_state(
 8258        &"
 8259            🏀{{{ˇ}}}
 8260            ε{{{ˇ}}}
 8261            ❤️{{{ˇ}}}
 8262        "
 8263        .unindent(),
 8264    );
 8265
 8266    // insert a different closing bracket
 8267    cx.update_editor(|editor, window, cx| {
 8268        editor.handle_input(")", window, cx);
 8269    });
 8270    cx.assert_editor_state(
 8271        &"
 8272            🏀{{{)ˇ}}}
 8273            ε{{{)ˇ}}}
 8274            ❤️{{{)ˇ}}}
 8275        "
 8276        .unindent(),
 8277    );
 8278
 8279    // skip over the auto-closed brackets when typing a closing bracket
 8280    cx.update_editor(|editor, window, cx| {
 8281        editor.move_right(&MoveRight, window, cx);
 8282        editor.handle_input("}", window, cx);
 8283        editor.handle_input("}", window, cx);
 8284        editor.handle_input("}", window, cx);
 8285    });
 8286    cx.assert_editor_state(
 8287        &"
 8288            🏀{{{)}}}}ˇ
 8289            ε{{{)}}}}ˇ
 8290            ❤️{{{)}}}}ˇ
 8291        "
 8292        .unindent(),
 8293    );
 8294
 8295    // autoclose multi-character pairs
 8296    cx.set_state(
 8297        &"
 8298            ˇ
 8299            ˇ
 8300        "
 8301        .unindent(),
 8302    );
 8303    cx.update_editor(|editor, window, cx| {
 8304        editor.handle_input("/", window, cx);
 8305        editor.handle_input("*", window, cx);
 8306    });
 8307    cx.assert_editor_state(
 8308        &"
 8309            /*ˇ */
 8310            /*ˇ */
 8311        "
 8312        .unindent(),
 8313    );
 8314
 8315    // one cursor autocloses a multi-character pair, one cursor
 8316    // does not autoclose.
 8317    cx.set_state(
 8318        &"
 8319 8320            ˇ
 8321        "
 8322        .unindent(),
 8323    );
 8324    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 8325    cx.assert_editor_state(
 8326        &"
 8327            /*ˇ */
 8328 8329        "
 8330        .unindent(),
 8331    );
 8332
 8333    // Don't autoclose if the next character isn't whitespace and isn't
 8334    // listed in the language's "autoclose_before" section.
 8335    cx.set_state("ˇa b");
 8336    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8337    cx.assert_editor_state("{ˇa b");
 8338
 8339    // Don't autoclose if `close` is false for the bracket pair
 8340    cx.set_state("ˇ");
 8341    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 8342    cx.assert_editor_state("");
 8343
 8344    // Surround with brackets if text is selected
 8345    cx.set_state("«aˇ» b");
 8346    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8347    cx.assert_editor_state("{«aˇ»} b");
 8348
 8349    // Autoclose when not immediately after a word character
 8350    cx.set_state("a ˇ");
 8351    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8352    cx.assert_editor_state("a \"ˇ\"");
 8353
 8354    // Autoclose pair where the start and end characters are the same
 8355    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8356    cx.assert_editor_state("a \"\"ˇ");
 8357
 8358    // Don't autoclose when immediately after a word character
 8359    cx.set_state("");
 8360    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8361    cx.assert_editor_state("a\"ˇ");
 8362
 8363    // Do autoclose when after a non-word character
 8364    cx.set_state("");
 8365    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8366    cx.assert_editor_state("{\"ˇ\"");
 8367
 8368    // Non identical pairs autoclose regardless of preceding character
 8369    cx.set_state("");
 8370    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8371    cx.assert_editor_state("a{ˇ}");
 8372
 8373    // Don't autoclose pair if autoclose is disabled
 8374    cx.set_state("ˇ");
 8375    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 8376    cx.assert_editor_state("");
 8377
 8378    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 8379    cx.set_state("«aˇ» b");
 8380    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 8381    cx.assert_editor_state("<«aˇ»> b");
 8382}
 8383
 8384#[gpui::test]
 8385async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 8386    init_test(cx, |settings| {
 8387        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 8388    });
 8389
 8390    let mut cx = EditorTestContext::new(cx).await;
 8391
 8392    let language = Arc::new(Language::new(
 8393        LanguageConfig {
 8394            brackets: BracketPairConfig {
 8395                pairs: vec![
 8396                    BracketPair {
 8397                        start: "{".to_string(),
 8398                        end: "}".to_string(),
 8399                        close: true,
 8400                        surround: true,
 8401                        newline: true,
 8402                    },
 8403                    BracketPair {
 8404                        start: "(".to_string(),
 8405                        end: ")".to_string(),
 8406                        close: true,
 8407                        surround: true,
 8408                        newline: true,
 8409                    },
 8410                    BracketPair {
 8411                        start: "[".to_string(),
 8412                        end: "]".to_string(),
 8413                        close: false,
 8414                        surround: false,
 8415                        newline: true,
 8416                    },
 8417                ],
 8418                ..Default::default()
 8419            },
 8420            autoclose_before: "})]".to_string(),
 8421            ..Default::default()
 8422        },
 8423        Some(tree_sitter_rust::LANGUAGE.into()),
 8424    ));
 8425
 8426    cx.language_registry().add(language.clone());
 8427    cx.update_buffer(|buffer, cx| {
 8428        buffer.set_language(Some(language), cx);
 8429    });
 8430
 8431    cx.set_state(
 8432        &"
 8433            ˇ
 8434            ˇ
 8435            ˇ
 8436        "
 8437        .unindent(),
 8438    );
 8439
 8440    // ensure only matching closing brackets are skipped over
 8441    cx.update_editor(|editor, window, cx| {
 8442        editor.handle_input("}", window, cx);
 8443        editor.move_left(&MoveLeft, window, cx);
 8444        editor.handle_input(")", window, cx);
 8445        editor.move_left(&MoveLeft, window, cx);
 8446    });
 8447    cx.assert_editor_state(
 8448        &"
 8449            ˇ)}
 8450            ˇ)}
 8451            ˇ)}
 8452        "
 8453        .unindent(),
 8454    );
 8455
 8456    // skip-over closing brackets at multiple cursors
 8457    cx.update_editor(|editor, window, cx| {
 8458        editor.handle_input(")", window, cx);
 8459        editor.handle_input("}", window, cx);
 8460    });
 8461    cx.assert_editor_state(
 8462        &"
 8463            )}ˇ
 8464            )}ˇ
 8465            )}ˇ
 8466        "
 8467        .unindent(),
 8468    );
 8469
 8470    // ignore non-close brackets
 8471    cx.update_editor(|editor, window, cx| {
 8472        editor.handle_input("]", window, cx);
 8473        editor.move_left(&MoveLeft, window, cx);
 8474        editor.handle_input("]", window, cx);
 8475    });
 8476    cx.assert_editor_state(
 8477        &"
 8478            )}]ˇ]
 8479            )}]ˇ]
 8480            )}]ˇ]
 8481        "
 8482        .unindent(),
 8483    );
 8484}
 8485
 8486#[gpui::test]
 8487async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
 8488    init_test(cx, |_| {});
 8489
 8490    let mut cx = EditorTestContext::new(cx).await;
 8491
 8492    let html_language = Arc::new(
 8493        Language::new(
 8494            LanguageConfig {
 8495                name: "HTML".into(),
 8496                brackets: BracketPairConfig {
 8497                    pairs: vec![
 8498                        BracketPair {
 8499                            start: "<".into(),
 8500                            end: ">".into(),
 8501                            close: true,
 8502                            ..Default::default()
 8503                        },
 8504                        BracketPair {
 8505                            start: "{".into(),
 8506                            end: "}".into(),
 8507                            close: true,
 8508                            ..Default::default()
 8509                        },
 8510                        BracketPair {
 8511                            start: "(".into(),
 8512                            end: ")".into(),
 8513                            close: true,
 8514                            ..Default::default()
 8515                        },
 8516                    ],
 8517                    ..Default::default()
 8518                },
 8519                autoclose_before: "})]>".into(),
 8520                ..Default::default()
 8521            },
 8522            Some(tree_sitter_html::LANGUAGE.into()),
 8523        )
 8524        .with_injection_query(
 8525            r#"
 8526            (script_element
 8527                (raw_text) @injection.content
 8528                (#set! injection.language "javascript"))
 8529            "#,
 8530        )
 8531        .unwrap(),
 8532    );
 8533
 8534    let javascript_language = Arc::new(Language::new(
 8535        LanguageConfig {
 8536            name: "JavaScript".into(),
 8537            brackets: BracketPairConfig {
 8538                pairs: vec![
 8539                    BracketPair {
 8540                        start: "/*".into(),
 8541                        end: " */".into(),
 8542                        close: true,
 8543                        ..Default::default()
 8544                    },
 8545                    BracketPair {
 8546                        start: "{".into(),
 8547                        end: "}".into(),
 8548                        close: true,
 8549                        ..Default::default()
 8550                    },
 8551                    BracketPair {
 8552                        start: "(".into(),
 8553                        end: ")".into(),
 8554                        close: true,
 8555                        ..Default::default()
 8556                    },
 8557                ],
 8558                ..Default::default()
 8559            },
 8560            autoclose_before: "})]>".into(),
 8561            ..Default::default()
 8562        },
 8563        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 8564    ));
 8565
 8566    cx.language_registry().add(html_language.clone());
 8567    cx.language_registry().add(javascript_language.clone());
 8568
 8569    cx.update_buffer(|buffer, cx| {
 8570        buffer.set_language(Some(html_language), cx);
 8571    });
 8572
 8573    cx.set_state(
 8574        &r#"
 8575            <body>ˇ
 8576                <script>
 8577                    var x = 1;ˇ
 8578                </script>
 8579            </body>ˇ
 8580        "#
 8581        .unindent(),
 8582    );
 8583
 8584    // Precondition: different languages are active at different locations.
 8585    cx.update_editor(|editor, window, cx| {
 8586        let snapshot = editor.snapshot(window, cx);
 8587        let cursors = editor.selections.ranges::<usize>(cx);
 8588        let languages = cursors
 8589            .iter()
 8590            .map(|c| snapshot.language_at(c.start).unwrap().name())
 8591            .collect::<Vec<_>>();
 8592        assert_eq!(
 8593            languages,
 8594            &["HTML".into(), "JavaScript".into(), "HTML".into()]
 8595        );
 8596    });
 8597
 8598    // Angle brackets autoclose in HTML, but not JavaScript.
 8599    cx.update_editor(|editor, window, cx| {
 8600        editor.handle_input("<", window, cx);
 8601        editor.handle_input("a", window, cx);
 8602    });
 8603    cx.assert_editor_state(
 8604        &r#"
 8605            <body><aˇ>
 8606                <script>
 8607                    var x = 1;<aˇ
 8608                </script>
 8609            </body><aˇ>
 8610        "#
 8611        .unindent(),
 8612    );
 8613
 8614    // Curly braces and parens autoclose in both HTML and JavaScript.
 8615    cx.update_editor(|editor, window, cx| {
 8616        editor.handle_input(" b=", window, cx);
 8617        editor.handle_input("{", window, cx);
 8618        editor.handle_input("c", window, cx);
 8619        editor.handle_input("(", window, cx);
 8620    });
 8621    cx.assert_editor_state(
 8622        &r#"
 8623            <body><a b={c(ˇ)}>
 8624                <script>
 8625                    var x = 1;<a b={c(ˇ)}
 8626                </script>
 8627            </body><a b={c(ˇ)}>
 8628        "#
 8629        .unindent(),
 8630    );
 8631
 8632    // Brackets that were already autoclosed are skipped.
 8633    cx.update_editor(|editor, window, cx| {
 8634        editor.handle_input(")", window, cx);
 8635        editor.handle_input("d", window, cx);
 8636        editor.handle_input("}", window, cx);
 8637    });
 8638    cx.assert_editor_state(
 8639        &r#"
 8640            <body><a b={c()d}ˇ>
 8641                <script>
 8642                    var x = 1;<a b={c()d}ˇ
 8643                </script>
 8644            </body><a b={c()d}ˇ>
 8645        "#
 8646        .unindent(),
 8647    );
 8648    cx.update_editor(|editor, window, cx| {
 8649        editor.handle_input(">", window, cx);
 8650    });
 8651    cx.assert_editor_state(
 8652        &r#"
 8653            <body><a b={c()d}>ˇ
 8654                <script>
 8655                    var x = 1;<a b={c()d}>ˇ
 8656                </script>
 8657            </body><a b={c()d}>ˇ
 8658        "#
 8659        .unindent(),
 8660    );
 8661
 8662    // Reset
 8663    cx.set_state(
 8664        &r#"
 8665            <body>ˇ
 8666                <script>
 8667                    var x = 1;ˇ
 8668                </script>
 8669            </body>ˇ
 8670        "#
 8671        .unindent(),
 8672    );
 8673
 8674    cx.update_editor(|editor, window, cx| {
 8675        editor.handle_input("<", window, cx);
 8676    });
 8677    cx.assert_editor_state(
 8678        &r#"
 8679            <body><ˇ>
 8680                <script>
 8681                    var x = 1;<ˇ
 8682                </script>
 8683            </body><ˇ>
 8684        "#
 8685        .unindent(),
 8686    );
 8687
 8688    // When backspacing, the closing angle brackets are removed.
 8689    cx.update_editor(|editor, window, cx| {
 8690        editor.backspace(&Backspace, window, cx);
 8691    });
 8692    cx.assert_editor_state(
 8693        &r#"
 8694            <body>ˇ
 8695                <script>
 8696                    var x = 1;ˇ
 8697                </script>
 8698            </body>ˇ
 8699        "#
 8700        .unindent(),
 8701    );
 8702
 8703    // Block comments autoclose in JavaScript, but not HTML.
 8704    cx.update_editor(|editor, window, cx| {
 8705        editor.handle_input("/", window, cx);
 8706        editor.handle_input("*", window, cx);
 8707    });
 8708    cx.assert_editor_state(
 8709        &r#"
 8710            <body>/*ˇ
 8711                <script>
 8712                    var x = 1;/*ˇ */
 8713                </script>
 8714            </body>/*ˇ
 8715        "#
 8716        .unindent(),
 8717    );
 8718}
 8719
 8720#[gpui::test]
 8721async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
 8722    init_test(cx, |_| {});
 8723
 8724    let mut cx = EditorTestContext::new(cx).await;
 8725
 8726    let rust_language = Arc::new(
 8727        Language::new(
 8728            LanguageConfig {
 8729                name: "Rust".into(),
 8730                brackets: serde_json::from_value(json!([
 8731                    { "start": "{", "end": "}", "close": true, "newline": true },
 8732                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
 8733                ]))
 8734                .unwrap(),
 8735                autoclose_before: "})]>".into(),
 8736                ..Default::default()
 8737            },
 8738            Some(tree_sitter_rust::LANGUAGE.into()),
 8739        )
 8740        .with_override_query("(string_literal) @string")
 8741        .unwrap(),
 8742    );
 8743
 8744    cx.language_registry().add(rust_language.clone());
 8745    cx.update_buffer(|buffer, cx| {
 8746        buffer.set_language(Some(rust_language), cx);
 8747    });
 8748
 8749    cx.set_state(
 8750        &r#"
 8751            let x = ˇ
 8752        "#
 8753        .unindent(),
 8754    );
 8755
 8756    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
 8757    cx.update_editor(|editor, window, cx| {
 8758        editor.handle_input("\"", window, cx);
 8759    });
 8760    cx.assert_editor_state(
 8761        &r#"
 8762            let x = "ˇ"
 8763        "#
 8764        .unindent(),
 8765    );
 8766
 8767    // Inserting another quotation mark. The cursor moves across the existing
 8768    // automatically-inserted quotation mark.
 8769    cx.update_editor(|editor, window, cx| {
 8770        editor.handle_input("\"", window, cx);
 8771    });
 8772    cx.assert_editor_state(
 8773        &r#"
 8774            let x = ""ˇ
 8775        "#
 8776        .unindent(),
 8777    );
 8778
 8779    // Reset
 8780    cx.set_state(
 8781        &r#"
 8782            let x = ˇ
 8783        "#
 8784        .unindent(),
 8785    );
 8786
 8787    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
 8788    cx.update_editor(|editor, window, cx| {
 8789        editor.handle_input("\"", window, cx);
 8790        editor.handle_input(" ", window, cx);
 8791        editor.move_left(&Default::default(), window, cx);
 8792        editor.handle_input("\\", window, cx);
 8793        editor.handle_input("\"", window, cx);
 8794    });
 8795    cx.assert_editor_state(
 8796        &r#"
 8797            let x = "\"ˇ "
 8798        "#
 8799        .unindent(),
 8800    );
 8801
 8802    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
 8803    // mark. Nothing is inserted.
 8804    cx.update_editor(|editor, window, cx| {
 8805        editor.move_right(&Default::default(), window, cx);
 8806        editor.handle_input("\"", window, cx);
 8807    });
 8808    cx.assert_editor_state(
 8809        &r#"
 8810            let x = "\" "ˇ
 8811        "#
 8812        .unindent(),
 8813    );
 8814}
 8815
 8816#[gpui::test]
 8817async fn test_surround_with_pair(cx: &mut TestAppContext) {
 8818    init_test(cx, |_| {});
 8819
 8820    let language = Arc::new(Language::new(
 8821        LanguageConfig {
 8822            brackets: BracketPairConfig {
 8823                pairs: vec![
 8824                    BracketPair {
 8825                        start: "{".to_string(),
 8826                        end: "}".to_string(),
 8827                        close: true,
 8828                        surround: true,
 8829                        newline: true,
 8830                    },
 8831                    BracketPair {
 8832                        start: "/* ".to_string(),
 8833                        end: "*/".to_string(),
 8834                        close: true,
 8835                        surround: true,
 8836                        ..Default::default()
 8837                    },
 8838                ],
 8839                ..Default::default()
 8840            },
 8841            ..Default::default()
 8842        },
 8843        Some(tree_sitter_rust::LANGUAGE.into()),
 8844    ));
 8845
 8846    let text = r#"
 8847        a
 8848        b
 8849        c
 8850    "#
 8851    .unindent();
 8852
 8853    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8854    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8855    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8856    editor
 8857        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8858        .await;
 8859
 8860    editor.update_in(cx, |editor, window, cx| {
 8861        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8862            s.select_display_ranges([
 8863                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8864                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8865                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
 8866            ])
 8867        });
 8868
 8869        editor.handle_input("{", window, cx);
 8870        editor.handle_input("{", window, cx);
 8871        editor.handle_input("{", window, cx);
 8872        assert_eq!(
 8873            editor.text(cx),
 8874            "
 8875                {{{a}}}
 8876                {{{b}}}
 8877                {{{c}}}
 8878            "
 8879            .unindent()
 8880        );
 8881        assert_eq!(
 8882            editor.selections.display_ranges(cx),
 8883            [
 8884                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
 8885                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
 8886                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
 8887            ]
 8888        );
 8889
 8890        editor.undo(&Undo, window, cx);
 8891        editor.undo(&Undo, window, cx);
 8892        editor.undo(&Undo, window, cx);
 8893        assert_eq!(
 8894            editor.text(cx),
 8895            "
 8896                a
 8897                b
 8898                c
 8899            "
 8900            .unindent()
 8901        );
 8902        assert_eq!(
 8903            editor.selections.display_ranges(cx),
 8904            [
 8905                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8906                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8907                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 8908            ]
 8909        );
 8910
 8911        // Ensure inserting the first character of a multi-byte bracket pair
 8912        // doesn't surround the selections with the bracket.
 8913        editor.handle_input("/", window, cx);
 8914        assert_eq!(
 8915            editor.text(cx),
 8916            "
 8917                /
 8918                /
 8919                /
 8920            "
 8921            .unindent()
 8922        );
 8923        assert_eq!(
 8924            editor.selections.display_ranges(cx),
 8925            [
 8926                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 8927                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 8928                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 8929            ]
 8930        );
 8931
 8932        editor.undo(&Undo, window, cx);
 8933        assert_eq!(
 8934            editor.text(cx),
 8935            "
 8936                a
 8937                b
 8938                c
 8939            "
 8940            .unindent()
 8941        );
 8942        assert_eq!(
 8943            editor.selections.display_ranges(cx),
 8944            [
 8945                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8946                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8947                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 8948            ]
 8949        );
 8950
 8951        // Ensure inserting the last character of a multi-byte bracket pair
 8952        // doesn't surround the selections with the bracket.
 8953        editor.handle_input("*", window, cx);
 8954        assert_eq!(
 8955            editor.text(cx),
 8956            "
 8957                *
 8958                *
 8959                *
 8960            "
 8961            .unindent()
 8962        );
 8963        assert_eq!(
 8964            editor.selections.display_ranges(cx),
 8965            [
 8966                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 8967                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 8968                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 8969            ]
 8970        );
 8971    });
 8972}
 8973
 8974#[gpui::test]
 8975async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
 8976    init_test(cx, |_| {});
 8977
 8978    let language = Arc::new(Language::new(
 8979        LanguageConfig {
 8980            brackets: BracketPairConfig {
 8981                pairs: vec![BracketPair {
 8982                    start: "{".to_string(),
 8983                    end: "}".to_string(),
 8984                    close: true,
 8985                    surround: true,
 8986                    newline: true,
 8987                }],
 8988                ..Default::default()
 8989            },
 8990            autoclose_before: "}".to_string(),
 8991            ..Default::default()
 8992        },
 8993        Some(tree_sitter_rust::LANGUAGE.into()),
 8994    ));
 8995
 8996    let text = r#"
 8997        a
 8998        b
 8999        c
 9000    "#
 9001    .unindent();
 9002
 9003    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9004    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9005    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9006    editor
 9007        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9008        .await;
 9009
 9010    editor.update_in(cx, |editor, window, cx| {
 9011        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9012            s.select_ranges([
 9013                Point::new(0, 1)..Point::new(0, 1),
 9014                Point::new(1, 1)..Point::new(1, 1),
 9015                Point::new(2, 1)..Point::new(2, 1),
 9016            ])
 9017        });
 9018
 9019        editor.handle_input("{", window, cx);
 9020        editor.handle_input("{", window, cx);
 9021        editor.handle_input("_", window, cx);
 9022        assert_eq!(
 9023            editor.text(cx),
 9024            "
 9025                a{{_}}
 9026                b{{_}}
 9027                c{{_}}
 9028            "
 9029            .unindent()
 9030        );
 9031        assert_eq!(
 9032            editor.selections.ranges::<Point>(cx),
 9033            [
 9034                Point::new(0, 4)..Point::new(0, 4),
 9035                Point::new(1, 4)..Point::new(1, 4),
 9036                Point::new(2, 4)..Point::new(2, 4)
 9037            ]
 9038        );
 9039
 9040        editor.backspace(&Default::default(), window, cx);
 9041        editor.backspace(&Default::default(), window, cx);
 9042        assert_eq!(
 9043            editor.text(cx),
 9044            "
 9045                a{}
 9046                b{}
 9047                c{}
 9048            "
 9049            .unindent()
 9050        );
 9051        assert_eq!(
 9052            editor.selections.ranges::<Point>(cx),
 9053            [
 9054                Point::new(0, 2)..Point::new(0, 2),
 9055                Point::new(1, 2)..Point::new(1, 2),
 9056                Point::new(2, 2)..Point::new(2, 2)
 9057            ]
 9058        );
 9059
 9060        editor.delete_to_previous_word_start(&Default::default(), window, cx);
 9061        assert_eq!(
 9062            editor.text(cx),
 9063            "
 9064                a
 9065                b
 9066                c
 9067            "
 9068            .unindent()
 9069        );
 9070        assert_eq!(
 9071            editor.selections.ranges::<Point>(cx),
 9072            [
 9073                Point::new(0, 1)..Point::new(0, 1),
 9074                Point::new(1, 1)..Point::new(1, 1),
 9075                Point::new(2, 1)..Point::new(2, 1)
 9076            ]
 9077        );
 9078    });
 9079}
 9080
 9081#[gpui::test]
 9082async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
 9083    init_test(cx, |settings| {
 9084        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 9085    });
 9086
 9087    let mut cx = EditorTestContext::new(cx).await;
 9088
 9089    let language = Arc::new(Language::new(
 9090        LanguageConfig {
 9091            brackets: BracketPairConfig {
 9092                pairs: vec![
 9093                    BracketPair {
 9094                        start: "{".to_string(),
 9095                        end: "}".to_string(),
 9096                        close: true,
 9097                        surround: true,
 9098                        newline: true,
 9099                    },
 9100                    BracketPair {
 9101                        start: "(".to_string(),
 9102                        end: ")".to_string(),
 9103                        close: true,
 9104                        surround: true,
 9105                        newline: true,
 9106                    },
 9107                    BracketPair {
 9108                        start: "[".to_string(),
 9109                        end: "]".to_string(),
 9110                        close: false,
 9111                        surround: true,
 9112                        newline: true,
 9113                    },
 9114                ],
 9115                ..Default::default()
 9116            },
 9117            autoclose_before: "})]".to_string(),
 9118            ..Default::default()
 9119        },
 9120        Some(tree_sitter_rust::LANGUAGE.into()),
 9121    ));
 9122
 9123    cx.language_registry().add(language.clone());
 9124    cx.update_buffer(|buffer, cx| {
 9125        buffer.set_language(Some(language), cx);
 9126    });
 9127
 9128    cx.set_state(
 9129        &"
 9130            {(ˇ)}
 9131            [[ˇ]]
 9132            {(ˇ)}
 9133        "
 9134        .unindent(),
 9135    );
 9136
 9137    cx.update_editor(|editor, window, cx| {
 9138        editor.backspace(&Default::default(), window, cx);
 9139        editor.backspace(&Default::default(), window, cx);
 9140    });
 9141
 9142    cx.assert_editor_state(
 9143        &"
 9144            ˇ
 9145            ˇ]]
 9146            ˇ
 9147        "
 9148        .unindent(),
 9149    );
 9150
 9151    cx.update_editor(|editor, window, cx| {
 9152        editor.handle_input("{", window, cx);
 9153        editor.handle_input("{", window, cx);
 9154        editor.move_right(&MoveRight, window, cx);
 9155        editor.move_right(&MoveRight, window, cx);
 9156        editor.move_left(&MoveLeft, window, cx);
 9157        editor.move_left(&MoveLeft, window, cx);
 9158        editor.backspace(&Default::default(), window, cx);
 9159    });
 9160
 9161    cx.assert_editor_state(
 9162        &"
 9163            {ˇ}
 9164            {ˇ}]]
 9165            {ˇ}
 9166        "
 9167        .unindent(),
 9168    );
 9169
 9170    cx.update_editor(|editor, window, cx| {
 9171        editor.backspace(&Default::default(), window, cx);
 9172    });
 9173
 9174    cx.assert_editor_state(
 9175        &"
 9176            ˇ
 9177            ˇ]]
 9178            ˇ
 9179        "
 9180        .unindent(),
 9181    );
 9182}
 9183
 9184#[gpui::test]
 9185async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
 9186    init_test(cx, |_| {});
 9187
 9188    let language = Arc::new(Language::new(
 9189        LanguageConfig::default(),
 9190        Some(tree_sitter_rust::LANGUAGE.into()),
 9191    ));
 9192
 9193    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
 9194    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9195    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9196    editor
 9197        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9198        .await;
 9199
 9200    editor.update_in(cx, |editor, window, cx| {
 9201        editor.set_auto_replace_emoji_shortcode(true);
 9202
 9203        editor.handle_input("Hello ", window, cx);
 9204        editor.handle_input(":wave", window, cx);
 9205        assert_eq!(editor.text(cx), "Hello :wave".unindent());
 9206
 9207        editor.handle_input(":", window, cx);
 9208        assert_eq!(editor.text(cx), "Hello 👋".unindent());
 9209
 9210        editor.handle_input(" :smile", window, cx);
 9211        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
 9212
 9213        editor.handle_input(":", window, cx);
 9214        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
 9215
 9216        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
 9217        editor.handle_input(":wave", window, cx);
 9218        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
 9219
 9220        editor.handle_input(":", window, cx);
 9221        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
 9222
 9223        editor.handle_input(":1", window, cx);
 9224        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
 9225
 9226        editor.handle_input(":", window, cx);
 9227        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
 9228
 9229        // Ensure shortcode does not get replaced when it is part of a word
 9230        editor.handle_input(" Test:wave", window, cx);
 9231        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
 9232
 9233        editor.handle_input(":", window, cx);
 9234        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
 9235
 9236        editor.set_auto_replace_emoji_shortcode(false);
 9237
 9238        // Ensure shortcode does not get replaced when auto replace is off
 9239        editor.handle_input(" :wave", window, cx);
 9240        assert_eq!(
 9241            editor.text(cx),
 9242            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
 9243        );
 9244
 9245        editor.handle_input(":", window, cx);
 9246        assert_eq!(
 9247            editor.text(cx),
 9248            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
 9249        );
 9250    });
 9251}
 9252
 9253#[gpui::test]
 9254async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
 9255    init_test(cx, |_| {});
 9256
 9257    let (text, insertion_ranges) = marked_text_ranges(
 9258        indoc! {"
 9259            ˇ
 9260        "},
 9261        false,
 9262    );
 9263
 9264    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
 9265    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9266
 9267    _ = editor.update_in(cx, |editor, window, cx| {
 9268        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
 9269
 9270        editor
 9271            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9272            .unwrap();
 9273
 9274        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
 9275            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
 9276            assert_eq!(editor.text(cx), expected_text);
 9277            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
 9278        }
 9279
 9280        assert(
 9281            editor,
 9282            cx,
 9283            indoc! {"
 9284            type «» =•
 9285            "},
 9286        );
 9287
 9288        assert!(editor.context_menu_visible(), "There should be a matches");
 9289    });
 9290}
 9291
 9292#[gpui::test]
 9293async fn test_snippets(cx: &mut TestAppContext) {
 9294    init_test(cx, |_| {});
 9295
 9296    let mut cx = EditorTestContext::new(cx).await;
 9297
 9298    cx.set_state(indoc! {"
 9299        a.ˇ b
 9300        a.ˇ b
 9301        a.ˇ b
 9302    "});
 9303
 9304    cx.update_editor(|editor, window, cx| {
 9305        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
 9306        let insertion_ranges = editor
 9307            .selections
 9308            .all(cx)
 9309            .iter()
 9310            .map(|s| s.range().clone())
 9311            .collect::<Vec<_>>();
 9312        editor
 9313            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9314            .unwrap();
 9315    });
 9316
 9317    cx.assert_editor_state(indoc! {"
 9318        a.f(«oneˇ», two, «threeˇ») b
 9319        a.f(«oneˇ», two, «threeˇ») b
 9320        a.f(«oneˇ», two, «threeˇ») b
 9321    "});
 9322
 9323    // Can't move earlier than the first tab stop
 9324    cx.update_editor(|editor, window, cx| {
 9325        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 9326    });
 9327    cx.assert_editor_state(indoc! {"
 9328        a.f(«oneˇ», two, «threeˇ») b
 9329        a.f(«oneˇ», two, «threeˇ») b
 9330        a.f(«oneˇ», two, «threeˇ») b
 9331    "});
 9332
 9333    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9334    cx.assert_editor_state(indoc! {"
 9335        a.f(one, «twoˇ», three) b
 9336        a.f(one, «twoˇ», three) b
 9337        a.f(one, «twoˇ», three) b
 9338    "});
 9339
 9340    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
 9341    cx.assert_editor_state(indoc! {"
 9342        a.f(«oneˇ», two, «threeˇ») b
 9343        a.f(«oneˇ», two, «threeˇ») b
 9344        a.f(«oneˇ», two, «threeˇ») b
 9345    "});
 9346
 9347    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9348    cx.assert_editor_state(indoc! {"
 9349        a.f(one, «twoˇ», three) b
 9350        a.f(one, «twoˇ», three) b
 9351        a.f(one, «twoˇ», three) b
 9352    "});
 9353    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9354    cx.assert_editor_state(indoc! {"
 9355        a.f(one, two, three)ˇ b
 9356        a.f(one, two, three)ˇ b
 9357        a.f(one, two, three)ˇ b
 9358    "});
 9359
 9360    // As soon as the last tab stop is reached, snippet state is gone
 9361    cx.update_editor(|editor, window, cx| {
 9362        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 9363    });
 9364    cx.assert_editor_state(indoc! {"
 9365        a.f(one, two, three)ˇ b
 9366        a.f(one, two, three)ˇ b
 9367        a.f(one, two, three)ˇ b
 9368    "});
 9369}
 9370
 9371#[gpui::test]
 9372async fn test_snippet_indentation(cx: &mut TestAppContext) {
 9373    init_test(cx, |_| {});
 9374
 9375    let mut cx = EditorTestContext::new(cx).await;
 9376
 9377    cx.update_editor(|editor, window, cx| {
 9378        let snippet = Snippet::parse(indoc! {"
 9379            /*
 9380             * Multiline comment with leading indentation
 9381             *
 9382             * $1
 9383             */
 9384            $0"})
 9385        .unwrap();
 9386        let insertion_ranges = editor
 9387            .selections
 9388            .all(cx)
 9389            .iter()
 9390            .map(|s| s.range().clone())
 9391            .collect::<Vec<_>>();
 9392        editor
 9393            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9394            .unwrap();
 9395    });
 9396
 9397    cx.assert_editor_state(indoc! {"
 9398        /*
 9399         * Multiline comment with leading indentation
 9400         *
 9401         * ˇ
 9402         */
 9403    "});
 9404
 9405    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9406    cx.assert_editor_state(indoc! {"
 9407        /*
 9408         * Multiline comment with leading indentation
 9409         *
 9410         *•
 9411         */
 9412        ˇ"});
 9413}
 9414
 9415#[gpui::test]
 9416async fn test_document_format_during_save(cx: &mut TestAppContext) {
 9417    init_test(cx, |_| {});
 9418
 9419    let fs = FakeFs::new(cx.executor());
 9420    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9421
 9422    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
 9423
 9424    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9425    language_registry.add(rust_lang());
 9426    let mut fake_servers = language_registry.register_fake_lsp(
 9427        "Rust",
 9428        FakeLspAdapter {
 9429            capabilities: lsp::ServerCapabilities {
 9430                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9431                ..Default::default()
 9432            },
 9433            ..Default::default()
 9434        },
 9435    );
 9436
 9437    let buffer = project
 9438        .update(cx, |project, cx| {
 9439            project.open_local_buffer(path!("/file.rs"), cx)
 9440        })
 9441        .await
 9442        .unwrap();
 9443
 9444    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9445    let (editor, cx) = cx.add_window_view(|window, cx| {
 9446        build_editor_with_project(project.clone(), buffer, window, cx)
 9447    });
 9448    editor.update_in(cx, |editor, window, cx| {
 9449        editor.set_text("one\ntwo\nthree\n", window, cx)
 9450    });
 9451    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9452
 9453    cx.executor().start_waiting();
 9454    let fake_server = fake_servers.next().await.unwrap();
 9455
 9456    {
 9457        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9458            move |params, _| async move {
 9459                assert_eq!(
 9460                    params.text_document.uri,
 9461                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9462                );
 9463                assert_eq!(params.options.tab_size, 4);
 9464                Ok(Some(vec![lsp::TextEdit::new(
 9465                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9466                    ", ".to_string(),
 9467                )]))
 9468            },
 9469        );
 9470        let save = editor
 9471            .update_in(cx, |editor, window, cx| {
 9472                editor.save(
 9473                    SaveOptions {
 9474                        format: true,
 9475                        autosave: false,
 9476                    },
 9477                    project.clone(),
 9478                    window,
 9479                    cx,
 9480                )
 9481            })
 9482            .unwrap();
 9483        cx.executor().start_waiting();
 9484        save.await;
 9485
 9486        assert_eq!(
 9487            editor.update(cx, |editor, cx| editor.text(cx)),
 9488            "one, two\nthree\n"
 9489        );
 9490        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9491    }
 9492
 9493    {
 9494        editor.update_in(cx, |editor, window, cx| {
 9495            editor.set_text("one\ntwo\nthree\n", window, cx)
 9496        });
 9497        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9498
 9499        // Ensure we can still save even if formatting hangs.
 9500        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9501            move |params, _| async move {
 9502                assert_eq!(
 9503                    params.text_document.uri,
 9504                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9505                );
 9506                futures::future::pending::<()>().await;
 9507                unreachable!()
 9508            },
 9509        );
 9510        let save = editor
 9511            .update_in(cx, |editor, window, cx| {
 9512                editor.save(
 9513                    SaveOptions {
 9514                        format: true,
 9515                        autosave: false,
 9516                    },
 9517                    project.clone(),
 9518                    window,
 9519                    cx,
 9520                )
 9521            })
 9522            .unwrap();
 9523        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9524        cx.executor().start_waiting();
 9525        save.await;
 9526        assert_eq!(
 9527            editor.update(cx, |editor, cx| editor.text(cx)),
 9528            "one\ntwo\nthree\n"
 9529        );
 9530    }
 9531
 9532    // Set rust language override and assert overridden tabsize is sent to language server
 9533    update_test_language_settings(cx, |settings| {
 9534        settings.languages.0.insert(
 9535            "Rust".into(),
 9536            LanguageSettingsContent {
 9537                tab_size: NonZeroU32::new(8),
 9538                ..Default::default()
 9539            },
 9540        );
 9541    });
 9542
 9543    {
 9544        editor.update_in(cx, |editor, window, cx| {
 9545            editor.set_text("somehting_new\n", window, cx)
 9546        });
 9547        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9548        let _formatting_request_signal = fake_server
 9549            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9550                assert_eq!(
 9551                    params.text_document.uri,
 9552                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9553                );
 9554                assert_eq!(params.options.tab_size, 8);
 9555                Ok(Some(vec![]))
 9556            });
 9557        let save = editor
 9558            .update_in(cx, |editor, window, cx| {
 9559                editor.save(
 9560                    SaveOptions {
 9561                        format: true,
 9562                        autosave: false,
 9563                    },
 9564                    project.clone(),
 9565                    window,
 9566                    cx,
 9567                )
 9568            })
 9569            .unwrap();
 9570        cx.executor().start_waiting();
 9571        save.await;
 9572    }
 9573}
 9574
 9575#[gpui::test]
 9576async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
 9577    init_test(cx, |_| {});
 9578
 9579    let cols = 4;
 9580    let rows = 10;
 9581    let sample_text_1 = sample_text(rows, cols, 'a');
 9582    assert_eq!(
 9583        sample_text_1,
 9584        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
 9585    );
 9586    let sample_text_2 = sample_text(rows, cols, 'l');
 9587    assert_eq!(
 9588        sample_text_2,
 9589        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
 9590    );
 9591    let sample_text_3 = sample_text(rows, cols, 'v');
 9592    assert_eq!(
 9593        sample_text_3,
 9594        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
 9595    );
 9596
 9597    let fs = FakeFs::new(cx.executor());
 9598    fs.insert_tree(
 9599        path!("/a"),
 9600        json!({
 9601            "main.rs": sample_text_1,
 9602            "other.rs": sample_text_2,
 9603            "lib.rs": sample_text_3,
 9604        }),
 9605    )
 9606    .await;
 9607
 9608    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
 9609    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9610    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 9611
 9612    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9613    language_registry.add(rust_lang());
 9614    let mut fake_servers = language_registry.register_fake_lsp(
 9615        "Rust",
 9616        FakeLspAdapter {
 9617            capabilities: lsp::ServerCapabilities {
 9618                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9619                ..Default::default()
 9620            },
 9621            ..Default::default()
 9622        },
 9623    );
 9624
 9625    let worktree = project.update(cx, |project, cx| {
 9626        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
 9627        assert_eq!(worktrees.len(), 1);
 9628        worktrees.pop().unwrap()
 9629    });
 9630    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 9631
 9632    let buffer_1 = project
 9633        .update(cx, |project, cx| {
 9634            project.open_buffer((worktree_id, "main.rs"), cx)
 9635        })
 9636        .await
 9637        .unwrap();
 9638    let buffer_2 = project
 9639        .update(cx, |project, cx| {
 9640            project.open_buffer((worktree_id, "other.rs"), cx)
 9641        })
 9642        .await
 9643        .unwrap();
 9644    let buffer_3 = project
 9645        .update(cx, |project, cx| {
 9646            project.open_buffer((worktree_id, "lib.rs"), cx)
 9647        })
 9648        .await
 9649        .unwrap();
 9650
 9651    let multi_buffer = cx.new(|cx| {
 9652        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 9653        multi_buffer.push_excerpts(
 9654            buffer_1.clone(),
 9655            [
 9656                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9657                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9658                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9659            ],
 9660            cx,
 9661        );
 9662        multi_buffer.push_excerpts(
 9663            buffer_2.clone(),
 9664            [
 9665                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9666                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9667                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9668            ],
 9669            cx,
 9670        );
 9671        multi_buffer.push_excerpts(
 9672            buffer_3.clone(),
 9673            [
 9674                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9675                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9676                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9677            ],
 9678            cx,
 9679        );
 9680        multi_buffer
 9681    });
 9682    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
 9683        Editor::new(
 9684            EditorMode::full(),
 9685            multi_buffer,
 9686            Some(project.clone()),
 9687            window,
 9688            cx,
 9689        )
 9690    });
 9691
 9692    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 9693        editor.change_selections(
 9694            SelectionEffects::scroll(Autoscroll::Next),
 9695            window,
 9696            cx,
 9697            |s| s.select_ranges(Some(1..2)),
 9698        );
 9699        editor.insert("|one|two|three|", window, cx);
 9700    });
 9701    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 9702    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 9703        editor.change_selections(
 9704            SelectionEffects::scroll(Autoscroll::Next),
 9705            window,
 9706            cx,
 9707            |s| s.select_ranges(Some(60..70)),
 9708        );
 9709        editor.insert("|four|five|six|", window, cx);
 9710    });
 9711    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 9712
 9713    // First two buffers should be edited, but not the third one.
 9714    assert_eq!(
 9715        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 9716        "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}",
 9717    );
 9718    buffer_1.update(cx, |buffer, _| {
 9719        assert!(buffer.is_dirty());
 9720        assert_eq!(
 9721            buffer.text(),
 9722            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
 9723        )
 9724    });
 9725    buffer_2.update(cx, |buffer, _| {
 9726        assert!(buffer.is_dirty());
 9727        assert_eq!(
 9728            buffer.text(),
 9729            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
 9730        )
 9731    });
 9732    buffer_3.update(cx, |buffer, _| {
 9733        assert!(!buffer.is_dirty());
 9734        assert_eq!(buffer.text(), sample_text_3,)
 9735    });
 9736    cx.executor().run_until_parked();
 9737
 9738    cx.executor().start_waiting();
 9739    let save = multi_buffer_editor
 9740        .update_in(cx, |editor, window, cx| {
 9741            editor.save(
 9742                SaveOptions {
 9743                    format: true,
 9744                    autosave: false,
 9745                },
 9746                project.clone(),
 9747                window,
 9748                cx,
 9749            )
 9750        })
 9751        .unwrap();
 9752
 9753    let fake_server = fake_servers.next().await.unwrap();
 9754    fake_server
 9755        .server
 9756        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9757            Ok(Some(vec![lsp::TextEdit::new(
 9758                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9759                format!("[{} formatted]", params.text_document.uri),
 9760            )]))
 9761        })
 9762        .detach();
 9763    save.await;
 9764
 9765    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
 9766    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
 9767    assert_eq!(
 9768        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 9769        uri!(
 9770            "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}"
 9771        ),
 9772    );
 9773    buffer_1.update(cx, |buffer, _| {
 9774        assert!(!buffer.is_dirty());
 9775        assert_eq!(
 9776            buffer.text(),
 9777            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
 9778        )
 9779    });
 9780    buffer_2.update(cx, |buffer, _| {
 9781        assert!(!buffer.is_dirty());
 9782        assert_eq!(
 9783            buffer.text(),
 9784            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
 9785        )
 9786    });
 9787    buffer_3.update(cx, |buffer, _| {
 9788        assert!(!buffer.is_dirty());
 9789        assert_eq!(buffer.text(), sample_text_3,)
 9790    });
 9791}
 9792
 9793#[gpui::test]
 9794async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
 9795    init_test(cx, |_| {});
 9796
 9797    let fs = FakeFs::new(cx.executor());
 9798    fs.insert_tree(
 9799        path!("/dir"),
 9800        json!({
 9801            "file1.rs": "fn main() { println!(\"hello\"); }",
 9802            "file2.rs": "fn test() { println!(\"test\"); }",
 9803            "file3.rs": "fn other() { println!(\"other\"); }\n",
 9804        }),
 9805    )
 9806    .await;
 9807
 9808    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
 9809    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9810    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 9811
 9812    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9813    language_registry.add(rust_lang());
 9814
 9815    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
 9816    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 9817
 9818    // Open three buffers
 9819    let buffer_1 = project
 9820        .update(cx, |project, cx| {
 9821            project.open_buffer((worktree_id, "file1.rs"), cx)
 9822        })
 9823        .await
 9824        .unwrap();
 9825    let buffer_2 = project
 9826        .update(cx, |project, cx| {
 9827            project.open_buffer((worktree_id, "file2.rs"), cx)
 9828        })
 9829        .await
 9830        .unwrap();
 9831    let buffer_3 = project
 9832        .update(cx, |project, cx| {
 9833            project.open_buffer((worktree_id, "file3.rs"), cx)
 9834        })
 9835        .await
 9836        .unwrap();
 9837
 9838    // Create a multi-buffer with all three buffers
 9839    let multi_buffer = cx.new(|cx| {
 9840        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 9841        multi_buffer.push_excerpts(
 9842            buffer_1.clone(),
 9843            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9844            cx,
 9845        );
 9846        multi_buffer.push_excerpts(
 9847            buffer_2.clone(),
 9848            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9849            cx,
 9850        );
 9851        multi_buffer.push_excerpts(
 9852            buffer_3.clone(),
 9853            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9854            cx,
 9855        );
 9856        multi_buffer
 9857    });
 9858
 9859    let editor = cx.new_window_entity(|window, cx| {
 9860        Editor::new(
 9861            EditorMode::full(),
 9862            multi_buffer,
 9863            Some(project.clone()),
 9864            window,
 9865            cx,
 9866        )
 9867    });
 9868
 9869    // Edit only the first buffer
 9870    editor.update_in(cx, |editor, window, cx| {
 9871        editor.change_selections(
 9872            SelectionEffects::scroll(Autoscroll::Next),
 9873            window,
 9874            cx,
 9875            |s| s.select_ranges(Some(10..10)),
 9876        );
 9877        editor.insert("// edited", window, cx);
 9878    });
 9879
 9880    // Verify that only buffer 1 is dirty
 9881    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
 9882    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9883    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9884
 9885    // Get write counts after file creation (files were created with initial content)
 9886    // We expect each file to have been written once during creation
 9887    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
 9888    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
 9889    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
 9890
 9891    // Perform autosave
 9892    let save_task = editor.update_in(cx, |editor, window, cx| {
 9893        editor.save(
 9894            SaveOptions {
 9895                format: true,
 9896                autosave: true,
 9897            },
 9898            project.clone(),
 9899            window,
 9900            cx,
 9901        )
 9902    });
 9903    save_task.await.unwrap();
 9904
 9905    // Only the dirty buffer should have been saved
 9906    assert_eq!(
 9907        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
 9908        1,
 9909        "Buffer 1 was dirty, so it should have been written once during autosave"
 9910    );
 9911    assert_eq!(
 9912        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
 9913        0,
 9914        "Buffer 2 was clean, so it should not have been written during autosave"
 9915    );
 9916    assert_eq!(
 9917        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
 9918        0,
 9919        "Buffer 3 was clean, so it should not have been written during autosave"
 9920    );
 9921
 9922    // Verify buffer states after autosave
 9923    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9924    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9925    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9926
 9927    // Now perform a manual save (format = true)
 9928    let save_task = editor.update_in(cx, |editor, window, cx| {
 9929        editor.save(
 9930            SaveOptions {
 9931                format: true,
 9932                autosave: false,
 9933            },
 9934            project.clone(),
 9935            window,
 9936            cx,
 9937        )
 9938    });
 9939    save_task.await.unwrap();
 9940
 9941    // During manual save, clean buffers don't get written to disk
 9942    // They just get did_save called for language server notifications
 9943    assert_eq!(
 9944        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
 9945        1,
 9946        "Buffer 1 should only have been written once total (during autosave, not manual save)"
 9947    );
 9948    assert_eq!(
 9949        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
 9950        0,
 9951        "Buffer 2 should not have been written at all"
 9952    );
 9953    assert_eq!(
 9954        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
 9955        0,
 9956        "Buffer 3 should not have been written at all"
 9957    );
 9958}
 9959
 9960#[gpui::test]
 9961async fn test_range_format_during_save(cx: &mut TestAppContext) {
 9962    init_test(cx, |_| {});
 9963
 9964    let fs = FakeFs::new(cx.executor());
 9965    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9966
 9967    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 9968
 9969    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9970    language_registry.add(rust_lang());
 9971    let mut fake_servers = language_registry.register_fake_lsp(
 9972        "Rust",
 9973        FakeLspAdapter {
 9974            capabilities: lsp::ServerCapabilities {
 9975                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
 9976                ..Default::default()
 9977            },
 9978            ..Default::default()
 9979        },
 9980    );
 9981
 9982    let buffer = project
 9983        .update(cx, |project, cx| {
 9984            project.open_local_buffer(path!("/file.rs"), cx)
 9985        })
 9986        .await
 9987        .unwrap();
 9988
 9989    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9990    let (editor, cx) = cx.add_window_view(|window, cx| {
 9991        build_editor_with_project(project.clone(), buffer, window, cx)
 9992    });
 9993    editor.update_in(cx, |editor, window, cx| {
 9994        editor.set_text("one\ntwo\nthree\n", window, cx)
 9995    });
 9996    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9997
 9998    cx.executor().start_waiting();
 9999    let fake_server = fake_servers.next().await.unwrap();
10000
10001    let save = editor
10002        .update_in(cx, |editor, window, cx| {
10003            editor.save(
10004                SaveOptions {
10005                    format: true,
10006                    autosave: false,
10007                },
10008                project.clone(),
10009                window,
10010                cx,
10011            )
10012        })
10013        .unwrap();
10014    fake_server
10015        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
10016            assert_eq!(
10017                params.text_document.uri,
10018                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10019            );
10020            assert_eq!(params.options.tab_size, 4);
10021            Ok(Some(vec![lsp::TextEdit::new(
10022                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10023                ", ".to_string(),
10024            )]))
10025        })
10026        .next()
10027        .await;
10028    cx.executor().start_waiting();
10029    save.await;
10030    assert_eq!(
10031        editor.update(cx, |editor, cx| editor.text(cx)),
10032        "one, two\nthree\n"
10033    );
10034    assert!(!cx.read(|cx| editor.is_dirty(cx)));
10035
10036    editor.update_in(cx, |editor, window, cx| {
10037        editor.set_text("one\ntwo\nthree\n", window, cx)
10038    });
10039    assert!(cx.read(|cx| editor.is_dirty(cx)));
10040
10041    // Ensure we can still save even if formatting hangs.
10042    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
10043        move |params, _| async move {
10044            assert_eq!(
10045                params.text_document.uri,
10046                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10047            );
10048            futures::future::pending::<()>().await;
10049            unreachable!()
10050        },
10051    );
10052    let save = editor
10053        .update_in(cx, |editor, window, cx| {
10054            editor.save(
10055                SaveOptions {
10056                    format: true,
10057                    autosave: false,
10058                },
10059                project.clone(),
10060                window,
10061                cx,
10062            )
10063        })
10064        .unwrap();
10065    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10066    cx.executor().start_waiting();
10067    save.await;
10068    assert_eq!(
10069        editor.update(cx, |editor, cx| editor.text(cx)),
10070        "one\ntwo\nthree\n"
10071    );
10072    assert!(!cx.read(|cx| editor.is_dirty(cx)));
10073
10074    // For non-dirty buffer, no formatting request should be sent
10075    let save = editor
10076        .update_in(cx, |editor, window, cx| {
10077            editor.save(
10078                SaveOptions {
10079                    format: false,
10080                    autosave: false,
10081                },
10082                project.clone(),
10083                window,
10084                cx,
10085            )
10086        })
10087        .unwrap();
10088    let _pending_format_request = fake_server
10089        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
10090            panic!("Should not be invoked");
10091        })
10092        .next();
10093    cx.executor().start_waiting();
10094    save.await;
10095
10096    // Set Rust language override and assert overridden tabsize is sent to language server
10097    update_test_language_settings(cx, |settings| {
10098        settings.languages.0.insert(
10099            "Rust".into(),
10100            LanguageSettingsContent {
10101                tab_size: NonZeroU32::new(8),
10102                ..Default::default()
10103            },
10104        );
10105    });
10106
10107    editor.update_in(cx, |editor, window, cx| {
10108        editor.set_text("somehting_new\n", window, cx)
10109    });
10110    assert!(cx.read(|cx| editor.is_dirty(cx)));
10111    let save = editor
10112        .update_in(cx, |editor, window, cx| {
10113            editor.save(
10114                SaveOptions {
10115                    format: true,
10116                    autosave: false,
10117                },
10118                project.clone(),
10119                window,
10120                cx,
10121            )
10122        })
10123        .unwrap();
10124    fake_server
10125        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
10126            assert_eq!(
10127                params.text_document.uri,
10128                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10129            );
10130            assert_eq!(params.options.tab_size, 8);
10131            Ok(Some(Vec::new()))
10132        })
10133        .next()
10134        .await;
10135    save.await;
10136}
10137
10138#[gpui::test]
10139async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
10140    init_test(cx, |settings| {
10141        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
10142            Formatter::LanguageServer { name: None },
10143        )))
10144    });
10145
10146    let fs = FakeFs::new(cx.executor());
10147    fs.insert_file(path!("/file.rs"), Default::default()).await;
10148
10149    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10150
10151    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10152    language_registry.add(Arc::new(Language::new(
10153        LanguageConfig {
10154            name: "Rust".into(),
10155            matcher: LanguageMatcher {
10156                path_suffixes: vec!["rs".to_string()],
10157                ..Default::default()
10158            },
10159            ..LanguageConfig::default()
10160        },
10161        Some(tree_sitter_rust::LANGUAGE.into()),
10162    )));
10163    update_test_language_settings(cx, |settings| {
10164        // Enable Prettier formatting for the same buffer, and ensure
10165        // LSP is called instead of Prettier.
10166        settings.defaults.prettier = Some(PrettierSettings {
10167            allowed: true,
10168            ..PrettierSettings::default()
10169        });
10170    });
10171    let mut fake_servers = language_registry.register_fake_lsp(
10172        "Rust",
10173        FakeLspAdapter {
10174            capabilities: lsp::ServerCapabilities {
10175                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10176                ..Default::default()
10177            },
10178            ..Default::default()
10179        },
10180    );
10181
10182    let buffer = project
10183        .update(cx, |project, cx| {
10184            project.open_local_buffer(path!("/file.rs"), cx)
10185        })
10186        .await
10187        .unwrap();
10188
10189    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10190    let (editor, cx) = cx.add_window_view(|window, cx| {
10191        build_editor_with_project(project.clone(), buffer, window, cx)
10192    });
10193    editor.update_in(cx, |editor, window, cx| {
10194        editor.set_text("one\ntwo\nthree\n", window, cx)
10195    });
10196
10197    cx.executor().start_waiting();
10198    let fake_server = fake_servers.next().await.unwrap();
10199
10200    let format = editor
10201        .update_in(cx, |editor, window, cx| {
10202            editor.perform_format(
10203                project.clone(),
10204                FormatTrigger::Manual,
10205                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10206                window,
10207                cx,
10208            )
10209        })
10210        .unwrap();
10211    fake_server
10212        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
10213            assert_eq!(
10214                params.text_document.uri,
10215                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10216            );
10217            assert_eq!(params.options.tab_size, 4);
10218            Ok(Some(vec![lsp::TextEdit::new(
10219                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10220                ", ".to_string(),
10221            )]))
10222        })
10223        .next()
10224        .await;
10225    cx.executor().start_waiting();
10226    format.await;
10227    assert_eq!(
10228        editor.update(cx, |editor, cx| editor.text(cx)),
10229        "one, two\nthree\n"
10230    );
10231
10232    editor.update_in(cx, |editor, window, cx| {
10233        editor.set_text("one\ntwo\nthree\n", window, cx)
10234    });
10235    // Ensure we don't lock if formatting hangs.
10236    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10237        move |params, _| async move {
10238            assert_eq!(
10239                params.text_document.uri,
10240                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10241            );
10242            futures::future::pending::<()>().await;
10243            unreachable!()
10244        },
10245    );
10246    let format = editor
10247        .update_in(cx, |editor, window, cx| {
10248            editor.perform_format(
10249                project,
10250                FormatTrigger::Manual,
10251                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10252                window,
10253                cx,
10254            )
10255        })
10256        .unwrap();
10257    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10258    cx.executor().start_waiting();
10259    format.await;
10260    assert_eq!(
10261        editor.update(cx, |editor, cx| editor.text(cx)),
10262        "one\ntwo\nthree\n"
10263    );
10264}
10265
10266#[gpui::test]
10267async fn test_multiple_formatters(cx: &mut TestAppContext) {
10268    init_test(cx, |settings| {
10269        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
10270        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
10271            Formatter::LanguageServer { name: None },
10272            Formatter::CodeActions(
10273                [
10274                    ("code-action-1".into(), true),
10275                    ("code-action-2".into(), true),
10276                ]
10277                .into_iter()
10278                .collect(),
10279            ),
10280        ])))
10281    });
10282
10283    let fs = FakeFs::new(cx.executor());
10284    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
10285        .await;
10286
10287    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10288    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10289    language_registry.add(rust_lang());
10290
10291    let mut fake_servers = language_registry.register_fake_lsp(
10292        "Rust",
10293        FakeLspAdapter {
10294            capabilities: lsp::ServerCapabilities {
10295                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10296                execute_command_provider: Some(lsp::ExecuteCommandOptions {
10297                    commands: vec!["the-command-for-code-action-1".into()],
10298                    ..Default::default()
10299                }),
10300                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10301                ..Default::default()
10302            },
10303            ..Default::default()
10304        },
10305    );
10306
10307    let buffer = project
10308        .update(cx, |project, cx| {
10309            project.open_local_buffer(path!("/file.rs"), cx)
10310        })
10311        .await
10312        .unwrap();
10313
10314    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10315    let (editor, cx) = cx.add_window_view(|window, cx| {
10316        build_editor_with_project(project.clone(), buffer, window, cx)
10317    });
10318
10319    cx.executor().start_waiting();
10320
10321    let fake_server = fake_servers.next().await.unwrap();
10322    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10323        move |_params, _| async move {
10324            Ok(Some(vec![lsp::TextEdit::new(
10325                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10326                "applied-formatting\n".to_string(),
10327            )]))
10328        },
10329    );
10330    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
10331        move |params, _| async move {
10332            assert_eq!(
10333                params.context.only,
10334                Some(vec!["code-action-1".into(), "code-action-2".into()])
10335            );
10336            let uri = lsp::Url::from_file_path(path!("/file.rs")).unwrap();
10337            Ok(Some(vec![
10338                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10339                    kind: Some("code-action-1".into()),
10340                    edit: Some(lsp::WorkspaceEdit::new(
10341                        [(
10342                            uri.clone(),
10343                            vec![lsp::TextEdit::new(
10344                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10345                                "applied-code-action-1-edit\n".to_string(),
10346                            )],
10347                        )]
10348                        .into_iter()
10349                        .collect(),
10350                    )),
10351                    command: Some(lsp::Command {
10352                        command: "the-command-for-code-action-1".into(),
10353                        ..Default::default()
10354                    }),
10355                    ..Default::default()
10356                }),
10357                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10358                    kind: Some("code-action-2".into()),
10359                    edit: Some(lsp::WorkspaceEdit::new(
10360                        [(
10361                            uri.clone(),
10362                            vec![lsp::TextEdit::new(
10363                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10364                                "applied-code-action-2-edit\n".to_string(),
10365                            )],
10366                        )]
10367                        .into_iter()
10368                        .collect(),
10369                    )),
10370                    ..Default::default()
10371                }),
10372            ]))
10373        },
10374    );
10375
10376    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
10377        move |params, _| async move { Ok(params) }
10378    });
10379
10380    let command_lock = Arc::new(futures::lock::Mutex::new(()));
10381    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
10382        let fake = fake_server.clone();
10383        let lock = command_lock.clone();
10384        move |params, _| {
10385            assert_eq!(params.command, "the-command-for-code-action-1");
10386            let fake = fake.clone();
10387            let lock = lock.clone();
10388            async move {
10389                lock.lock().await;
10390                fake.server
10391                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
10392                        label: None,
10393                        edit: lsp::WorkspaceEdit {
10394                            changes: Some(
10395                                [(
10396                                    lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
10397                                    vec![lsp::TextEdit {
10398                                        range: lsp::Range::new(
10399                                            lsp::Position::new(0, 0),
10400                                            lsp::Position::new(0, 0),
10401                                        ),
10402                                        new_text: "applied-code-action-1-command\n".into(),
10403                                    }],
10404                                )]
10405                                .into_iter()
10406                                .collect(),
10407                            ),
10408                            ..Default::default()
10409                        },
10410                    })
10411                    .await
10412                    .into_response()
10413                    .unwrap();
10414                Ok(Some(json!(null)))
10415            }
10416        }
10417    });
10418
10419    cx.executor().start_waiting();
10420    editor
10421        .update_in(cx, |editor, window, cx| {
10422            editor.perform_format(
10423                project.clone(),
10424                FormatTrigger::Manual,
10425                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10426                window,
10427                cx,
10428            )
10429        })
10430        .unwrap()
10431        .await;
10432    editor.update(cx, |editor, cx| {
10433        assert_eq!(
10434            editor.text(cx),
10435            r#"
10436                applied-code-action-2-edit
10437                applied-code-action-1-command
10438                applied-code-action-1-edit
10439                applied-formatting
10440                one
10441                two
10442                three
10443            "#
10444            .unindent()
10445        );
10446    });
10447
10448    editor.update_in(cx, |editor, window, cx| {
10449        editor.undo(&Default::default(), window, cx);
10450        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10451    });
10452
10453    // Perform a manual edit while waiting for an LSP command
10454    // that's being run as part of a formatting code action.
10455    let lock_guard = command_lock.lock().await;
10456    let format = editor
10457        .update_in(cx, |editor, window, cx| {
10458            editor.perform_format(
10459                project.clone(),
10460                FormatTrigger::Manual,
10461                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10462                window,
10463                cx,
10464            )
10465        })
10466        .unwrap();
10467    cx.run_until_parked();
10468    editor.update(cx, |editor, cx| {
10469        assert_eq!(
10470            editor.text(cx),
10471            r#"
10472                applied-code-action-1-edit
10473                applied-formatting
10474                one
10475                two
10476                three
10477            "#
10478            .unindent()
10479        );
10480
10481        editor.buffer.update(cx, |buffer, cx| {
10482            let ix = buffer.len(cx);
10483            buffer.edit([(ix..ix, "edited\n")], None, cx);
10484        });
10485    });
10486
10487    // Allow the LSP command to proceed. Because the buffer was edited,
10488    // the second code action will not be run.
10489    drop(lock_guard);
10490    format.await;
10491    editor.update_in(cx, |editor, window, cx| {
10492        assert_eq!(
10493            editor.text(cx),
10494            r#"
10495                applied-code-action-1-command
10496                applied-code-action-1-edit
10497                applied-formatting
10498                one
10499                two
10500                three
10501                edited
10502            "#
10503            .unindent()
10504        );
10505
10506        // The manual edit is undone first, because it is the last thing the user did
10507        // (even though the command completed afterwards).
10508        editor.undo(&Default::default(), window, cx);
10509        assert_eq!(
10510            editor.text(cx),
10511            r#"
10512                applied-code-action-1-command
10513                applied-code-action-1-edit
10514                applied-formatting
10515                one
10516                two
10517                three
10518            "#
10519            .unindent()
10520        );
10521
10522        // All the formatting (including the command, which completed after the manual edit)
10523        // is undone together.
10524        editor.undo(&Default::default(), window, cx);
10525        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10526    });
10527}
10528
10529#[gpui::test]
10530async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
10531    init_test(cx, |settings| {
10532        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
10533            Formatter::LanguageServer { name: None },
10534        ])))
10535    });
10536
10537    let fs = FakeFs::new(cx.executor());
10538    fs.insert_file(path!("/file.ts"), Default::default()).await;
10539
10540    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10541
10542    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10543    language_registry.add(Arc::new(Language::new(
10544        LanguageConfig {
10545            name: "TypeScript".into(),
10546            matcher: LanguageMatcher {
10547                path_suffixes: vec!["ts".to_string()],
10548                ..Default::default()
10549            },
10550            ..LanguageConfig::default()
10551        },
10552        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
10553    )));
10554    update_test_language_settings(cx, |settings| {
10555        settings.defaults.prettier = Some(PrettierSettings {
10556            allowed: true,
10557            ..PrettierSettings::default()
10558        });
10559    });
10560    let mut fake_servers = language_registry.register_fake_lsp(
10561        "TypeScript",
10562        FakeLspAdapter {
10563            capabilities: lsp::ServerCapabilities {
10564                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10565                ..Default::default()
10566            },
10567            ..Default::default()
10568        },
10569    );
10570
10571    let buffer = project
10572        .update(cx, |project, cx| {
10573            project.open_local_buffer(path!("/file.ts"), cx)
10574        })
10575        .await
10576        .unwrap();
10577
10578    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10579    let (editor, cx) = cx.add_window_view(|window, cx| {
10580        build_editor_with_project(project.clone(), buffer, window, cx)
10581    });
10582    editor.update_in(cx, |editor, window, cx| {
10583        editor.set_text(
10584            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
10585            window,
10586            cx,
10587        )
10588    });
10589
10590    cx.executor().start_waiting();
10591    let fake_server = fake_servers.next().await.unwrap();
10592
10593    let format = editor
10594        .update_in(cx, |editor, window, cx| {
10595            editor.perform_code_action_kind(
10596                project.clone(),
10597                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
10598                window,
10599                cx,
10600            )
10601        })
10602        .unwrap();
10603    fake_server
10604        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
10605            assert_eq!(
10606                params.text_document.uri,
10607                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
10608            );
10609            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
10610                lsp::CodeAction {
10611                    title: "Organize Imports".to_string(),
10612                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
10613                    edit: Some(lsp::WorkspaceEdit {
10614                        changes: Some(
10615                            [(
10616                                params.text_document.uri.clone(),
10617                                vec![lsp::TextEdit::new(
10618                                    lsp::Range::new(
10619                                        lsp::Position::new(1, 0),
10620                                        lsp::Position::new(2, 0),
10621                                    ),
10622                                    "".to_string(),
10623                                )],
10624                            )]
10625                            .into_iter()
10626                            .collect(),
10627                        ),
10628                        ..Default::default()
10629                    }),
10630                    ..Default::default()
10631                },
10632            )]))
10633        })
10634        .next()
10635        .await;
10636    cx.executor().start_waiting();
10637    format.await;
10638    assert_eq!(
10639        editor.update(cx, |editor, cx| editor.text(cx)),
10640        "import { a } from 'module';\n\nconst x = a;\n"
10641    );
10642
10643    editor.update_in(cx, |editor, window, cx| {
10644        editor.set_text(
10645            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
10646            window,
10647            cx,
10648        )
10649    });
10650    // Ensure we don't lock if code action hangs.
10651    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
10652        move |params, _| async move {
10653            assert_eq!(
10654                params.text_document.uri,
10655                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
10656            );
10657            futures::future::pending::<()>().await;
10658            unreachable!()
10659        },
10660    );
10661    let format = editor
10662        .update_in(cx, |editor, window, cx| {
10663            editor.perform_code_action_kind(
10664                project,
10665                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
10666                window,
10667                cx,
10668            )
10669        })
10670        .unwrap();
10671    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
10672    cx.executor().start_waiting();
10673    format.await;
10674    assert_eq!(
10675        editor.update(cx, |editor, cx| editor.text(cx)),
10676        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
10677    );
10678}
10679
10680#[gpui::test]
10681async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
10682    init_test(cx, |_| {});
10683
10684    let mut cx = EditorLspTestContext::new_rust(
10685        lsp::ServerCapabilities {
10686            document_formatting_provider: Some(lsp::OneOf::Left(true)),
10687            ..Default::default()
10688        },
10689        cx,
10690    )
10691    .await;
10692
10693    cx.set_state(indoc! {"
10694        one.twoˇ
10695    "});
10696
10697    // The format request takes a long time. When it completes, it inserts
10698    // a newline and an indent before the `.`
10699    cx.lsp
10700        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
10701            let executor = cx.background_executor().clone();
10702            async move {
10703                executor.timer(Duration::from_millis(100)).await;
10704                Ok(Some(vec![lsp::TextEdit {
10705                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
10706                    new_text: "\n    ".into(),
10707                }]))
10708            }
10709        });
10710
10711    // Submit a format request.
10712    let format_1 = cx
10713        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10714        .unwrap();
10715    cx.executor().run_until_parked();
10716
10717    // Submit a second format request.
10718    let format_2 = cx
10719        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10720        .unwrap();
10721    cx.executor().run_until_parked();
10722
10723    // Wait for both format requests to complete
10724    cx.executor().advance_clock(Duration::from_millis(200));
10725    cx.executor().start_waiting();
10726    format_1.await.unwrap();
10727    cx.executor().start_waiting();
10728    format_2.await.unwrap();
10729
10730    // The formatting edits only happens once.
10731    cx.assert_editor_state(indoc! {"
10732        one
10733            .twoˇ
10734    "});
10735}
10736
10737#[gpui::test]
10738async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
10739    init_test(cx, |settings| {
10740        settings.defaults.formatter = Some(SelectedFormatter::Auto)
10741    });
10742
10743    let mut cx = EditorLspTestContext::new_rust(
10744        lsp::ServerCapabilities {
10745            document_formatting_provider: Some(lsp::OneOf::Left(true)),
10746            ..Default::default()
10747        },
10748        cx,
10749    )
10750    .await;
10751
10752    // Set up a buffer white some trailing whitespace and no trailing newline.
10753    cx.set_state(
10754        &[
10755            "one ",   //
10756            "twoˇ",   //
10757            "three ", //
10758            "four",   //
10759        ]
10760        .join("\n"),
10761    );
10762
10763    // Submit a format request.
10764    let format = cx
10765        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10766        .unwrap();
10767
10768    // Record which buffer changes have been sent to the language server
10769    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
10770    cx.lsp
10771        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
10772            let buffer_changes = buffer_changes.clone();
10773            move |params, _| {
10774                buffer_changes.lock().extend(
10775                    params
10776                        .content_changes
10777                        .into_iter()
10778                        .map(|e| (e.range.unwrap(), e.text)),
10779                );
10780            }
10781        });
10782
10783    // Handle formatting requests to the language server.
10784    cx.lsp
10785        .set_request_handler::<lsp::request::Formatting, _, _>({
10786            let buffer_changes = buffer_changes.clone();
10787            move |_, _| {
10788                // When formatting is requested, trailing whitespace has already been stripped,
10789                // and the trailing newline has already been added.
10790                assert_eq!(
10791                    &buffer_changes.lock()[1..],
10792                    &[
10793                        (
10794                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
10795                            "".into()
10796                        ),
10797                        (
10798                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
10799                            "".into()
10800                        ),
10801                        (
10802                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
10803                            "\n".into()
10804                        ),
10805                    ]
10806                );
10807
10808                // Insert blank lines between each line of the buffer.
10809                async move {
10810                    Ok(Some(vec![
10811                        lsp::TextEdit {
10812                            range: lsp::Range::new(
10813                                lsp::Position::new(1, 0),
10814                                lsp::Position::new(1, 0),
10815                            ),
10816                            new_text: "\n".into(),
10817                        },
10818                        lsp::TextEdit {
10819                            range: lsp::Range::new(
10820                                lsp::Position::new(2, 0),
10821                                lsp::Position::new(2, 0),
10822                            ),
10823                            new_text: "\n".into(),
10824                        },
10825                    ]))
10826                }
10827            }
10828        });
10829
10830    // After formatting the buffer, the trailing whitespace is stripped,
10831    // a newline is appended, and the edits provided by the language server
10832    // have been applied.
10833    format.await.unwrap();
10834    cx.assert_editor_state(
10835        &[
10836            "one",   //
10837            "",      //
10838            "twoˇ",  //
10839            "",      //
10840            "three", //
10841            "four",  //
10842            "",      //
10843        ]
10844        .join("\n"),
10845    );
10846
10847    // Undoing the formatting undoes the trailing whitespace removal, the
10848    // trailing newline, and the LSP edits.
10849    cx.update_buffer(|buffer, cx| buffer.undo(cx));
10850    cx.assert_editor_state(
10851        &[
10852            "one ",   //
10853            "twoˇ",   //
10854            "three ", //
10855            "four",   //
10856        ]
10857        .join("\n"),
10858    );
10859}
10860
10861#[gpui::test]
10862async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
10863    cx: &mut TestAppContext,
10864) {
10865    init_test(cx, |_| {});
10866
10867    cx.update(|cx| {
10868        cx.update_global::<SettingsStore, _>(|settings, cx| {
10869            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10870                settings.auto_signature_help = Some(true);
10871            });
10872        });
10873    });
10874
10875    let mut cx = EditorLspTestContext::new_rust(
10876        lsp::ServerCapabilities {
10877            signature_help_provider: Some(lsp::SignatureHelpOptions {
10878                ..Default::default()
10879            }),
10880            ..Default::default()
10881        },
10882        cx,
10883    )
10884    .await;
10885
10886    let language = Language::new(
10887        LanguageConfig {
10888            name: "Rust".into(),
10889            brackets: BracketPairConfig {
10890                pairs: vec![
10891                    BracketPair {
10892                        start: "{".to_string(),
10893                        end: "}".to_string(),
10894                        close: true,
10895                        surround: true,
10896                        newline: true,
10897                    },
10898                    BracketPair {
10899                        start: "(".to_string(),
10900                        end: ")".to_string(),
10901                        close: true,
10902                        surround: true,
10903                        newline: true,
10904                    },
10905                    BracketPair {
10906                        start: "/*".to_string(),
10907                        end: " */".to_string(),
10908                        close: true,
10909                        surround: true,
10910                        newline: true,
10911                    },
10912                    BracketPair {
10913                        start: "[".to_string(),
10914                        end: "]".to_string(),
10915                        close: false,
10916                        surround: false,
10917                        newline: true,
10918                    },
10919                    BracketPair {
10920                        start: "\"".to_string(),
10921                        end: "\"".to_string(),
10922                        close: true,
10923                        surround: true,
10924                        newline: false,
10925                    },
10926                    BracketPair {
10927                        start: "<".to_string(),
10928                        end: ">".to_string(),
10929                        close: false,
10930                        surround: true,
10931                        newline: true,
10932                    },
10933                ],
10934                ..Default::default()
10935            },
10936            autoclose_before: "})]".to_string(),
10937            ..Default::default()
10938        },
10939        Some(tree_sitter_rust::LANGUAGE.into()),
10940    );
10941    let language = Arc::new(language);
10942
10943    cx.language_registry().add(language.clone());
10944    cx.update_buffer(|buffer, cx| {
10945        buffer.set_language(Some(language), cx);
10946    });
10947
10948    cx.set_state(
10949        &r#"
10950            fn main() {
10951                sampleˇ
10952            }
10953        "#
10954        .unindent(),
10955    );
10956
10957    cx.update_editor(|editor, window, cx| {
10958        editor.handle_input("(", window, cx);
10959    });
10960    cx.assert_editor_state(
10961        &"
10962            fn main() {
10963                sample(ˇ)
10964            }
10965        "
10966        .unindent(),
10967    );
10968
10969    let mocked_response = lsp::SignatureHelp {
10970        signatures: vec![lsp::SignatureInformation {
10971            label: "fn sample(param1: u8, param2: u8)".to_string(),
10972            documentation: None,
10973            parameters: Some(vec![
10974                lsp::ParameterInformation {
10975                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10976                    documentation: None,
10977                },
10978                lsp::ParameterInformation {
10979                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10980                    documentation: None,
10981                },
10982            ]),
10983            active_parameter: None,
10984        }],
10985        active_signature: Some(0),
10986        active_parameter: Some(0),
10987    };
10988    handle_signature_help_request(&mut cx, mocked_response).await;
10989
10990    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10991        .await;
10992
10993    cx.editor(|editor, _, _| {
10994        let signature_help_state = editor.signature_help_state.popover().cloned();
10995        let signature = signature_help_state.unwrap();
10996        assert_eq!(
10997            signature.signatures[signature.current_signature].label,
10998            "fn sample(param1: u8, param2: u8)"
10999        );
11000    });
11001}
11002
11003#[gpui::test]
11004async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
11005    init_test(cx, |_| {});
11006
11007    cx.update(|cx| {
11008        cx.update_global::<SettingsStore, _>(|settings, cx| {
11009            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11010                settings.auto_signature_help = Some(false);
11011                settings.show_signature_help_after_edits = Some(false);
11012            });
11013        });
11014    });
11015
11016    let mut cx = EditorLspTestContext::new_rust(
11017        lsp::ServerCapabilities {
11018            signature_help_provider: Some(lsp::SignatureHelpOptions {
11019                ..Default::default()
11020            }),
11021            ..Default::default()
11022        },
11023        cx,
11024    )
11025    .await;
11026
11027    let language = Language::new(
11028        LanguageConfig {
11029            name: "Rust".into(),
11030            brackets: BracketPairConfig {
11031                pairs: vec![
11032                    BracketPair {
11033                        start: "{".to_string(),
11034                        end: "}".to_string(),
11035                        close: true,
11036                        surround: true,
11037                        newline: true,
11038                    },
11039                    BracketPair {
11040                        start: "(".to_string(),
11041                        end: ")".to_string(),
11042                        close: true,
11043                        surround: true,
11044                        newline: true,
11045                    },
11046                    BracketPair {
11047                        start: "/*".to_string(),
11048                        end: " */".to_string(),
11049                        close: true,
11050                        surround: true,
11051                        newline: true,
11052                    },
11053                    BracketPair {
11054                        start: "[".to_string(),
11055                        end: "]".to_string(),
11056                        close: false,
11057                        surround: false,
11058                        newline: true,
11059                    },
11060                    BracketPair {
11061                        start: "\"".to_string(),
11062                        end: "\"".to_string(),
11063                        close: true,
11064                        surround: true,
11065                        newline: false,
11066                    },
11067                    BracketPair {
11068                        start: "<".to_string(),
11069                        end: ">".to_string(),
11070                        close: false,
11071                        surround: true,
11072                        newline: true,
11073                    },
11074                ],
11075                ..Default::default()
11076            },
11077            autoclose_before: "})]".to_string(),
11078            ..Default::default()
11079        },
11080        Some(tree_sitter_rust::LANGUAGE.into()),
11081    );
11082    let language = Arc::new(language);
11083
11084    cx.language_registry().add(language.clone());
11085    cx.update_buffer(|buffer, cx| {
11086        buffer.set_language(Some(language), cx);
11087    });
11088
11089    // Ensure that signature_help is not called when no signature help is enabled.
11090    cx.set_state(
11091        &r#"
11092            fn main() {
11093                sampleˇ
11094            }
11095        "#
11096        .unindent(),
11097    );
11098    cx.update_editor(|editor, window, cx| {
11099        editor.handle_input("(", window, cx);
11100    });
11101    cx.assert_editor_state(
11102        &"
11103            fn main() {
11104                sample(ˇ)
11105            }
11106        "
11107        .unindent(),
11108    );
11109    cx.editor(|editor, _, _| {
11110        assert!(editor.signature_help_state.task().is_none());
11111    });
11112
11113    let mocked_response = lsp::SignatureHelp {
11114        signatures: vec![lsp::SignatureInformation {
11115            label: "fn sample(param1: u8, param2: u8)".to_string(),
11116            documentation: None,
11117            parameters: Some(vec![
11118                lsp::ParameterInformation {
11119                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11120                    documentation: None,
11121                },
11122                lsp::ParameterInformation {
11123                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11124                    documentation: None,
11125                },
11126            ]),
11127            active_parameter: None,
11128        }],
11129        active_signature: Some(0),
11130        active_parameter: Some(0),
11131    };
11132
11133    // Ensure that signature_help is called when enabled afte edits
11134    cx.update(|_, cx| {
11135        cx.update_global::<SettingsStore, _>(|settings, cx| {
11136            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11137                settings.auto_signature_help = Some(false);
11138                settings.show_signature_help_after_edits = Some(true);
11139            });
11140        });
11141    });
11142    cx.set_state(
11143        &r#"
11144            fn main() {
11145                sampleˇ
11146            }
11147        "#
11148        .unindent(),
11149    );
11150    cx.update_editor(|editor, window, cx| {
11151        editor.handle_input("(", window, cx);
11152    });
11153    cx.assert_editor_state(
11154        &"
11155            fn main() {
11156                sample(ˇ)
11157            }
11158        "
11159        .unindent(),
11160    );
11161    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11162    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11163        .await;
11164    cx.update_editor(|editor, _, _| {
11165        let signature_help_state = editor.signature_help_state.popover().cloned();
11166        assert!(signature_help_state.is_some());
11167        let signature = signature_help_state.unwrap();
11168        assert_eq!(
11169            signature.signatures[signature.current_signature].label,
11170            "fn sample(param1: u8, param2: u8)"
11171        );
11172        editor.signature_help_state = SignatureHelpState::default();
11173    });
11174
11175    // Ensure that signature_help is called when auto signature help override is enabled
11176    cx.update(|_, cx| {
11177        cx.update_global::<SettingsStore, _>(|settings, cx| {
11178            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11179                settings.auto_signature_help = Some(true);
11180                settings.show_signature_help_after_edits = Some(false);
11181            });
11182        });
11183    });
11184    cx.set_state(
11185        &r#"
11186            fn main() {
11187                sampleˇ
11188            }
11189        "#
11190        .unindent(),
11191    );
11192    cx.update_editor(|editor, window, cx| {
11193        editor.handle_input("(", window, cx);
11194    });
11195    cx.assert_editor_state(
11196        &"
11197            fn main() {
11198                sample(ˇ)
11199            }
11200        "
11201        .unindent(),
11202    );
11203    handle_signature_help_request(&mut cx, mocked_response).await;
11204    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11205        .await;
11206    cx.editor(|editor, _, _| {
11207        let signature_help_state = editor.signature_help_state.popover().cloned();
11208        assert!(signature_help_state.is_some());
11209        let signature = signature_help_state.unwrap();
11210        assert_eq!(
11211            signature.signatures[signature.current_signature].label,
11212            "fn sample(param1: u8, param2: u8)"
11213        );
11214    });
11215}
11216
11217#[gpui::test]
11218async fn test_signature_help(cx: &mut TestAppContext) {
11219    init_test(cx, |_| {});
11220    cx.update(|cx| {
11221        cx.update_global::<SettingsStore, _>(|settings, cx| {
11222            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11223                settings.auto_signature_help = Some(true);
11224            });
11225        });
11226    });
11227
11228    let mut cx = EditorLspTestContext::new_rust(
11229        lsp::ServerCapabilities {
11230            signature_help_provider: Some(lsp::SignatureHelpOptions {
11231                ..Default::default()
11232            }),
11233            ..Default::default()
11234        },
11235        cx,
11236    )
11237    .await;
11238
11239    // A test that directly calls `show_signature_help`
11240    cx.update_editor(|editor, window, cx| {
11241        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11242    });
11243
11244    let mocked_response = lsp::SignatureHelp {
11245        signatures: vec![lsp::SignatureInformation {
11246            label: "fn sample(param1: u8, param2: u8)".to_string(),
11247            documentation: None,
11248            parameters: Some(vec![
11249                lsp::ParameterInformation {
11250                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11251                    documentation: None,
11252                },
11253                lsp::ParameterInformation {
11254                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11255                    documentation: None,
11256                },
11257            ]),
11258            active_parameter: None,
11259        }],
11260        active_signature: Some(0),
11261        active_parameter: Some(0),
11262    };
11263    handle_signature_help_request(&mut cx, mocked_response).await;
11264
11265    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11266        .await;
11267
11268    cx.editor(|editor, _, _| {
11269        let signature_help_state = editor.signature_help_state.popover().cloned();
11270        assert!(signature_help_state.is_some());
11271        let signature = signature_help_state.unwrap();
11272        assert_eq!(
11273            signature.signatures[signature.current_signature].label,
11274            "fn sample(param1: u8, param2: u8)"
11275        );
11276    });
11277
11278    // When exiting outside from inside the brackets, `signature_help` is closed.
11279    cx.set_state(indoc! {"
11280        fn main() {
11281            sample(ˇ);
11282        }
11283
11284        fn sample(param1: u8, param2: u8) {}
11285    "});
11286
11287    cx.update_editor(|editor, window, cx| {
11288        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11289            s.select_ranges([0..0])
11290        });
11291    });
11292
11293    let mocked_response = lsp::SignatureHelp {
11294        signatures: Vec::new(),
11295        active_signature: None,
11296        active_parameter: None,
11297    };
11298    handle_signature_help_request(&mut cx, mocked_response).await;
11299
11300    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11301        .await;
11302
11303    cx.editor(|editor, _, _| {
11304        assert!(!editor.signature_help_state.is_shown());
11305    });
11306
11307    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
11308    cx.set_state(indoc! {"
11309        fn main() {
11310            sample(ˇ);
11311        }
11312
11313        fn sample(param1: u8, param2: u8) {}
11314    "});
11315
11316    let mocked_response = lsp::SignatureHelp {
11317        signatures: vec![lsp::SignatureInformation {
11318            label: "fn sample(param1: u8, param2: u8)".to_string(),
11319            documentation: None,
11320            parameters: Some(vec![
11321                lsp::ParameterInformation {
11322                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11323                    documentation: None,
11324                },
11325                lsp::ParameterInformation {
11326                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11327                    documentation: None,
11328                },
11329            ]),
11330            active_parameter: None,
11331        }],
11332        active_signature: Some(0),
11333        active_parameter: Some(0),
11334    };
11335    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11336    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11337        .await;
11338    cx.editor(|editor, _, _| {
11339        assert!(editor.signature_help_state.is_shown());
11340    });
11341
11342    // Restore the popover with more parameter input
11343    cx.set_state(indoc! {"
11344        fn main() {
11345            sample(param1, param2ˇ);
11346        }
11347
11348        fn sample(param1: u8, param2: u8) {}
11349    "});
11350
11351    let mocked_response = lsp::SignatureHelp {
11352        signatures: vec![lsp::SignatureInformation {
11353            label: "fn sample(param1: u8, param2: u8)".to_string(),
11354            documentation: None,
11355            parameters: Some(vec![
11356                lsp::ParameterInformation {
11357                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11358                    documentation: None,
11359                },
11360                lsp::ParameterInformation {
11361                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11362                    documentation: None,
11363                },
11364            ]),
11365            active_parameter: None,
11366        }],
11367        active_signature: Some(0),
11368        active_parameter: Some(1),
11369    };
11370    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11371    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11372        .await;
11373
11374    // When selecting a range, the popover is gone.
11375    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
11376    cx.update_editor(|editor, window, cx| {
11377        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11378            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11379        })
11380    });
11381    cx.assert_editor_state(indoc! {"
11382        fn main() {
11383            sample(param1, «ˇparam2»);
11384        }
11385
11386        fn sample(param1: u8, param2: u8) {}
11387    "});
11388    cx.editor(|editor, _, _| {
11389        assert!(!editor.signature_help_state.is_shown());
11390    });
11391
11392    // When unselecting again, the popover is back if within the brackets.
11393    cx.update_editor(|editor, window, cx| {
11394        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11395            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11396        })
11397    });
11398    cx.assert_editor_state(indoc! {"
11399        fn main() {
11400            sample(param1, ˇparam2);
11401        }
11402
11403        fn sample(param1: u8, param2: u8) {}
11404    "});
11405    handle_signature_help_request(&mut cx, mocked_response).await;
11406    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11407        .await;
11408    cx.editor(|editor, _, _| {
11409        assert!(editor.signature_help_state.is_shown());
11410    });
11411
11412    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
11413    cx.update_editor(|editor, window, cx| {
11414        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11415            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
11416            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11417        })
11418    });
11419    cx.assert_editor_state(indoc! {"
11420        fn main() {
11421            sample(param1, ˇparam2);
11422        }
11423
11424        fn sample(param1: u8, param2: u8) {}
11425    "});
11426
11427    let mocked_response = lsp::SignatureHelp {
11428        signatures: vec![lsp::SignatureInformation {
11429            label: "fn sample(param1: u8, param2: u8)".to_string(),
11430            documentation: None,
11431            parameters: Some(vec![
11432                lsp::ParameterInformation {
11433                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11434                    documentation: None,
11435                },
11436                lsp::ParameterInformation {
11437                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11438                    documentation: None,
11439                },
11440            ]),
11441            active_parameter: None,
11442        }],
11443        active_signature: Some(0),
11444        active_parameter: Some(1),
11445    };
11446    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11447    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11448        .await;
11449    cx.update_editor(|editor, _, cx| {
11450        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
11451    });
11452    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11453        .await;
11454    cx.update_editor(|editor, window, cx| {
11455        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11456            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11457        })
11458    });
11459    cx.assert_editor_state(indoc! {"
11460        fn main() {
11461            sample(param1, «ˇparam2»);
11462        }
11463
11464        fn sample(param1: u8, param2: u8) {}
11465    "});
11466    cx.update_editor(|editor, window, cx| {
11467        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11468            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11469        })
11470    });
11471    cx.assert_editor_state(indoc! {"
11472        fn main() {
11473            sample(param1, ˇparam2);
11474        }
11475
11476        fn sample(param1: u8, param2: u8) {}
11477    "});
11478    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
11479        .await;
11480}
11481
11482#[gpui::test]
11483async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
11484    init_test(cx, |_| {});
11485
11486    let mut cx = EditorLspTestContext::new_rust(
11487        lsp::ServerCapabilities {
11488            signature_help_provider: Some(lsp::SignatureHelpOptions {
11489                ..Default::default()
11490            }),
11491            ..Default::default()
11492        },
11493        cx,
11494    )
11495    .await;
11496
11497    cx.set_state(indoc! {"
11498        fn main() {
11499            overloadedˇ
11500        }
11501    "});
11502
11503    cx.update_editor(|editor, window, cx| {
11504        editor.handle_input("(", window, cx);
11505        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11506    });
11507
11508    // Mock response with 3 signatures
11509    let mocked_response = lsp::SignatureHelp {
11510        signatures: vec![
11511            lsp::SignatureInformation {
11512                label: "fn overloaded(x: i32)".to_string(),
11513                documentation: None,
11514                parameters: Some(vec![lsp::ParameterInformation {
11515                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11516                    documentation: None,
11517                }]),
11518                active_parameter: None,
11519            },
11520            lsp::SignatureInformation {
11521                label: "fn overloaded(x: i32, y: i32)".to_string(),
11522                documentation: None,
11523                parameters: Some(vec![
11524                    lsp::ParameterInformation {
11525                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11526                        documentation: None,
11527                    },
11528                    lsp::ParameterInformation {
11529                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
11530                        documentation: None,
11531                    },
11532                ]),
11533                active_parameter: None,
11534            },
11535            lsp::SignatureInformation {
11536                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
11537                documentation: None,
11538                parameters: Some(vec![
11539                    lsp::ParameterInformation {
11540                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11541                        documentation: None,
11542                    },
11543                    lsp::ParameterInformation {
11544                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
11545                        documentation: None,
11546                    },
11547                    lsp::ParameterInformation {
11548                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
11549                        documentation: None,
11550                    },
11551                ]),
11552                active_parameter: None,
11553            },
11554        ],
11555        active_signature: Some(1),
11556        active_parameter: Some(0),
11557    };
11558    handle_signature_help_request(&mut cx, mocked_response).await;
11559
11560    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11561        .await;
11562
11563    // Verify we have multiple signatures and the right one is selected
11564    cx.editor(|editor, _, _| {
11565        let popover = editor.signature_help_state.popover().cloned().unwrap();
11566        assert_eq!(popover.signatures.len(), 3);
11567        // active_signature was 1, so that should be the current
11568        assert_eq!(popover.current_signature, 1);
11569        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
11570        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
11571        assert_eq!(
11572            popover.signatures[2].label,
11573            "fn overloaded(x: i32, y: i32, z: i32)"
11574        );
11575    });
11576
11577    // Test navigation functionality
11578    cx.update_editor(|editor, window, cx| {
11579        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
11580    });
11581
11582    cx.editor(|editor, _, _| {
11583        let popover = editor.signature_help_state.popover().cloned().unwrap();
11584        assert_eq!(popover.current_signature, 2);
11585    });
11586
11587    // Test wrap around
11588    cx.update_editor(|editor, window, cx| {
11589        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
11590    });
11591
11592    cx.editor(|editor, _, _| {
11593        let popover = editor.signature_help_state.popover().cloned().unwrap();
11594        assert_eq!(popover.current_signature, 0);
11595    });
11596
11597    // Test previous navigation
11598    cx.update_editor(|editor, window, cx| {
11599        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
11600    });
11601
11602    cx.editor(|editor, _, _| {
11603        let popover = editor.signature_help_state.popover().cloned().unwrap();
11604        assert_eq!(popover.current_signature, 2);
11605    });
11606}
11607
11608#[gpui::test]
11609async fn test_completion_mode(cx: &mut TestAppContext) {
11610    init_test(cx, |_| {});
11611    let mut cx = EditorLspTestContext::new_rust(
11612        lsp::ServerCapabilities {
11613            completion_provider: Some(lsp::CompletionOptions {
11614                resolve_provider: Some(true),
11615                ..Default::default()
11616            }),
11617            ..Default::default()
11618        },
11619        cx,
11620    )
11621    .await;
11622
11623    struct Run {
11624        run_description: &'static str,
11625        initial_state: String,
11626        buffer_marked_text: String,
11627        completion_label: &'static str,
11628        completion_text: &'static str,
11629        expected_with_insert_mode: String,
11630        expected_with_replace_mode: String,
11631        expected_with_replace_subsequence_mode: String,
11632        expected_with_replace_suffix_mode: String,
11633    }
11634
11635    let runs = [
11636        Run {
11637            run_description: "Start of word matches completion text",
11638            initial_state: "before ediˇ after".into(),
11639            buffer_marked_text: "before <edi|> after".into(),
11640            completion_label: "editor",
11641            completion_text: "editor",
11642            expected_with_insert_mode: "before editorˇ after".into(),
11643            expected_with_replace_mode: "before editorˇ after".into(),
11644            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11645            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11646        },
11647        Run {
11648            run_description: "Accept same text at the middle of the word",
11649            initial_state: "before ediˇtor after".into(),
11650            buffer_marked_text: "before <edi|tor> after".into(),
11651            completion_label: "editor",
11652            completion_text: "editor",
11653            expected_with_insert_mode: "before editorˇtor after".into(),
11654            expected_with_replace_mode: "before editorˇ after".into(),
11655            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11656            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11657        },
11658        Run {
11659            run_description: "End of word matches completion text -- cursor at end",
11660            initial_state: "before torˇ after".into(),
11661            buffer_marked_text: "before <tor|> after".into(),
11662            completion_label: "editor",
11663            completion_text: "editor",
11664            expected_with_insert_mode: "before editorˇ after".into(),
11665            expected_with_replace_mode: "before editorˇ after".into(),
11666            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11667            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11668        },
11669        Run {
11670            run_description: "End of word matches completion text -- cursor at start",
11671            initial_state: "before ˇtor after".into(),
11672            buffer_marked_text: "before <|tor> after".into(),
11673            completion_label: "editor",
11674            completion_text: "editor",
11675            expected_with_insert_mode: "before editorˇtor after".into(),
11676            expected_with_replace_mode: "before editorˇ after".into(),
11677            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11678            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11679        },
11680        Run {
11681            run_description: "Prepend text containing whitespace",
11682            initial_state: "pˇfield: bool".into(),
11683            buffer_marked_text: "<p|field>: bool".into(),
11684            completion_label: "pub ",
11685            completion_text: "pub ",
11686            expected_with_insert_mode: "pub ˇfield: bool".into(),
11687            expected_with_replace_mode: "pub ˇ: bool".into(),
11688            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
11689            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
11690        },
11691        Run {
11692            run_description: "Add element to start of list",
11693            initial_state: "[element_ˇelement_2]".into(),
11694            buffer_marked_text: "[<element_|element_2>]".into(),
11695            completion_label: "element_1",
11696            completion_text: "element_1",
11697            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
11698            expected_with_replace_mode: "[element_1ˇ]".into(),
11699            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
11700            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
11701        },
11702        Run {
11703            run_description: "Add element to start of list -- first and second elements are equal",
11704            initial_state: "[elˇelement]".into(),
11705            buffer_marked_text: "[<el|element>]".into(),
11706            completion_label: "element",
11707            completion_text: "element",
11708            expected_with_insert_mode: "[elementˇelement]".into(),
11709            expected_with_replace_mode: "[elementˇ]".into(),
11710            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
11711            expected_with_replace_suffix_mode: "[elementˇ]".into(),
11712        },
11713        Run {
11714            run_description: "Ends with matching suffix",
11715            initial_state: "SubˇError".into(),
11716            buffer_marked_text: "<Sub|Error>".into(),
11717            completion_label: "SubscriptionError",
11718            completion_text: "SubscriptionError",
11719            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
11720            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11721            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11722            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
11723        },
11724        Run {
11725            run_description: "Suffix is a subsequence -- contiguous",
11726            initial_state: "SubˇErr".into(),
11727            buffer_marked_text: "<Sub|Err>".into(),
11728            completion_label: "SubscriptionError",
11729            completion_text: "SubscriptionError",
11730            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
11731            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11732            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11733            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
11734        },
11735        Run {
11736            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
11737            initial_state: "Suˇscrirr".into(),
11738            buffer_marked_text: "<Su|scrirr>".into(),
11739            completion_label: "SubscriptionError",
11740            completion_text: "SubscriptionError",
11741            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
11742            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11743            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11744            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
11745        },
11746        Run {
11747            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
11748            initial_state: "foo(indˇix)".into(),
11749            buffer_marked_text: "foo(<ind|ix>)".into(),
11750            completion_label: "node_index",
11751            completion_text: "node_index",
11752            expected_with_insert_mode: "foo(node_indexˇix)".into(),
11753            expected_with_replace_mode: "foo(node_indexˇ)".into(),
11754            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
11755            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
11756        },
11757        Run {
11758            run_description: "Replace range ends before cursor - should extend to cursor",
11759            initial_state: "before editˇo after".into(),
11760            buffer_marked_text: "before <{ed}>it|o after".into(),
11761            completion_label: "editor",
11762            completion_text: "editor",
11763            expected_with_insert_mode: "before editorˇo after".into(),
11764            expected_with_replace_mode: "before editorˇo after".into(),
11765            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
11766            expected_with_replace_suffix_mode: "before editorˇo after".into(),
11767        },
11768        Run {
11769            run_description: "Uses label for suffix matching",
11770            initial_state: "before ediˇtor after".into(),
11771            buffer_marked_text: "before <edi|tor> after".into(),
11772            completion_label: "editor",
11773            completion_text: "editor()",
11774            expected_with_insert_mode: "before editor()ˇtor after".into(),
11775            expected_with_replace_mode: "before editor()ˇ after".into(),
11776            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
11777            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
11778        },
11779        Run {
11780            run_description: "Case insensitive subsequence and suffix matching",
11781            initial_state: "before EDiˇtoR after".into(),
11782            buffer_marked_text: "before <EDi|toR> after".into(),
11783            completion_label: "editor",
11784            completion_text: "editor",
11785            expected_with_insert_mode: "before editorˇtoR after".into(),
11786            expected_with_replace_mode: "before editorˇ after".into(),
11787            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11788            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11789        },
11790    ];
11791
11792    for run in runs {
11793        let run_variations = [
11794            (LspInsertMode::Insert, run.expected_with_insert_mode),
11795            (LspInsertMode::Replace, run.expected_with_replace_mode),
11796            (
11797                LspInsertMode::ReplaceSubsequence,
11798                run.expected_with_replace_subsequence_mode,
11799            ),
11800            (
11801                LspInsertMode::ReplaceSuffix,
11802                run.expected_with_replace_suffix_mode,
11803            ),
11804        ];
11805
11806        for (lsp_insert_mode, expected_text) in run_variations {
11807            eprintln!(
11808                "run = {:?}, mode = {lsp_insert_mode:.?}",
11809                run.run_description,
11810            );
11811
11812            update_test_language_settings(&mut cx, |settings| {
11813                settings.defaults.completions = Some(CompletionSettings {
11814                    lsp_insert_mode,
11815                    words: WordsCompletionMode::Disabled,
11816                    lsp: true,
11817                    lsp_fetch_timeout_ms: 0,
11818                });
11819            });
11820
11821            cx.set_state(&run.initial_state);
11822            cx.update_editor(|editor, window, cx| {
11823                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11824            });
11825
11826            let counter = Arc::new(AtomicUsize::new(0));
11827            handle_completion_request_with_insert_and_replace(
11828                &mut cx,
11829                &run.buffer_marked_text,
11830                vec![(run.completion_label, run.completion_text)],
11831                counter.clone(),
11832            )
11833            .await;
11834            cx.condition(|editor, _| editor.context_menu_visible())
11835                .await;
11836            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11837
11838            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11839                editor
11840                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
11841                    .unwrap()
11842            });
11843            cx.assert_editor_state(&expected_text);
11844            handle_resolve_completion_request(&mut cx, None).await;
11845            apply_additional_edits.await.unwrap();
11846        }
11847    }
11848}
11849
11850#[gpui::test]
11851async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
11852    init_test(cx, |_| {});
11853    let mut cx = EditorLspTestContext::new_rust(
11854        lsp::ServerCapabilities {
11855            completion_provider: Some(lsp::CompletionOptions {
11856                resolve_provider: Some(true),
11857                ..Default::default()
11858            }),
11859            ..Default::default()
11860        },
11861        cx,
11862    )
11863    .await;
11864
11865    let initial_state = "SubˇError";
11866    let buffer_marked_text = "<Sub|Error>";
11867    let completion_text = "SubscriptionError";
11868    let expected_with_insert_mode = "SubscriptionErrorˇError";
11869    let expected_with_replace_mode = "SubscriptionErrorˇ";
11870
11871    update_test_language_settings(&mut cx, |settings| {
11872        settings.defaults.completions = Some(CompletionSettings {
11873            words: WordsCompletionMode::Disabled,
11874            // set the opposite here to ensure that the action is overriding the default behavior
11875            lsp_insert_mode: LspInsertMode::Insert,
11876            lsp: true,
11877            lsp_fetch_timeout_ms: 0,
11878        });
11879    });
11880
11881    cx.set_state(initial_state);
11882    cx.update_editor(|editor, window, cx| {
11883        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11884    });
11885
11886    let counter = Arc::new(AtomicUsize::new(0));
11887    handle_completion_request_with_insert_and_replace(
11888        &mut cx,
11889        &buffer_marked_text,
11890        vec![(completion_text, completion_text)],
11891        counter.clone(),
11892    )
11893    .await;
11894    cx.condition(|editor, _| editor.context_menu_visible())
11895        .await;
11896    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11897
11898    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11899        editor
11900            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11901            .unwrap()
11902    });
11903    cx.assert_editor_state(&expected_with_replace_mode);
11904    handle_resolve_completion_request(&mut cx, None).await;
11905    apply_additional_edits.await.unwrap();
11906
11907    update_test_language_settings(&mut cx, |settings| {
11908        settings.defaults.completions = Some(CompletionSettings {
11909            words: WordsCompletionMode::Disabled,
11910            // set the opposite here to ensure that the action is overriding the default behavior
11911            lsp_insert_mode: LspInsertMode::Replace,
11912            lsp: true,
11913            lsp_fetch_timeout_ms: 0,
11914        });
11915    });
11916
11917    cx.set_state(initial_state);
11918    cx.update_editor(|editor, window, cx| {
11919        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11920    });
11921    handle_completion_request_with_insert_and_replace(
11922        &mut cx,
11923        &buffer_marked_text,
11924        vec![(completion_text, completion_text)],
11925        counter.clone(),
11926    )
11927    .await;
11928    cx.condition(|editor, _| editor.context_menu_visible())
11929        .await;
11930    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
11931
11932    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11933        editor
11934            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
11935            .unwrap()
11936    });
11937    cx.assert_editor_state(&expected_with_insert_mode);
11938    handle_resolve_completion_request(&mut cx, None).await;
11939    apply_additional_edits.await.unwrap();
11940}
11941
11942#[gpui::test]
11943async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
11944    init_test(cx, |_| {});
11945    let mut cx = EditorLspTestContext::new_rust(
11946        lsp::ServerCapabilities {
11947            completion_provider: Some(lsp::CompletionOptions {
11948                resolve_provider: Some(true),
11949                ..Default::default()
11950            }),
11951            ..Default::default()
11952        },
11953        cx,
11954    )
11955    .await;
11956
11957    // scenario: surrounding text matches completion text
11958    let completion_text = "to_offset";
11959    let initial_state = indoc! {"
11960        1. buf.to_offˇsuffix
11961        2. buf.to_offˇsuf
11962        3. buf.to_offˇfix
11963        4. buf.to_offˇ
11964        5. into_offˇensive
11965        6. ˇsuffix
11966        7. let ˇ //
11967        8. aaˇzz
11968        9. buf.to_off«zzzzzˇ»suffix
11969        10. buf.«ˇzzzzz»suffix
11970        11. to_off«ˇzzzzz»
11971
11972        buf.to_offˇsuffix  // newest cursor
11973    "};
11974    let completion_marked_buffer = indoc! {"
11975        1. buf.to_offsuffix
11976        2. buf.to_offsuf
11977        3. buf.to_offfix
11978        4. buf.to_off
11979        5. into_offensive
11980        6. suffix
11981        7. let  //
11982        8. aazz
11983        9. buf.to_offzzzzzsuffix
11984        10. buf.zzzzzsuffix
11985        11. to_offzzzzz
11986
11987        buf.<to_off|suffix>  // newest cursor
11988    "};
11989    let expected = indoc! {"
11990        1. buf.to_offsetˇ
11991        2. buf.to_offsetˇsuf
11992        3. buf.to_offsetˇfix
11993        4. buf.to_offsetˇ
11994        5. into_offsetˇensive
11995        6. to_offsetˇsuffix
11996        7. let to_offsetˇ //
11997        8. aato_offsetˇzz
11998        9. buf.to_offsetˇ
11999        10. buf.to_offsetˇsuffix
12000        11. to_offsetˇ
12001
12002        buf.to_offsetˇ  // newest cursor
12003    "};
12004    cx.set_state(initial_state);
12005    cx.update_editor(|editor, window, cx| {
12006        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12007    });
12008    handle_completion_request_with_insert_and_replace(
12009        &mut cx,
12010        completion_marked_buffer,
12011        vec![(completion_text, completion_text)],
12012        Arc::new(AtomicUsize::new(0)),
12013    )
12014    .await;
12015    cx.condition(|editor, _| editor.context_menu_visible())
12016        .await;
12017    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12018        editor
12019            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12020            .unwrap()
12021    });
12022    cx.assert_editor_state(expected);
12023    handle_resolve_completion_request(&mut cx, None).await;
12024    apply_additional_edits.await.unwrap();
12025
12026    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
12027    let completion_text = "foo_and_bar";
12028    let initial_state = indoc! {"
12029        1. ooanbˇ
12030        2. zooanbˇ
12031        3. ooanbˇz
12032        4. zooanbˇz
12033        5. ooanˇ
12034        6. oanbˇ
12035
12036        ooanbˇ
12037    "};
12038    let completion_marked_buffer = indoc! {"
12039        1. ooanb
12040        2. zooanb
12041        3. ooanbz
12042        4. zooanbz
12043        5. ooan
12044        6. oanb
12045
12046        <ooanb|>
12047    "};
12048    let expected = indoc! {"
12049        1. foo_and_barˇ
12050        2. zfoo_and_barˇ
12051        3. foo_and_barˇz
12052        4. zfoo_and_barˇz
12053        5. ooanfoo_and_barˇ
12054        6. oanbfoo_and_barˇ
12055
12056        foo_and_barˇ
12057    "};
12058    cx.set_state(initial_state);
12059    cx.update_editor(|editor, window, cx| {
12060        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12061    });
12062    handle_completion_request_with_insert_and_replace(
12063        &mut cx,
12064        completion_marked_buffer,
12065        vec![(completion_text, completion_text)],
12066        Arc::new(AtomicUsize::new(0)),
12067    )
12068    .await;
12069    cx.condition(|editor, _| editor.context_menu_visible())
12070        .await;
12071    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12072        editor
12073            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12074            .unwrap()
12075    });
12076    cx.assert_editor_state(expected);
12077    handle_resolve_completion_request(&mut cx, None).await;
12078    apply_additional_edits.await.unwrap();
12079
12080    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
12081    // (expects the same as if it was inserted at the end)
12082    let completion_text = "foo_and_bar";
12083    let initial_state = indoc! {"
12084        1. ooˇanb
12085        2. zooˇanb
12086        3. ooˇanbz
12087        4. zooˇanbz
12088
12089        ooˇanb
12090    "};
12091    let completion_marked_buffer = indoc! {"
12092        1. ooanb
12093        2. zooanb
12094        3. ooanbz
12095        4. zooanbz
12096
12097        <oo|anb>
12098    "};
12099    let expected = indoc! {"
12100        1. foo_and_barˇ
12101        2. zfoo_and_barˇ
12102        3. foo_and_barˇz
12103        4. zfoo_and_barˇz
12104
12105        foo_and_barˇ
12106    "};
12107    cx.set_state(initial_state);
12108    cx.update_editor(|editor, window, cx| {
12109        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12110    });
12111    handle_completion_request_with_insert_and_replace(
12112        &mut cx,
12113        completion_marked_buffer,
12114        vec![(completion_text, completion_text)],
12115        Arc::new(AtomicUsize::new(0)),
12116    )
12117    .await;
12118    cx.condition(|editor, _| editor.context_menu_visible())
12119        .await;
12120    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12121        editor
12122            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12123            .unwrap()
12124    });
12125    cx.assert_editor_state(expected);
12126    handle_resolve_completion_request(&mut cx, None).await;
12127    apply_additional_edits.await.unwrap();
12128}
12129
12130// This used to crash
12131#[gpui::test]
12132async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
12133    init_test(cx, |_| {});
12134
12135    let buffer_text = indoc! {"
12136        fn main() {
12137            10.satu;
12138
12139            //
12140            // separate cursors so they open in different excerpts (manually reproducible)
12141            //
12142
12143            10.satu20;
12144        }
12145    "};
12146    let multibuffer_text_with_selections = indoc! {"
12147        fn main() {
12148            10.satuˇ;
12149
12150            //
12151
12152            //
12153
12154            10.satuˇ20;
12155        }
12156    "};
12157    let expected_multibuffer = indoc! {"
12158        fn main() {
12159            10.saturating_sub()ˇ;
12160
12161            //
12162
12163            //
12164
12165            10.saturating_sub()ˇ;
12166        }
12167    "};
12168
12169    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
12170    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
12171
12172    let fs = FakeFs::new(cx.executor());
12173    fs.insert_tree(
12174        path!("/a"),
12175        json!({
12176            "main.rs": buffer_text,
12177        }),
12178    )
12179    .await;
12180
12181    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12182    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12183    language_registry.add(rust_lang());
12184    let mut fake_servers = language_registry.register_fake_lsp(
12185        "Rust",
12186        FakeLspAdapter {
12187            capabilities: lsp::ServerCapabilities {
12188                completion_provider: Some(lsp::CompletionOptions {
12189                    resolve_provider: None,
12190                    ..lsp::CompletionOptions::default()
12191                }),
12192                ..lsp::ServerCapabilities::default()
12193            },
12194            ..FakeLspAdapter::default()
12195        },
12196    );
12197    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12198    let cx = &mut VisualTestContext::from_window(*workspace, cx);
12199    let buffer = project
12200        .update(cx, |project, cx| {
12201            project.open_local_buffer(path!("/a/main.rs"), cx)
12202        })
12203        .await
12204        .unwrap();
12205
12206    let multi_buffer = cx.new(|cx| {
12207        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
12208        multi_buffer.push_excerpts(
12209            buffer.clone(),
12210            [ExcerptRange::new(0..first_excerpt_end)],
12211            cx,
12212        );
12213        multi_buffer.push_excerpts(
12214            buffer.clone(),
12215            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
12216            cx,
12217        );
12218        multi_buffer
12219    });
12220
12221    let editor = workspace
12222        .update(cx, |_, window, cx| {
12223            cx.new(|cx| {
12224                Editor::new(
12225                    EditorMode::Full {
12226                        scale_ui_elements_with_buffer_font_size: false,
12227                        show_active_line_background: false,
12228                        sized_by_content: false,
12229                    },
12230                    multi_buffer.clone(),
12231                    Some(project.clone()),
12232                    window,
12233                    cx,
12234                )
12235            })
12236        })
12237        .unwrap();
12238
12239    let pane = workspace
12240        .update(cx, |workspace, _, _| workspace.active_pane().clone())
12241        .unwrap();
12242    pane.update_in(cx, |pane, window, cx| {
12243        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
12244    });
12245
12246    let fake_server = fake_servers.next().await.unwrap();
12247
12248    editor.update_in(cx, |editor, window, cx| {
12249        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12250            s.select_ranges([
12251                Point::new(1, 11)..Point::new(1, 11),
12252                Point::new(7, 11)..Point::new(7, 11),
12253            ])
12254        });
12255
12256        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
12257    });
12258
12259    editor.update_in(cx, |editor, window, cx| {
12260        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12261    });
12262
12263    fake_server
12264        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
12265            let completion_item = lsp::CompletionItem {
12266                label: "saturating_sub()".into(),
12267                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
12268                    lsp::InsertReplaceEdit {
12269                        new_text: "saturating_sub()".to_owned(),
12270                        insert: lsp::Range::new(
12271                            lsp::Position::new(7, 7),
12272                            lsp::Position::new(7, 11),
12273                        ),
12274                        replace: lsp::Range::new(
12275                            lsp::Position::new(7, 7),
12276                            lsp::Position::new(7, 13),
12277                        ),
12278                    },
12279                )),
12280                ..lsp::CompletionItem::default()
12281            };
12282
12283            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
12284        })
12285        .next()
12286        .await
12287        .unwrap();
12288
12289    cx.condition(&editor, |editor, _| editor.context_menu_visible())
12290        .await;
12291
12292    editor
12293        .update_in(cx, |editor, window, cx| {
12294            editor
12295                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12296                .unwrap()
12297        })
12298        .await
12299        .unwrap();
12300
12301    editor.update(cx, |editor, cx| {
12302        assert_text_with_selections(editor, expected_multibuffer, cx);
12303    })
12304}
12305
12306#[gpui::test]
12307async fn test_completion(cx: &mut TestAppContext) {
12308    init_test(cx, |_| {});
12309
12310    let mut cx = EditorLspTestContext::new_rust(
12311        lsp::ServerCapabilities {
12312            completion_provider: Some(lsp::CompletionOptions {
12313                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12314                resolve_provider: Some(true),
12315                ..Default::default()
12316            }),
12317            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12318            ..Default::default()
12319        },
12320        cx,
12321    )
12322    .await;
12323    let counter = Arc::new(AtomicUsize::new(0));
12324
12325    cx.set_state(indoc! {"
12326        oneˇ
12327        two
12328        three
12329    "});
12330    cx.simulate_keystroke(".");
12331    handle_completion_request(
12332        indoc! {"
12333            one.|<>
12334            two
12335            three
12336        "},
12337        vec!["first_completion", "second_completion"],
12338        true,
12339        counter.clone(),
12340        &mut cx,
12341    )
12342    .await;
12343    cx.condition(|editor, _| editor.context_menu_visible())
12344        .await;
12345    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12346
12347    let _handler = handle_signature_help_request(
12348        &mut cx,
12349        lsp::SignatureHelp {
12350            signatures: vec![lsp::SignatureInformation {
12351                label: "test signature".to_string(),
12352                documentation: None,
12353                parameters: Some(vec![lsp::ParameterInformation {
12354                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
12355                    documentation: None,
12356                }]),
12357                active_parameter: None,
12358            }],
12359            active_signature: None,
12360            active_parameter: None,
12361        },
12362    );
12363    cx.update_editor(|editor, window, cx| {
12364        assert!(
12365            !editor.signature_help_state.is_shown(),
12366            "No signature help was called for"
12367        );
12368        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12369    });
12370    cx.run_until_parked();
12371    cx.update_editor(|editor, _, _| {
12372        assert!(
12373            !editor.signature_help_state.is_shown(),
12374            "No signature help should be shown when completions menu is open"
12375        );
12376    });
12377
12378    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12379        editor.context_menu_next(&Default::default(), window, cx);
12380        editor
12381            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12382            .unwrap()
12383    });
12384    cx.assert_editor_state(indoc! {"
12385        one.second_completionˇ
12386        two
12387        three
12388    "});
12389
12390    handle_resolve_completion_request(
12391        &mut cx,
12392        Some(vec![
12393            (
12394                //This overlaps with the primary completion edit which is
12395                //misbehavior from the LSP spec, test that we filter it out
12396                indoc! {"
12397                    one.second_ˇcompletion
12398                    two
12399                    threeˇ
12400                "},
12401                "overlapping additional edit",
12402            ),
12403            (
12404                indoc! {"
12405                    one.second_completion
12406                    two
12407                    threeˇ
12408                "},
12409                "\nadditional edit",
12410            ),
12411        ]),
12412    )
12413    .await;
12414    apply_additional_edits.await.unwrap();
12415    cx.assert_editor_state(indoc! {"
12416        one.second_completionˇ
12417        two
12418        three
12419        additional edit
12420    "});
12421
12422    cx.set_state(indoc! {"
12423        one.second_completion
12424        twoˇ
12425        threeˇ
12426        additional edit
12427    "});
12428    cx.simulate_keystroke(" ");
12429    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12430    cx.simulate_keystroke("s");
12431    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12432
12433    cx.assert_editor_state(indoc! {"
12434        one.second_completion
12435        two sˇ
12436        three sˇ
12437        additional edit
12438    "});
12439    handle_completion_request(
12440        indoc! {"
12441            one.second_completion
12442            two s
12443            three <s|>
12444            additional edit
12445        "},
12446        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
12447        true,
12448        counter.clone(),
12449        &mut cx,
12450    )
12451    .await;
12452    cx.condition(|editor, _| editor.context_menu_visible())
12453        .await;
12454    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12455
12456    cx.simulate_keystroke("i");
12457
12458    handle_completion_request(
12459        indoc! {"
12460            one.second_completion
12461            two si
12462            three <si|>
12463            additional edit
12464        "},
12465        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
12466        true,
12467        counter.clone(),
12468        &mut cx,
12469    )
12470    .await;
12471    cx.condition(|editor, _| editor.context_menu_visible())
12472        .await;
12473    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12474
12475    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12476        editor
12477            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12478            .unwrap()
12479    });
12480    cx.assert_editor_state(indoc! {"
12481        one.second_completion
12482        two sixth_completionˇ
12483        three sixth_completionˇ
12484        additional edit
12485    "});
12486
12487    apply_additional_edits.await.unwrap();
12488
12489    update_test_language_settings(&mut cx, |settings| {
12490        settings.defaults.show_completions_on_input = Some(false);
12491    });
12492    cx.set_state("editorˇ");
12493    cx.simulate_keystroke(".");
12494    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12495    cx.simulate_keystrokes("c l o");
12496    cx.assert_editor_state("editor.cloˇ");
12497    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12498    cx.update_editor(|editor, window, cx| {
12499        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12500    });
12501    handle_completion_request(
12502        "editor.<clo|>",
12503        vec!["close", "clobber"],
12504        true,
12505        counter.clone(),
12506        &mut cx,
12507    )
12508    .await;
12509    cx.condition(|editor, _| editor.context_menu_visible())
12510        .await;
12511    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
12512
12513    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12514        editor
12515            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12516            .unwrap()
12517    });
12518    cx.assert_editor_state("editor.clobberˇ");
12519    handle_resolve_completion_request(&mut cx, None).await;
12520    apply_additional_edits.await.unwrap();
12521}
12522
12523#[gpui::test]
12524async fn test_completion_reuse(cx: &mut TestAppContext) {
12525    init_test(cx, |_| {});
12526
12527    let mut cx = EditorLspTestContext::new_rust(
12528        lsp::ServerCapabilities {
12529            completion_provider: Some(lsp::CompletionOptions {
12530                trigger_characters: Some(vec![".".to_string()]),
12531                ..Default::default()
12532            }),
12533            ..Default::default()
12534        },
12535        cx,
12536    )
12537    .await;
12538
12539    let counter = Arc::new(AtomicUsize::new(0));
12540    cx.set_state("objˇ");
12541    cx.simulate_keystroke(".");
12542
12543    // Initial completion request returns complete results
12544    let is_incomplete = false;
12545    handle_completion_request(
12546        "obj.|<>",
12547        vec!["a", "ab", "abc"],
12548        is_incomplete,
12549        counter.clone(),
12550        &mut cx,
12551    )
12552    .await;
12553    cx.run_until_parked();
12554    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12555    cx.assert_editor_state("obj.ˇ");
12556    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12557
12558    // Type "a" - filters existing completions
12559    cx.simulate_keystroke("a");
12560    cx.run_until_parked();
12561    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12562    cx.assert_editor_state("obj.aˇ");
12563    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12564
12565    // Type "b" - filters existing completions
12566    cx.simulate_keystroke("b");
12567    cx.run_until_parked();
12568    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12569    cx.assert_editor_state("obj.abˇ");
12570    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12571
12572    // Type "c" - filters existing completions
12573    cx.simulate_keystroke("c");
12574    cx.run_until_parked();
12575    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12576    cx.assert_editor_state("obj.abcˇ");
12577    check_displayed_completions(vec!["abc"], &mut cx);
12578
12579    // Backspace to delete "c" - filters existing completions
12580    cx.update_editor(|editor, window, cx| {
12581        editor.backspace(&Backspace, window, cx);
12582    });
12583    cx.run_until_parked();
12584    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12585    cx.assert_editor_state("obj.abˇ");
12586    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12587
12588    // Moving cursor to the left dismisses menu.
12589    cx.update_editor(|editor, window, cx| {
12590        editor.move_left(&MoveLeft, window, cx);
12591    });
12592    cx.run_until_parked();
12593    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12594    cx.assert_editor_state("obj.aˇb");
12595    cx.update_editor(|editor, _, _| {
12596        assert_eq!(editor.context_menu_visible(), false);
12597    });
12598
12599    // Type "b" - new request
12600    cx.simulate_keystroke("b");
12601    let is_incomplete = false;
12602    handle_completion_request(
12603        "obj.<ab|>a",
12604        vec!["ab", "abc"],
12605        is_incomplete,
12606        counter.clone(),
12607        &mut cx,
12608    )
12609    .await;
12610    cx.run_until_parked();
12611    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12612    cx.assert_editor_state("obj.abˇb");
12613    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12614
12615    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
12616    cx.update_editor(|editor, window, cx| {
12617        editor.backspace(&Backspace, window, cx);
12618    });
12619    let is_incomplete = false;
12620    handle_completion_request(
12621        "obj.<a|>b",
12622        vec!["a", "ab", "abc"],
12623        is_incomplete,
12624        counter.clone(),
12625        &mut cx,
12626    )
12627    .await;
12628    cx.run_until_parked();
12629    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12630    cx.assert_editor_state("obj.aˇb");
12631    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12632
12633    // Backspace to delete "a" - dismisses menu.
12634    cx.update_editor(|editor, window, cx| {
12635        editor.backspace(&Backspace, window, cx);
12636    });
12637    cx.run_until_parked();
12638    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12639    cx.assert_editor_state("obj.ˇb");
12640    cx.update_editor(|editor, _, _| {
12641        assert_eq!(editor.context_menu_visible(), false);
12642    });
12643}
12644
12645#[gpui::test]
12646async fn test_word_completion(cx: &mut TestAppContext) {
12647    let lsp_fetch_timeout_ms = 10;
12648    init_test(cx, |language_settings| {
12649        language_settings.defaults.completions = Some(CompletionSettings {
12650            words: WordsCompletionMode::Fallback,
12651            lsp: true,
12652            lsp_fetch_timeout_ms: 10,
12653            lsp_insert_mode: LspInsertMode::Insert,
12654        });
12655    });
12656
12657    let mut cx = EditorLspTestContext::new_rust(
12658        lsp::ServerCapabilities {
12659            completion_provider: Some(lsp::CompletionOptions {
12660                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12661                ..lsp::CompletionOptions::default()
12662            }),
12663            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12664            ..lsp::ServerCapabilities::default()
12665        },
12666        cx,
12667    )
12668    .await;
12669
12670    let throttle_completions = Arc::new(AtomicBool::new(false));
12671
12672    let lsp_throttle_completions = throttle_completions.clone();
12673    let _completion_requests_handler =
12674        cx.lsp
12675            .server
12676            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
12677                let lsp_throttle_completions = lsp_throttle_completions.clone();
12678                let cx = cx.clone();
12679                async move {
12680                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
12681                        cx.background_executor()
12682                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
12683                            .await;
12684                    }
12685                    Ok(Some(lsp::CompletionResponse::Array(vec![
12686                        lsp::CompletionItem {
12687                            label: "first".into(),
12688                            ..lsp::CompletionItem::default()
12689                        },
12690                        lsp::CompletionItem {
12691                            label: "last".into(),
12692                            ..lsp::CompletionItem::default()
12693                        },
12694                    ])))
12695                }
12696            });
12697
12698    cx.set_state(indoc! {"
12699        oneˇ
12700        two
12701        three
12702    "});
12703    cx.simulate_keystroke(".");
12704    cx.executor().run_until_parked();
12705    cx.condition(|editor, _| editor.context_menu_visible())
12706        .await;
12707    cx.update_editor(|editor, window, cx| {
12708        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12709        {
12710            assert_eq!(
12711                completion_menu_entries(&menu),
12712                &["first", "last"],
12713                "When LSP server is fast to reply, no fallback word completions are used"
12714            );
12715        } else {
12716            panic!("expected completion menu to be open");
12717        }
12718        editor.cancel(&Cancel, window, cx);
12719    });
12720    cx.executor().run_until_parked();
12721    cx.condition(|editor, _| !editor.context_menu_visible())
12722        .await;
12723
12724    throttle_completions.store(true, atomic::Ordering::Release);
12725    cx.simulate_keystroke(".");
12726    cx.executor()
12727        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
12728    cx.executor().run_until_parked();
12729    cx.condition(|editor, _| editor.context_menu_visible())
12730        .await;
12731    cx.update_editor(|editor, _, _| {
12732        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12733        {
12734            assert_eq!(completion_menu_entries(&menu), &["one", "three", "two"],
12735                "When LSP server is slow, document words can be shown instead, if configured accordingly");
12736        } else {
12737            panic!("expected completion menu to be open");
12738        }
12739    });
12740}
12741
12742#[gpui::test]
12743async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
12744    init_test(cx, |language_settings| {
12745        language_settings.defaults.completions = Some(CompletionSettings {
12746            words: WordsCompletionMode::Enabled,
12747            lsp: true,
12748            lsp_fetch_timeout_ms: 0,
12749            lsp_insert_mode: LspInsertMode::Insert,
12750        });
12751    });
12752
12753    let mut cx = EditorLspTestContext::new_rust(
12754        lsp::ServerCapabilities {
12755            completion_provider: Some(lsp::CompletionOptions {
12756                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12757                ..lsp::CompletionOptions::default()
12758            }),
12759            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12760            ..lsp::ServerCapabilities::default()
12761        },
12762        cx,
12763    )
12764    .await;
12765
12766    let _completion_requests_handler =
12767        cx.lsp
12768            .server
12769            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12770                Ok(Some(lsp::CompletionResponse::Array(vec![
12771                    lsp::CompletionItem {
12772                        label: "first".into(),
12773                        ..lsp::CompletionItem::default()
12774                    },
12775                    lsp::CompletionItem {
12776                        label: "last".into(),
12777                        ..lsp::CompletionItem::default()
12778                    },
12779                ])))
12780            });
12781
12782    cx.set_state(indoc! {"ˇ
12783        first
12784        last
12785        second
12786    "});
12787    cx.simulate_keystroke(".");
12788    cx.executor().run_until_parked();
12789    cx.condition(|editor, _| editor.context_menu_visible())
12790        .await;
12791    cx.update_editor(|editor, _, _| {
12792        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12793        {
12794            assert_eq!(
12795                completion_menu_entries(&menu),
12796                &["first", "last", "second"],
12797                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
12798            );
12799        } else {
12800            panic!("expected completion menu to be open");
12801        }
12802    });
12803}
12804
12805#[gpui::test]
12806async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
12807    init_test(cx, |language_settings| {
12808        language_settings.defaults.completions = Some(CompletionSettings {
12809            words: WordsCompletionMode::Disabled,
12810            lsp: true,
12811            lsp_fetch_timeout_ms: 0,
12812            lsp_insert_mode: LspInsertMode::Insert,
12813        });
12814    });
12815
12816    let mut cx = EditorLspTestContext::new_rust(
12817        lsp::ServerCapabilities {
12818            completion_provider: Some(lsp::CompletionOptions {
12819                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12820                ..lsp::CompletionOptions::default()
12821            }),
12822            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12823            ..lsp::ServerCapabilities::default()
12824        },
12825        cx,
12826    )
12827    .await;
12828
12829    let _completion_requests_handler =
12830        cx.lsp
12831            .server
12832            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12833                panic!("LSP completions should not be queried when dealing with word completions")
12834            });
12835
12836    cx.set_state(indoc! {"ˇ
12837        first
12838        last
12839        second
12840    "});
12841    cx.update_editor(|editor, window, cx| {
12842        editor.show_word_completions(&ShowWordCompletions, window, cx);
12843    });
12844    cx.executor().run_until_parked();
12845    cx.condition(|editor, _| editor.context_menu_visible())
12846        .await;
12847    cx.update_editor(|editor, _, _| {
12848        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12849        {
12850            assert_eq!(
12851                completion_menu_entries(&menu),
12852                &["first", "last", "second"],
12853                "`ShowWordCompletions` action should show word completions"
12854            );
12855        } else {
12856            panic!("expected completion menu to be open");
12857        }
12858    });
12859
12860    cx.simulate_keystroke("l");
12861    cx.executor().run_until_parked();
12862    cx.condition(|editor, _| editor.context_menu_visible())
12863        .await;
12864    cx.update_editor(|editor, _, _| {
12865        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12866        {
12867            assert_eq!(
12868                completion_menu_entries(&menu),
12869                &["last"],
12870                "After showing word completions, further editing should filter them and not query the LSP"
12871            );
12872        } else {
12873            panic!("expected completion menu to be open");
12874        }
12875    });
12876}
12877
12878#[gpui::test]
12879async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
12880    init_test(cx, |language_settings| {
12881        language_settings.defaults.completions = Some(CompletionSettings {
12882            words: WordsCompletionMode::Fallback,
12883            lsp: false,
12884            lsp_fetch_timeout_ms: 0,
12885            lsp_insert_mode: LspInsertMode::Insert,
12886        });
12887    });
12888
12889    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12890
12891    cx.set_state(indoc! {"ˇ
12892        0_usize
12893        let
12894        33
12895        4.5f32
12896    "});
12897    cx.update_editor(|editor, window, cx| {
12898        editor.show_completions(&ShowCompletions::default(), window, cx);
12899    });
12900    cx.executor().run_until_parked();
12901    cx.condition(|editor, _| editor.context_menu_visible())
12902        .await;
12903    cx.update_editor(|editor, window, cx| {
12904        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12905        {
12906            assert_eq!(
12907                completion_menu_entries(&menu),
12908                &["let"],
12909                "With no digits in the completion query, no digits should be in the word completions"
12910            );
12911        } else {
12912            panic!("expected completion menu to be open");
12913        }
12914        editor.cancel(&Cancel, window, cx);
12915    });
12916
12917    cx.set_state(indoc! {"12918        0_usize
12919        let
12920        3
12921        33.35f32
12922    "});
12923    cx.update_editor(|editor, window, cx| {
12924        editor.show_completions(&ShowCompletions::default(), window, cx);
12925    });
12926    cx.executor().run_until_parked();
12927    cx.condition(|editor, _| editor.context_menu_visible())
12928        .await;
12929    cx.update_editor(|editor, _, _| {
12930        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12931        {
12932            assert_eq!(completion_menu_entries(&menu), &["33", "35f32"], "The digit is in the completion query, \
12933                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
12934        } else {
12935            panic!("expected completion menu to be open");
12936        }
12937    });
12938}
12939
12940fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
12941    let position = || lsp::Position {
12942        line: params.text_document_position.position.line,
12943        character: params.text_document_position.position.character,
12944    };
12945    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12946        range: lsp::Range {
12947            start: position(),
12948            end: position(),
12949        },
12950        new_text: text.to_string(),
12951    }))
12952}
12953
12954#[gpui::test]
12955async fn test_multiline_completion(cx: &mut TestAppContext) {
12956    init_test(cx, |_| {});
12957
12958    let fs = FakeFs::new(cx.executor());
12959    fs.insert_tree(
12960        path!("/a"),
12961        json!({
12962            "main.ts": "a",
12963        }),
12964    )
12965    .await;
12966
12967    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12968    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12969    let typescript_language = Arc::new(Language::new(
12970        LanguageConfig {
12971            name: "TypeScript".into(),
12972            matcher: LanguageMatcher {
12973                path_suffixes: vec!["ts".to_string()],
12974                ..LanguageMatcher::default()
12975            },
12976            line_comments: vec!["// ".into()],
12977            ..LanguageConfig::default()
12978        },
12979        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12980    ));
12981    language_registry.add(typescript_language.clone());
12982    let mut fake_servers = language_registry.register_fake_lsp(
12983        "TypeScript",
12984        FakeLspAdapter {
12985            capabilities: lsp::ServerCapabilities {
12986                completion_provider: Some(lsp::CompletionOptions {
12987                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12988                    ..lsp::CompletionOptions::default()
12989                }),
12990                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12991                ..lsp::ServerCapabilities::default()
12992            },
12993            // Emulate vtsls label generation
12994            label_for_completion: Some(Box::new(|item, _| {
12995                let text = if let Some(description) = item
12996                    .label_details
12997                    .as_ref()
12998                    .and_then(|label_details| label_details.description.as_ref())
12999                {
13000                    format!("{} {}", item.label, description)
13001                } else if let Some(detail) = &item.detail {
13002                    format!("{} {}", item.label, detail)
13003                } else {
13004                    item.label.clone()
13005                };
13006                let len = text.len();
13007                Some(language::CodeLabel {
13008                    text,
13009                    runs: Vec::new(),
13010                    filter_range: 0..len,
13011                })
13012            })),
13013            ..FakeLspAdapter::default()
13014        },
13015    );
13016    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13017    let cx = &mut VisualTestContext::from_window(*workspace, cx);
13018    let worktree_id = workspace
13019        .update(cx, |workspace, _window, cx| {
13020            workspace.project().update(cx, |project, cx| {
13021                project.worktrees(cx).next().unwrap().read(cx).id()
13022            })
13023        })
13024        .unwrap();
13025    let _buffer = project
13026        .update(cx, |project, cx| {
13027            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
13028        })
13029        .await
13030        .unwrap();
13031    let editor = workspace
13032        .update(cx, |workspace, window, cx| {
13033            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
13034        })
13035        .unwrap()
13036        .await
13037        .unwrap()
13038        .downcast::<Editor>()
13039        .unwrap();
13040    let fake_server = fake_servers.next().await.unwrap();
13041
13042    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
13043    let multiline_label_2 = "a\nb\nc\n";
13044    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
13045    let multiline_description = "d\ne\nf\n";
13046    let multiline_detail_2 = "g\nh\ni\n";
13047
13048    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
13049        move |params, _| async move {
13050            Ok(Some(lsp::CompletionResponse::Array(vec![
13051                lsp::CompletionItem {
13052                    label: multiline_label.to_string(),
13053                    text_edit: gen_text_edit(&params, "new_text_1"),
13054                    ..lsp::CompletionItem::default()
13055                },
13056                lsp::CompletionItem {
13057                    label: "single line label 1".to_string(),
13058                    detail: Some(multiline_detail.to_string()),
13059                    text_edit: gen_text_edit(&params, "new_text_2"),
13060                    ..lsp::CompletionItem::default()
13061                },
13062                lsp::CompletionItem {
13063                    label: "single line label 2".to_string(),
13064                    label_details: Some(lsp::CompletionItemLabelDetails {
13065                        description: Some(multiline_description.to_string()),
13066                        detail: None,
13067                    }),
13068                    text_edit: gen_text_edit(&params, "new_text_2"),
13069                    ..lsp::CompletionItem::default()
13070                },
13071                lsp::CompletionItem {
13072                    label: multiline_label_2.to_string(),
13073                    detail: Some(multiline_detail_2.to_string()),
13074                    text_edit: gen_text_edit(&params, "new_text_3"),
13075                    ..lsp::CompletionItem::default()
13076                },
13077                lsp::CompletionItem {
13078                    label: "Label with many     spaces and \t but without newlines".to_string(),
13079                    detail: Some(
13080                        "Details with many     spaces and \t but without newlines".to_string(),
13081                    ),
13082                    text_edit: gen_text_edit(&params, "new_text_4"),
13083                    ..lsp::CompletionItem::default()
13084                },
13085            ])))
13086        },
13087    );
13088
13089    editor.update_in(cx, |editor, window, cx| {
13090        cx.focus_self(window);
13091        editor.move_to_end(&MoveToEnd, window, cx);
13092        editor.handle_input(".", window, cx);
13093    });
13094    cx.run_until_parked();
13095    completion_handle.next().await.unwrap();
13096
13097    editor.update(cx, |editor, _| {
13098        assert!(editor.context_menu_visible());
13099        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13100        {
13101            let completion_labels = menu
13102                .completions
13103                .borrow()
13104                .iter()
13105                .map(|c| c.label.text.clone())
13106                .collect::<Vec<_>>();
13107            assert_eq!(
13108                completion_labels,
13109                &[
13110                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
13111                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
13112                    "single line label 2 d e f ",
13113                    "a b c g h i ",
13114                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
13115                ],
13116                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
13117            );
13118
13119            for completion in menu
13120                .completions
13121                .borrow()
13122                .iter() {
13123                    assert_eq!(
13124                        completion.label.filter_range,
13125                        0..completion.label.text.len(),
13126                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
13127                    );
13128                }
13129        } else {
13130            panic!("expected completion menu to be open");
13131        }
13132    });
13133}
13134
13135#[gpui::test]
13136async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
13137    init_test(cx, |_| {});
13138    let mut cx = EditorLspTestContext::new_rust(
13139        lsp::ServerCapabilities {
13140            completion_provider: Some(lsp::CompletionOptions {
13141                trigger_characters: Some(vec![".".to_string()]),
13142                ..Default::default()
13143            }),
13144            ..Default::default()
13145        },
13146        cx,
13147    )
13148    .await;
13149    cx.lsp
13150        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13151            Ok(Some(lsp::CompletionResponse::Array(vec![
13152                lsp::CompletionItem {
13153                    label: "first".into(),
13154                    ..Default::default()
13155                },
13156                lsp::CompletionItem {
13157                    label: "last".into(),
13158                    ..Default::default()
13159                },
13160            ])))
13161        });
13162    cx.set_state("variableˇ");
13163    cx.simulate_keystroke(".");
13164    cx.executor().run_until_parked();
13165
13166    cx.update_editor(|editor, _, _| {
13167        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13168        {
13169            assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
13170        } else {
13171            panic!("expected completion menu to be open");
13172        }
13173    });
13174
13175    cx.update_editor(|editor, window, cx| {
13176        editor.move_page_down(&MovePageDown::default(), window, cx);
13177        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13178        {
13179            assert!(
13180                menu.selected_item == 1,
13181                "expected PageDown to select the last item from the context menu"
13182            );
13183        } else {
13184            panic!("expected completion menu to stay open after PageDown");
13185        }
13186    });
13187
13188    cx.update_editor(|editor, window, cx| {
13189        editor.move_page_up(&MovePageUp::default(), window, cx);
13190        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13191        {
13192            assert!(
13193                menu.selected_item == 0,
13194                "expected PageUp to select the first item from the context menu"
13195            );
13196        } else {
13197            panic!("expected completion menu to stay open after PageUp");
13198        }
13199    });
13200}
13201
13202#[gpui::test]
13203async fn test_as_is_completions(cx: &mut TestAppContext) {
13204    init_test(cx, |_| {});
13205    let mut cx = EditorLspTestContext::new_rust(
13206        lsp::ServerCapabilities {
13207            completion_provider: Some(lsp::CompletionOptions {
13208                ..Default::default()
13209            }),
13210            ..Default::default()
13211        },
13212        cx,
13213    )
13214    .await;
13215    cx.lsp
13216        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13217            Ok(Some(lsp::CompletionResponse::Array(vec![
13218                lsp::CompletionItem {
13219                    label: "unsafe".into(),
13220                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13221                        range: lsp::Range {
13222                            start: lsp::Position {
13223                                line: 1,
13224                                character: 2,
13225                            },
13226                            end: lsp::Position {
13227                                line: 1,
13228                                character: 3,
13229                            },
13230                        },
13231                        new_text: "unsafe".to_string(),
13232                    })),
13233                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
13234                    ..Default::default()
13235                },
13236            ])))
13237        });
13238    cx.set_state("fn a() {}\n");
13239    cx.executor().run_until_parked();
13240    cx.update_editor(|editor, window, cx| {
13241        editor.show_completions(
13242            &ShowCompletions {
13243                trigger: Some("\n".into()),
13244            },
13245            window,
13246            cx,
13247        );
13248    });
13249    cx.executor().run_until_parked();
13250
13251    cx.update_editor(|editor, window, cx| {
13252        editor.confirm_completion(&Default::default(), window, cx)
13253    });
13254    cx.executor().run_until_parked();
13255    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
13256}
13257
13258#[gpui::test]
13259async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
13260    init_test(cx, |_| {});
13261
13262    let mut cx = EditorLspTestContext::new_rust(
13263        lsp::ServerCapabilities {
13264            completion_provider: Some(lsp::CompletionOptions {
13265                trigger_characters: Some(vec![".".to_string()]),
13266                resolve_provider: Some(true),
13267                ..Default::default()
13268            }),
13269            ..Default::default()
13270        },
13271        cx,
13272    )
13273    .await;
13274
13275    cx.set_state("fn main() { let a = 2ˇ; }");
13276    cx.simulate_keystroke(".");
13277    let completion_item = lsp::CompletionItem {
13278        label: "Some".into(),
13279        kind: Some(lsp::CompletionItemKind::SNIPPET),
13280        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
13281        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
13282            kind: lsp::MarkupKind::Markdown,
13283            value: "```rust\nSome(2)\n```".to_string(),
13284        })),
13285        deprecated: Some(false),
13286        sort_text: Some("Some".to_string()),
13287        filter_text: Some("Some".to_string()),
13288        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
13289        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13290            range: lsp::Range {
13291                start: lsp::Position {
13292                    line: 0,
13293                    character: 22,
13294                },
13295                end: lsp::Position {
13296                    line: 0,
13297                    character: 22,
13298                },
13299            },
13300            new_text: "Some(2)".to_string(),
13301        })),
13302        additional_text_edits: Some(vec![lsp::TextEdit {
13303            range: lsp::Range {
13304                start: lsp::Position {
13305                    line: 0,
13306                    character: 20,
13307                },
13308                end: lsp::Position {
13309                    line: 0,
13310                    character: 22,
13311                },
13312            },
13313            new_text: "".to_string(),
13314        }]),
13315        ..Default::default()
13316    };
13317
13318    let closure_completion_item = completion_item.clone();
13319    let counter = Arc::new(AtomicUsize::new(0));
13320    let counter_clone = counter.clone();
13321    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
13322        let task_completion_item = closure_completion_item.clone();
13323        counter_clone.fetch_add(1, atomic::Ordering::Release);
13324        async move {
13325            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
13326                is_incomplete: true,
13327                item_defaults: None,
13328                items: vec![task_completion_item],
13329            })))
13330        }
13331    });
13332
13333    cx.condition(|editor, _| editor.context_menu_visible())
13334        .await;
13335    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
13336    assert!(request.next().await.is_some());
13337    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13338
13339    cx.simulate_keystrokes("S o m");
13340    cx.condition(|editor, _| editor.context_menu_visible())
13341        .await;
13342    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
13343    assert!(request.next().await.is_some());
13344    assert!(request.next().await.is_some());
13345    assert!(request.next().await.is_some());
13346    request.close();
13347    assert!(request.next().await.is_none());
13348    assert_eq!(
13349        counter.load(atomic::Ordering::Acquire),
13350        4,
13351        "With the completions menu open, only one LSP request should happen per input"
13352    );
13353}
13354
13355#[gpui::test]
13356async fn test_toggle_comment(cx: &mut TestAppContext) {
13357    init_test(cx, |_| {});
13358    let mut cx = EditorTestContext::new(cx).await;
13359    let language = Arc::new(Language::new(
13360        LanguageConfig {
13361            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
13362            ..Default::default()
13363        },
13364        Some(tree_sitter_rust::LANGUAGE.into()),
13365    ));
13366    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
13367
13368    // If multiple selections intersect a line, the line is only toggled once.
13369    cx.set_state(indoc! {"
13370        fn a() {
13371            «//b();
13372            ˇ»// «c();
13373            //ˇ»  d();
13374        }
13375    "});
13376
13377    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13378
13379    cx.assert_editor_state(indoc! {"
13380        fn a() {
13381            «b();
13382            c();
13383            ˇ» d();
13384        }
13385    "});
13386
13387    // The comment prefix is inserted at the same column for every line in a
13388    // selection.
13389    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13390
13391    cx.assert_editor_state(indoc! {"
13392        fn a() {
13393            // «b();
13394            // c();
13395            ˇ»//  d();
13396        }
13397    "});
13398
13399    // If a selection ends at the beginning of a line, that line is not toggled.
13400    cx.set_selections_state(indoc! {"
13401        fn a() {
13402            // b();
13403            «// c();
13404        ˇ»    //  d();
13405        }
13406    "});
13407
13408    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13409
13410    cx.assert_editor_state(indoc! {"
13411        fn a() {
13412            // b();
13413            «c();
13414        ˇ»    //  d();
13415        }
13416    "});
13417
13418    // If a selection span a single line and is empty, the line is toggled.
13419    cx.set_state(indoc! {"
13420        fn a() {
13421            a();
13422            b();
13423        ˇ
13424        }
13425    "});
13426
13427    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13428
13429    cx.assert_editor_state(indoc! {"
13430        fn a() {
13431            a();
13432            b();
13433        //•ˇ
13434        }
13435    "});
13436
13437    // If a selection span multiple lines, empty lines are not toggled.
13438    cx.set_state(indoc! {"
13439        fn a() {
13440            «a();
13441
13442            c();ˇ»
13443        }
13444    "});
13445
13446    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13447
13448    cx.assert_editor_state(indoc! {"
13449        fn a() {
13450            // «a();
13451
13452            // c();ˇ»
13453        }
13454    "});
13455
13456    // If a selection includes multiple comment prefixes, all lines are uncommented.
13457    cx.set_state(indoc! {"
13458        fn a() {
13459            «// a();
13460            /// b();
13461            //! c();ˇ»
13462        }
13463    "});
13464
13465    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13466
13467    cx.assert_editor_state(indoc! {"
13468        fn a() {
13469            «a();
13470            b();
13471            c();ˇ»
13472        }
13473    "});
13474}
13475
13476#[gpui::test]
13477async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
13478    init_test(cx, |_| {});
13479    let mut cx = EditorTestContext::new(cx).await;
13480    let language = Arc::new(Language::new(
13481        LanguageConfig {
13482            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
13483            ..Default::default()
13484        },
13485        Some(tree_sitter_rust::LANGUAGE.into()),
13486    ));
13487    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
13488
13489    let toggle_comments = &ToggleComments {
13490        advance_downwards: false,
13491        ignore_indent: true,
13492    };
13493
13494    // If multiple selections intersect a line, the line is only toggled once.
13495    cx.set_state(indoc! {"
13496        fn a() {
13497        //    «b();
13498        //    c();
13499        //    ˇ» d();
13500        }
13501    "});
13502
13503    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13504
13505    cx.assert_editor_state(indoc! {"
13506        fn a() {
13507            «b();
13508            c();
13509            ˇ» d();
13510        }
13511    "});
13512
13513    // The comment prefix is inserted at the beginning of each line
13514    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13515
13516    cx.assert_editor_state(indoc! {"
13517        fn a() {
13518        //    «b();
13519        //    c();
13520        //    ˇ» d();
13521        }
13522    "});
13523
13524    // If a selection ends at the beginning of a line, that line is not toggled.
13525    cx.set_selections_state(indoc! {"
13526        fn a() {
13527        //    b();
13528        //    «c();
13529        ˇ»//     d();
13530        }
13531    "});
13532
13533    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13534
13535    cx.assert_editor_state(indoc! {"
13536        fn a() {
13537        //    b();
13538            «c();
13539        ˇ»//     d();
13540        }
13541    "});
13542
13543    // If a selection span a single line and is empty, the line is toggled.
13544    cx.set_state(indoc! {"
13545        fn a() {
13546            a();
13547            b();
13548        ˇ
13549        }
13550    "});
13551
13552    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13553
13554    cx.assert_editor_state(indoc! {"
13555        fn a() {
13556            a();
13557            b();
13558        //ˇ
13559        }
13560    "});
13561
13562    // If a selection span multiple lines, empty lines are not toggled.
13563    cx.set_state(indoc! {"
13564        fn a() {
13565            «a();
13566
13567            c();ˇ»
13568        }
13569    "});
13570
13571    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13572
13573    cx.assert_editor_state(indoc! {"
13574        fn a() {
13575        //    «a();
13576
13577        //    c();ˇ»
13578        }
13579    "});
13580
13581    // If a selection includes multiple comment prefixes, all lines are uncommented.
13582    cx.set_state(indoc! {"
13583        fn a() {
13584        //    «a();
13585        ///    b();
13586        //!    c();ˇ»
13587        }
13588    "});
13589
13590    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13591
13592    cx.assert_editor_state(indoc! {"
13593        fn a() {
13594            «a();
13595            b();
13596            c();ˇ»
13597        }
13598    "});
13599}
13600
13601#[gpui::test]
13602async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
13603    init_test(cx, |_| {});
13604
13605    let language = Arc::new(Language::new(
13606        LanguageConfig {
13607            line_comments: vec!["// ".into()],
13608            ..Default::default()
13609        },
13610        Some(tree_sitter_rust::LANGUAGE.into()),
13611    ));
13612
13613    let mut cx = EditorTestContext::new(cx).await;
13614
13615    cx.language_registry().add(language.clone());
13616    cx.update_buffer(|buffer, cx| {
13617        buffer.set_language(Some(language), cx);
13618    });
13619
13620    let toggle_comments = &ToggleComments {
13621        advance_downwards: true,
13622        ignore_indent: false,
13623    };
13624
13625    // Single cursor on one line -> advance
13626    // Cursor moves horizontally 3 characters as well on non-blank line
13627    cx.set_state(indoc!(
13628        "fn a() {
13629             ˇdog();
13630             cat();
13631        }"
13632    ));
13633    cx.update_editor(|editor, window, cx| {
13634        editor.toggle_comments(toggle_comments, window, cx);
13635    });
13636    cx.assert_editor_state(indoc!(
13637        "fn a() {
13638             // dog();
13639             catˇ();
13640        }"
13641    ));
13642
13643    // Single selection on one line -> don't advance
13644    cx.set_state(indoc!(
13645        "fn a() {
13646             «dog()ˇ»;
13647             cat();
13648        }"
13649    ));
13650    cx.update_editor(|editor, window, cx| {
13651        editor.toggle_comments(toggle_comments, window, cx);
13652    });
13653    cx.assert_editor_state(indoc!(
13654        "fn a() {
13655             // «dog()ˇ»;
13656             cat();
13657        }"
13658    ));
13659
13660    // Multiple cursors on one line -> advance
13661    cx.set_state(indoc!(
13662        "fn a() {
13663             ˇdˇog();
13664             cat();
13665        }"
13666    ));
13667    cx.update_editor(|editor, window, cx| {
13668        editor.toggle_comments(toggle_comments, window, cx);
13669    });
13670    cx.assert_editor_state(indoc!(
13671        "fn a() {
13672             // dog();
13673             catˇ(ˇ);
13674        }"
13675    ));
13676
13677    // Multiple cursors on one line, with selection -> don't advance
13678    cx.set_state(indoc!(
13679        "fn a() {
13680             ˇdˇog«()ˇ»;
13681             cat();
13682        }"
13683    ));
13684    cx.update_editor(|editor, window, cx| {
13685        editor.toggle_comments(toggle_comments, window, cx);
13686    });
13687    cx.assert_editor_state(indoc!(
13688        "fn a() {
13689             // ˇdˇog«()ˇ»;
13690             cat();
13691        }"
13692    ));
13693
13694    // Single cursor on one line -> advance
13695    // Cursor moves to column 0 on blank line
13696    cx.set_state(indoc!(
13697        "fn a() {
13698             ˇdog();
13699
13700             cat();
13701        }"
13702    ));
13703    cx.update_editor(|editor, window, cx| {
13704        editor.toggle_comments(toggle_comments, window, cx);
13705    });
13706    cx.assert_editor_state(indoc!(
13707        "fn a() {
13708             // dog();
13709        ˇ
13710             cat();
13711        }"
13712    ));
13713
13714    // Single cursor on one line -> advance
13715    // Cursor starts and ends at column 0
13716    cx.set_state(indoc!(
13717        "fn a() {
13718         ˇ    dog();
13719             cat();
13720        }"
13721    ));
13722    cx.update_editor(|editor, window, cx| {
13723        editor.toggle_comments(toggle_comments, window, cx);
13724    });
13725    cx.assert_editor_state(indoc!(
13726        "fn a() {
13727             // dog();
13728         ˇ    cat();
13729        }"
13730    ));
13731}
13732
13733#[gpui::test]
13734async fn test_toggle_block_comment(cx: &mut TestAppContext) {
13735    init_test(cx, |_| {});
13736
13737    let mut cx = EditorTestContext::new(cx).await;
13738
13739    let html_language = Arc::new(
13740        Language::new(
13741            LanguageConfig {
13742                name: "HTML".into(),
13743                block_comment: Some(("<!-- ".into(), " -->".into())),
13744                ..Default::default()
13745            },
13746            Some(tree_sitter_html::LANGUAGE.into()),
13747        )
13748        .with_injection_query(
13749            r#"
13750            (script_element
13751                (raw_text) @injection.content
13752                (#set! injection.language "javascript"))
13753            "#,
13754        )
13755        .unwrap(),
13756    );
13757
13758    let javascript_language = Arc::new(Language::new(
13759        LanguageConfig {
13760            name: "JavaScript".into(),
13761            line_comments: vec!["// ".into()],
13762            ..Default::default()
13763        },
13764        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
13765    ));
13766
13767    cx.language_registry().add(html_language.clone());
13768    cx.language_registry().add(javascript_language.clone());
13769    cx.update_buffer(|buffer, cx| {
13770        buffer.set_language(Some(html_language), cx);
13771    });
13772
13773    // Toggle comments for empty selections
13774    cx.set_state(
13775        &r#"
13776            <p>A</p>ˇ
13777            <p>B</p>ˇ
13778            <p>C</p>ˇ
13779        "#
13780        .unindent(),
13781    );
13782    cx.update_editor(|editor, window, cx| {
13783        editor.toggle_comments(&ToggleComments::default(), window, cx)
13784    });
13785    cx.assert_editor_state(
13786        &r#"
13787            <!-- <p>A</p>ˇ -->
13788            <!-- <p>B</p>ˇ -->
13789            <!-- <p>C</p>ˇ -->
13790        "#
13791        .unindent(),
13792    );
13793    cx.update_editor(|editor, window, cx| {
13794        editor.toggle_comments(&ToggleComments::default(), window, cx)
13795    });
13796    cx.assert_editor_state(
13797        &r#"
13798            <p>A</p>ˇ
13799            <p>B</p>ˇ
13800            <p>C</p>ˇ
13801        "#
13802        .unindent(),
13803    );
13804
13805    // Toggle comments for mixture of empty and non-empty selections, where
13806    // multiple selections occupy a given line.
13807    cx.set_state(
13808        &r#"
13809            <p>A«</p>
13810            <p>ˇ»B</p>ˇ
13811            <p>C«</p>
13812            <p>ˇ»D</p>ˇ
13813        "#
13814        .unindent(),
13815    );
13816
13817    cx.update_editor(|editor, window, cx| {
13818        editor.toggle_comments(&ToggleComments::default(), window, cx)
13819    });
13820    cx.assert_editor_state(
13821        &r#"
13822            <!-- <p>A«</p>
13823            <p>ˇ»B</p>ˇ -->
13824            <!-- <p>C«</p>
13825            <p>ˇ»D</p>ˇ -->
13826        "#
13827        .unindent(),
13828    );
13829    cx.update_editor(|editor, window, cx| {
13830        editor.toggle_comments(&ToggleComments::default(), window, cx)
13831    });
13832    cx.assert_editor_state(
13833        &r#"
13834            <p>A«</p>
13835            <p>ˇ»B</p>ˇ
13836            <p>C«</p>
13837            <p>ˇ»D</p>ˇ
13838        "#
13839        .unindent(),
13840    );
13841
13842    // Toggle comments when different languages are active for different
13843    // selections.
13844    cx.set_state(
13845        &r#"
13846            ˇ<script>
13847                ˇvar x = new Y();
13848            ˇ</script>
13849        "#
13850        .unindent(),
13851    );
13852    cx.executor().run_until_parked();
13853    cx.update_editor(|editor, window, cx| {
13854        editor.toggle_comments(&ToggleComments::default(), window, cx)
13855    });
13856    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
13857    // Uncommenting and commenting from this position brings in even more wrong artifacts.
13858    cx.assert_editor_state(
13859        &r#"
13860            <!-- ˇ<script> -->
13861                // ˇvar x = new Y();
13862            <!-- ˇ</script> -->
13863        "#
13864        .unindent(),
13865    );
13866}
13867
13868#[gpui::test]
13869fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
13870    init_test(cx, |_| {});
13871
13872    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
13873    let multibuffer = cx.new(|cx| {
13874        let mut multibuffer = MultiBuffer::new(ReadWrite);
13875        multibuffer.push_excerpts(
13876            buffer.clone(),
13877            [
13878                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
13879                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
13880            ],
13881            cx,
13882        );
13883        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
13884        multibuffer
13885    });
13886
13887    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
13888    editor.update_in(cx, |editor, window, cx| {
13889        assert_eq!(editor.text(cx), "aaaa\nbbbb");
13890        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13891            s.select_ranges([
13892                Point::new(0, 0)..Point::new(0, 0),
13893                Point::new(1, 0)..Point::new(1, 0),
13894            ])
13895        });
13896
13897        editor.handle_input("X", window, cx);
13898        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
13899        assert_eq!(
13900            editor.selections.ranges(cx),
13901            [
13902                Point::new(0, 1)..Point::new(0, 1),
13903                Point::new(1, 1)..Point::new(1, 1),
13904            ]
13905        );
13906
13907        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
13908        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13909            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
13910        });
13911        editor.backspace(&Default::default(), window, cx);
13912        assert_eq!(editor.text(cx), "Xa\nbbb");
13913        assert_eq!(
13914            editor.selections.ranges(cx),
13915            [Point::new(1, 0)..Point::new(1, 0)]
13916        );
13917
13918        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13919            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
13920        });
13921        editor.backspace(&Default::default(), window, cx);
13922        assert_eq!(editor.text(cx), "X\nbb");
13923        assert_eq!(
13924            editor.selections.ranges(cx),
13925            [Point::new(0, 1)..Point::new(0, 1)]
13926        );
13927    });
13928}
13929
13930#[gpui::test]
13931fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
13932    init_test(cx, |_| {});
13933
13934    let markers = vec![('[', ']').into(), ('(', ')').into()];
13935    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
13936        indoc! {"
13937            [aaaa
13938            (bbbb]
13939            cccc)",
13940        },
13941        markers.clone(),
13942    );
13943    let excerpt_ranges = markers.into_iter().map(|marker| {
13944        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
13945        ExcerptRange::new(context.clone())
13946    });
13947    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
13948    let multibuffer = cx.new(|cx| {
13949        let mut multibuffer = MultiBuffer::new(ReadWrite);
13950        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
13951        multibuffer
13952    });
13953
13954    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
13955    editor.update_in(cx, |editor, window, cx| {
13956        let (expected_text, selection_ranges) = marked_text_ranges(
13957            indoc! {"
13958                aaaa
13959                bˇbbb
13960                bˇbbˇb
13961                cccc"
13962            },
13963            true,
13964        );
13965        assert_eq!(editor.text(cx), expected_text);
13966        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13967            s.select_ranges(selection_ranges)
13968        });
13969
13970        editor.handle_input("X", window, cx);
13971
13972        let (expected_text, expected_selections) = marked_text_ranges(
13973            indoc! {"
13974                aaaa
13975                bXˇbbXb
13976                bXˇbbXˇb
13977                cccc"
13978            },
13979            false,
13980        );
13981        assert_eq!(editor.text(cx), expected_text);
13982        assert_eq!(editor.selections.ranges(cx), expected_selections);
13983
13984        editor.newline(&Newline, window, cx);
13985        let (expected_text, expected_selections) = marked_text_ranges(
13986            indoc! {"
13987                aaaa
13988                bX
13989                ˇbbX
13990                b
13991                bX
13992                ˇbbX
13993                ˇb
13994                cccc"
13995            },
13996            false,
13997        );
13998        assert_eq!(editor.text(cx), expected_text);
13999        assert_eq!(editor.selections.ranges(cx), expected_selections);
14000    });
14001}
14002
14003#[gpui::test]
14004fn test_refresh_selections(cx: &mut TestAppContext) {
14005    init_test(cx, |_| {});
14006
14007    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
14008    let mut excerpt1_id = None;
14009    let multibuffer = cx.new(|cx| {
14010        let mut multibuffer = MultiBuffer::new(ReadWrite);
14011        excerpt1_id = multibuffer
14012            .push_excerpts(
14013                buffer.clone(),
14014                [
14015                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
14016                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
14017                ],
14018                cx,
14019            )
14020            .into_iter()
14021            .next();
14022        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
14023        multibuffer
14024    });
14025
14026    let editor = cx.add_window(|window, cx| {
14027        let mut editor = build_editor(multibuffer.clone(), window, cx);
14028        let snapshot = editor.snapshot(window, cx);
14029        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14030            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
14031        });
14032        editor.begin_selection(
14033            Point::new(2, 1).to_display_point(&snapshot),
14034            true,
14035            1,
14036            window,
14037            cx,
14038        );
14039        assert_eq!(
14040            editor.selections.ranges(cx),
14041            [
14042                Point::new(1, 3)..Point::new(1, 3),
14043                Point::new(2, 1)..Point::new(2, 1),
14044            ]
14045        );
14046        editor
14047    });
14048
14049    // Refreshing selections is a no-op when excerpts haven't changed.
14050    _ = editor.update(cx, |editor, window, cx| {
14051        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14052        assert_eq!(
14053            editor.selections.ranges(cx),
14054            [
14055                Point::new(1, 3)..Point::new(1, 3),
14056                Point::new(2, 1)..Point::new(2, 1),
14057            ]
14058        );
14059    });
14060
14061    multibuffer.update(cx, |multibuffer, cx| {
14062        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
14063    });
14064    _ = editor.update(cx, |editor, window, cx| {
14065        // Removing an excerpt causes the first selection to become degenerate.
14066        assert_eq!(
14067            editor.selections.ranges(cx),
14068            [
14069                Point::new(0, 0)..Point::new(0, 0),
14070                Point::new(0, 1)..Point::new(0, 1)
14071            ]
14072        );
14073
14074        // Refreshing selections will relocate the first selection to the original buffer
14075        // location.
14076        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14077        assert_eq!(
14078            editor.selections.ranges(cx),
14079            [
14080                Point::new(0, 1)..Point::new(0, 1),
14081                Point::new(0, 3)..Point::new(0, 3)
14082            ]
14083        );
14084        assert!(editor.selections.pending_anchor().is_some());
14085    });
14086}
14087
14088#[gpui::test]
14089fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
14090    init_test(cx, |_| {});
14091
14092    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
14093    let mut excerpt1_id = None;
14094    let multibuffer = cx.new(|cx| {
14095        let mut multibuffer = MultiBuffer::new(ReadWrite);
14096        excerpt1_id = multibuffer
14097            .push_excerpts(
14098                buffer.clone(),
14099                [
14100                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
14101                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
14102                ],
14103                cx,
14104            )
14105            .into_iter()
14106            .next();
14107        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
14108        multibuffer
14109    });
14110
14111    let editor = cx.add_window(|window, cx| {
14112        let mut editor = build_editor(multibuffer.clone(), window, cx);
14113        let snapshot = editor.snapshot(window, cx);
14114        editor.begin_selection(
14115            Point::new(1, 3).to_display_point(&snapshot),
14116            false,
14117            1,
14118            window,
14119            cx,
14120        );
14121        assert_eq!(
14122            editor.selections.ranges(cx),
14123            [Point::new(1, 3)..Point::new(1, 3)]
14124        );
14125        editor
14126    });
14127
14128    multibuffer.update(cx, |multibuffer, cx| {
14129        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
14130    });
14131    _ = editor.update(cx, |editor, window, cx| {
14132        assert_eq!(
14133            editor.selections.ranges(cx),
14134            [Point::new(0, 0)..Point::new(0, 0)]
14135        );
14136
14137        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
14138        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14139        assert_eq!(
14140            editor.selections.ranges(cx),
14141            [Point::new(0, 3)..Point::new(0, 3)]
14142        );
14143        assert!(editor.selections.pending_anchor().is_some());
14144    });
14145}
14146
14147#[gpui::test]
14148async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
14149    init_test(cx, |_| {});
14150
14151    let language = Arc::new(
14152        Language::new(
14153            LanguageConfig {
14154                brackets: BracketPairConfig {
14155                    pairs: vec![
14156                        BracketPair {
14157                            start: "{".to_string(),
14158                            end: "}".to_string(),
14159                            close: true,
14160                            surround: true,
14161                            newline: true,
14162                        },
14163                        BracketPair {
14164                            start: "/* ".to_string(),
14165                            end: " */".to_string(),
14166                            close: true,
14167                            surround: true,
14168                            newline: true,
14169                        },
14170                    ],
14171                    ..Default::default()
14172                },
14173                ..Default::default()
14174            },
14175            Some(tree_sitter_rust::LANGUAGE.into()),
14176        )
14177        .with_indents_query("")
14178        .unwrap(),
14179    );
14180
14181    let text = concat!(
14182        "{   }\n",     //
14183        "  x\n",       //
14184        "  /*   */\n", //
14185        "x\n",         //
14186        "{{} }\n",     //
14187    );
14188
14189    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
14190    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
14191    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
14192    editor
14193        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
14194        .await;
14195
14196    editor.update_in(cx, |editor, window, cx| {
14197        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14198            s.select_display_ranges([
14199                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
14200                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
14201                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
14202            ])
14203        });
14204        editor.newline(&Newline, window, cx);
14205
14206        assert_eq!(
14207            editor.buffer().read(cx).read(cx).text(),
14208            concat!(
14209                "{ \n",    // Suppress rustfmt
14210                "\n",      //
14211                "}\n",     //
14212                "  x\n",   //
14213                "  /* \n", //
14214                "  \n",    //
14215                "  */\n",  //
14216                "x\n",     //
14217                "{{} \n",  //
14218                "}\n",     //
14219            )
14220        );
14221    });
14222}
14223
14224#[gpui::test]
14225fn test_highlighted_ranges(cx: &mut TestAppContext) {
14226    init_test(cx, |_| {});
14227
14228    let editor = cx.add_window(|window, cx| {
14229        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
14230        build_editor(buffer.clone(), window, cx)
14231    });
14232
14233    _ = editor.update(cx, |editor, window, cx| {
14234        struct Type1;
14235        struct Type2;
14236
14237        let buffer = editor.buffer.read(cx).snapshot(cx);
14238
14239        let anchor_range =
14240            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
14241
14242        editor.highlight_background::<Type1>(
14243            &[
14244                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
14245                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
14246                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
14247                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
14248            ],
14249            |_| Hsla::red(),
14250            cx,
14251        );
14252        editor.highlight_background::<Type2>(
14253            &[
14254                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
14255                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
14256                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
14257                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
14258            ],
14259            |_| Hsla::green(),
14260            cx,
14261        );
14262
14263        let snapshot = editor.snapshot(window, cx);
14264        let mut highlighted_ranges = editor.background_highlights_in_range(
14265            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
14266            &snapshot,
14267            cx.theme(),
14268        );
14269        // Enforce a consistent ordering based on color without relying on the ordering of the
14270        // highlight's `TypeId` which is non-executor.
14271        highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
14272        assert_eq!(
14273            highlighted_ranges,
14274            &[
14275                (
14276                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
14277                    Hsla::red(),
14278                ),
14279                (
14280                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
14281                    Hsla::red(),
14282                ),
14283                (
14284                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
14285                    Hsla::green(),
14286                ),
14287                (
14288                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
14289                    Hsla::green(),
14290                ),
14291            ]
14292        );
14293        assert_eq!(
14294            editor.background_highlights_in_range(
14295                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
14296                &snapshot,
14297                cx.theme(),
14298            ),
14299            &[(
14300                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
14301                Hsla::red(),
14302            )]
14303        );
14304    });
14305}
14306
14307#[gpui::test]
14308async fn test_following(cx: &mut TestAppContext) {
14309    init_test(cx, |_| {});
14310
14311    let fs = FakeFs::new(cx.executor());
14312    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
14313
14314    let buffer = project.update(cx, |project, cx| {
14315        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
14316        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
14317    });
14318    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
14319    let follower = cx.update(|cx| {
14320        cx.open_window(
14321            WindowOptions {
14322                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
14323                    gpui::Point::new(px(0.), px(0.)),
14324                    gpui::Point::new(px(10.), px(80.)),
14325                ))),
14326                ..Default::default()
14327            },
14328            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
14329        )
14330        .unwrap()
14331    });
14332
14333    let is_still_following = Rc::new(RefCell::new(true));
14334    let follower_edit_event_count = Rc::new(RefCell::new(0));
14335    let pending_update = Rc::new(RefCell::new(None));
14336    let leader_entity = leader.root(cx).unwrap();
14337    let follower_entity = follower.root(cx).unwrap();
14338    _ = follower.update(cx, {
14339        let update = pending_update.clone();
14340        let is_still_following = is_still_following.clone();
14341        let follower_edit_event_count = follower_edit_event_count.clone();
14342        |_, window, cx| {
14343            cx.subscribe_in(
14344                &leader_entity,
14345                window,
14346                move |_, leader, event, window, cx| {
14347                    leader.read(cx).add_event_to_update_proto(
14348                        event,
14349                        &mut update.borrow_mut(),
14350                        window,
14351                        cx,
14352                    );
14353                },
14354            )
14355            .detach();
14356
14357            cx.subscribe_in(
14358                &follower_entity,
14359                window,
14360                move |_, _, event: &EditorEvent, _window, _cx| {
14361                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
14362                        *is_still_following.borrow_mut() = false;
14363                    }
14364
14365                    if let EditorEvent::BufferEdited = event {
14366                        *follower_edit_event_count.borrow_mut() += 1;
14367                    }
14368                },
14369            )
14370            .detach();
14371        }
14372    });
14373
14374    // Update the selections only
14375    _ = leader.update(cx, |leader, window, cx| {
14376        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14377            s.select_ranges([1..1])
14378        });
14379    });
14380    follower
14381        .update(cx, |follower, window, cx| {
14382            follower.apply_update_proto(
14383                &project,
14384                pending_update.borrow_mut().take().unwrap(),
14385                window,
14386                cx,
14387            )
14388        })
14389        .unwrap()
14390        .await
14391        .unwrap();
14392    _ = follower.update(cx, |follower, _, cx| {
14393        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
14394    });
14395    assert!(*is_still_following.borrow());
14396    assert_eq!(*follower_edit_event_count.borrow(), 0);
14397
14398    // Update the scroll position only
14399    _ = leader.update(cx, |leader, window, cx| {
14400        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
14401    });
14402    follower
14403        .update(cx, |follower, window, cx| {
14404            follower.apply_update_proto(
14405                &project,
14406                pending_update.borrow_mut().take().unwrap(),
14407                window,
14408                cx,
14409            )
14410        })
14411        .unwrap()
14412        .await
14413        .unwrap();
14414    assert_eq!(
14415        follower
14416            .update(cx, |follower, _, cx| follower.scroll_position(cx))
14417            .unwrap(),
14418        gpui::Point::new(1.5, 3.5)
14419    );
14420    assert!(*is_still_following.borrow());
14421    assert_eq!(*follower_edit_event_count.borrow(), 0);
14422
14423    // Update the selections and scroll position. The follower's scroll position is updated
14424    // via autoscroll, not via the leader's exact scroll position.
14425    _ = leader.update(cx, |leader, window, cx| {
14426        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14427            s.select_ranges([0..0])
14428        });
14429        leader.request_autoscroll(Autoscroll::newest(), cx);
14430        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
14431    });
14432    follower
14433        .update(cx, |follower, window, cx| {
14434            follower.apply_update_proto(
14435                &project,
14436                pending_update.borrow_mut().take().unwrap(),
14437                window,
14438                cx,
14439            )
14440        })
14441        .unwrap()
14442        .await
14443        .unwrap();
14444    _ = follower.update(cx, |follower, _, cx| {
14445        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
14446        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
14447    });
14448    assert!(*is_still_following.borrow());
14449
14450    // Creating a pending selection that precedes another selection
14451    _ = leader.update(cx, |leader, window, cx| {
14452        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14453            s.select_ranges([1..1])
14454        });
14455        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
14456    });
14457    follower
14458        .update(cx, |follower, window, cx| {
14459            follower.apply_update_proto(
14460                &project,
14461                pending_update.borrow_mut().take().unwrap(),
14462                window,
14463                cx,
14464            )
14465        })
14466        .unwrap()
14467        .await
14468        .unwrap();
14469    _ = follower.update(cx, |follower, _, cx| {
14470        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
14471    });
14472    assert!(*is_still_following.borrow());
14473
14474    // Extend the pending selection so that it surrounds another selection
14475    _ = leader.update(cx, |leader, window, cx| {
14476        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
14477    });
14478    follower
14479        .update(cx, |follower, window, cx| {
14480            follower.apply_update_proto(
14481                &project,
14482                pending_update.borrow_mut().take().unwrap(),
14483                window,
14484                cx,
14485            )
14486        })
14487        .unwrap()
14488        .await
14489        .unwrap();
14490    _ = follower.update(cx, |follower, _, cx| {
14491        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
14492    });
14493
14494    // Scrolling locally breaks the follow
14495    _ = follower.update(cx, |follower, window, cx| {
14496        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
14497        follower.set_scroll_anchor(
14498            ScrollAnchor {
14499                anchor: top_anchor,
14500                offset: gpui::Point::new(0.0, 0.5),
14501            },
14502            window,
14503            cx,
14504        );
14505    });
14506    assert!(!(*is_still_following.borrow()));
14507}
14508
14509#[gpui::test]
14510async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
14511    init_test(cx, |_| {});
14512
14513    let fs = FakeFs::new(cx.executor());
14514    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
14515    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14516    let pane = workspace
14517        .update(cx, |workspace, _, _| workspace.active_pane().clone())
14518        .unwrap();
14519
14520    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14521
14522    let leader = pane.update_in(cx, |_, window, cx| {
14523        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
14524        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
14525    });
14526
14527    // Start following the editor when it has no excerpts.
14528    let mut state_message =
14529        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
14530    let workspace_entity = workspace.root(cx).unwrap();
14531    let follower_1 = cx
14532        .update_window(*workspace.deref(), |_, window, cx| {
14533            Editor::from_state_proto(
14534                workspace_entity,
14535                ViewId {
14536                    creator: CollaboratorId::PeerId(PeerId::default()),
14537                    id: 0,
14538                },
14539                &mut state_message,
14540                window,
14541                cx,
14542            )
14543        })
14544        .unwrap()
14545        .unwrap()
14546        .await
14547        .unwrap();
14548
14549    let update_message = Rc::new(RefCell::new(None));
14550    follower_1.update_in(cx, {
14551        let update = update_message.clone();
14552        |_, window, cx| {
14553            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
14554                leader.read(cx).add_event_to_update_proto(
14555                    event,
14556                    &mut update.borrow_mut(),
14557                    window,
14558                    cx,
14559                );
14560            })
14561            .detach();
14562        }
14563    });
14564
14565    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
14566        (
14567            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
14568            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
14569        )
14570    });
14571
14572    // Insert some excerpts.
14573    leader.update(cx, |leader, cx| {
14574        leader.buffer.update(cx, |multibuffer, cx| {
14575            multibuffer.set_excerpts_for_path(
14576                PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
14577                buffer_1.clone(),
14578                vec![
14579                    Point::row_range(0..3),
14580                    Point::row_range(1..6),
14581                    Point::row_range(12..15),
14582                ],
14583                0,
14584                cx,
14585            );
14586            multibuffer.set_excerpts_for_path(
14587                PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
14588                buffer_2.clone(),
14589                vec![Point::row_range(0..6), Point::row_range(8..12)],
14590                0,
14591                cx,
14592            );
14593        });
14594    });
14595
14596    // Apply the update of adding the excerpts.
14597    follower_1
14598        .update_in(cx, |follower, window, cx| {
14599            follower.apply_update_proto(
14600                &project,
14601                update_message.borrow().clone().unwrap(),
14602                window,
14603                cx,
14604            )
14605        })
14606        .await
14607        .unwrap();
14608    assert_eq!(
14609        follower_1.update(cx, |editor, cx| editor.text(cx)),
14610        leader.update(cx, |editor, cx| editor.text(cx))
14611    );
14612    update_message.borrow_mut().take();
14613
14614    // Start following separately after it already has excerpts.
14615    let mut state_message =
14616        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
14617    let workspace_entity = workspace.root(cx).unwrap();
14618    let follower_2 = cx
14619        .update_window(*workspace.deref(), |_, window, cx| {
14620            Editor::from_state_proto(
14621                workspace_entity,
14622                ViewId {
14623                    creator: CollaboratorId::PeerId(PeerId::default()),
14624                    id: 0,
14625                },
14626                &mut state_message,
14627                window,
14628                cx,
14629            )
14630        })
14631        .unwrap()
14632        .unwrap()
14633        .await
14634        .unwrap();
14635    assert_eq!(
14636        follower_2.update(cx, |editor, cx| editor.text(cx)),
14637        leader.update(cx, |editor, cx| editor.text(cx))
14638    );
14639
14640    // Remove some excerpts.
14641    leader.update(cx, |leader, cx| {
14642        leader.buffer.update(cx, |multibuffer, cx| {
14643            let excerpt_ids = multibuffer.excerpt_ids();
14644            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
14645            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
14646        });
14647    });
14648
14649    // Apply the update of removing the excerpts.
14650    follower_1
14651        .update_in(cx, |follower, window, cx| {
14652            follower.apply_update_proto(
14653                &project,
14654                update_message.borrow().clone().unwrap(),
14655                window,
14656                cx,
14657            )
14658        })
14659        .await
14660        .unwrap();
14661    follower_2
14662        .update_in(cx, |follower, window, cx| {
14663            follower.apply_update_proto(
14664                &project,
14665                update_message.borrow().clone().unwrap(),
14666                window,
14667                cx,
14668            )
14669        })
14670        .await
14671        .unwrap();
14672    update_message.borrow_mut().take();
14673    assert_eq!(
14674        follower_1.update(cx, |editor, cx| editor.text(cx)),
14675        leader.update(cx, |editor, cx| editor.text(cx))
14676    );
14677}
14678
14679#[gpui::test]
14680async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14681    init_test(cx, |_| {});
14682
14683    let mut cx = EditorTestContext::new(cx).await;
14684    let lsp_store =
14685        cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
14686
14687    cx.set_state(indoc! {"
14688        ˇfn func(abc def: i32) -> u32 {
14689        }
14690    "});
14691
14692    cx.update(|_, cx| {
14693        lsp_store.update(cx, |lsp_store, cx| {
14694            lsp_store
14695                .update_diagnostics(
14696                    LanguageServerId(0),
14697                    lsp::PublishDiagnosticsParams {
14698                        uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
14699                        version: None,
14700                        diagnostics: vec![
14701                            lsp::Diagnostic {
14702                                range: lsp::Range::new(
14703                                    lsp::Position::new(0, 11),
14704                                    lsp::Position::new(0, 12),
14705                                ),
14706                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14707                                ..Default::default()
14708                            },
14709                            lsp::Diagnostic {
14710                                range: lsp::Range::new(
14711                                    lsp::Position::new(0, 12),
14712                                    lsp::Position::new(0, 15),
14713                                ),
14714                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14715                                ..Default::default()
14716                            },
14717                            lsp::Diagnostic {
14718                                range: lsp::Range::new(
14719                                    lsp::Position::new(0, 25),
14720                                    lsp::Position::new(0, 28),
14721                                ),
14722                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14723                                ..Default::default()
14724                            },
14725                        ],
14726                    },
14727                    None,
14728                    DiagnosticSourceKind::Pushed,
14729                    &[],
14730                    cx,
14731                )
14732                .unwrap()
14733        });
14734    });
14735
14736    executor.run_until_parked();
14737
14738    cx.update_editor(|editor, window, cx| {
14739        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
14740    });
14741
14742    cx.assert_editor_state(indoc! {"
14743        fn func(abc def: i32) -> ˇu32 {
14744        }
14745    "});
14746
14747    cx.update_editor(|editor, window, cx| {
14748        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
14749    });
14750
14751    cx.assert_editor_state(indoc! {"
14752        fn func(abc ˇdef: i32) -> u32 {
14753        }
14754    "});
14755
14756    cx.update_editor(|editor, window, cx| {
14757        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
14758    });
14759
14760    cx.assert_editor_state(indoc! {"
14761        fn func(abcˇ def: i32) -> u32 {
14762        }
14763    "});
14764
14765    cx.update_editor(|editor, window, cx| {
14766        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
14767    });
14768
14769    cx.assert_editor_state(indoc! {"
14770        fn func(abc def: i32) -> ˇu32 {
14771        }
14772    "});
14773}
14774
14775#[gpui::test]
14776async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14777    init_test(cx, |_| {});
14778
14779    let mut cx = EditorTestContext::new(cx).await;
14780
14781    let diff_base = r#"
14782        use some::mod;
14783
14784        const A: u32 = 42;
14785
14786        fn main() {
14787            println!("hello");
14788
14789            println!("world");
14790        }
14791        "#
14792    .unindent();
14793
14794    // Edits are modified, removed, modified, added
14795    cx.set_state(
14796        &r#"
14797        use some::modified;
14798
14799        ˇ
14800        fn main() {
14801            println!("hello there");
14802
14803            println!("around the");
14804            println!("world");
14805        }
14806        "#
14807        .unindent(),
14808    );
14809
14810    cx.set_head_text(&diff_base);
14811    executor.run_until_parked();
14812
14813    cx.update_editor(|editor, window, cx| {
14814        //Wrap around the bottom of the buffer
14815        for _ in 0..3 {
14816            editor.go_to_next_hunk(&GoToHunk, window, cx);
14817        }
14818    });
14819
14820    cx.assert_editor_state(
14821        &r#"
14822        ˇuse some::modified;
14823
14824
14825        fn main() {
14826            println!("hello there");
14827
14828            println!("around the");
14829            println!("world");
14830        }
14831        "#
14832        .unindent(),
14833    );
14834
14835    cx.update_editor(|editor, window, cx| {
14836        //Wrap around the top of the buffer
14837        for _ in 0..2 {
14838            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14839        }
14840    });
14841
14842    cx.assert_editor_state(
14843        &r#"
14844        use some::modified;
14845
14846
14847        fn main() {
14848        ˇ    println!("hello there");
14849
14850            println!("around the");
14851            println!("world");
14852        }
14853        "#
14854        .unindent(),
14855    );
14856
14857    cx.update_editor(|editor, window, cx| {
14858        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14859    });
14860
14861    cx.assert_editor_state(
14862        &r#"
14863        use some::modified;
14864
14865        ˇ
14866        fn main() {
14867            println!("hello there");
14868
14869            println!("around the");
14870            println!("world");
14871        }
14872        "#
14873        .unindent(),
14874    );
14875
14876    cx.update_editor(|editor, window, cx| {
14877        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14878    });
14879
14880    cx.assert_editor_state(
14881        &r#"
14882        ˇuse some::modified;
14883
14884
14885        fn main() {
14886            println!("hello there");
14887
14888            println!("around the");
14889            println!("world");
14890        }
14891        "#
14892        .unindent(),
14893    );
14894
14895    cx.update_editor(|editor, window, cx| {
14896        for _ in 0..2 {
14897            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14898        }
14899    });
14900
14901    cx.assert_editor_state(
14902        &r#"
14903        use some::modified;
14904
14905
14906        fn main() {
14907        ˇ    println!("hello there");
14908
14909            println!("around the");
14910            println!("world");
14911        }
14912        "#
14913        .unindent(),
14914    );
14915
14916    cx.update_editor(|editor, window, cx| {
14917        editor.fold(&Fold, window, cx);
14918    });
14919
14920    cx.update_editor(|editor, window, cx| {
14921        editor.go_to_next_hunk(&GoToHunk, window, cx);
14922    });
14923
14924    cx.assert_editor_state(
14925        &r#"
14926        ˇuse some::modified;
14927
14928
14929        fn main() {
14930            println!("hello there");
14931
14932            println!("around the");
14933            println!("world");
14934        }
14935        "#
14936        .unindent(),
14937    );
14938}
14939
14940#[test]
14941fn test_split_words() {
14942    fn split(text: &str) -> Vec<&str> {
14943        split_words(text).collect()
14944    }
14945
14946    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
14947    assert_eq!(split("hello_world"), &["hello_", "world"]);
14948    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
14949    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
14950    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
14951    assert_eq!(split("helloworld"), &["helloworld"]);
14952
14953    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
14954}
14955
14956#[gpui::test]
14957async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
14958    init_test(cx, |_| {});
14959
14960    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
14961    let mut assert = |before, after| {
14962        let _state_context = cx.set_state(before);
14963        cx.run_until_parked();
14964        cx.update_editor(|editor, window, cx| {
14965            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
14966        });
14967        cx.run_until_parked();
14968        cx.assert_editor_state(after);
14969    };
14970
14971    // Outside bracket jumps to outside of matching bracket
14972    assert("console.logˇ(var);", "console.log(var)ˇ;");
14973    assert("console.log(var)ˇ;", "console.logˇ(var);");
14974
14975    // Inside bracket jumps to inside of matching bracket
14976    assert("console.log(ˇvar);", "console.log(varˇ);");
14977    assert("console.log(varˇ);", "console.log(ˇvar);");
14978
14979    // When outside a bracket and inside, favor jumping to the inside bracket
14980    assert(
14981        "console.log('foo', [1, 2, 3]ˇ);",
14982        "console.log(ˇ'foo', [1, 2, 3]);",
14983    );
14984    assert(
14985        "console.log(ˇ'foo', [1, 2, 3]);",
14986        "console.log('foo', [1, 2, 3]ˇ);",
14987    );
14988
14989    // Bias forward if two options are equally likely
14990    assert(
14991        "let result = curried_fun()ˇ();",
14992        "let result = curried_fun()()ˇ;",
14993    );
14994
14995    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
14996    assert(
14997        indoc! {"
14998            function test() {
14999                console.log('test')ˇ
15000            }"},
15001        indoc! {"
15002            function test() {
15003                console.logˇ('test')
15004            }"},
15005    );
15006}
15007
15008#[gpui::test]
15009async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
15010    init_test(cx, |_| {});
15011
15012    let fs = FakeFs::new(cx.executor());
15013    fs.insert_tree(
15014        path!("/a"),
15015        json!({
15016            "main.rs": "fn main() { let a = 5; }",
15017            "other.rs": "// Test file",
15018        }),
15019    )
15020    .await;
15021    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15022
15023    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15024    language_registry.add(Arc::new(Language::new(
15025        LanguageConfig {
15026            name: "Rust".into(),
15027            matcher: LanguageMatcher {
15028                path_suffixes: vec!["rs".to_string()],
15029                ..Default::default()
15030            },
15031            brackets: BracketPairConfig {
15032                pairs: vec![BracketPair {
15033                    start: "{".to_string(),
15034                    end: "}".to_string(),
15035                    close: true,
15036                    surround: true,
15037                    newline: true,
15038                }],
15039                disabled_scopes_by_bracket_ix: Vec::new(),
15040            },
15041            ..Default::default()
15042        },
15043        Some(tree_sitter_rust::LANGUAGE.into()),
15044    )));
15045    let mut fake_servers = language_registry.register_fake_lsp(
15046        "Rust",
15047        FakeLspAdapter {
15048            capabilities: lsp::ServerCapabilities {
15049                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
15050                    first_trigger_character: "{".to_string(),
15051                    more_trigger_character: None,
15052                }),
15053                ..Default::default()
15054            },
15055            ..Default::default()
15056        },
15057    );
15058
15059    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15060
15061    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15062
15063    let worktree_id = workspace
15064        .update(cx, |workspace, _, cx| {
15065            workspace.project().update(cx, |project, cx| {
15066                project.worktrees(cx).next().unwrap().read(cx).id()
15067            })
15068        })
15069        .unwrap();
15070
15071    let buffer = project
15072        .update(cx, |project, cx| {
15073            project.open_local_buffer(path!("/a/main.rs"), cx)
15074        })
15075        .await
15076        .unwrap();
15077    let editor_handle = workspace
15078        .update(cx, |workspace, window, cx| {
15079            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
15080        })
15081        .unwrap()
15082        .await
15083        .unwrap()
15084        .downcast::<Editor>()
15085        .unwrap();
15086
15087    cx.executor().start_waiting();
15088    let fake_server = fake_servers.next().await.unwrap();
15089
15090    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
15091        |params, _| async move {
15092            assert_eq!(
15093                params.text_document_position.text_document.uri,
15094                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
15095            );
15096            assert_eq!(
15097                params.text_document_position.position,
15098                lsp::Position::new(0, 21),
15099            );
15100
15101            Ok(Some(vec![lsp::TextEdit {
15102                new_text: "]".to_string(),
15103                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15104            }]))
15105        },
15106    );
15107
15108    editor_handle.update_in(cx, |editor, window, cx| {
15109        window.focus(&editor.focus_handle(cx));
15110        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15111            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
15112        });
15113        editor.handle_input("{", window, cx);
15114    });
15115
15116    cx.executor().run_until_parked();
15117
15118    buffer.update(cx, |buffer, _| {
15119        assert_eq!(
15120            buffer.text(),
15121            "fn main() { let a = {5}; }",
15122            "No extra braces from on type formatting should appear in the buffer"
15123        )
15124    });
15125}
15126
15127#[gpui::test(iterations = 20, seeds(31))]
15128async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
15129    init_test(cx, |_| {});
15130
15131    let mut cx = EditorLspTestContext::new_rust(
15132        lsp::ServerCapabilities {
15133            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
15134                first_trigger_character: ".".to_string(),
15135                more_trigger_character: None,
15136            }),
15137            ..Default::default()
15138        },
15139        cx,
15140    )
15141    .await;
15142
15143    cx.update_buffer(|buffer, _| {
15144        // This causes autoindent to be async.
15145        buffer.set_sync_parse_timeout(Duration::ZERO)
15146    });
15147
15148    cx.set_state("fn c() {\n    d()ˇ\n}\n");
15149    cx.simulate_keystroke("\n");
15150    cx.run_until_parked();
15151
15152    let buffer_cloned =
15153        cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap().clone());
15154    let mut request =
15155        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
15156            let buffer_cloned = buffer_cloned.clone();
15157            async move {
15158                buffer_cloned.update(&mut cx, |buffer, _| {
15159                    assert_eq!(
15160                        buffer.text(),
15161                        "fn c() {\n    d()\n        .\n}\n",
15162                        "OnTypeFormatting should triggered after autoindent applied"
15163                    )
15164                })?;
15165
15166                Ok(Some(vec![]))
15167            }
15168        });
15169
15170    cx.simulate_keystroke(".");
15171    cx.run_until_parked();
15172
15173    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
15174    assert!(request.next().await.is_some());
15175    request.close();
15176    assert!(request.next().await.is_none());
15177}
15178
15179#[gpui::test]
15180async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
15181    init_test(cx, |_| {});
15182
15183    let fs = FakeFs::new(cx.executor());
15184    fs.insert_tree(
15185        path!("/a"),
15186        json!({
15187            "main.rs": "fn main() { let a = 5; }",
15188            "other.rs": "// Test file",
15189        }),
15190    )
15191    .await;
15192
15193    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15194
15195    let server_restarts = Arc::new(AtomicUsize::new(0));
15196    let closure_restarts = Arc::clone(&server_restarts);
15197    let language_server_name = "test language server";
15198    let language_name: LanguageName = "Rust".into();
15199
15200    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15201    language_registry.add(Arc::new(Language::new(
15202        LanguageConfig {
15203            name: language_name.clone(),
15204            matcher: LanguageMatcher {
15205                path_suffixes: vec!["rs".to_string()],
15206                ..Default::default()
15207            },
15208            ..Default::default()
15209        },
15210        Some(tree_sitter_rust::LANGUAGE.into()),
15211    )));
15212    let mut fake_servers = language_registry.register_fake_lsp(
15213        "Rust",
15214        FakeLspAdapter {
15215            name: language_server_name,
15216            initialization_options: Some(json!({
15217                "testOptionValue": true
15218            })),
15219            initializer: Some(Box::new(move |fake_server| {
15220                let task_restarts = Arc::clone(&closure_restarts);
15221                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
15222                    task_restarts.fetch_add(1, atomic::Ordering::Release);
15223                    futures::future::ready(Ok(()))
15224                });
15225            })),
15226            ..Default::default()
15227        },
15228    );
15229
15230    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15231    let _buffer = project
15232        .update(cx, |project, cx| {
15233            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
15234        })
15235        .await
15236        .unwrap();
15237    let _fake_server = fake_servers.next().await.unwrap();
15238    update_test_language_settings(cx, |language_settings| {
15239        language_settings.languages.0.insert(
15240            language_name.clone(),
15241            LanguageSettingsContent {
15242                tab_size: NonZeroU32::new(8),
15243                ..Default::default()
15244            },
15245        );
15246    });
15247    cx.executor().run_until_parked();
15248    assert_eq!(
15249        server_restarts.load(atomic::Ordering::Acquire),
15250        0,
15251        "Should not restart LSP server on an unrelated change"
15252    );
15253
15254    update_test_project_settings(cx, |project_settings| {
15255        project_settings.lsp.insert(
15256            "Some other server name".into(),
15257            LspSettings {
15258                binary: None,
15259                settings: None,
15260                initialization_options: Some(json!({
15261                    "some other init value": false
15262                })),
15263                enable_lsp_tasks: false,
15264            },
15265        );
15266    });
15267    cx.executor().run_until_parked();
15268    assert_eq!(
15269        server_restarts.load(atomic::Ordering::Acquire),
15270        0,
15271        "Should not restart LSP server on an unrelated LSP settings change"
15272    );
15273
15274    update_test_project_settings(cx, |project_settings| {
15275        project_settings.lsp.insert(
15276            language_server_name.into(),
15277            LspSettings {
15278                binary: None,
15279                settings: None,
15280                initialization_options: Some(json!({
15281                    "anotherInitValue": false
15282                })),
15283                enable_lsp_tasks: false,
15284            },
15285        );
15286    });
15287    cx.executor().run_until_parked();
15288    assert_eq!(
15289        server_restarts.load(atomic::Ordering::Acquire),
15290        1,
15291        "Should restart LSP server on a related LSP settings change"
15292    );
15293
15294    update_test_project_settings(cx, |project_settings| {
15295        project_settings.lsp.insert(
15296            language_server_name.into(),
15297            LspSettings {
15298                binary: None,
15299                settings: None,
15300                initialization_options: Some(json!({
15301                    "anotherInitValue": false
15302                })),
15303                enable_lsp_tasks: false,
15304            },
15305        );
15306    });
15307    cx.executor().run_until_parked();
15308    assert_eq!(
15309        server_restarts.load(atomic::Ordering::Acquire),
15310        1,
15311        "Should not restart LSP server on a related LSP settings change that is the same"
15312    );
15313
15314    update_test_project_settings(cx, |project_settings| {
15315        project_settings.lsp.insert(
15316            language_server_name.into(),
15317            LspSettings {
15318                binary: None,
15319                settings: None,
15320                initialization_options: None,
15321                enable_lsp_tasks: false,
15322            },
15323        );
15324    });
15325    cx.executor().run_until_parked();
15326    assert_eq!(
15327        server_restarts.load(atomic::Ordering::Acquire),
15328        2,
15329        "Should restart LSP server on another related LSP settings change"
15330    );
15331}
15332
15333#[gpui::test]
15334async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
15335    init_test(cx, |_| {});
15336
15337    let mut cx = EditorLspTestContext::new_rust(
15338        lsp::ServerCapabilities {
15339            completion_provider: Some(lsp::CompletionOptions {
15340                trigger_characters: Some(vec![".".to_string()]),
15341                resolve_provider: Some(true),
15342                ..Default::default()
15343            }),
15344            ..Default::default()
15345        },
15346        cx,
15347    )
15348    .await;
15349
15350    cx.set_state("fn main() { let a = 2ˇ; }");
15351    cx.simulate_keystroke(".");
15352    let completion_item = lsp::CompletionItem {
15353        label: "some".into(),
15354        kind: Some(lsp::CompletionItemKind::SNIPPET),
15355        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15356        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15357            kind: lsp::MarkupKind::Markdown,
15358            value: "```rust\nSome(2)\n```".to_string(),
15359        })),
15360        deprecated: Some(false),
15361        sort_text: Some("fffffff2".to_string()),
15362        filter_text: Some("some".to_string()),
15363        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15364        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15365            range: lsp::Range {
15366                start: lsp::Position {
15367                    line: 0,
15368                    character: 22,
15369                },
15370                end: lsp::Position {
15371                    line: 0,
15372                    character: 22,
15373                },
15374            },
15375            new_text: "Some(2)".to_string(),
15376        })),
15377        additional_text_edits: Some(vec![lsp::TextEdit {
15378            range: lsp::Range {
15379                start: lsp::Position {
15380                    line: 0,
15381                    character: 20,
15382                },
15383                end: lsp::Position {
15384                    line: 0,
15385                    character: 22,
15386                },
15387            },
15388            new_text: "".to_string(),
15389        }]),
15390        ..Default::default()
15391    };
15392
15393    let closure_completion_item = completion_item.clone();
15394    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15395        let task_completion_item = closure_completion_item.clone();
15396        async move {
15397            Ok(Some(lsp::CompletionResponse::Array(vec![
15398                task_completion_item,
15399            ])))
15400        }
15401    });
15402
15403    request.next().await;
15404
15405    cx.condition(|editor, _| editor.context_menu_visible())
15406        .await;
15407    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15408        editor
15409            .confirm_completion(&ConfirmCompletion::default(), window, cx)
15410            .unwrap()
15411    });
15412    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
15413
15414    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
15415        let task_completion_item = completion_item.clone();
15416        async move { Ok(task_completion_item) }
15417    })
15418    .next()
15419    .await
15420    .unwrap();
15421    apply_additional_edits.await.unwrap();
15422    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
15423}
15424
15425#[gpui::test]
15426async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
15427    init_test(cx, |_| {});
15428
15429    let mut cx = EditorLspTestContext::new_rust(
15430        lsp::ServerCapabilities {
15431            completion_provider: Some(lsp::CompletionOptions {
15432                trigger_characters: Some(vec![".".to_string()]),
15433                resolve_provider: Some(true),
15434                ..Default::default()
15435            }),
15436            ..Default::default()
15437        },
15438        cx,
15439    )
15440    .await;
15441
15442    cx.set_state("fn main() { let a = 2ˇ; }");
15443    cx.simulate_keystroke(".");
15444
15445    let item1 = lsp::CompletionItem {
15446        label: "method id()".to_string(),
15447        filter_text: Some("id".to_string()),
15448        detail: None,
15449        documentation: None,
15450        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15451            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15452            new_text: ".id".to_string(),
15453        })),
15454        ..lsp::CompletionItem::default()
15455    };
15456
15457    let item2 = lsp::CompletionItem {
15458        label: "other".to_string(),
15459        filter_text: Some("other".to_string()),
15460        detail: None,
15461        documentation: None,
15462        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15463            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15464            new_text: ".other".to_string(),
15465        })),
15466        ..lsp::CompletionItem::default()
15467    };
15468
15469    let item1 = item1.clone();
15470    cx.set_request_handler::<lsp::request::Completion, _, _>({
15471        let item1 = item1.clone();
15472        move |_, _, _| {
15473            let item1 = item1.clone();
15474            let item2 = item2.clone();
15475            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
15476        }
15477    })
15478    .next()
15479    .await;
15480
15481    cx.condition(|editor, _| editor.context_menu_visible())
15482        .await;
15483    cx.update_editor(|editor, _, _| {
15484        let context_menu = editor.context_menu.borrow_mut();
15485        let context_menu = context_menu
15486            .as_ref()
15487            .expect("Should have the context menu deployed");
15488        match context_menu {
15489            CodeContextMenu::Completions(completions_menu) => {
15490                let completions = completions_menu.completions.borrow_mut();
15491                assert_eq!(
15492                    completions
15493                        .iter()
15494                        .map(|completion| &completion.label.text)
15495                        .collect::<Vec<_>>(),
15496                    vec!["method id()", "other"]
15497                )
15498            }
15499            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15500        }
15501    });
15502
15503    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
15504        let item1 = item1.clone();
15505        move |_, item_to_resolve, _| {
15506            let item1 = item1.clone();
15507            async move {
15508                if item1 == item_to_resolve {
15509                    Ok(lsp::CompletionItem {
15510                        label: "method id()".to_string(),
15511                        filter_text: Some("id".to_string()),
15512                        detail: Some("Now resolved!".to_string()),
15513                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
15514                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15515                            range: lsp::Range::new(
15516                                lsp::Position::new(0, 22),
15517                                lsp::Position::new(0, 22),
15518                            ),
15519                            new_text: ".id".to_string(),
15520                        })),
15521                        ..lsp::CompletionItem::default()
15522                    })
15523                } else {
15524                    Ok(item_to_resolve)
15525                }
15526            }
15527        }
15528    })
15529    .next()
15530    .await
15531    .unwrap();
15532    cx.run_until_parked();
15533
15534    cx.update_editor(|editor, window, cx| {
15535        editor.context_menu_next(&Default::default(), window, cx);
15536    });
15537
15538    cx.update_editor(|editor, _, _| {
15539        let context_menu = editor.context_menu.borrow_mut();
15540        let context_menu = context_menu
15541            .as_ref()
15542            .expect("Should have the context menu deployed");
15543        match context_menu {
15544            CodeContextMenu::Completions(completions_menu) => {
15545                let completions = completions_menu.completions.borrow_mut();
15546                assert_eq!(
15547                    completions
15548                        .iter()
15549                        .map(|completion| &completion.label.text)
15550                        .collect::<Vec<_>>(),
15551                    vec!["method id() Now resolved!", "other"],
15552                    "Should update first completion label, but not second as the filter text did not match."
15553                );
15554            }
15555            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15556        }
15557    });
15558}
15559
15560#[gpui::test]
15561async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
15562    init_test(cx, |_| {});
15563    let mut cx = EditorLspTestContext::new_rust(
15564        lsp::ServerCapabilities {
15565            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
15566            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
15567            completion_provider: Some(lsp::CompletionOptions {
15568                resolve_provider: Some(true),
15569                ..Default::default()
15570            }),
15571            ..Default::default()
15572        },
15573        cx,
15574    )
15575    .await;
15576    cx.set_state(indoc! {"
15577        struct TestStruct {
15578            field: i32
15579        }
15580
15581        fn mainˇ() {
15582            let unused_var = 42;
15583            let test_struct = TestStruct { field: 42 };
15584        }
15585    "});
15586    let symbol_range = cx.lsp_range(indoc! {"
15587        struct TestStruct {
15588            field: i32
15589        }
15590
15591        «fn main»() {
15592            let unused_var = 42;
15593            let test_struct = TestStruct { field: 42 };
15594        }
15595    "});
15596    let mut hover_requests =
15597        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
15598            Ok(Some(lsp::Hover {
15599                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
15600                    kind: lsp::MarkupKind::Markdown,
15601                    value: "Function documentation".to_string(),
15602                }),
15603                range: Some(symbol_range),
15604            }))
15605        });
15606
15607    // Case 1: Test that code action menu hide hover popover
15608    cx.dispatch_action(Hover);
15609    hover_requests.next().await;
15610    cx.condition(|editor, _| editor.hover_state.visible()).await;
15611    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
15612        move |_, _, _| async move {
15613            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
15614                lsp::CodeAction {
15615                    title: "Remove unused variable".to_string(),
15616                    kind: Some(CodeActionKind::QUICKFIX),
15617                    edit: Some(lsp::WorkspaceEdit {
15618                        changes: Some(
15619                            [(
15620                                lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
15621                                vec![lsp::TextEdit {
15622                                    range: lsp::Range::new(
15623                                        lsp::Position::new(5, 4),
15624                                        lsp::Position::new(5, 27),
15625                                    ),
15626                                    new_text: "".to_string(),
15627                                }],
15628                            )]
15629                            .into_iter()
15630                            .collect(),
15631                        ),
15632                        ..Default::default()
15633                    }),
15634                    ..Default::default()
15635                },
15636            )]))
15637        },
15638    );
15639    cx.update_editor(|editor, window, cx| {
15640        editor.toggle_code_actions(
15641            &ToggleCodeActions {
15642                deployed_from: None,
15643                quick_launch: false,
15644            },
15645            window,
15646            cx,
15647        );
15648    });
15649    code_action_requests.next().await;
15650    cx.run_until_parked();
15651    cx.condition(|editor, _| editor.context_menu_visible())
15652        .await;
15653    cx.update_editor(|editor, _, _| {
15654        assert!(
15655            !editor.hover_state.visible(),
15656            "Hover popover should be hidden when code action menu is shown"
15657        );
15658        // Hide code actions
15659        editor.context_menu.take();
15660    });
15661
15662    // Case 2: Test that code completions hide hover popover
15663    cx.dispatch_action(Hover);
15664    hover_requests.next().await;
15665    cx.condition(|editor, _| editor.hover_state.visible()).await;
15666    let counter = Arc::new(AtomicUsize::new(0));
15667    let mut completion_requests =
15668        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15669            let counter = counter.clone();
15670            async move {
15671                counter.fetch_add(1, atomic::Ordering::Release);
15672                Ok(Some(lsp::CompletionResponse::Array(vec![
15673                    lsp::CompletionItem {
15674                        label: "main".into(),
15675                        kind: Some(lsp::CompletionItemKind::FUNCTION),
15676                        detail: Some("() -> ()".to_string()),
15677                        ..Default::default()
15678                    },
15679                    lsp::CompletionItem {
15680                        label: "TestStruct".into(),
15681                        kind: Some(lsp::CompletionItemKind::STRUCT),
15682                        detail: Some("struct TestStruct".to_string()),
15683                        ..Default::default()
15684                    },
15685                ])))
15686            }
15687        });
15688    cx.update_editor(|editor, window, cx| {
15689        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15690    });
15691    completion_requests.next().await;
15692    cx.condition(|editor, _| editor.context_menu_visible())
15693        .await;
15694    cx.update_editor(|editor, _, _| {
15695        assert!(
15696            !editor.hover_state.visible(),
15697            "Hover popover should be hidden when completion menu is shown"
15698        );
15699    });
15700}
15701
15702#[gpui::test]
15703async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
15704    init_test(cx, |_| {});
15705
15706    let mut cx = EditorLspTestContext::new_rust(
15707        lsp::ServerCapabilities {
15708            completion_provider: Some(lsp::CompletionOptions {
15709                trigger_characters: Some(vec![".".to_string()]),
15710                resolve_provider: Some(true),
15711                ..Default::default()
15712            }),
15713            ..Default::default()
15714        },
15715        cx,
15716    )
15717    .await;
15718
15719    cx.set_state("fn main() { let a = 2ˇ; }");
15720    cx.simulate_keystroke(".");
15721
15722    let unresolved_item_1 = lsp::CompletionItem {
15723        label: "id".to_string(),
15724        filter_text: Some("id".to_string()),
15725        detail: None,
15726        documentation: None,
15727        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15728            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15729            new_text: ".id".to_string(),
15730        })),
15731        ..lsp::CompletionItem::default()
15732    };
15733    let resolved_item_1 = lsp::CompletionItem {
15734        additional_text_edits: Some(vec![lsp::TextEdit {
15735            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
15736            new_text: "!!".to_string(),
15737        }]),
15738        ..unresolved_item_1.clone()
15739    };
15740    let unresolved_item_2 = lsp::CompletionItem {
15741        label: "other".to_string(),
15742        filter_text: Some("other".to_string()),
15743        detail: None,
15744        documentation: None,
15745        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15746            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15747            new_text: ".other".to_string(),
15748        })),
15749        ..lsp::CompletionItem::default()
15750    };
15751    let resolved_item_2 = lsp::CompletionItem {
15752        additional_text_edits: Some(vec![lsp::TextEdit {
15753            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
15754            new_text: "??".to_string(),
15755        }]),
15756        ..unresolved_item_2.clone()
15757    };
15758
15759    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
15760    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
15761    cx.lsp
15762        .server
15763        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
15764            let unresolved_item_1 = unresolved_item_1.clone();
15765            let resolved_item_1 = resolved_item_1.clone();
15766            let unresolved_item_2 = unresolved_item_2.clone();
15767            let resolved_item_2 = resolved_item_2.clone();
15768            let resolve_requests_1 = resolve_requests_1.clone();
15769            let resolve_requests_2 = resolve_requests_2.clone();
15770            move |unresolved_request, _| {
15771                let unresolved_item_1 = unresolved_item_1.clone();
15772                let resolved_item_1 = resolved_item_1.clone();
15773                let unresolved_item_2 = unresolved_item_2.clone();
15774                let resolved_item_2 = resolved_item_2.clone();
15775                let resolve_requests_1 = resolve_requests_1.clone();
15776                let resolve_requests_2 = resolve_requests_2.clone();
15777                async move {
15778                    if unresolved_request == unresolved_item_1 {
15779                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
15780                        Ok(resolved_item_1.clone())
15781                    } else if unresolved_request == unresolved_item_2 {
15782                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
15783                        Ok(resolved_item_2.clone())
15784                    } else {
15785                        panic!("Unexpected completion item {unresolved_request:?}")
15786                    }
15787                }
15788            }
15789        })
15790        .detach();
15791
15792    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15793        let unresolved_item_1 = unresolved_item_1.clone();
15794        let unresolved_item_2 = unresolved_item_2.clone();
15795        async move {
15796            Ok(Some(lsp::CompletionResponse::Array(vec![
15797                unresolved_item_1,
15798                unresolved_item_2,
15799            ])))
15800        }
15801    })
15802    .next()
15803    .await;
15804
15805    cx.condition(|editor, _| editor.context_menu_visible())
15806        .await;
15807    cx.update_editor(|editor, _, _| {
15808        let context_menu = editor.context_menu.borrow_mut();
15809        let context_menu = context_menu
15810            .as_ref()
15811            .expect("Should have the context menu deployed");
15812        match context_menu {
15813            CodeContextMenu::Completions(completions_menu) => {
15814                let completions = completions_menu.completions.borrow_mut();
15815                assert_eq!(
15816                    completions
15817                        .iter()
15818                        .map(|completion| &completion.label.text)
15819                        .collect::<Vec<_>>(),
15820                    vec!["id", "other"]
15821                )
15822            }
15823            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15824        }
15825    });
15826    cx.run_until_parked();
15827
15828    cx.update_editor(|editor, window, cx| {
15829        editor.context_menu_next(&ContextMenuNext, window, cx);
15830    });
15831    cx.run_until_parked();
15832    cx.update_editor(|editor, window, cx| {
15833        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
15834    });
15835    cx.run_until_parked();
15836    cx.update_editor(|editor, window, cx| {
15837        editor.context_menu_next(&ContextMenuNext, window, cx);
15838    });
15839    cx.run_until_parked();
15840    cx.update_editor(|editor, window, cx| {
15841        editor
15842            .compose_completion(&ComposeCompletion::default(), window, cx)
15843            .expect("No task returned")
15844    })
15845    .await
15846    .expect("Completion failed");
15847    cx.run_until_parked();
15848
15849    cx.update_editor(|editor, _, cx| {
15850        assert_eq!(
15851            resolve_requests_1.load(atomic::Ordering::Acquire),
15852            1,
15853            "Should always resolve once despite multiple selections"
15854        );
15855        assert_eq!(
15856            resolve_requests_2.load(atomic::Ordering::Acquire),
15857            1,
15858            "Should always resolve once after multiple selections and applying the completion"
15859        );
15860        assert_eq!(
15861            editor.text(cx),
15862            "fn main() { let a = ??.other; }",
15863            "Should use resolved data when applying the completion"
15864        );
15865    });
15866}
15867
15868#[gpui::test]
15869async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
15870    init_test(cx, |_| {});
15871
15872    let item_0 = lsp::CompletionItem {
15873        label: "abs".into(),
15874        insert_text: Some("abs".into()),
15875        data: Some(json!({ "very": "special"})),
15876        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
15877        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
15878            lsp::InsertReplaceEdit {
15879                new_text: "abs".to_string(),
15880                insert: lsp::Range::default(),
15881                replace: lsp::Range::default(),
15882            },
15883        )),
15884        ..lsp::CompletionItem::default()
15885    };
15886    let items = iter::once(item_0.clone())
15887        .chain((11..51).map(|i| lsp::CompletionItem {
15888            label: format!("item_{}", i),
15889            insert_text: Some(format!("item_{}", i)),
15890            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15891            ..lsp::CompletionItem::default()
15892        }))
15893        .collect::<Vec<_>>();
15894
15895    let default_commit_characters = vec!["?".to_string()];
15896    let default_data = json!({ "default": "data"});
15897    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
15898    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
15899    let default_edit_range = lsp::Range {
15900        start: lsp::Position {
15901            line: 0,
15902            character: 5,
15903        },
15904        end: lsp::Position {
15905            line: 0,
15906            character: 5,
15907        },
15908    };
15909
15910    let mut cx = EditorLspTestContext::new_rust(
15911        lsp::ServerCapabilities {
15912            completion_provider: Some(lsp::CompletionOptions {
15913                trigger_characters: Some(vec![".".to_string()]),
15914                resolve_provider: Some(true),
15915                ..Default::default()
15916            }),
15917            ..Default::default()
15918        },
15919        cx,
15920    )
15921    .await;
15922
15923    cx.set_state("fn main() { let a = 2ˇ; }");
15924    cx.simulate_keystroke(".");
15925
15926    let completion_data = default_data.clone();
15927    let completion_characters = default_commit_characters.clone();
15928    let completion_items = items.clone();
15929    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15930        let default_data = completion_data.clone();
15931        let default_commit_characters = completion_characters.clone();
15932        let items = completion_items.clone();
15933        async move {
15934            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15935                items,
15936                item_defaults: Some(lsp::CompletionListItemDefaults {
15937                    data: Some(default_data.clone()),
15938                    commit_characters: Some(default_commit_characters.clone()),
15939                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
15940                        default_edit_range,
15941                    )),
15942                    insert_text_format: Some(default_insert_text_format),
15943                    insert_text_mode: Some(default_insert_text_mode),
15944                }),
15945                ..lsp::CompletionList::default()
15946            })))
15947        }
15948    })
15949    .next()
15950    .await;
15951
15952    let resolved_items = Arc::new(Mutex::new(Vec::new()));
15953    cx.lsp
15954        .server
15955        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
15956            let closure_resolved_items = resolved_items.clone();
15957            move |item_to_resolve, _| {
15958                let closure_resolved_items = closure_resolved_items.clone();
15959                async move {
15960                    closure_resolved_items.lock().push(item_to_resolve.clone());
15961                    Ok(item_to_resolve)
15962                }
15963            }
15964        })
15965        .detach();
15966
15967    cx.condition(|editor, _| editor.context_menu_visible())
15968        .await;
15969    cx.run_until_parked();
15970    cx.update_editor(|editor, _, _| {
15971        let menu = editor.context_menu.borrow_mut();
15972        match menu.as_ref().expect("should have the completions menu") {
15973            CodeContextMenu::Completions(completions_menu) => {
15974                assert_eq!(
15975                    completions_menu
15976                        .entries
15977                        .borrow()
15978                        .iter()
15979                        .map(|mat| mat.string.clone())
15980                        .collect::<Vec<String>>(),
15981                    items
15982                        .iter()
15983                        .map(|completion| completion.label.clone())
15984                        .collect::<Vec<String>>()
15985                );
15986            }
15987            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
15988        }
15989    });
15990    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
15991    // with 4 from the end.
15992    assert_eq!(
15993        *resolved_items.lock(),
15994        [&items[0..16], &items[items.len() - 4..items.len()]]
15995            .concat()
15996            .iter()
15997            .cloned()
15998            .map(|mut item| {
15999                if item.data.is_none() {
16000                    item.data = Some(default_data.clone());
16001                }
16002                item
16003            })
16004            .collect::<Vec<lsp::CompletionItem>>(),
16005        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
16006    );
16007    resolved_items.lock().clear();
16008
16009    cx.update_editor(|editor, window, cx| {
16010        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
16011    });
16012    cx.run_until_parked();
16013    // Completions that have already been resolved are skipped.
16014    assert_eq!(
16015        *resolved_items.lock(),
16016        items[items.len() - 17..items.len() - 4]
16017            .iter()
16018            .cloned()
16019            .map(|mut item| {
16020                if item.data.is_none() {
16021                    item.data = Some(default_data.clone());
16022                }
16023                item
16024            })
16025            .collect::<Vec<lsp::CompletionItem>>()
16026    );
16027    resolved_items.lock().clear();
16028}
16029
16030#[gpui::test]
16031async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
16032    init_test(cx, |_| {});
16033
16034    let mut cx = EditorLspTestContext::new(
16035        Language::new(
16036            LanguageConfig {
16037                matcher: LanguageMatcher {
16038                    path_suffixes: vec!["jsx".into()],
16039                    ..Default::default()
16040                },
16041                overrides: [(
16042                    "element".into(),
16043                    LanguageConfigOverride {
16044                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
16045                        ..Default::default()
16046                    },
16047                )]
16048                .into_iter()
16049                .collect(),
16050                ..Default::default()
16051            },
16052            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
16053        )
16054        .with_override_query("(jsx_self_closing_element) @element")
16055        .unwrap(),
16056        lsp::ServerCapabilities {
16057            completion_provider: Some(lsp::CompletionOptions {
16058                trigger_characters: Some(vec![":".to_string()]),
16059                ..Default::default()
16060            }),
16061            ..Default::default()
16062        },
16063        cx,
16064    )
16065    .await;
16066
16067    cx.lsp
16068        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16069            Ok(Some(lsp::CompletionResponse::Array(vec![
16070                lsp::CompletionItem {
16071                    label: "bg-blue".into(),
16072                    ..Default::default()
16073                },
16074                lsp::CompletionItem {
16075                    label: "bg-red".into(),
16076                    ..Default::default()
16077                },
16078                lsp::CompletionItem {
16079                    label: "bg-yellow".into(),
16080                    ..Default::default()
16081                },
16082            ])))
16083        });
16084
16085    cx.set_state(r#"<p class="bgˇ" />"#);
16086
16087    // Trigger completion when typing a dash, because the dash is an extra
16088    // word character in the 'element' scope, which contains the cursor.
16089    cx.simulate_keystroke("-");
16090    cx.executor().run_until_parked();
16091    cx.update_editor(|editor, _, _| {
16092        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16093        {
16094            assert_eq!(
16095                completion_menu_entries(&menu),
16096                &["bg-blue", "bg-red", "bg-yellow"]
16097            );
16098        } else {
16099            panic!("expected completion menu to be open");
16100        }
16101    });
16102
16103    cx.simulate_keystroke("l");
16104    cx.executor().run_until_parked();
16105    cx.update_editor(|editor, _, _| {
16106        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16107        {
16108            assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
16109        } else {
16110            panic!("expected completion menu to be open");
16111        }
16112    });
16113
16114    // When filtering completions, consider the character after the '-' to
16115    // be the start of a subword.
16116    cx.set_state(r#"<p class="yelˇ" />"#);
16117    cx.simulate_keystroke("l");
16118    cx.executor().run_until_parked();
16119    cx.update_editor(|editor, _, _| {
16120        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16121        {
16122            assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
16123        } else {
16124            panic!("expected completion menu to be open");
16125        }
16126    });
16127}
16128
16129fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
16130    let entries = menu.entries.borrow();
16131    entries.iter().map(|mat| mat.string.clone()).collect()
16132}
16133
16134#[gpui::test]
16135async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
16136    init_test(cx, |settings| {
16137        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
16138            Formatter::Prettier,
16139        )))
16140    });
16141
16142    let fs = FakeFs::new(cx.executor());
16143    fs.insert_file(path!("/file.ts"), Default::default()).await;
16144
16145    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
16146    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16147
16148    language_registry.add(Arc::new(Language::new(
16149        LanguageConfig {
16150            name: "TypeScript".into(),
16151            matcher: LanguageMatcher {
16152                path_suffixes: vec!["ts".to_string()],
16153                ..Default::default()
16154            },
16155            ..Default::default()
16156        },
16157        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
16158    )));
16159    update_test_language_settings(cx, |settings| {
16160        settings.defaults.prettier = Some(PrettierSettings {
16161            allowed: true,
16162            ..PrettierSettings::default()
16163        });
16164    });
16165
16166    let test_plugin = "test_plugin";
16167    let _ = language_registry.register_fake_lsp(
16168        "TypeScript",
16169        FakeLspAdapter {
16170            prettier_plugins: vec![test_plugin],
16171            ..Default::default()
16172        },
16173    );
16174
16175    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
16176    let buffer = project
16177        .update(cx, |project, cx| {
16178            project.open_local_buffer(path!("/file.ts"), cx)
16179        })
16180        .await
16181        .unwrap();
16182
16183    let buffer_text = "one\ntwo\nthree\n";
16184    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16185    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16186    editor.update_in(cx, |editor, window, cx| {
16187        editor.set_text(buffer_text, window, cx)
16188    });
16189
16190    editor
16191        .update_in(cx, |editor, window, cx| {
16192            editor.perform_format(
16193                project.clone(),
16194                FormatTrigger::Manual,
16195                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
16196                window,
16197                cx,
16198            )
16199        })
16200        .unwrap()
16201        .await;
16202    assert_eq!(
16203        editor.update(cx, |editor, cx| editor.text(cx)),
16204        buffer_text.to_string() + prettier_format_suffix,
16205        "Test prettier formatting was not applied to the original buffer text",
16206    );
16207
16208    update_test_language_settings(cx, |settings| {
16209        settings.defaults.formatter = Some(SelectedFormatter::Auto)
16210    });
16211    let format = editor.update_in(cx, |editor, window, cx| {
16212        editor.perform_format(
16213            project.clone(),
16214            FormatTrigger::Manual,
16215            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
16216            window,
16217            cx,
16218        )
16219    });
16220    format.await.unwrap();
16221    assert_eq!(
16222        editor.update(cx, |editor, cx| editor.text(cx)),
16223        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
16224        "Autoformatting (via test prettier) was not applied to the original buffer text",
16225    );
16226}
16227
16228#[gpui::test]
16229async fn test_addition_reverts(cx: &mut TestAppContext) {
16230    init_test(cx, |_| {});
16231    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16232    let base_text = indoc! {r#"
16233        struct Row;
16234        struct Row1;
16235        struct Row2;
16236
16237        struct Row4;
16238        struct Row5;
16239        struct Row6;
16240
16241        struct Row8;
16242        struct Row9;
16243        struct Row10;"#};
16244
16245    // When addition hunks are not adjacent to carets, no hunk revert is performed
16246    assert_hunk_revert(
16247        indoc! {r#"struct Row;
16248                   struct Row1;
16249                   struct Row1.1;
16250                   struct Row1.2;
16251                   struct Row2;ˇ
16252
16253                   struct Row4;
16254                   struct Row5;
16255                   struct Row6;
16256
16257                   struct Row8;
16258                   ˇstruct Row9;
16259                   struct Row9.1;
16260                   struct Row9.2;
16261                   struct Row9.3;
16262                   struct Row10;"#},
16263        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
16264        indoc! {r#"struct Row;
16265                   struct Row1;
16266                   struct Row1.1;
16267                   struct Row1.2;
16268                   struct Row2;ˇ
16269
16270                   struct Row4;
16271                   struct Row5;
16272                   struct Row6;
16273
16274                   struct Row8;
16275                   ˇstruct Row9;
16276                   struct Row9.1;
16277                   struct Row9.2;
16278                   struct Row9.3;
16279                   struct Row10;"#},
16280        base_text,
16281        &mut cx,
16282    );
16283    // Same for selections
16284    assert_hunk_revert(
16285        indoc! {r#"struct Row;
16286                   struct Row1;
16287                   struct Row2;
16288                   struct Row2.1;
16289                   struct Row2.2;
16290                   «ˇ
16291                   struct Row4;
16292                   struct» Row5;
16293                   «struct Row6;
16294                   ˇ»
16295                   struct Row9.1;
16296                   struct Row9.2;
16297                   struct Row9.3;
16298                   struct Row8;
16299                   struct Row9;
16300                   struct Row10;"#},
16301        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
16302        indoc! {r#"struct Row;
16303                   struct Row1;
16304                   struct Row2;
16305                   struct Row2.1;
16306                   struct Row2.2;
16307                   «ˇ
16308                   struct Row4;
16309                   struct» Row5;
16310                   «struct Row6;
16311                   ˇ»
16312                   struct Row9.1;
16313                   struct Row9.2;
16314                   struct Row9.3;
16315                   struct Row8;
16316                   struct Row9;
16317                   struct Row10;"#},
16318        base_text,
16319        &mut cx,
16320    );
16321
16322    // When carets and selections intersect the addition hunks, those are reverted.
16323    // Adjacent carets got merged.
16324    assert_hunk_revert(
16325        indoc! {r#"struct Row;
16326                   ˇ// something on the top
16327                   struct Row1;
16328                   struct Row2;
16329                   struct Roˇw3.1;
16330                   struct Row2.2;
16331                   struct Row2.3;ˇ
16332
16333                   struct Row4;
16334                   struct ˇRow5.1;
16335                   struct Row5.2;
16336                   struct «Rowˇ»5.3;
16337                   struct Row5;
16338                   struct Row6;
16339                   ˇ
16340                   struct Row9.1;
16341                   struct «Rowˇ»9.2;
16342                   struct «ˇRow»9.3;
16343                   struct Row8;
16344                   struct Row9;
16345                   «ˇ// something on bottom»
16346                   struct Row10;"#},
16347        vec![
16348            DiffHunkStatusKind::Added,
16349            DiffHunkStatusKind::Added,
16350            DiffHunkStatusKind::Added,
16351            DiffHunkStatusKind::Added,
16352            DiffHunkStatusKind::Added,
16353        ],
16354        indoc! {r#"struct Row;
16355                   ˇstruct Row1;
16356                   struct Row2;
16357                   ˇ
16358                   struct Row4;
16359                   ˇstruct Row5;
16360                   struct Row6;
16361                   ˇ
16362                   ˇstruct Row8;
16363                   struct Row9;
16364                   ˇstruct Row10;"#},
16365        base_text,
16366        &mut cx,
16367    );
16368}
16369
16370#[gpui::test]
16371async fn test_modification_reverts(cx: &mut TestAppContext) {
16372    init_test(cx, |_| {});
16373    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16374    let base_text = indoc! {r#"
16375        struct Row;
16376        struct Row1;
16377        struct Row2;
16378
16379        struct Row4;
16380        struct Row5;
16381        struct Row6;
16382
16383        struct Row8;
16384        struct Row9;
16385        struct Row10;"#};
16386
16387    // Modification hunks behave the same as the addition ones.
16388    assert_hunk_revert(
16389        indoc! {r#"struct Row;
16390                   struct Row1;
16391                   struct Row33;
16392                   ˇ
16393                   struct Row4;
16394                   struct Row5;
16395                   struct Row6;
16396                   ˇ
16397                   struct Row99;
16398                   struct Row9;
16399                   struct Row10;"#},
16400        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
16401        indoc! {r#"struct Row;
16402                   struct Row1;
16403                   struct Row33;
16404                   ˇ
16405                   struct Row4;
16406                   struct Row5;
16407                   struct Row6;
16408                   ˇ
16409                   struct Row99;
16410                   struct Row9;
16411                   struct Row10;"#},
16412        base_text,
16413        &mut cx,
16414    );
16415    assert_hunk_revert(
16416        indoc! {r#"struct Row;
16417                   struct Row1;
16418                   struct Row33;
16419                   «ˇ
16420                   struct Row4;
16421                   struct» Row5;
16422                   «struct Row6;
16423                   ˇ»
16424                   struct Row99;
16425                   struct Row9;
16426                   struct Row10;"#},
16427        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
16428        indoc! {r#"struct Row;
16429                   struct Row1;
16430                   struct Row33;
16431                   «ˇ
16432                   struct Row4;
16433                   struct» Row5;
16434                   «struct Row6;
16435                   ˇ»
16436                   struct Row99;
16437                   struct Row9;
16438                   struct Row10;"#},
16439        base_text,
16440        &mut cx,
16441    );
16442
16443    assert_hunk_revert(
16444        indoc! {r#"ˇstruct Row1.1;
16445                   struct Row1;
16446                   «ˇstr»uct Row22;
16447
16448                   struct ˇRow44;
16449                   struct Row5;
16450                   struct «Rˇ»ow66;ˇ
16451
16452                   «struˇ»ct Row88;
16453                   struct Row9;
16454                   struct Row1011;ˇ"#},
16455        vec![
16456            DiffHunkStatusKind::Modified,
16457            DiffHunkStatusKind::Modified,
16458            DiffHunkStatusKind::Modified,
16459            DiffHunkStatusKind::Modified,
16460            DiffHunkStatusKind::Modified,
16461            DiffHunkStatusKind::Modified,
16462        ],
16463        indoc! {r#"struct Row;
16464                   ˇstruct Row1;
16465                   struct Row2;
16466                   ˇ
16467                   struct Row4;
16468                   ˇstruct Row5;
16469                   struct Row6;
16470                   ˇ
16471                   struct Row8;
16472                   ˇstruct Row9;
16473                   struct Row10;ˇ"#},
16474        base_text,
16475        &mut cx,
16476    );
16477}
16478
16479#[gpui::test]
16480async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
16481    init_test(cx, |_| {});
16482    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16483    let base_text = indoc! {r#"
16484        one
16485
16486        two
16487        three
16488        "#};
16489
16490    cx.set_head_text(base_text);
16491    cx.set_state("\nˇ\n");
16492    cx.executor().run_until_parked();
16493    cx.update_editor(|editor, _window, cx| {
16494        editor.expand_selected_diff_hunks(cx);
16495    });
16496    cx.executor().run_until_parked();
16497    cx.update_editor(|editor, window, cx| {
16498        editor.backspace(&Default::default(), window, cx);
16499    });
16500    cx.run_until_parked();
16501    cx.assert_state_with_diff(
16502        indoc! {r#"
16503
16504        - two
16505        - threeˇ
16506        +
16507        "#}
16508        .to_string(),
16509    );
16510}
16511
16512#[gpui::test]
16513async fn test_deletion_reverts(cx: &mut TestAppContext) {
16514    init_test(cx, |_| {});
16515    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16516    let base_text = indoc! {r#"struct Row;
16517struct Row1;
16518struct Row2;
16519
16520struct Row4;
16521struct Row5;
16522struct Row6;
16523
16524struct Row8;
16525struct Row9;
16526struct Row10;"#};
16527
16528    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
16529    assert_hunk_revert(
16530        indoc! {r#"struct Row;
16531                   struct Row2;
16532
16533                   ˇstruct Row4;
16534                   struct Row5;
16535                   struct Row6;
16536                   ˇ
16537                   struct Row8;
16538                   struct Row10;"#},
16539        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16540        indoc! {r#"struct Row;
16541                   struct Row2;
16542
16543                   ˇstruct Row4;
16544                   struct Row5;
16545                   struct Row6;
16546                   ˇ
16547                   struct Row8;
16548                   struct Row10;"#},
16549        base_text,
16550        &mut cx,
16551    );
16552    assert_hunk_revert(
16553        indoc! {r#"struct Row;
16554                   struct Row2;
16555
16556                   «ˇstruct Row4;
16557                   struct» Row5;
16558                   «struct Row6;
16559                   ˇ»
16560                   struct Row8;
16561                   struct Row10;"#},
16562        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16563        indoc! {r#"struct Row;
16564                   struct Row2;
16565
16566                   «ˇstruct Row4;
16567                   struct» Row5;
16568                   «struct Row6;
16569                   ˇ»
16570                   struct Row8;
16571                   struct Row10;"#},
16572        base_text,
16573        &mut cx,
16574    );
16575
16576    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
16577    assert_hunk_revert(
16578        indoc! {r#"struct Row;
16579                   ˇstruct Row2;
16580
16581                   struct Row4;
16582                   struct Row5;
16583                   struct Row6;
16584
16585                   struct Row8;ˇ
16586                   struct Row10;"#},
16587        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16588        indoc! {r#"struct Row;
16589                   struct Row1;
16590                   ˇstruct Row2;
16591
16592                   struct Row4;
16593                   struct Row5;
16594                   struct Row6;
16595
16596                   struct Row8;ˇ
16597                   struct Row9;
16598                   struct Row10;"#},
16599        base_text,
16600        &mut cx,
16601    );
16602    assert_hunk_revert(
16603        indoc! {r#"struct Row;
16604                   struct Row2«ˇ;
16605                   struct Row4;
16606                   struct» Row5;
16607                   «struct Row6;
16608
16609                   struct Row8;ˇ»
16610                   struct Row10;"#},
16611        vec![
16612            DiffHunkStatusKind::Deleted,
16613            DiffHunkStatusKind::Deleted,
16614            DiffHunkStatusKind::Deleted,
16615        ],
16616        indoc! {r#"struct Row;
16617                   struct Row1;
16618                   struct Row2«ˇ;
16619
16620                   struct Row4;
16621                   struct» Row5;
16622                   «struct Row6;
16623
16624                   struct Row8;ˇ»
16625                   struct Row9;
16626                   struct Row10;"#},
16627        base_text,
16628        &mut cx,
16629    );
16630}
16631
16632#[gpui::test]
16633async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
16634    init_test(cx, |_| {});
16635
16636    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
16637    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
16638    let base_text_3 =
16639        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
16640
16641    let text_1 = edit_first_char_of_every_line(base_text_1);
16642    let text_2 = edit_first_char_of_every_line(base_text_2);
16643    let text_3 = edit_first_char_of_every_line(base_text_3);
16644
16645    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
16646    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
16647    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
16648
16649    let multibuffer = cx.new(|cx| {
16650        let mut multibuffer = MultiBuffer::new(ReadWrite);
16651        multibuffer.push_excerpts(
16652            buffer_1.clone(),
16653            [
16654                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16655                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16656                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16657            ],
16658            cx,
16659        );
16660        multibuffer.push_excerpts(
16661            buffer_2.clone(),
16662            [
16663                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16664                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16665                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16666            ],
16667            cx,
16668        );
16669        multibuffer.push_excerpts(
16670            buffer_3.clone(),
16671            [
16672                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16673                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16674                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16675            ],
16676            cx,
16677        );
16678        multibuffer
16679    });
16680
16681    let fs = FakeFs::new(cx.executor());
16682    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
16683    let (editor, cx) = cx
16684        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
16685    editor.update_in(cx, |editor, _window, cx| {
16686        for (buffer, diff_base) in [
16687            (buffer_1.clone(), base_text_1),
16688            (buffer_2.clone(), base_text_2),
16689            (buffer_3.clone(), base_text_3),
16690        ] {
16691            let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
16692            editor
16693                .buffer
16694                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
16695        }
16696    });
16697    cx.executor().run_until_parked();
16698
16699    editor.update_in(cx, |editor, window, cx| {
16700        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}");
16701        editor.select_all(&SelectAll, window, cx);
16702        editor.git_restore(&Default::default(), window, cx);
16703    });
16704    cx.executor().run_until_parked();
16705
16706    // When all ranges are selected, all buffer hunks are reverted.
16707    editor.update(cx, |editor, cx| {
16708        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");
16709    });
16710    buffer_1.update(cx, |buffer, _| {
16711        assert_eq!(buffer.text(), base_text_1);
16712    });
16713    buffer_2.update(cx, |buffer, _| {
16714        assert_eq!(buffer.text(), base_text_2);
16715    });
16716    buffer_3.update(cx, |buffer, _| {
16717        assert_eq!(buffer.text(), base_text_3);
16718    });
16719
16720    editor.update_in(cx, |editor, window, cx| {
16721        editor.undo(&Default::default(), window, cx);
16722    });
16723
16724    editor.update_in(cx, |editor, window, cx| {
16725        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16726            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
16727        });
16728        editor.git_restore(&Default::default(), window, cx);
16729    });
16730
16731    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
16732    // but not affect buffer_2 and its related excerpts.
16733    editor.update(cx, |editor, cx| {
16734        assert_eq!(
16735            editor.text(cx),
16736            "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}"
16737        );
16738    });
16739    buffer_1.update(cx, |buffer, _| {
16740        assert_eq!(buffer.text(), base_text_1);
16741    });
16742    buffer_2.update(cx, |buffer, _| {
16743        assert_eq!(
16744            buffer.text(),
16745            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
16746        );
16747    });
16748    buffer_3.update(cx, |buffer, _| {
16749        assert_eq!(
16750            buffer.text(),
16751            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
16752        );
16753    });
16754
16755    fn edit_first_char_of_every_line(text: &str) -> String {
16756        text.split('\n')
16757            .map(|line| format!("X{}", &line[1..]))
16758            .collect::<Vec<_>>()
16759            .join("\n")
16760    }
16761}
16762
16763#[gpui::test]
16764async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
16765    init_test(cx, |_| {});
16766
16767    let cols = 4;
16768    let rows = 10;
16769    let sample_text_1 = sample_text(rows, cols, 'a');
16770    assert_eq!(
16771        sample_text_1,
16772        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
16773    );
16774    let sample_text_2 = sample_text(rows, cols, 'l');
16775    assert_eq!(
16776        sample_text_2,
16777        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
16778    );
16779    let sample_text_3 = sample_text(rows, cols, 'v');
16780    assert_eq!(
16781        sample_text_3,
16782        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
16783    );
16784
16785    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
16786    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
16787    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
16788
16789    let multi_buffer = cx.new(|cx| {
16790        let mut multibuffer = MultiBuffer::new(ReadWrite);
16791        multibuffer.push_excerpts(
16792            buffer_1.clone(),
16793            [
16794                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16795                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16796                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16797            ],
16798            cx,
16799        );
16800        multibuffer.push_excerpts(
16801            buffer_2.clone(),
16802            [
16803                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16804                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16805                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16806            ],
16807            cx,
16808        );
16809        multibuffer.push_excerpts(
16810            buffer_3.clone(),
16811            [
16812                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16813                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16814                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16815            ],
16816            cx,
16817        );
16818        multibuffer
16819    });
16820
16821    let fs = FakeFs::new(cx.executor());
16822    fs.insert_tree(
16823        "/a",
16824        json!({
16825            "main.rs": sample_text_1,
16826            "other.rs": sample_text_2,
16827            "lib.rs": sample_text_3,
16828        }),
16829    )
16830    .await;
16831    let project = Project::test(fs, ["/a".as_ref()], cx).await;
16832    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16833    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16834    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16835        Editor::new(
16836            EditorMode::full(),
16837            multi_buffer,
16838            Some(project.clone()),
16839            window,
16840            cx,
16841        )
16842    });
16843    let multibuffer_item_id = workspace
16844        .update(cx, |workspace, window, cx| {
16845            assert!(
16846                workspace.active_item(cx).is_none(),
16847                "active item should be None before the first item is added"
16848            );
16849            workspace.add_item_to_active_pane(
16850                Box::new(multi_buffer_editor.clone()),
16851                None,
16852                true,
16853                window,
16854                cx,
16855            );
16856            let active_item = workspace
16857                .active_item(cx)
16858                .expect("should have an active item after adding the multi buffer");
16859            assert!(
16860                !active_item.is_singleton(cx),
16861                "A multi buffer was expected to active after adding"
16862            );
16863            active_item.item_id()
16864        })
16865        .unwrap();
16866    cx.executor().run_until_parked();
16867
16868    multi_buffer_editor.update_in(cx, |editor, window, cx| {
16869        editor.change_selections(
16870            SelectionEffects::scroll(Autoscroll::Next),
16871            window,
16872            cx,
16873            |s| s.select_ranges(Some(1..2)),
16874        );
16875        editor.open_excerpts(&OpenExcerpts, window, cx);
16876    });
16877    cx.executor().run_until_parked();
16878    let first_item_id = workspace
16879        .update(cx, |workspace, window, cx| {
16880            let active_item = workspace
16881                .active_item(cx)
16882                .expect("should have an active item after navigating into the 1st buffer");
16883            let first_item_id = active_item.item_id();
16884            assert_ne!(
16885                first_item_id, multibuffer_item_id,
16886                "Should navigate into the 1st buffer and activate it"
16887            );
16888            assert!(
16889                active_item.is_singleton(cx),
16890                "New active item should be a singleton buffer"
16891            );
16892            assert_eq!(
16893                active_item
16894                    .act_as::<Editor>(cx)
16895                    .expect("should have navigated into an editor for the 1st buffer")
16896                    .read(cx)
16897                    .text(cx),
16898                sample_text_1
16899            );
16900
16901            workspace
16902                .go_back(workspace.active_pane().downgrade(), window, cx)
16903                .detach_and_log_err(cx);
16904
16905            first_item_id
16906        })
16907        .unwrap();
16908    cx.executor().run_until_parked();
16909    workspace
16910        .update(cx, |workspace, _, cx| {
16911            let active_item = workspace
16912                .active_item(cx)
16913                .expect("should have an active item after navigating back");
16914            assert_eq!(
16915                active_item.item_id(),
16916                multibuffer_item_id,
16917                "Should navigate back to the multi buffer"
16918            );
16919            assert!(!active_item.is_singleton(cx));
16920        })
16921        .unwrap();
16922
16923    multi_buffer_editor.update_in(cx, |editor, window, cx| {
16924        editor.change_selections(
16925            SelectionEffects::scroll(Autoscroll::Next),
16926            window,
16927            cx,
16928            |s| s.select_ranges(Some(39..40)),
16929        );
16930        editor.open_excerpts(&OpenExcerpts, window, cx);
16931    });
16932    cx.executor().run_until_parked();
16933    let second_item_id = workspace
16934        .update(cx, |workspace, window, cx| {
16935            let active_item = workspace
16936                .active_item(cx)
16937                .expect("should have an active item after navigating into the 2nd buffer");
16938            let second_item_id = active_item.item_id();
16939            assert_ne!(
16940                second_item_id, multibuffer_item_id,
16941                "Should navigate away from the multibuffer"
16942            );
16943            assert_ne!(
16944                second_item_id, first_item_id,
16945                "Should navigate into the 2nd buffer and activate it"
16946            );
16947            assert!(
16948                active_item.is_singleton(cx),
16949                "New active item should be a singleton buffer"
16950            );
16951            assert_eq!(
16952                active_item
16953                    .act_as::<Editor>(cx)
16954                    .expect("should have navigated into an editor")
16955                    .read(cx)
16956                    .text(cx),
16957                sample_text_2
16958            );
16959
16960            workspace
16961                .go_back(workspace.active_pane().downgrade(), window, cx)
16962                .detach_and_log_err(cx);
16963
16964            second_item_id
16965        })
16966        .unwrap();
16967    cx.executor().run_until_parked();
16968    workspace
16969        .update(cx, |workspace, _, cx| {
16970            let active_item = workspace
16971                .active_item(cx)
16972                .expect("should have an active item after navigating back from the 2nd buffer");
16973            assert_eq!(
16974                active_item.item_id(),
16975                multibuffer_item_id,
16976                "Should navigate back from the 2nd buffer to the multi buffer"
16977            );
16978            assert!(!active_item.is_singleton(cx));
16979        })
16980        .unwrap();
16981
16982    multi_buffer_editor.update_in(cx, |editor, window, cx| {
16983        editor.change_selections(
16984            SelectionEffects::scroll(Autoscroll::Next),
16985            window,
16986            cx,
16987            |s| s.select_ranges(Some(70..70)),
16988        );
16989        editor.open_excerpts(&OpenExcerpts, window, cx);
16990    });
16991    cx.executor().run_until_parked();
16992    workspace
16993        .update(cx, |workspace, window, cx| {
16994            let active_item = workspace
16995                .active_item(cx)
16996                .expect("should have an active item after navigating into the 3rd buffer");
16997            let third_item_id = active_item.item_id();
16998            assert_ne!(
16999                third_item_id, multibuffer_item_id,
17000                "Should navigate into the 3rd buffer and activate it"
17001            );
17002            assert_ne!(third_item_id, first_item_id);
17003            assert_ne!(third_item_id, second_item_id);
17004            assert!(
17005                active_item.is_singleton(cx),
17006                "New active item should be a singleton buffer"
17007            );
17008            assert_eq!(
17009                active_item
17010                    .act_as::<Editor>(cx)
17011                    .expect("should have navigated into an editor")
17012                    .read(cx)
17013                    .text(cx),
17014                sample_text_3
17015            );
17016
17017            workspace
17018                .go_back(workspace.active_pane().downgrade(), window, cx)
17019                .detach_and_log_err(cx);
17020        })
17021        .unwrap();
17022    cx.executor().run_until_parked();
17023    workspace
17024        .update(cx, |workspace, _, cx| {
17025            let active_item = workspace
17026                .active_item(cx)
17027                .expect("should have an active item after navigating back from the 3rd buffer");
17028            assert_eq!(
17029                active_item.item_id(),
17030                multibuffer_item_id,
17031                "Should navigate back from the 3rd buffer to the multi buffer"
17032            );
17033            assert!(!active_item.is_singleton(cx));
17034        })
17035        .unwrap();
17036}
17037
17038#[gpui::test]
17039async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17040    init_test(cx, |_| {});
17041
17042    let mut cx = EditorTestContext::new(cx).await;
17043
17044    let diff_base = r#"
17045        use some::mod;
17046
17047        const A: u32 = 42;
17048
17049        fn main() {
17050            println!("hello");
17051
17052            println!("world");
17053        }
17054        "#
17055    .unindent();
17056
17057    cx.set_state(
17058        &r#"
17059        use some::modified;
17060
17061        ˇ
17062        fn main() {
17063            println!("hello there");
17064
17065            println!("around the");
17066            println!("world");
17067        }
17068        "#
17069        .unindent(),
17070    );
17071
17072    cx.set_head_text(&diff_base);
17073    executor.run_until_parked();
17074
17075    cx.update_editor(|editor, window, cx| {
17076        editor.go_to_next_hunk(&GoToHunk, window, cx);
17077        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17078    });
17079    executor.run_until_parked();
17080    cx.assert_state_with_diff(
17081        r#"
17082          use some::modified;
17083
17084
17085          fn main() {
17086        -     println!("hello");
17087        + ˇ    println!("hello there");
17088
17089              println!("around the");
17090              println!("world");
17091          }
17092        "#
17093        .unindent(),
17094    );
17095
17096    cx.update_editor(|editor, window, cx| {
17097        for _ in 0..2 {
17098            editor.go_to_next_hunk(&GoToHunk, window, cx);
17099            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17100        }
17101    });
17102    executor.run_until_parked();
17103    cx.assert_state_with_diff(
17104        r#"
17105        - use some::mod;
17106        + ˇuse some::modified;
17107
17108
17109          fn main() {
17110        -     println!("hello");
17111        +     println!("hello there");
17112
17113        +     println!("around the");
17114              println!("world");
17115          }
17116        "#
17117        .unindent(),
17118    );
17119
17120    cx.update_editor(|editor, window, cx| {
17121        editor.go_to_next_hunk(&GoToHunk, window, cx);
17122        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17123    });
17124    executor.run_until_parked();
17125    cx.assert_state_with_diff(
17126        r#"
17127        - use some::mod;
17128        + use some::modified;
17129
17130        - const A: u32 = 42;
17131          ˇ
17132          fn main() {
17133        -     println!("hello");
17134        +     println!("hello there");
17135
17136        +     println!("around the");
17137              println!("world");
17138          }
17139        "#
17140        .unindent(),
17141    );
17142
17143    cx.update_editor(|editor, window, cx| {
17144        editor.cancel(&Cancel, window, cx);
17145    });
17146
17147    cx.assert_state_with_diff(
17148        r#"
17149          use some::modified;
17150
17151          ˇ
17152          fn main() {
17153              println!("hello there");
17154
17155              println!("around the");
17156              println!("world");
17157          }
17158        "#
17159        .unindent(),
17160    );
17161}
17162
17163#[gpui::test]
17164async fn test_diff_base_change_with_expanded_diff_hunks(
17165    executor: BackgroundExecutor,
17166    cx: &mut TestAppContext,
17167) {
17168    init_test(cx, |_| {});
17169
17170    let mut cx = EditorTestContext::new(cx).await;
17171
17172    let diff_base = r#"
17173        use some::mod1;
17174        use some::mod2;
17175
17176        const A: u32 = 42;
17177        const B: u32 = 42;
17178        const C: u32 = 42;
17179
17180        fn main() {
17181            println!("hello");
17182
17183            println!("world");
17184        }
17185        "#
17186    .unindent();
17187
17188    cx.set_state(
17189        &r#"
17190        use some::mod2;
17191
17192        const A: u32 = 42;
17193        const C: u32 = 42;
17194
17195        fn main(ˇ) {
17196            //println!("hello");
17197
17198            println!("world");
17199            //
17200            //
17201        }
17202        "#
17203        .unindent(),
17204    );
17205
17206    cx.set_head_text(&diff_base);
17207    executor.run_until_parked();
17208
17209    cx.update_editor(|editor, window, cx| {
17210        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17211    });
17212    executor.run_until_parked();
17213    cx.assert_state_with_diff(
17214        r#"
17215        - use some::mod1;
17216          use some::mod2;
17217
17218          const A: u32 = 42;
17219        - const B: u32 = 42;
17220          const C: u32 = 42;
17221
17222          fn main(ˇ) {
17223        -     println!("hello");
17224        +     //println!("hello");
17225
17226              println!("world");
17227        +     //
17228        +     //
17229          }
17230        "#
17231        .unindent(),
17232    );
17233
17234    cx.set_head_text("new diff base!");
17235    executor.run_until_parked();
17236    cx.assert_state_with_diff(
17237        r#"
17238        - new diff base!
17239        + use some::mod2;
17240        +
17241        + const A: u32 = 42;
17242        + const C: u32 = 42;
17243        +
17244        + fn main(ˇ) {
17245        +     //println!("hello");
17246        +
17247        +     println!("world");
17248        +     //
17249        +     //
17250        + }
17251        "#
17252        .unindent(),
17253    );
17254}
17255
17256#[gpui::test]
17257async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
17258    init_test(cx, |_| {});
17259
17260    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
17261    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
17262    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
17263    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
17264    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
17265    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
17266
17267    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
17268    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
17269    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
17270
17271    let multi_buffer = cx.new(|cx| {
17272        let mut multibuffer = MultiBuffer::new(ReadWrite);
17273        multibuffer.push_excerpts(
17274            buffer_1.clone(),
17275            [
17276                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17277                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17278                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17279            ],
17280            cx,
17281        );
17282        multibuffer.push_excerpts(
17283            buffer_2.clone(),
17284            [
17285                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17286                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17287                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17288            ],
17289            cx,
17290        );
17291        multibuffer.push_excerpts(
17292            buffer_3.clone(),
17293            [
17294                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17295                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17296                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17297            ],
17298            cx,
17299        );
17300        multibuffer
17301    });
17302
17303    let editor =
17304        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
17305    editor
17306        .update(cx, |editor, _window, cx| {
17307            for (buffer, diff_base) in [
17308                (buffer_1.clone(), file_1_old),
17309                (buffer_2.clone(), file_2_old),
17310                (buffer_3.clone(), file_3_old),
17311            ] {
17312                let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
17313                editor
17314                    .buffer
17315                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
17316            }
17317        })
17318        .unwrap();
17319
17320    let mut cx = EditorTestContext::for_editor(editor, cx).await;
17321    cx.run_until_parked();
17322
17323    cx.assert_editor_state(
17324        &"
17325            ˇaaa
17326            ccc
17327            ddd
17328
17329            ggg
17330            hhh
17331
17332
17333            lll
17334            mmm
17335            NNN
17336
17337            qqq
17338            rrr
17339
17340            uuu
17341            111
17342            222
17343            333
17344
17345            666
17346            777
17347
17348            000
17349            !!!"
17350        .unindent(),
17351    );
17352
17353    cx.update_editor(|editor, window, cx| {
17354        editor.select_all(&SelectAll, window, cx);
17355        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17356    });
17357    cx.executor().run_until_parked();
17358
17359    cx.assert_state_with_diff(
17360        "
17361            «aaa
17362          - bbb
17363            ccc
17364            ddd
17365
17366            ggg
17367            hhh
17368
17369
17370            lll
17371            mmm
17372          - nnn
17373          + NNN
17374
17375            qqq
17376            rrr
17377
17378            uuu
17379            111
17380            222
17381            333
17382
17383          + 666
17384            777
17385
17386            000
17387            !!!ˇ»"
17388            .unindent(),
17389    );
17390}
17391
17392#[gpui::test]
17393async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
17394    init_test(cx, |_| {});
17395
17396    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
17397    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
17398
17399    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
17400    let multi_buffer = cx.new(|cx| {
17401        let mut multibuffer = MultiBuffer::new(ReadWrite);
17402        multibuffer.push_excerpts(
17403            buffer.clone(),
17404            [
17405                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
17406                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
17407                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
17408            ],
17409            cx,
17410        );
17411        multibuffer
17412    });
17413
17414    let editor =
17415        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
17416    editor
17417        .update(cx, |editor, _window, cx| {
17418            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
17419            editor
17420                .buffer
17421                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
17422        })
17423        .unwrap();
17424
17425    let mut cx = EditorTestContext::for_editor(editor, cx).await;
17426    cx.run_until_parked();
17427
17428    cx.update_editor(|editor, window, cx| {
17429        editor.expand_all_diff_hunks(&Default::default(), window, cx)
17430    });
17431    cx.executor().run_until_parked();
17432
17433    // When the start of a hunk coincides with the start of its excerpt,
17434    // the hunk is expanded. When the start of a a hunk is earlier than
17435    // the start of its excerpt, the hunk is not expanded.
17436    cx.assert_state_with_diff(
17437        "
17438            ˇaaa
17439          - bbb
17440          + BBB
17441
17442          - ddd
17443          - eee
17444          + DDD
17445          + EEE
17446            fff
17447
17448            iii
17449        "
17450        .unindent(),
17451    );
17452}
17453
17454#[gpui::test]
17455async fn test_edits_around_expanded_insertion_hunks(
17456    executor: BackgroundExecutor,
17457    cx: &mut TestAppContext,
17458) {
17459    init_test(cx, |_| {});
17460
17461    let mut cx = EditorTestContext::new(cx).await;
17462
17463    let diff_base = r#"
17464        use some::mod1;
17465        use some::mod2;
17466
17467        const A: u32 = 42;
17468
17469        fn main() {
17470            println!("hello");
17471
17472            println!("world");
17473        }
17474        "#
17475    .unindent();
17476    executor.run_until_parked();
17477    cx.set_state(
17478        &r#"
17479        use some::mod1;
17480        use some::mod2;
17481
17482        const A: u32 = 42;
17483        const B: u32 = 42;
17484        const C: u32 = 42;
17485        ˇ
17486
17487        fn main() {
17488            println!("hello");
17489
17490            println!("world");
17491        }
17492        "#
17493        .unindent(),
17494    );
17495
17496    cx.set_head_text(&diff_base);
17497    executor.run_until_parked();
17498
17499    cx.update_editor(|editor, window, cx| {
17500        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17501    });
17502    executor.run_until_parked();
17503
17504    cx.assert_state_with_diff(
17505        r#"
17506        use some::mod1;
17507        use some::mod2;
17508
17509        const A: u32 = 42;
17510      + const B: u32 = 42;
17511      + const C: u32 = 42;
17512      + ˇ
17513
17514        fn main() {
17515            println!("hello");
17516
17517            println!("world");
17518        }
17519      "#
17520        .unindent(),
17521    );
17522
17523    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
17524    executor.run_until_parked();
17525
17526    cx.assert_state_with_diff(
17527        r#"
17528        use some::mod1;
17529        use some::mod2;
17530
17531        const A: u32 = 42;
17532      + const B: u32 = 42;
17533      + const C: u32 = 42;
17534      + const D: u32 = 42;
17535      + ˇ
17536
17537        fn main() {
17538            println!("hello");
17539
17540            println!("world");
17541        }
17542      "#
17543        .unindent(),
17544    );
17545
17546    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
17547    executor.run_until_parked();
17548
17549    cx.assert_state_with_diff(
17550        r#"
17551        use some::mod1;
17552        use some::mod2;
17553
17554        const A: u32 = 42;
17555      + const B: u32 = 42;
17556      + const C: u32 = 42;
17557      + const D: u32 = 42;
17558      + const E: u32 = 42;
17559      + ˇ
17560
17561        fn main() {
17562            println!("hello");
17563
17564            println!("world");
17565        }
17566      "#
17567        .unindent(),
17568    );
17569
17570    cx.update_editor(|editor, window, cx| {
17571        editor.delete_line(&DeleteLine, window, cx);
17572    });
17573    executor.run_until_parked();
17574
17575    cx.assert_state_with_diff(
17576        r#"
17577        use some::mod1;
17578        use some::mod2;
17579
17580        const A: u32 = 42;
17581      + const B: u32 = 42;
17582      + const C: u32 = 42;
17583      + const D: u32 = 42;
17584      + const E: u32 = 42;
17585        ˇ
17586        fn main() {
17587            println!("hello");
17588
17589            println!("world");
17590        }
17591      "#
17592        .unindent(),
17593    );
17594
17595    cx.update_editor(|editor, window, cx| {
17596        editor.move_up(&MoveUp, window, cx);
17597        editor.delete_line(&DeleteLine, window, cx);
17598        editor.move_up(&MoveUp, window, cx);
17599        editor.delete_line(&DeleteLine, window, cx);
17600        editor.move_up(&MoveUp, window, cx);
17601        editor.delete_line(&DeleteLine, window, cx);
17602    });
17603    executor.run_until_parked();
17604    cx.assert_state_with_diff(
17605        r#"
17606        use some::mod1;
17607        use some::mod2;
17608
17609        const A: u32 = 42;
17610      + const B: u32 = 42;
17611        ˇ
17612        fn main() {
17613            println!("hello");
17614
17615            println!("world");
17616        }
17617      "#
17618        .unindent(),
17619    );
17620
17621    cx.update_editor(|editor, window, cx| {
17622        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
17623        editor.delete_line(&DeleteLine, window, cx);
17624    });
17625    executor.run_until_parked();
17626    cx.assert_state_with_diff(
17627        r#"
17628        ˇ
17629        fn main() {
17630            println!("hello");
17631
17632            println!("world");
17633        }
17634      "#
17635        .unindent(),
17636    );
17637}
17638
17639#[gpui::test]
17640async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
17641    init_test(cx, |_| {});
17642
17643    let mut cx = EditorTestContext::new(cx).await;
17644    cx.set_head_text(indoc! { "
17645        one
17646        two
17647        three
17648        four
17649        five
17650        "
17651    });
17652    cx.set_state(indoc! { "
17653        one
17654        ˇthree
17655        five
17656    "});
17657    cx.run_until_parked();
17658    cx.update_editor(|editor, window, cx| {
17659        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17660    });
17661    cx.assert_state_with_diff(
17662        indoc! { "
17663        one
17664      - two
17665        ˇthree
17666      - four
17667        five
17668    "}
17669        .to_string(),
17670    );
17671    cx.update_editor(|editor, window, cx| {
17672        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17673    });
17674
17675    cx.assert_state_with_diff(
17676        indoc! { "
17677        one
17678        ˇthree
17679        five
17680    "}
17681        .to_string(),
17682    );
17683
17684    cx.set_state(indoc! { "
17685        one
17686        ˇTWO
17687        three
17688        four
17689        five
17690    "});
17691    cx.run_until_parked();
17692    cx.update_editor(|editor, window, cx| {
17693        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17694    });
17695
17696    cx.assert_state_with_diff(
17697        indoc! { "
17698            one
17699          - two
17700          + ˇTWO
17701            three
17702            four
17703            five
17704        "}
17705        .to_string(),
17706    );
17707    cx.update_editor(|editor, window, cx| {
17708        editor.move_up(&Default::default(), window, cx);
17709        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17710    });
17711    cx.assert_state_with_diff(
17712        indoc! { "
17713            one
17714            ˇTWO
17715            three
17716            four
17717            five
17718        "}
17719        .to_string(),
17720    );
17721}
17722
17723#[gpui::test]
17724async fn test_edits_around_expanded_deletion_hunks(
17725    executor: BackgroundExecutor,
17726    cx: &mut TestAppContext,
17727) {
17728    init_test(cx, |_| {});
17729
17730    let mut cx = EditorTestContext::new(cx).await;
17731
17732    let diff_base = r#"
17733        use some::mod1;
17734        use some::mod2;
17735
17736        const A: u32 = 42;
17737        const B: u32 = 42;
17738        const C: u32 = 42;
17739
17740
17741        fn main() {
17742            println!("hello");
17743
17744            println!("world");
17745        }
17746    "#
17747    .unindent();
17748    executor.run_until_parked();
17749    cx.set_state(
17750        &r#"
17751        use some::mod1;
17752        use some::mod2;
17753
17754        ˇconst B: u32 = 42;
17755        const C: u32 = 42;
17756
17757
17758        fn main() {
17759            println!("hello");
17760
17761            println!("world");
17762        }
17763        "#
17764        .unindent(),
17765    );
17766
17767    cx.set_head_text(&diff_base);
17768    executor.run_until_parked();
17769
17770    cx.update_editor(|editor, window, cx| {
17771        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17772    });
17773    executor.run_until_parked();
17774
17775    cx.assert_state_with_diff(
17776        r#"
17777        use some::mod1;
17778        use some::mod2;
17779
17780      - const A: u32 = 42;
17781        ˇconst B: u32 = 42;
17782        const C: u32 = 42;
17783
17784
17785        fn main() {
17786            println!("hello");
17787
17788            println!("world");
17789        }
17790      "#
17791        .unindent(),
17792    );
17793
17794    cx.update_editor(|editor, window, cx| {
17795        editor.delete_line(&DeleteLine, window, cx);
17796    });
17797    executor.run_until_parked();
17798    cx.assert_state_with_diff(
17799        r#"
17800        use some::mod1;
17801        use some::mod2;
17802
17803      - const A: u32 = 42;
17804      - const B: u32 = 42;
17805        ˇconst C: u32 = 42;
17806
17807
17808        fn main() {
17809            println!("hello");
17810
17811            println!("world");
17812        }
17813      "#
17814        .unindent(),
17815    );
17816
17817    cx.update_editor(|editor, window, cx| {
17818        editor.delete_line(&DeleteLine, window, cx);
17819    });
17820    executor.run_until_parked();
17821    cx.assert_state_with_diff(
17822        r#"
17823        use some::mod1;
17824        use some::mod2;
17825
17826      - const A: u32 = 42;
17827      - const B: u32 = 42;
17828      - const C: u32 = 42;
17829        ˇ
17830
17831        fn main() {
17832            println!("hello");
17833
17834            println!("world");
17835        }
17836      "#
17837        .unindent(),
17838    );
17839
17840    cx.update_editor(|editor, window, cx| {
17841        editor.handle_input("replacement", window, cx);
17842    });
17843    executor.run_until_parked();
17844    cx.assert_state_with_diff(
17845        r#"
17846        use some::mod1;
17847        use some::mod2;
17848
17849      - const A: u32 = 42;
17850      - const B: u32 = 42;
17851      - const C: u32 = 42;
17852      -
17853      + replacementˇ
17854
17855        fn main() {
17856            println!("hello");
17857
17858            println!("world");
17859        }
17860      "#
17861        .unindent(),
17862    );
17863}
17864
17865#[gpui::test]
17866async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17867    init_test(cx, |_| {});
17868
17869    let mut cx = EditorTestContext::new(cx).await;
17870
17871    let base_text = r#"
17872        one
17873        two
17874        three
17875        four
17876        five
17877    "#
17878    .unindent();
17879    executor.run_until_parked();
17880    cx.set_state(
17881        &r#"
17882        one
17883        two
17884        fˇour
17885        five
17886        "#
17887        .unindent(),
17888    );
17889
17890    cx.set_head_text(&base_text);
17891    executor.run_until_parked();
17892
17893    cx.update_editor(|editor, window, cx| {
17894        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17895    });
17896    executor.run_until_parked();
17897
17898    cx.assert_state_with_diff(
17899        r#"
17900          one
17901          two
17902        - three
17903          fˇour
17904          five
17905        "#
17906        .unindent(),
17907    );
17908
17909    cx.update_editor(|editor, window, cx| {
17910        editor.backspace(&Backspace, window, cx);
17911        editor.backspace(&Backspace, window, cx);
17912    });
17913    executor.run_until_parked();
17914    cx.assert_state_with_diff(
17915        r#"
17916          one
17917          two
17918        - threeˇ
17919        - four
17920        + our
17921          five
17922        "#
17923        .unindent(),
17924    );
17925}
17926
17927#[gpui::test]
17928async fn test_edit_after_expanded_modification_hunk(
17929    executor: BackgroundExecutor,
17930    cx: &mut TestAppContext,
17931) {
17932    init_test(cx, |_| {});
17933
17934    let mut cx = EditorTestContext::new(cx).await;
17935
17936    let diff_base = r#"
17937        use some::mod1;
17938        use some::mod2;
17939
17940        const A: u32 = 42;
17941        const B: u32 = 42;
17942        const C: u32 = 42;
17943        const D: u32 = 42;
17944
17945
17946        fn main() {
17947            println!("hello");
17948
17949            println!("world");
17950        }"#
17951    .unindent();
17952
17953    cx.set_state(
17954        &r#"
17955        use some::mod1;
17956        use some::mod2;
17957
17958        const A: u32 = 42;
17959        const B: u32 = 42;
17960        const C: u32 = 43ˇ
17961        const D: u32 = 42;
17962
17963
17964        fn main() {
17965            println!("hello");
17966
17967            println!("world");
17968        }"#
17969        .unindent(),
17970    );
17971
17972    cx.set_head_text(&diff_base);
17973    executor.run_until_parked();
17974    cx.update_editor(|editor, window, cx| {
17975        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17976    });
17977    executor.run_until_parked();
17978
17979    cx.assert_state_with_diff(
17980        r#"
17981        use some::mod1;
17982        use some::mod2;
17983
17984        const A: u32 = 42;
17985        const B: u32 = 42;
17986      - const C: u32 = 42;
17987      + const C: u32 = 43ˇ
17988        const D: u32 = 42;
17989
17990
17991        fn main() {
17992            println!("hello");
17993
17994            println!("world");
17995        }"#
17996        .unindent(),
17997    );
17998
17999    cx.update_editor(|editor, window, cx| {
18000        editor.handle_input("\nnew_line\n", window, cx);
18001    });
18002    executor.run_until_parked();
18003
18004    cx.assert_state_with_diff(
18005        r#"
18006        use some::mod1;
18007        use some::mod2;
18008
18009        const A: u32 = 42;
18010        const B: u32 = 42;
18011      - const C: u32 = 42;
18012      + const C: u32 = 43
18013      + new_line
18014      + ˇ
18015        const D: u32 = 42;
18016
18017
18018        fn main() {
18019            println!("hello");
18020
18021            println!("world");
18022        }"#
18023        .unindent(),
18024    );
18025}
18026
18027#[gpui::test]
18028async fn test_stage_and_unstage_added_file_hunk(
18029    executor: BackgroundExecutor,
18030    cx: &mut TestAppContext,
18031) {
18032    init_test(cx, |_| {});
18033
18034    let mut cx = EditorTestContext::new(cx).await;
18035    cx.update_editor(|editor, _, cx| {
18036        editor.set_expand_all_diff_hunks(cx);
18037    });
18038
18039    let working_copy = r#"
18040            ˇfn main() {
18041                println!("hello, world!");
18042            }
18043        "#
18044    .unindent();
18045
18046    cx.set_state(&working_copy);
18047    executor.run_until_parked();
18048
18049    cx.assert_state_with_diff(
18050        r#"
18051            + ˇfn main() {
18052            +     println!("hello, world!");
18053            + }
18054        "#
18055        .unindent(),
18056    );
18057    cx.assert_index_text(None);
18058
18059    cx.update_editor(|editor, window, cx| {
18060        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18061    });
18062    executor.run_until_parked();
18063    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
18064    cx.assert_state_with_diff(
18065        r#"
18066            + ˇfn main() {
18067            +     println!("hello, world!");
18068            + }
18069        "#
18070        .unindent(),
18071    );
18072
18073    cx.update_editor(|editor, window, cx| {
18074        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18075    });
18076    executor.run_until_parked();
18077    cx.assert_index_text(None);
18078}
18079
18080async fn setup_indent_guides_editor(
18081    text: &str,
18082    cx: &mut TestAppContext,
18083) -> (BufferId, EditorTestContext) {
18084    init_test(cx, |_| {});
18085
18086    let mut cx = EditorTestContext::new(cx).await;
18087
18088    let buffer_id = cx.update_editor(|editor, window, cx| {
18089        editor.set_text(text, window, cx);
18090        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
18091
18092        buffer_ids[0]
18093    });
18094
18095    (buffer_id, cx)
18096}
18097
18098fn assert_indent_guides(
18099    range: Range<u32>,
18100    expected: Vec<IndentGuide>,
18101    active_indices: Option<Vec<usize>>,
18102    cx: &mut EditorTestContext,
18103) {
18104    let indent_guides = cx.update_editor(|editor, window, cx| {
18105        let snapshot = editor.snapshot(window, cx).display_snapshot;
18106        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
18107            editor,
18108            MultiBufferRow(range.start)..MultiBufferRow(range.end),
18109            true,
18110            &snapshot,
18111            cx,
18112        );
18113
18114        indent_guides.sort_by(|a, b| {
18115            a.depth.cmp(&b.depth).then(
18116                a.start_row
18117                    .cmp(&b.start_row)
18118                    .then(a.end_row.cmp(&b.end_row)),
18119            )
18120        });
18121        indent_guides
18122    });
18123
18124    if let Some(expected) = active_indices {
18125        let active_indices = cx.update_editor(|editor, window, cx| {
18126            let snapshot = editor.snapshot(window, cx).display_snapshot;
18127            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
18128        });
18129
18130        assert_eq!(
18131            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
18132            expected,
18133            "Active indent guide indices do not match"
18134        );
18135    }
18136
18137    assert_eq!(indent_guides, expected, "Indent guides do not match");
18138}
18139
18140fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
18141    IndentGuide {
18142        buffer_id,
18143        start_row: MultiBufferRow(start_row),
18144        end_row: MultiBufferRow(end_row),
18145        depth,
18146        tab_size: 4,
18147        settings: IndentGuideSettings {
18148            enabled: true,
18149            line_width: 1,
18150            active_line_width: 1,
18151            ..Default::default()
18152        },
18153    }
18154}
18155
18156#[gpui::test]
18157async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
18158    let (buffer_id, mut cx) = setup_indent_guides_editor(
18159        &"
18160        fn main() {
18161            let a = 1;
18162        }"
18163        .unindent(),
18164        cx,
18165    )
18166    .await;
18167
18168    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
18169}
18170
18171#[gpui::test]
18172async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
18173    let (buffer_id, mut cx) = setup_indent_guides_editor(
18174        &"
18175        fn main() {
18176            let a = 1;
18177            let b = 2;
18178        }"
18179        .unindent(),
18180        cx,
18181    )
18182    .await;
18183
18184    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
18185}
18186
18187#[gpui::test]
18188async fn test_indent_guide_nested(cx: &mut TestAppContext) {
18189    let (buffer_id, mut cx) = setup_indent_guides_editor(
18190        &"
18191        fn main() {
18192            let a = 1;
18193            if a == 3 {
18194                let b = 2;
18195            } else {
18196                let c = 3;
18197            }
18198        }"
18199        .unindent(),
18200        cx,
18201    )
18202    .await;
18203
18204    assert_indent_guides(
18205        0..8,
18206        vec![
18207            indent_guide(buffer_id, 1, 6, 0),
18208            indent_guide(buffer_id, 3, 3, 1),
18209            indent_guide(buffer_id, 5, 5, 1),
18210        ],
18211        None,
18212        &mut cx,
18213    );
18214}
18215
18216#[gpui::test]
18217async fn test_indent_guide_tab(cx: &mut TestAppContext) {
18218    let (buffer_id, mut cx) = setup_indent_guides_editor(
18219        &"
18220        fn main() {
18221            let a = 1;
18222                let b = 2;
18223            let c = 3;
18224        }"
18225        .unindent(),
18226        cx,
18227    )
18228    .await;
18229
18230    assert_indent_guides(
18231        0..5,
18232        vec![
18233            indent_guide(buffer_id, 1, 3, 0),
18234            indent_guide(buffer_id, 2, 2, 1),
18235        ],
18236        None,
18237        &mut cx,
18238    );
18239}
18240
18241#[gpui::test]
18242async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
18243    let (buffer_id, mut cx) = setup_indent_guides_editor(
18244        &"
18245        fn main() {
18246            let a = 1;
18247
18248            let c = 3;
18249        }"
18250        .unindent(),
18251        cx,
18252    )
18253    .await;
18254
18255    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
18256}
18257
18258#[gpui::test]
18259async fn test_indent_guide_complex(cx: &mut TestAppContext) {
18260    let (buffer_id, mut cx) = setup_indent_guides_editor(
18261        &"
18262        fn main() {
18263            let a = 1;
18264
18265            let c = 3;
18266
18267            if a == 3 {
18268                let b = 2;
18269            } else {
18270                let c = 3;
18271            }
18272        }"
18273        .unindent(),
18274        cx,
18275    )
18276    .await;
18277
18278    assert_indent_guides(
18279        0..11,
18280        vec![
18281            indent_guide(buffer_id, 1, 9, 0),
18282            indent_guide(buffer_id, 6, 6, 1),
18283            indent_guide(buffer_id, 8, 8, 1),
18284        ],
18285        None,
18286        &mut cx,
18287    );
18288}
18289
18290#[gpui::test]
18291async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
18292    let (buffer_id, mut cx) = setup_indent_guides_editor(
18293        &"
18294        fn main() {
18295            let a = 1;
18296
18297            let c = 3;
18298
18299            if a == 3 {
18300                let b = 2;
18301            } else {
18302                let c = 3;
18303            }
18304        }"
18305        .unindent(),
18306        cx,
18307    )
18308    .await;
18309
18310    assert_indent_guides(
18311        1..11,
18312        vec![
18313            indent_guide(buffer_id, 1, 9, 0),
18314            indent_guide(buffer_id, 6, 6, 1),
18315            indent_guide(buffer_id, 8, 8, 1),
18316        ],
18317        None,
18318        &mut cx,
18319    );
18320}
18321
18322#[gpui::test]
18323async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
18324    let (buffer_id, mut cx) = setup_indent_guides_editor(
18325        &"
18326        fn main() {
18327            let a = 1;
18328
18329            let c = 3;
18330
18331            if a == 3 {
18332                let b = 2;
18333            } else {
18334                let c = 3;
18335            }
18336        }"
18337        .unindent(),
18338        cx,
18339    )
18340    .await;
18341
18342    assert_indent_guides(
18343        1..10,
18344        vec![
18345            indent_guide(buffer_id, 1, 9, 0),
18346            indent_guide(buffer_id, 6, 6, 1),
18347            indent_guide(buffer_id, 8, 8, 1),
18348        ],
18349        None,
18350        &mut cx,
18351    );
18352}
18353
18354#[gpui::test]
18355async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
18356    let (buffer_id, mut cx) = setup_indent_guides_editor(
18357        &"
18358        fn main() {
18359            if a {
18360                b(
18361                    c,
18362                    d,
18363                )
18364            } else {
18365                e(
18366                    f
18367                )
18368            }
18369        }"
18370        .unindent(),
18371        cx,
18372    )
18373    .await;
18374
18375    assert_indent_guides(
18376        0..11,
18377        vec![
18378            indent_guide(buffer_id, 1, 10, 0),
18379            indent_guide(buffer_id, 2, 5, 1),
18380            indent_guide(buffer_id, 7, 9, 1),
18381            indent_guide(buffer_id, 3, 4, 2),
18382            indent_guide(buffer_id, 8, 8, 2),
18383        ],
18384        None,
18385        &mut cx,
18386    );
18387
18388    cx.update_editor(|editor, window, cx| {
18389        editor.fold_at(MultiBufferRow(2), window, cx);
18390        assert_eq!(
18391            editor.display_text(cx),
18392            "
18393            fn main() {
18394                if a {
18395                    b(⋯
18396                    )
18397                } else {
18398                    e(
18399                        f
18400                    )
18401                }
18402            }"
18403            .unindent()
18404        );
18405    });
18406
18407    assert_indent_guides(
18408        0..11,
18409        vec![
18410            indent_guide(buffer_id, 1, 10, 0),
18411            indent_guide(buffer_id, 2, 5, 1),
18412            indent_guide(buffer_id, 7, 9, 1),
18413            indent_guide(buffer_id, 8, 8, 2),
18414        ],
18415        None,
18416        &mut cx,
18417    );
18418}
18419
18420#[gpui::test]
18421async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
18422    let (buffer_id, mut cx) = setup_indent_guides_editor(
18423        &"
18424        block1
18425            block2
18426                block3
18427                    block4
18428            block2
18429        block1
18430        block1"
18431            .unindent(),
18432        cx,
18433    )
18434    .await;
18435
18436    assert_indent_guides(
18437        1..10,
18438        vec![
18439            indent_guide(buffer_id, 1, 4, 0),
18440            indent_guide(buffer_id, 2, 3, 1),
18441            indent_guide(buffer_id, 3, 3, 2),
18442        ],
18443        None,
18444        &mut cx,
18445    );
18446}
18447
18448#[gpui::test]
18449async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
18450    let (buffer_id, mut cx) = setup_indent_guides_editor(
18451        &"
18452        block1
18453            block2
18454                block3
18455
18456        block1
18457        block1"
18458            .unindent(),
18459        cx,
18460    )
18461    .await;
18462
18463    assert_indent_guides(
18464        0..6,
18465        vec![
18466            indent_guide(buffer_id, 1, 2, 0),
18467            indent_guide(buffer_id, 2, 2, 1),
18468        ],
18469        None,
18470        &mut cx,
18471    );
18472}
18473
18474#[gpui::test]
18475async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
18476    let (buffer_id, mut cx) = setup_indent_guides_editor(
18477        &"
18478        function component() {
18479        \treturn (
18480        \t\t\t
18481        \t\t<div>
18482        \t\t\t<abc></abc>
18483        \t\t</div>
18484        \t)
18485        }"
18486        .unindent(),
18487        cx,
18488    )
18489    .await;
18490
18491    assert_indent_guides(
18492        0..8,
18493        vec![
18494            indent_guide(buffer_id, 1, 6, 0),
18495            indent_guide(buffer_id, 2, 5, 1),
18496            indent_guide(buffer_id, 4, 4, 2),
18497        ],
18498        None,
18499        &mut cx,
18500    );
18501}
18502
18503#[gpui::test]
18504async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
18505    let (buffer_id, mut cx) = setup_indent_guides_editor(
18506        &"
18507        function component() {
18508        \treturn (
18509        \t
18510        \t\t<div>
18511        \t\t\t<abc></abc>
18512        \t\t</div>
18513        \t)
18514        }"
18515        .unindent(),
18516        cx,
18517    )
18518    .await;
18519
18520    assert_indent_guides(
18521        0..8,
18522        vec![
18523            indent_guide(buffer_id, 1, 6, 0),
18524            indent_guide(buffer_id, 2, 5, 1),
18525            indent_guide(buffer_id, 4, 4, 2),
18526        ],
18527        None,
18528        &mut cx,
18529    );
18530}
18531
18532#[gpui::test]
18533async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
18534    let (buffer_id, mut cx) = setup_indent_guides_editor(
18535        &"
18536        block1
18537
18538
18539
18540            block2
18541        "
18542        .unindent(),
18543        cx,
18544    )
18545    .await;
18546
18547    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
18548}
18549
18550#[gpui::test]
18551async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
18552    let (buffer_id, mut cx) = setup_indent_guides_editor(
18553        &"
18554        def a:
18555        \tb = 3
18556        \tif True:
18557        \t\tc = 4
18558        \t\td = 5
18559        \tprint(b)
18560        "
18561        .unindent(),
18562        cx,
18563    )
18564    .await;
18565
18566    assert_indent_guides(
18567        0..6,
18568        vec![
18569            indent_guide(buffer_id, 1, 5, 0),
18570            indent_guide(buffer_id, 3, 4, 1),
18571        ],
18572        None,
18573        &mut cx,
18574    );
18575}
18576
18577#[gpui::test]
18578async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
18579    let (buffer_id, mut cx) = setup_indent_guides_editor(
18580        &"
18581    fn main() {
18582        let a = 1;
18583    }"
18584        .unindent(),
18585        cx,
18586    )
18587    .await;
18588
18589    cx.update_editor(|editor, window, cx| {
18590        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18591            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18592        });
18593    });
18594
18595    assert_indent_guides(
18596        0..3,
18597        vec![indent_guide(buffer_id, 1, 1, 0)],
18598        Some(vec![0]),
18599        &mut cx,
18600    );
18601}
18602
18603#[gpui::test]
18604async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
18605    let (buffer_id, mut cx) = setup_indent_guides_editor(
18606        &"
18607    fn main() {
18608        if 1 == 2 {
18609            let a = 1;
18610        }
18611    }"
18612        .unindent(),
18613        cx,
18614    )
18615    .await;
18616
18617    cx.update_editor(|editor, window, cx| {
18618        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18619            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18620        });
18621    });
18622
18623    assert_indent_guides(
18624        0..4,
18625        vec![
18626            indent_guide(buffer_id, 1, 3, 0),
18627            indent_guide(buffer_id, 2, 2, 1),
18628        ],
18629        Some(vec![1]),
18630        &mut cx,
18631    );
18632
18633    cx.update_editor(|editor, window, cx| {
18634        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18635            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
18636        });
18637    });
18638
18639    assert_indent_guides(
18640        0..4,
18641        vec![
18642            indent_guide(buffer_id, 1, 3, 0),
18643            indent_guide(buffer_id, 2, 2, 1),
18644        ],
18645        Some(vec![1]),
18646        &mut cx,
18647    );
18648
18649    cx.update_editor(|editor, window, cx| {
18650        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18651            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
18652        });
18653    });
18654
18655    assert_indent_guides(
18656        0..4,
18657        vec![
18658            indent_guide(buffer_id, 1, 3, 0),
18659            indent_guide(buffer_id, 2, 2, 1),
18660        ],
18661        Some(vec![0]),
18662        &mut cx,
18663    );
18664}
18665
18666#[gpui::test]
18667async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
18668    let (buffer_id, mut cx) = setup_indent_guides_editor(
18669        &"
18670    fn main() {
18671        let a = 1;
18672
18673        let b = 2;
18674    }"
18675        .unindent(),
18676        cx,
18677    )
18678    .await;
18679
18680    cx.update_editor(|editor, window, cx| {
18681        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18682            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
18683        });
18684    });
18685
18686    assert_indent_guides(
18687        0..5,
18688        vec![indent_guide(buffer_id, 1, 3, 0)],
18689        Some(vec![0]),
18690        &mut cx,
18691    );
18692}
18693
18694#[gpui::test]
18695async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
18696    let (buffer_id, mut cx) = setup_indent_guides_editor(
18697        &"
18698    def m:
18699        a = 1
18700        pass"
18701            .unindent(),
18702        cx,
18703    )
18704    .await;
18705
18706    cx.update_editor(|editor, window, cx| {
18707        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18708            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18709        });
18710    });
18711
18712    assert_indent_guides(
18713        0..3,
18714        vec![indent_guide(buffer_id, 1, 2, 0)],
18715        Some(vec![0]),
18716        &mut cx,
18717    );
18718}
18719
18720#[gpui::test]
18721async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
18722    init_test(cx, |_| {});
18723    let mut cx = EditorTestContext::new(cx).await;
18724    let text = indoc! {
18725        "
18726        impl A {
18727            fn b() {
18728                0;
18729                3;
18730                5;
18731                6;
18732                7;
18733            }
18734        }
18735        "
18736    };
18737    let base_text = indoc! {
18738        "
18739        impl A {
18740            fn b() {
18741                0;
18742                1;
18743                2;
18744                3;
18745                4;
18746            }
18747            fn c() {
18748                5;
18749                6;
18750                7;
18751            }
18752        }
18753        "
18754    };
18755
18756    cx.update_editor(|editor, window, cx| {
18757        editor.set_text(text, window, cx);
18758
18759        editor.buffer().update(cx, |multibuffer, cx| {
18760            let buffer = multibuffer.as_singleton().unwrap();
18761            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
18762
18763            multibuffer.set_all_diff_hunks_expanded(cx);
18764            multibuffer.add_diff(diff, cx);
18765
18766            buffer.read(cx).remote_id()
18767        })
18768    });
18769    cx.run_until_parked();
18770
18771    cx.assert_state_with_diff(
18772        indoc! { "
18773          impl A {
18774              fn b() {
18775                  0;
18776        -         1;
18777        -         2;
18778                  3;
18779        -         4;
18780        -     }
18781        -     fn c() {
18782                  5;
18783                  6;
18784                  7;
18785              }
18786          }
18787          ˇ"
18788        }
18789        .to_string(),
18790    );
18791
18792    let mut actual_guides = cx.update_editor(|editor, window, cx| {
18793        editor
18794            .snapshot(window, cx)
18795            .buffer_snapshot
18796            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
18797            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
18798            .collect::<Vec<_>>()
18799    });
18800    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
18801    assert_eq!(
18802        actual_guides,
18803        vec![
18804            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
18805            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
18806            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
18807        ]
18808    );
18809}
18810
18811#[gpui::test]
18812async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18813    init_test(cx, |_| {});
18814    let mut cx = EditorTestContext::new(cx).await;
18815
18816    let diff_base = r#"
18817        a
18818        b
18819        c
18820        "#
18821    .unindent();
18822
18823    cx.set_state(
18824        &r#"
18825        ˇA
18826        b
18827        C
18828        "#
18829        .unindent(),
18830    );
18831    cx.set_head_text(&diff_base);
18832    cx.update_editor(|editor, window, cx| {
18833        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18834    });
18835    executor.run_until_parked();
18836
18837    let both_hunks_expanded = r#"
18838        - a
18839        + ˇA
18840          b
18841        - c
18842        + C
18843        "#
18844    .unindent();
18845
18846    cx.assert_state_with_diff(both_hunks_expanded.clone());
18847
18848    let hunk_ranges = cx.update_editor(|editor, window, cx| {
18849        let snapshot = editor.snapshot(window, cx);
18850        let hunks = editor
18851            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18852            .collect::<Vec<_>>();
18853        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
18854        let buffer_id = hunks[0].buffer_id;
18855        hunks
18856            .into_iter()
18857            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
18858            .collect::<Vec<_>>()
18859    });
18860    assert_eq!(hunk_ranges.len(), 2);
18861
18862    cx.update_editor(|editor, _, cx| {
18863        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18864    });
18865    executor.run_until_parked();
18866
18867    let second_hunk_expanded = r#"
18868          ˇA
18869          b
18870        - c
18871        + C
18872        "#
18873    .unindent();
18874
18875    cx.assert_state_with_diff(second_hunk_expanded);
18876
18877    cx.update_editor(|editor, _, cx| {
18878        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18879    });
18880    executor.run_until_parked();
18881
18882    cx.assert_state_with_diff(both_hunks_expanded.clone());
18883
18884    cx.update_editor(|editor, _, cx| {
18885        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
18886    });
18887    executor.run_until_parked();
18888
18889    let first_hunk_expanded = r#"
18890        - a
18891        + ˇA
18892          b
18893          C
18894        "#
18895    .unindent();
18896
18897    cx.assert_state_with_diff(first_hunk_expanded);
18898
18899    cx.update_editor(|editor, _, cx| {
18900        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
18901    });
18902    executor.run_until_parked();
18903
18904    cx.assert_state_with_diff(both_hunks_expanded);
18905
18906    cx.set_state(
18907        &r#"
18908        ˇA
18909        b
18910        "#
18911        .unindent(),
18912    );
18913    cx.run_until_parked();
18914
18915    // TODO this cursor position seems bad
18916    cx.assert_state_with_diff(
18917        r#"
18918        - ˇa
18919        + A
18920          b
18921        "#
18922        .unindent(),
18923    );
18924
18925    cx.update_editor(|editor, window, cx| {
18926        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18927    });
18928
18929    cx.assert_state_with_diff(
18930        r#"
18931            - ˇa
18932            + A
18933              b
18934            - c
18935            "#
18936        .unindent(),
18937    );
18938
18939    let hunk_ranges = cx.update_editor(|editor, window, cx| {
18940        let snapshot = editor.snapshot(window, cx);
18941        let hunks = editor
18942            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18943            .collect::<Vec<_>>();
18944        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
18945        let buffer_id = hunks[0].buffer_id;
18946        hunks
18947            .into_iter()
18948            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
18949            .collect::<Vec<_>>()
18950    });
18951    assert_eq!(hunk_ranges.len(), 2);
18952
18953    cx.update_editor(|editor, _, cx| {
18954        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
18955    });
18956    executor.run_until_parked();
18957
18958    cx.assert_state_with_diff(
18959        r#"
18960        - ˇa
18961        + A
18962          b
18963        "#
18964        .unindent(),
18965    );
18966}
18967
18968#[gpui::test]
18969async fn test_toggle_deletion_hunk_at_start_of_file(
18970    executor: BackgroundExecutor,
18971    cx: &mut TestAppContext,
18972) {
18973    init_test(cx, |_| {});
18974    let mut cx = EditorTestContext::new(cx).await;
18975
18976    let diff_base = r#"
18977        a
18978        b
18979        c
18980        "#
18981    .unindent();
18982
18983    cx.set_state(
18984        &r#"
18985        ˇb
18986        c
18987        "#
18988        .unindent(),
18989    );
18990    cx.set_head_text(&diff_base);
18991    cx.update_editor(|editor, window, cx| {
18992        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18993    });
18994    executor.run_until_parked();
18995
18996    let hunk_expanded = r#"
18997        - a
18998          ˇb
18999          c
19000        "#
19001    .unindent();
19002
19003    cx.assert_state_with_diff(hunk_expanded.clone());
19004
19005    let hunk_ranges = cx.update_editor(|editor, window, cx| {
19006        let snapshot = editor.snapshot(window, cx);
19007        let hunks = editor
19008            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19009            .collect::<Vec<_>>();
19010        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
19011        let buffer_id = hunks[0].buffer_id;
19012        hunks
19013            .into_iter()
19014            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
19015            .collect::<Vec<_>>()
19016    });
19017    assert_eq!(hunk_ranges.len(), 1);
19018
19019    cx.update_editor(|editor, _, cx| {
19020        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19021    });
19022    executor.run_until_parked();
19023
19024    let hunk_collapsed = r#"
19025          ˇb
19026          c
19027        "#
19028    .unindent();
19029
19030    cx.assert_state_with_diff(hunk_collapsed);
19031
19032    cx.update_editor(|editor, _, cx| {
19033        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19034    });
19035    executor.run_until_parked();
19036
19037    cx.assert_state_with_diff(hunk_expanded.clone());
19038}
19039
19040#[gpui::test]
19041async fn test_display_diff_hunks(cx: &mut TestAppContext) {
19042    init_test(cx, |_| {});
19043
19044    let fs = FakeFs::new(cx.executor());
19045    fs.insert_tree(
19046        path!("/test"),
19047        json!({
19048            ".git": {},
19049            "file-1": "ONE\n",
19050            "file-2": "TWO\n",
19051            "file-3": "THREE\n",
19052        }),
19053    )
19054    .await;
19055
19056    fs.set_head_for_repo(
19057        path!("/test/.git").as_ref(),
19058        &[
19059            ("file-1".into(), "one\n".into()),
19060            ("file-2".into(), "two\n".into()),
19061            ("file-3".into(), "three\n".into()),
19062        ],
19063        "deadbeef",
19064    );
19065
19066    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
19067    let mut buffers = vec![];
19068    for i in 1..=3 {
19069        let buffer = project
19070            .update(cx, |project, cx| {
19071                let path = format!(path!("/test/file-{}"), i);
19072                project.open_local_buffer(path, cx)
19073            })
19074            .await
19075            .unwrap();
19076        buffers.push(buffer);
19077    }
19078
19079    let multibuffer = cx.new(|cx| {
19080        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
19081        multibuffer.set_all_diff_hunks_expanded(cx);
19082        for buffer in &buffers {
19083            let snapshot = buffer.read(cx).snapshot();
19084            multibuffer.set_excerpts_for_path(
19085                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
19086                buffer.clone(),
19087                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
19088                DEFAULT_MULTIBUFFER_CONTEXT,
19089                cx,
19090            );
19091        }
19092        multibuffer
19093    });
19094
19095    let editor = cx.add_window(|window, cx| {
19096        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
19097    });
19098    cx.run_until_parked();
19099
19100    let snapshot = editor
19101        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19102        .unwrap();
19103    let hunks = snapshot
19104        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
19105        .map(|hunk| match hunk {
19106            DisplayDiffHunk::Unfolded {
19107                display_row_range, ..
19108            } => display_row_range,
19109            DisplayDiffHunk::Folded { .. } => unreachable!(),
19110        })
19111        .collect::<Vec<_>>();
19112    assert_eq!(
19113        hunks,
19114        [
19115            DisplayRow(2)..DisplayRow(4),
19116            DisplayRow(7)..DisplayRow(9),
19117            DisplayRow(12)..DisplayRow(14),
19118        ]
19119    );
19120}
19121
19122#[gpui::test]
19123async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
19124    init_test(cx, |_| {});
19125
19126    let mut cx = EditorTestContext::new(cx).await;
19127    cx.set_head_text(indoc! { "
19128        one
19129        two
19130        three
19131        four
19132        five
19133        "
19134    });
19135    cx.set_index_text(indoc! { "
19136        one
19137        two
19138        three
19139        four
19140        five
19141        "
19142    });
19143    cx.set_state(indoc! {"
19144        one
19145        TWO
19146        ˇTHREE
19147        FOUR
19148        five
19149    "});
19150    cx.run_until_parked();
19151    cx.update_editor(|editor, window, cx| {
19152        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19153    });
19154    cx.run_until_parked();
19155    cx.assert_index_text(Some(indoc! {"
19156        one
19157        TWO
19158        THREE
19159        FOUR
19160        five
19161    "}));
19162    cx.set_state(indoc! { "
19163        one
19164        TWO
19165        ˇTHREE-HUNDRED
19166        FOUR
19167        five
19168    "});
19169    cx.run_until_parked();
19170    cx.update_editor(|editor, window, cx| {
19171        let snapshot = editor.snapshot(window, cx);
19172        let hunks = editor
19173            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19174            .collect::<Vec<_>>();
19175        assert_eq!(hunks.len(), 1);
19176        assert_eq!(
19177            hunks[0].status(),
19178            DiffHunkStatus {
19179                kind: DiffHunkStatusKind::Modified,
19180                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
19181            }
19182        );
19183
19184        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19185    });
19186    cx.run_until_parked();
19187    cx.assert_index_text(Some(indoc! {"
19188        one
19189        TWO
19190        THREE-HUNDRED
19191        FOUR
19192        five
19193    "}));
19194}
19195
19196#[gpui::test]
19197fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
19198    init_test(cx, |_| {});
19199
19200    let editor = cx.add_window(|window, cx| {
19201        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
19202        build_editor(buffer, window, cx)
19203    });
19204
19205    let render_args = Arc::new(Mutex::new(None));
19206    let snapshot = editor
19207        .update(cx, |editor, window, cx| {
19208            let snapshot = editor.buffer().read(cx).snapshot(cx);
19209            let range =
19210                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
19211
19212            struct RenderArgs {
19213                row: MultiBufferRow,
19214                folded: bool,
19215                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
19216            }
19217
19218            let crease = Crease::inline(
19219                range,
19220                FoldPlaceholder::test(),
19221                {
19222                    let toggle_callback = render_args.clone();
19223                    move |row, folded, callback, _window, _cx| {
19224                        *toggle_callback.lock() = Some(RenderArgs {
19225                            row,
19226                            folded,
19227                            callback,
19228                        });
19229                        div()
19230                    }
19231                },
19232                |_row, _folded, _window, _cx| div(),
19233            );
19234
19235            editor.insert_creases(Some(crease), cx);
19236            let snapshot = editor.snapshot(window, cx);
19237            let _div = snapshot.render_crease_toggle(
19238                MultiBufferRow(1),
19239                false,
19240                cx.entity().clone(),
19241                window,
19242                cx,
19243            );
19244            snapshot
19245        })
19246        .unwrap();
19247
19248    let render_args = render_args.lock().take().unwrap();
19249    assert_eq!(render_args.row, MultiBufferRow(1));
19250    assert!(!render_args.folded);
19251    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
19252
19253    cx.update_window(*editor, |_, window, cx| {
19254        (render_args.callback)(true, window, cx)
19255    })
19256    .unwrap();
19257    let snapshot = editor
19258        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19259        .unwrap();
19260    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
19261
19262    cx.update_window(*editor, |_, window, cx| {
19263        (render_args.callback)(false, window, cx)
19264    })
19265    .unwrap();
19266    let snapshot = editor
19267        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19268        .unwrap();
19269    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
19270}
19271
19272#[gpui::test]
19273async fn test_input_text(cx: &mut TestAppContext) {
19274    init_test(cx, |_| {});
19275    let mut cx = EditorTestContext::new(cx).await;
19276
19277    cx.set_state(
19278        &r#"ˇone
19279        two
19280
19281        three
19282        fourˇ
19283        five
19284
19285        siˇx"#
19286            .unindent(),
19287    );
19288
19289    cx.dispatch_action(HandleInput(String::new()));
19290    cx.assert_editor_state(
19291        &r#"ˇone
19292        two
19293
19294        three
19295        fourˇ
19296        five
19297
19298        siˇx"#
19299            .unindent(),
19300    );
19301
19302    cx.dispatch_action(HandleInput("AAAA".to_string()));
19303    cx.assert_editor_state(
19304        &r#"AAAAˇone
19305        two
19306
19307        three
19308        fourAAAAˇ
19309        five
19310
19311        siAAAAˇx"#
19312            .unindent(),
19313    );
19314}
19315
19316#[gpui::test]
19317async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
19318    init_test(cx, |_| {});
19319
19320    let mut cx = EditorTestContext::new(cx).await;
19321    cx.set_state(
19322        r#"let foo = 1;
19323let foo = 2;
19324let foo = 3;
19325let fooˇ = 4;
19326let foo = 5;
19327let foo = 6;
19328let foo = 7;
19329let foo = 8;
19330let foo = 9;
19331let foo = 10;
19332let foo = 11;
19333let foo = 12;
19334let foo = 13;
19335let foo = 14;
19336let foo = 15;"#,
19337    );
19338
19339    cx.update_editor(|e, window, cx| {
19340        assert_eq!(
19341            e.next_scroll_position,
19342            NextScrollCursorCenterTopBottom::Center,
19343            "Default next scroll direction is center",
19344        );
19345
19346        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19347        assert_eq!(
19348            e.next_scroll_position,
19349            NextScrollCursorCenterTopBottom::Top,
19350            "After center, next scroll direction should be top",
19351        );
19352
19353        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19354        assert_eq!(
19355            e.next_scroll_position,
19356            NextScrollCursorCenterTopBottom::Bottom,
19357            "After top, next scroll direction should be bottom",
19358        );
19359
19360        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19361        assert_eq!(
19362            e.next_scroll_position,
19363            NextScrollCursorCenterTopBottom::Center,
19364            "After bottom, scrolling should start over",
19365        );
19366
19367        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19368        assert_eq!(
19369            e.next_scroll_position,
19370            NextScrollCursorCenterTopBottom::Top,
19371            "Scrolling continues if retriggered fast enough"
19372        );
19373    });
19374
19375    cx.executor()
19376        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
19377    cx.executor().run_until_parked();
19378    cx.update_editor(|e, _, _| {
19379        assert_eq!(
19380            e.next_scroll_position,
19381            NextScrollCursorCenterTopBottom::Center,
19382            "If scrolling is not triggered fast enough, it should reset"
19383        );
19384    });
19385}
19386
19387#[gpui::test]
19388async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
19389    init_test(cx, |_| {});
19390    let mut cx = EditorLspTestContext::new_rust(
19391        lsp::ServerCapabilities {
19392            definition_provider: Some(lsp::OneOf::Left(true)),
19393            references_provider: Some(lsp::OneOf::Left(true)),
19394            ..lsp::ServerCapabilities::default()
19395        },
19396        cx,
19397    )
19398    .await;
19399
19400    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
19401        let go_to_definition = cx
19402            .lsp
19403            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
19404                move |params, _| async move {
19405                    if empty_go_to_definition {
19406                        Ok(None)
19407                    } else {
19408                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
19409                            uri: params.text_document_position_params.text_document.uri,
19410                            range: lsp::Range::new(
19411                                lsp::Position::new(4, 3),
19412                                lsp::Position::new(4, 6),
19413                            ),
19414                        })))
19415                    }
19416                },
19417            );
19418        let references = cx
19419            .lsp
19420            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
19421                Ok(Some(vec![lsp::Location {
19422                    uri: params.text_document_position.text_document.uri,
19423                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
19424                }]))
19425            });
19426        (go_to_definition, references)
19427    };
19428
19429    cx.set_state(
19430        &r#"fn one() {
19431            let mut a = ˇtwo();
19432        }
19433
19434        fn two() {}"#
19435            .unindent(),
19436    );
19437    set_up_lsp_handlers(false, &mut cx);
19438    let navigated = cx
19439        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19440        .await
19441        .expect("Failed to navigate to definition");
19442    assert_eq!(
19443        navigated,
19444        Navigated::Yes,
19445        "Should have navigated to definition from the GetDefinition response"
19446    );
19447    cx.assert_editor_state(
19448        &r#"fn one() {
19449            let mut a = two();
19450        }
19451
19452        fn «twoˇ»() {}"#
19453            .unindent(),
19454    );
19455
19456    let editors = cx.update_workspace(|workspace, _, cx| {
19457        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19458    });
19459    cx.update_editor(|_, _, test_editor_cx| {
19460        assert_eq!(
19461            editors.len(),
19462            1,
19463            "Initially, only one, test, editor should be open in the workspace"
19464        );
19465        assert_eq!(
19466            test_editor_cx.entity(),
19467            editors.last().expect("Asserted len is 1").clone()
19468        );
19469    });
19470
19471    set_up_lsp_handlers(true, &mut cx);
19472    let navigated = cx
19473        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19474        .await
19475        .expect("Failed to navigate to lookup references");
19476    assert_eq!(
19477        navigated,
19478        Navigated::Yes,
19479        "Should have navigated to references as a fallback after empty GoToDefinition response"
19480    );
19481    // We should not change the selections in the existing file,
19482    // if opening another milti buffer with the references
19483    cx.assert_editor_state(
19484        &r#"fn one() {
19485            let mut a = two();
19486        }
19487
19488        fn «twoˇ»() {}"#
19489            .unindent(),
19490    );
19491    let editors = cx.update_workspace(|workspace, _, cx| {
19492        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19493    });
19494    cx.update_editor(|_, _, test_editor_cx| {
19495        assert_eq!(
19496            editors.len(),
19497            2,
19498            "After falling back to references search, we open a new editor with the results"
19499        );
19500        let references_fallback_text = editors
19501            .into_iter()
19502            .find(|new_editor| *new_editor != test_editor_cx.entity())
19503            .expect("Should have one non-test editor now")
19504            .read(test_editor_cx)
19505            .text(test_editor_cx);
19506        assert_eq!(
19507            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
19508            "Should use the range from the references response and not the GoToDefinition one"
19509        );
19510    });
19511}
19512
19513#[gpui::test]
19514async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
19515    init_test(cx, |_| {});
19516    cx.update(|cx| {
19517        let mut editor_settings = EditorSettings::get_global(cx).clone();
19518        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
19519        EditorSettings::override_global(editor_settings, cx);
19520    });
19521    let mut cx = EditorLspTestContext::new_rust(
19522        lsp::ServerCapabilities {
19523            definition_provider: Some(lsp::OneOf::Left(true)),
19524            references_provider: Some(lsp::OneOf::Left(true)),
19525            ..lsp::ServerCapabilities::default()
19526        },
19527        cx,
19528    )
19529    .await;
19530    let original_state = r#"fn one() {
19531        let mut a = ˇtwo();
19532    }
19533
19534    fn two() {}"#
19535        .unindent();
19536    cx.set_state(&original_state);
19537
19538    let mut go_to_definition = cx
19539        .lsp
19540        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
19541            move |_, _| async move { Ok(None) },
19542        );
19543    let _references = cx
19544        .lsp
19545        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
19546            panic!("Should not call for references with no go to definition fallback")
19547        });
19548
19549    let navigated = cx
19550        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19551        .await
19552        .expect("Failed to navigate to lookup references");
19553    go_to_definition
19554        .next()
19555        .await
19556        .expect("Should have called the go_to_definition handler");
19557
19558    assert_eq!(
19559        navigated,
19560        Navigated::No,
19561        "Should have navigated to references as a fallback after empty GoToDefinition response"
19562    );
19563    cx.assert_editor_state(&original_state);
19564    let editors = cx.update_workspace(|workspace, _, cx| {
19565        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19566    });
19567    cx.update_editor(|_, _, _| {
19568        assert_eq!(
19569            editors.len(),
19570            1,
19571            "After unsuccessful fallback, no other editor should have been opened"
19572        );
19573    });
19574}
19575
19576#[gpui::test]
19577async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
19578    init_test(cx, |_| {});
19579
19580    let language = Arc::new(Language::new(
19581        LanguageConfig::default(),
19582        Some(tree_sitter_rust::LANGUAGE.into()),
19583    ));
19584
19585    let text = r#"
19586        #[cfg(test)]
19587        mod tests() {
19588            #[test]
19589            fn runnable_1() {
19590                let a = 1;
19591            }
19592
19593            #[test]
19594            fn runnable_2() {
19595                let a = 1;
19596                let b = 2;
19597            }
19598        }
19599    "#
19600    .unindent();
19601
19602    let fs = FakeFs::new(cx.executor());
19603    fs.insert_file("/file.rs", Default::default()).await;
19604
19605    let project = Project::test(fs, ["/a".as_ref()], cx).await;
19606    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19607    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19608    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
19609    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
19610
19611    let editor = cx.new_window_entity(|window, cx| {
19612        Editor::new(
19613            EditorMode::full(),
19614            multi_buffer,
19615            Some(project.clone()),
19616            window,
19617            cx,
19618        )
19619    });
19620
19621    editor.update_in(cx, |editor, window, cx| {
19622        let snapshot = editor.buffer().read(cx).snapshot(cx);
19623        editor.tasks.insert(
19624            (buffer.read(cx).remote_id(), 3),
19625            RunnableTasks {
19626                templates: vec![],
19627                offset: snapshot.anchor_before(43),
19628                column: 0,
19629                extra_variables: HashMap::default(),
19630                context_range: BufferOffset(43)..BufferOffset(85),
19631            },
19632        );
19633        editor.tasks.insert(
19634            (buffer.read(cx).remote_id(), 8),
19635            RunnableTasks {
19636                templates: vec![],
19637                offset: snapshot.anchor_before(86),
19638                column: 0,
19639                extra_variables: HashMap::default(),
19640                context_range: BufferOffset(86)..BufferOffset(191),
19641            },
19642        );
19643
19644        // Test finding task when cursor is inside function body
19645        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19646            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
19647        });
19648        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
19649        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
19650
19651        // Test finding task when cursor is on function name
19652        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19653            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
19654        });
19655        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
19656        assert_eq!(row, 8, "Should find task when cursor is on function name");
19657    });
19658}
19659
19660#[gpui::test]
19661async fn test_folding_buffers(cx: &mut TestAppContext) {
19662    init_test(cx, |_| {});
19663
19664    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
19665    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
19666    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
19667
19668    let fs = FakeFs::new(cx.executor());
19669    fs.insert_tree(
19670        path!("/a"),
19671        json!({
19672            "first.rs": sample_text_1,
19673            "second.rs": sample_text_2,
19674            "third.rs": sample_text_3,
19675        }),
19676    )
19677    .await;
19678    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19679    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19680    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19681    let worktree = project.update(cx, |project, cx| {
19682        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19683        assert_eq!(worktrees.len(), 1);
19684        worktrees.pop().unwrap()
19685    });
19686    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19687
19688    let buffer_1 = project
19689        .update(cx, |project, cx| {
19690            project.open_buffer((worktree_id, "first.rs"), cx)
19691        })
19692        .await
19693        .unwrap();
19694    let buffer_2 = project
19695        .update(cx, |project, cx| {
19696            project.open_buffer((worktree_id, "second.rs"), cx)
19697        })
19698        .await
19699        .unwrap();
19700    let buffer_3 = project
19701        .update(cx, |project, cx| {
19702            project.open_buffer((worktree_id, "third.rs"), cx)
19703        })
19704        .await
19705        .unwrap();
19706
19707    let multi_buffer = cx.new(|cx| {
19708        let mut multi_buffer = MultiBuffer::new(ReadWrite);
19709        multi_buffer.push_excerpts(
19710            buffer_1.clone(),
19711            [
19712                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19713                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19714                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19715            ],
19716            cx,
19717        );
19718        multi_buffer.push_excerpts(
19719            buffer_2.clone(),
19720            [
19721                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19722                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19723                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19724            ],
19725            cx,
19726        );
19727        multi_buffer.push_excerpts(
19728            buffer_3.clone(),
19729            [
19730                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19731                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19732                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19733            ],
19734            cx,
19735        );
19736        multi_buffer
19737    });
19738    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19739        Editor::new(
19740            EditorMode::full(),
19741            multi_buffer.clone(),
19742            Some(project.clone()),
19743            window,
19744            cx,
19745        )
19746    });
19747
19748    assert_eq!(
19749        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19750        "\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",
19751    );
19752
19753    multi_buffer_editor.update(cx, |editor, cx| {
19754        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
19755    });
19756    assert_eq!(
19757        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19758        "\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",
19759        "After folding the first buffer, its text should not be displayed"
19760    );
19761
19762    multi_buffer_editor.update(cx, |editor, cx| {
19763        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
19764    });
19765    assert_eq!(
19766        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19767        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
19768        "After folding the second buffer, its text should not be displayed"
19769    );
19770
19771    multi_buffer_editor.update(cx, |editor, cx| {
19772        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
19773    });
19774    assert_eq!(
19775        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19776        "\n\n\n\n\n",
19777        "After folding the third buffer, its text should not be displayed"
19778    );
19779
19780    // Emulate selection inside the fold logic, that should work
19781    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19782        editor
19783            .snapshot(window, cx)
19784            .next_line_boundary(Point::new(0, 4));
19785    });
19786
19787    multi_buffer_editor.update(cx, |editor, cx| {
19788        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
19789    });
19790    assert_eq!(
19791        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19792        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
19793        "After unfolding the second buffer, its text should be displayed"
19794    );
19795
19796    // Typing inside of buffer 1 causes that buffer to be unfolded.
19797    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19798        assert_eq!(
19799            multi_buffer
19800                .read(cx)
19801                .snapshot(cx)
19802                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
19803                .collect::<String>(),
19804            "bbbb"
19805        );
19806        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
19807            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
19808        });
19809        editor.handle_input("B", window, cx);
19810    });
19811
19812    assert_eq!(
19813        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19814        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
19815        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
19816    );
19817
19818    multi_buffer_editor.update(cx, |editor, cx| {
19819        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
19820    });
19821    assert_eq!(
19822        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19823        "\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",
19824        "After unfolding the all buffers, all original text should be displayed"
19825    );
19826}
19827
19828#[gpui::test]
19829async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
19830    init_test(cx, |_| {});
19831
19832    let sample_text_1 = "1111\n2222\n3333".to_string();
19833    let sample_text_2 = "4444\n5555\n6666".to_string();
19834    let sample_text_3 = "7777\n8888\n9999".to_string();
19835
19836    let fs = FakeFs::new(cx.executor());
19837    fs.insert_tree(
19838        path!("/a"),
19839        json!({
19840            "first.rs": sample_text_1,
19841            "second.rs": sample_text_2,
19842            "third.rs": sample_text_3,
19843        }),
19844    )
19845    .await;
19846    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19847    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19848    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19849    let worktree = project.update(cx, |project, cx| {
19850        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19851        assert_eq!(worktrees.len(), 1);
19852        worktrees.pop().unwrap()
19853    });
19854    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19855
19856    let buffer_1 = project
19857        .update(cx, |project, cx| {
19858            project.open_buffer((worktree_id, "first.rs"), cx)
19859        })
19860        .await
19861        .unwrap();
19862    let buffer_2 = project
19863        .update(cx, |project, cx| {
19864            project.open_buffer((worktree_id, "second.rs"), cx)
19865        })
19866        .await
19867        .unwrap();
19868    let buffer_3 = project
19869        .update(cx, |project, cx| {
19870            project.open_buffer((worktree_id, "third.rs"), cx)
19871        })
19872        .await
19873        .unwrap();
19874
19875    let multi_buffer = cx.new(|cx| {
19876        let mut multi_buffer = MultiBuffer::new(ReadWrite);
19877        multi_buffer.push_excerpts(
19878            buffer_1.clone(),
19879            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19880            cx,
19881        );
19882        multi_buffer.push_excerpts(
19883            buffer_2.clone(),
19884            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19885            cx,
19886        );
19887        multi_buffer.push_excerpts(
19888            buffer_3.clone(),
19889            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19890            cx,
19891        );
19892        multi_buffer
19893    });
19894
19895    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19896        Editor::new(
19897            EditorMode::full(),
19898            multi_buffer,
19899            Some(project.clone()),
19900            window,
19901            cx,
19902        )
19903    });
19904
19905    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
19906    assert_eq!(
19907        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19908        full_text,
19909    );
19910
19911    multi_buffer_editor.update(cx, |editor, cx| {
19912        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
19913    });
19914    assert_eq!(
19915        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19916        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
19917        "After folding the first buffer, its text should not be displayed"
19918    );
19919
19920    multi_buffer_editor.update(cx, |editor, cx| {
19921        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
19922    });
19923
19924    assert_eq!(
19925        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19926        "\n\n\n\n\n\n7777\n8888\n9999",
19927        "After folding the second buffer, its text should not be displayed"
19928    );
19929
19930    multi_buffer_editor.update(cx, |editor, cx| {
19931        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
19932    });
19933    assert_eq!(
19934        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19935        "\n\n\n\n\n",
19936        "After folding the third buffer, its text should not be displayed"
19937    );
19938
19939    multi_buffer_editor.update(cx, |editor, cx| {
19940        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
19941    });
19942    assert_eq!(
19943        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19944        "\n\n\n\n4444\n5555\n6666\n\n",
19945        "After unfolding the second buffer, its text should be displayed"
19946    );
19947
19948    multi_buffer_editor.update(cx, |editor, cx| {
19949        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
19950    });
19951    assert_eq!(
19952        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19953        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
19954        "After unfolding the first buffer, its text should be displayed"
19955    );
19956
19957    multi_buffer_editor.update(cx, |editor, cx| {
19958        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
19959    });
19960    assert_eq!(
19961        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19962        full_text,
19963        "After unfolding all buffers, all original text should be displayed"
19964    );
19965}
19966
19967#[gpui::test]
19968async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
19969    init_test(cx, |_| {});
19970
19971    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
19972
19973    let fs = FakeFs::new(cx.executor());
19974    fs.insert_tree(
19975        path!("/a"),
19976        json!({
19977            "main.rs": sample_text,
19978        }),
19979    )
19980    .await;
19981    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19982    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19983    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19984    let worktree = project.update(cx, |project, cx| {
19985        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19986        assert_eq!(worktrees.len(), 1);
19987        worktrees.pop().unwrap()
19988    });
19989    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19990
19991    let buffer_1 = project
19992        .update(cx, |project, cx| {
19993            project.open_buffer((worktree_id, "main.rs"), cx)
19994        })
19995        .await
19996        .unwrap();
19997
19998    let multi_buffer = cx.new(|cx| {
19999        let mut multi_buffer = MultiBuffer::new(ReadWrite);
20000        multi_buffer.push_excerpts(
20001            buffer_1.clone(),
20002            [ExcerptRange::new(
20003                Point::new(0, 0)
20004                    ..Point::new(
20005                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
20006                        0,
20007                    ),
20008            )],
20009            cx,
20010        );
20011        multi_buffer
20012    });
20013    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20014        Editor::new(
20015            EditorMode::full(),
20016            multi_buffer,
20017            Some(project.clone()),
20018            window,
20019            cx,
20020        )
20021    });
20022
20023    let selection_range = Point::new(1, 0)..Point::new(2, 0);
20024    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20025        enum TestHighlight {}
20026        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
20027        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
20028        editor.highlight_text::<TestHighlight>(
20029            vec![highlight_range.clone()],
20030            HighlightStyle::color(Hsla::green()),
20031            cx,
20032        );
20033        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20034            s.select_ranges(Some(highlight_range))
20035        });
20036    });
20037
20038    let full_text = format!("\n\n{sample_text}");
20039    assert_eq!(
20040        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20041        full_text,
20042    );
20043}
20044
20045#[gpui::test]
20046async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
20047    init_test(cx, |_| {});
20048    cx.update(|cx| {
20049        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
20050            "keymaps/default-linux.json",
20051            cx,
20052        )
20053        .unwrap();
20054        cx.bind_keys(default_key_bindings);
20055    });
20056
20057    let (editor, cx) = cx.add_window_view(|window, cx| {
20058        let multi_buffer = MultiBuffer::build_multi(
20059            [
20060                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
20061                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
20062                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
20063                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
20064            ],
20065            cx,
20066        );
20067        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
20068
20069        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
20070        // fold all but the second buffer, so that we test navigating between two
20071        // adjacent folded buffers, as well as folded buffers at the start and
20072        // end the multibuffer
20073        editor.fold_buffer(buffer_ids[0], cx);
20074        editor.fold_buffer(buffer_ids[2], cx);
20075        editor.fold_buffer(buffer_ids[3], cx);
20076
20077        editor
20078    });
20079    cx.simulate_resize(size(px(1000.), px(1000.)));
20080
20081    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
20082    cx.assert_excerpts_with_selections(indoc! {"
20083        [EXCERPT]
20084        ˇ[FOLDED]
20085        [EXCERPT]
20086        a1
20087        b1
20088        [EXCERPT]
20089        [FOLDED]
20090        [EXCERPT]
20091        [FOLDED]
20092        "
20093    });
20094    cx.simulate_keystroke("down");
20095    cx.assert_excerpts_with_selections(indoc! {"
20096        [EXCERPT]
20097        [FOLDED]
20098        [EXCERPT]
20099        ˇa1
20100        b1
20101        [EXCERPT]
20102        [FOLDED]
20103        [EXCERPT]
20104        [FOLDED]
20105        "
20106    });
20107    cx.simulate_keystroke("down");
20108    cx.assert_excerpts_with_selections(indoc! {"
20109        [EXCERPT]
20110        [FOLDED]
20111        [EXCERPT]
20112        a1
20113        ˇb1
20114        [EXCERPT]
20115        [FOLDED]
20116        [EXCERPT]
20117        [FOLDED]
20118        "
20119    });
20120    cx.simulate_keystroke("down");
20121    cx.assert_excerpts_with_selections(indoc! {"
20122        [EXCERPT]
20123        [FOLDED]
20124        [EXCERPT]
20125        a1
20126        b1
20127        ˇ[EXCERPT]
20128        [FOLDED]
20129        [EXCERPT]
20130        [FOLDED]
20131        "
20132    });
20133    cx.simulate_keystroke("down");
20134    cx.assert_excerpts_with_selections(indoc! {"
20135        [EXCERPT]
20136        [FOLDED]
20137        [EXCERPT]
20138        a1
20139        b1
20140        [EXCERPT]
20141        ˇ[FOLDED]
20142        [EXCERPT]
20143        [FOLDED]
20144        "
20145    });
20146    for _ in 0..5 {
20147        cx.simulate_keystroke("down");
20148        cx.assert_excerpts_with_selections(indoc! {"
20149            [EXCERPT]
20150            [FOLDED]
20151            [EXCERPT]
20152            a1
20153            b1
20154            [EXCERPT]
20155            [FOLDED]
20156            [EXCERPT]
20157            ˇ[FOLDED]
20158            "
20159        });
20160    }
20161
20162    cx.simulate_keystroke("up");
20163    cx.assert_excerpts_with_selections(indoc! {"
20164        [EXCERPT]
20165        [FOLDED]
20166        [EXCERPT]
20167        a1
20168        b1
20169        [EXCERPT]
20170        ˇ[FOLDED]
20171        [EXCERPT]
20172        [FOLDED]
20173        "
20174    });
20175    cx.simulate_keystroke("up");
20176    cx.assert_excerpts_with_selections(indoc! {"
20177        [EXCERPT]
20178        [FOLDED]
20179        [EXCERPT]
20180        a1
20181        b1
20182        ˇ[EXCERPT]
20183        [FOLDED]
20184        [EXCERPT]
20185        [FOLDED]
20186        "
20187    });
20188    cx.simulate_keystroke("up");
20189    cx.assert_excerpts_with_selections(indoc! {"
20190        [EXCERPT]
20191        [FOLDED]
20192        [EXCERPT]
20193        a1
20194        ˇb1
20195        [EXCERPT]
20196        [FOLDED]
20197        [EXCERPT]
20198        [FOLDED]
20199        "
20200    });
20201    cx.simulate_keystroke("up");
20202    cx.assert_excerpts_with_selections(indoc! {"
20203        [EXCERPT]
20204        [FOLDED]
20205        [EXCERPT]
20206        ˇa1
20207        b1
20208        [EXCERPT]
20209        [FOLDED]
20210        [EXCERPT]
20211        [FOLDED]
20212        "
20213    });
20214    for _ in 0..5 {
20215        cx.simulate_keystroke("up");
20216        cx.assert_excerpts_with_selections(indoc! {"
20217            [EXCERPT]
20218            ˇ[FOLDED]
20219            [EXCERPT]
20220            a1
20221            b1
20222            [EXCERPT]
20223            [FOLDED]
20224            [EXCERPT]
20225            [FOLDED]
20226            "
20227        });
20228    }
20229}
20230
20231#[gpui::test]
20232async fn test_inline_completion_text(cx: &mut TestAppContext) {
20233    init_test(cx, |_| {});
20234
20235    // Simple insertion
20236    assert_highlighted_edits(
20237        "Hello, world!",
20238        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
20239        true,
20240        cx,
20241        |highlighted_edits, cx| {
20242            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
20243            assert_eq!(highlighted_edits.highlights.len(), 1);
20244            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
20245            assert_eq!(
20246                highlighted_edits.highlights[0].1.background_color,
20247                Some(cx.theme().status().created_background)
20248            );
20249        },
20250    )
20251    .await;
20252
20253    // Replacement
20254    assert_highlighted_edits(
20255        "This is a test.",
20256        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
20257        false,
20258        cx,
20259        |highlighted_edits, cx| {
20260            assert_eq!(highlighted_edits.text, "That is a test.");
20261            assert_eq!(highlighted_edits.highlights.len(), 1);
20262            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
20263            assert_eq!(
20264                highlighted_edits.highlights[0].1.background_color,
20265                Some(cx.theme().status().created_background)
20266            );
20267        },
20268    )
20269    .await;
20270
20271    // Multiple edits
20272    assert_highlighted_edits(
20273        "Hello, world!",
20274        vec![
20275            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
20276            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
20277        ],
20278        false,
20279        cx,
20280        |highlighted_edits, cx| {
20281            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
20282            assert_eq!(highlighted_edits.highlights.len(), 2);
20283            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
20284            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
20285            assert_eq!(
20286                highlighted_edits.highlights[0].1.background_color,
20287                Some(cx.theme().status().created_background)
20288            );
20289            assert_eq!(
20290                highlighted_edits.highlights[1].1.background_color,
20291                Some(cx.theme().status().created_background)
20292            );
20293        },
20294    )
20295    .await;
20296
20297    // Multiple lines with edits
20298    assert_highlighted_edits(
20299        "First line\nSecond line\nThird line\nFourth line",
20300        vec![
20301            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
20302            (
20303                Point::new(2, 0)..Point::new(2, 10),
20304                "New third line".to_string(),
20305            ),
20306            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
20307        ],
20308        false,
20309        cx,
20310        |highlighted_edits, cx| {
20311            assert_eq!(
20312                highlighted_edits.text,
20313                "Second modified\nNew third line\nFourth updated line"
20314            );
20315            assert_eq!(highlighted_edits.highlights.len(), 3);
20316            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
20317            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
20318            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
20319            for highlight in &highlighted_edits.highlights {
20320                assert_eq!(
20321                    highlight.1.background_color,
20322                    Some(cx.theme().status().created_background)
20323                );
20324            }
20325        },
20326    )
20327    .await;
20328}
20329
20330#[gpui::test]
20331async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
20332    init_test(cx, |_| {});
20333
20334    // Deletion
20335    assert_highlighted_edits(
20336        "Hello, world!",
20337        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
20338        true,
20339        cx,
20340        |highlighted_edits, cx| {
20341            assert_eq!(highlighted_edits.text, "Hello, world!");
20342            assert_eq!(highlighted_edits.highlights.len(), 1);
20343            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
20344            assert_eq!(
20345                highlighted_edits.highlights[0].1.background_color,
20346                Some(cx.theme().status().deleted_background)
20347            );
20348        },
20349    )
20350    .await;
20351
20352    // Insertion
20353    assert_highlighted_edits(
20354        "Hello, world!",
20355        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
20356        true,
20357        cx,
20358        |highlighted_edits, cx| {
20359            assert_eq!(highlighted_edits.highlights.len(), 1);
20360            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
20361            assert_eq!(
20362                highlighted_edits.highlights[0].1.background_color,
20363                Some(cx.theme().status().created_background)
20364            );
20365        },
20366    )
20367    .await;
20368}
20369
20370async fn assert_highlighted_edits(
20371    text: &str,
20372    edits: Vec<(Range<Point>, String)>,
20373    include_deletions: bool,
20374    cx: &mut TestAppContext,
20375    assertion_fn: impl Fn(HighlightedText, &App),
20376) {
20377    let window = cx.add_window(|window, cx| {
20378        let buffer = MultiBuffer::build_simple(text, cx);
20379        Editor::new(EditorMode::full(), buffer, None, window, cx)
20380    });
20381    let cx = &mut VisualTestContext::from_window(*window, cx);
20382
20383    let (buffer, snapshot) = window
20384        .update(cx, |editor, _window, cx| {
20385            (
20386                editor.buffer().clone(),
20387                editor.buffer().read(cx).snapshot(cx),
20388            )
20389        })
20390        .unwrap();
20391
20392    let edits = edits
20393        .into_iter()
20394        .map(|(range, edit)| {
20395            (
20396                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
20397                edit,
20398            )
20399        })
20400        .collect::<Vec<_>>();
20401
20402    let text_anchor_edits = edits
20403        .clone()
20404        .into_iter()
20405        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
20406        .collect::<Vec<_>>();
20407
20408    let edit_preview = window
20409        .update(cx, |_, _window, cx| {
20410            buffer
20411                .read(cx)
20412                .as_singleton()
20413                .unwrap()
20414                .read(cx)
20415                .preview_edits(text_anchor_edits.into(), cx)
20416        })
20417        .unwrap()
20418        .await;
20419
20420    cx.update(|_window, cx| {
20421        let highlighted_edits = inline_completion_edit_text(
20422            &snapshot.as_singleton().unwrap().2,
20423            &edits,
20424            &edit_preview,
20425            include_deletions,
20426            cx,
20427        );
20428        assertion_fn(highlighted_edits, cx)
20429    });
20430}
20431
20432#[track_caller]
20433fn assert_breakpoint(
20434    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
20435    path: &Arc<Path>,
20436    expected: Vec<(u32, Breakpoint)>,
20437) {
20438    if expected.len() == 0usize {
20439        assert!(!breakpoints.contains_key(path), "{}", path.display());
20440    } else {
20441        let mut breakpoint = breakpoints
20442            .get(path)
20443            .unwrap()
20444            .into_iter()
20445            .map(|breakpoint| {
20446                (
20447                    breakpoint.row,
20448                    Breakpoint {
20449                        message: breakpoint.message.clone(),
20450                        state: breakpoint.state,
20451                        condition: breakpoint.condition.clone(),
20452                        hit_condition: breakpoint.hit_condition.clone(),
20453                    },
20454                )
20455            })
20456            .collect::<Vec<_>>();
20457
20458        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
20459
20460        assert_eq!(expected, breakpoint);
20461    }
20462}
20463
20464fn add_log_breakpoint_at_cursor(
20465    editor: &mut Editor,
20466    log_message: &str,
20467    window: &mut Window,
20468    cx: &mut Context<Editor>,
20469) {
20470    let (anchor, bp) = editor
20471        .breakpoints_at_cursors(window, cx)
20472        .first()
20473        .and_then(|(anchor, bp)| {
20474            if let Some(bp) = bp {
20475                Some((*anchor, bp.clone()))
20476            } else {
20477                None
20478            }
20479        })
20480        .unwrap_or_else(|| {
20481            let cursor_position: Point = editor.selections.newest(cx).head();
20482
20483            let breakpoint_position = editor
20484                .snapshot(window, cx)
20485                .display_snapshot
20486                .buffer_snapshot
20487                .anchor_before(Point::new(cursor_position.row, 0));
20488
20489            (breakpoint_position, Breakpoint::new_log(&log_message))
20490        });
20491
20492    editor.edit_breakpoint_at_anchor(
20493        anchor,
20494        bp,
20495        BreakpointEditAction::EditLogMessage(log_message.into()),
20496        cx,
20497    );
20498}
20499
20500#[gpui::test]
20501async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
20502    init_test(cx, |_| {});
20503
20504    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20505    let fs = FakeFs::new(cx.executor());
20506    fs.insert_tree(
20507        path!("/a"),
20508        json!({
20509            "main.rs": sample_text,
20510        }),
20511    )
20512    .await;
20513    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20514    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20515    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20516
20517    let fs = FakeFs::new(cx.executor());
20518    fs.insert_tree(
20519        path!("/a"),
20520        json!({
20521            "main.rs": sample_text,
20522        }),
20523    )
20524    .await;
20525    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20526    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20527    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20528    let worktree_id = workspace
20529        .update(cx, |workspace, _window, cx| {
20530            workspace.project().update(cx, |project, cx| {
20531                project.worktrees(cx).next().unwrap().read(cx).id()
20532            })
20533        })
20534        .unwrap();
20535
20536    let buffer = project
20537        .update(cx, |project, cx| {
20538            project.open_buffer((worktree_id, "main.rs"), cx)
20539        })
20540        .await
20541        .unwrap();
20542
20543    let (editor, cx) = cx.add_window_view(|window, cx| {
20544        Editor::new(
20545            EditorMode::full(),
20546            MultiBuffer::build_from_buffer(buffer, cx),
20547            Some(project.clone()),
20548            window,
20549            cx,
20550        )
20551    });
20552
20553    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20554    let abs_path = project.read_with(cx, |project, cx| {
20555        project
20556            .absolute_path(&project_path, cx)
20557            .map(|path_buf| Arc::from(path_buf.to_owned()))
20558            .unwrap()
20559    });
20560
20561    // assert we can add breakpoint on the first line
20562    editor.update_in(cx, |editor, window, cx| {
20563        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20564        editor.move_to_end(&MoveToEnd, window, cx);
20565        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20566    });
20567
20568    let breakpoints = editor.update(cx, |editor, cx| {
20569        editor
20570            .breakpoint_store()
20571            .as_ref()
20572            .unwrap()
20573            .read(cx)
20574            .all_source_breakpoints(cx)
20575            .clone()
20576    });
20577
20578    assert_eq!(1, breakpoints.len());
20579    assert_breakpoint(
20580        &breakpoints,
20581        &abs_path,
20582        vec![
20583            (0, Breakpoint::new_standard()),
20584            (3, Breakpoint::new_standard()),
20585        ],
20586    );
20587
20588    editor.update_in(cx, |editor, window, cx| {
20589        editor.move_to_beginning(&MoveToBeginning, window, cx);
20590        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20591    });
20592
20593    let breakpoints = editor.update(cx, |editor, cx| {
20594        editor
20595            .breakpoint_store()
20596            .as_ref()
20597            .unwrap()
20598            .read(cx)
20599            .all_source_breakpoints(cx)
20600            .clone()
20601    });
20602
20603    assert_eq!(1, breakpoints.len());
20604    assert_breakpoint(
20605        &breakpoints,
20606        &abs_path,
20607        vec![(3, Breakpoint::new_standard())],
20608    );
20609
20610    editor.update_in(cx, |editor, window, cx| {
20611        editor.move_to_end(&MoveToEnd, window, cx);
20612        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20613    });
20614
20615    let breakpoints = editor.update(cx, |editor, cx| {
20616        editor
20617            .breakpoint_store()
20618            .as_ref()
20619            .unwrap()
20620            .read(cx)
20621            .all_source_breakpoints(cx)
20622            .clone()
20623    });
20624
20625    assert_eq!(0, breakpoints.len());
20626    assert_breakpoint(&breakpoints, &abs_path, vec![]);
20627}
20628
20629#[gpui::test]
20630async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
20631    init_test(cx, |_| {});
20632
20633    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20634
20635    let fs = FakeFs::new(cx.executor());
20636    fs.insert_tree(
20637        path!("/a"),
20638        json!({
20639            "main.rs": sample_text,
20640        }),
20641    )
20642    .await;
20643    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20644    let (workspace, cx) =
20645        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
20646
20647    let worktree_id = workspace.update(cx, |workspace, cx| {
20648        workspace.project().update(cx, |project, cx| {
20649            project.worktrees(cx).next().unwrap().read(cx).id()
20650        })
20651    });
20652
20653    let buffer = project
20654        .update(cx, |project, cx| {
20655            project.open_buffer((worktree_id, "main.rs"), cx)
20656        })
20657        .await
20658        .unwrap();
20659
20660    let (editor, cx) = cx.add_window_view(|window, cx| {
20661        Editor::new(
20662            EditorMode::full(),
20663            MultiBuffer::build_from_buffer(buffer, cx),
20664            Some(project.clone()),
20665            window,
20666            cx,
20667        )
20668    });
20669
20670    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20671    let abs_path = project.read_with(cx, |project, cx| {
20672        project
20673            .absolute_path(&project_path, cx)
20674            .map(|path_buf| Arc::from(path_buf.to_owned()))
20675            .unwrap()
20676    });
20677
20678    editor.update_in(cx, |editor, window, cx| {
20679        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
20680    });
20681
20682    let breakpoints = editor.update(cx, |editor, cx| {
20683        editor
20684            .breakpoint_store()
20685            .as_ref()
20686            .unwrap()
20687            .read(cx)
20688            .all_source_breakpoints(cx)
20689            .clone()
20690    });
20691
20692    assert_breakpoint(
20693        &breakpoints,
20694        &abs_path,
20695        vec![(0, Breakpoint::new_log("hello world"))],
20696    );
20697
20698    // Removing a log message from a log breakpoint should remove it
20699    editor.update_in(cx, |editor, window, cx| {
20700        add_log_breakpoint_at_cursor(editor, "", window, cx);
20701    });
20702
20703    let breakpoints = editor.update(cx, |editor, cx| {
20704        editor
20705            .breakpoint_store()
20706            .as_ref()
20707            .unwrap()
20708            .read(cx)
20709            .all_source_breakpoints(cx)
20710            .clone()
20711    });
20712
20713    assert_breakpoint(&breakpoints, &abs_path, vec![]);
20714
20715    editor.update_in(cx, |editor, window, cx| {
20716        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20717        editor.move_to_end(&MoveToEnd, window, cx);
20718        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20719        // Not adding a log message to a standard breakpoint shouldn't remove it
20720        add_log_breakpoint_at_cursor(editor, "", window, cx);
20721    });
20722
20723    let breakpoints = editor.update(cx, |editor, cx| {
20724        editor
20725            .breakpoint_store()
20726            .as_ref()
20727            .unwrap()
20728            .read(cx)
20729            .all_source_breakpoints(cx)
20730            .clone()
20731    });
20732
20733    assert_breakpoint(
20734        &breakpoints,
20735        &abs_path,
20736        vec![
20737            (0, Breakpoint::new_standard()),
20738            (3, Breakpoint::new_standard()),
20739        ],
20740    );
20741
20742    editor.update_in(cx, |editor, window, cx| {
20743        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
20744    });
20745
20746    let breakpoints = editor.update(cx, |editor, cx| {
20747        editor
20748            .breakpoint_store()
20749            .as_ref()
20750            .unwrap()
20751            .read(cx)
20752            .all_source_breakpoints(cx)
20753            .clone()
20754    });
20755
20756    assert_breakpoint(
20757        &breakpoints,
20758        &abs_path,
20759        vec![
20760            (0, Breakpoint::new_standard()),
20761            (3, Breakpoint::new_log("hello world")),
20762        ],
20763    );
20764
20765    editor.update_in(cx, |editor, window, cx| {
20766        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
20767    });
20768
20769    let breakpoints = editor.update(cx, |editor, cx| {
20770        editor
20771            .breakpoint_store()
20772            .as_ref()
20773            .unwrap()
20774            .read(cx)
20775            .all_source_breakpoints(cx)
20776            .clone()
20777    });
20778
20779    assert_breakpoint(
20780        &breakpoints,
20781        &abs_path,
20782        vec![
20783            (0, Breakpoint::new_standard()),
20784            (3, Breakpoint::new_log("hello Earth!!")),
20785        ],
20786    );
20787}
20788
20789/// This also tests that Editor::breakpoint_at_cursor_head is working properly
20790/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
20791/// or when breakpoints were placed out of order. This tests for a regression too
20792#[gpui::test]
20793async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
20794    init_test(cx, |_| {});
20795
20796    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20797    let fs = FakeFs::new(cx.executor());
20798    fs.insert_tree(
20799        path!("/a"),
20800        json!({
20801            "main.rs": sample_text,
20802        }),
20803    )
20804    .await;
20805    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20806    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20807    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20808
20809    let fs = FakeFs::new(cx.executor());
20810    fs.insert_tree(
20811        path!("/a"),
20812        json!({
20813            "main.rs": sample_text,
20814        }),
20815    )
20816    .await;
20817    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20818    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20819    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20820    let worktree_id = workspace
20821        .update(cx, |workspace, _window, cx| {
20822            workspace.project().update(cx, |project, cx| {
20823                project.worktrees(cx).next().unwrap().read(cx).id()
20824            })
20825        })
20826        .unwrap();
20827
20828    let buffer = project
20829        .update(cx, |project, cx| {
20830            project.open_buffer((worktree_id, "main.rs"), cx)
20831        })
20832        .await
20833        .unwrap();
20834
20835    let (editor, cx) = cx.add_window_view(|window, cx| {
20836        Editor::new(
20837            EditorMode::full(),
20838            MultiBuffer::build_from_buffer(buffer, cx),
20839            Some(project.clone()),
20840            window,
20841            cx,
20842        )
20843    });
20844
20845    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20846    let abs_path = project.read_with(cx, |project, cx| {
20847        project
20848            .absolute_path(&project_path, cx)
20849            .map(|path_buf| Arc::from(path_buf.to_owned()))
20850            .unwrap()
20851    });
20852
20853    // assert we can add breakpoint on the first line
20854    editor.update_in(cx, |editor, window, cx| {
20855        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20856        editor.move_to_end(&MoveToEnd, window, cx);
20857        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20858        editor.move_up(&MoveUp, window, cx);
20859        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20860    });
20861
20862    let breakpoints = editor.update(cx, |editor, cx| {
20863        editor
20864            .breakpoint_store()
20865            .as_ref()
20866            .unwrap()
20867            .read(cx)
20868            .all_source_breakpoints(cx)
20869            .clone()
20870    });
20871
20872    assert_eq!(1, breakpoints.len());
20873    assert_breakpoint(
20874        &breakpoints,
20875        &abs_path,
20876        vec![
20877            (0, Breakpoint::new_standard()),
20878            (2, Breakpoint::new_standard()),
20879            (3, Breakpoint::new_standard()),
20880        ],
20881    );
20882
20883    editor.update_in(cx, |editor, window, cx| {
20884        editor.move_to_beginning(&MoveToBeginning, window, cx);
20885        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20886        editor.move_to_end(&MoveToEnd, window, cx);
20887        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20888        // Disabling a breakpoint that doesn't exist should do nothing
20889        editor.move_up(&MoveUp, window, cx);
20890        editor.move_up(&MoveUp, window, cx);
20891        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20892    });
20893
20894    let breakpoints = editor.update(cx, |editor, cx| {
20895        editor
20896            .breakpoint_store()
20897            .as_ref()
20898            .unwrap()
20899            .read(cx)
20900            .all_source_breakpoints(cx)
20901            .clone()
20902    });
20903
20904    let disable_breakpoint = {
20905        let mut bp = Breakpoint::new_standard();
20906        bp.state = BreakpointState::Disabled;
20907        bp
20908    };
20909
20910    assert_eq!(1, breakpoints.len());
20911    assert_breakpoint(
20912        &breakpoints,
20913        &abs_path,
20914        vec![
20915            (0, disable_breakpoint.clone()),
20916            (2, Breakpoint::new_standard()),
20917            (3, disable_breakpoint.clone()),
20918        ],
20919    );
20920
20921    editor.update_in(cx, |editor, window, cx| {
20922        editor.move_to_beginning(&MoveToBeginning, window, cx);
20923        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
20924        editor.move_to_end(&MoveToEnd, window, cx);
20925        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
20926        editor.move_up(&MoveUp, window, cx);
20927        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20928    });
20929
20930    let breakpoints = editor.update(cx, |editor, cx| {
20931        editor
20932            .breakpoint_store()
20933            .as_ref()
20934            .unwrap()
20935            .read(cx)
20936            .all_source_breakpoints(cx)
20937            .clone()
20938    });
20939
20940    assert_eq!(1, breakpoints.len());
20941    assert_breakpoint(
20942        &breakpoints,
20943        &abs_path,
20944        vec![
20945            (0, Breakpoint::new_standard()),
20946            (2, disable_breakpoint),
20947            (3, Breakpoint::new_standard()),
20948        ],
20949    );
20950}
20951
20952#[gpui::test]
20953async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
20954    init_test(cx, |_| {});
20955    let capabilities = lsp::ServerCapabilities {
20956        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
20957            prepare_provider: Some(true),
20958            work_done_progress_options: Default::default(),
20959        })),
20960        ..Default::default()
20961    };
20962    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
20963
20964    cx.set_state(indoc! {"
20965        struct Fˇoo {}
20966    "});
20967
20968    cx.update_editor(|editor, _, cx| {
20969        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
20970        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
20971        editor.highlight_background::<DocumentHighlightRead>(
20972            &[highlight_range],
20973            |theme| theme.colors().editor_document_highlight_read_background,
20974            cx,
20975        );
20976    });
20977
20978    let mut prepare_rename_handler = cx
20979        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
20980            move |_, _, _| async move {
20981                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
20982                    start: lsp::Position {
20983                        line: 0,
20984                        character: 7,
20985                    },
20986                    end: lsp::Position {
20987                        line: 0,
20988                        character: 10,
20989                    },
20990                })))
20991            },
20992        );
20993    let prepare_rename_task = cx
20994        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
20995        .expect("Prepare rename was not started");
20996    prepare_rename_handler.next().await.unwrap();
20997    prepare_rename_task.await.expect("Prepare rename failed");
20998
20999    let mut rename_handler =
21000        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
21001            let edit = lsp::TextEdit {
21002                range: lsp::Range {
21003                    start: lsp::Position {
21004                        line: 0,
21005                        character: 7,
21006                    },
21007                    end: lsp::Position {
21008                        line: 0,
21009                        character: 10,
21010                    },
21011                },
21012                new_text: "FooRenamed".to_string(),
21013            };
21014            Ok(Some(lsp::WorkspaceEdit::new(
21015                // Specify the same edit twice
21016                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
21017            )))
21018        });
21019    let rename_task = cx
21020        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
21021        .expect("Confirm rename was not started");
21022    rename_handler.next().await.unwrap();
21023    rename_task.await.expect("Confirm rename failed");
21024    cx.run_until_parked();
21025
21026    // Despite two edits, only one is actually applied as those are identical
21027    cx.assert_editor_state(indoc! {"
21028        struct FooRenamedˇ {}
21029    "});
21030}
21031
21032#[gpui::test]
21033async fn test_rename_without_prepare(cx: &mut TestAppContext) {
21034    init_test(cx, |_| {});
21035    // These capabilities indicate that the server does not support prepare rename.
21036    let capabilities = lsp::ServerCapabilities {
21037        rename_provider: Some(lsp::OneOf::Left(true)),
21038        ..Default::default()
21039    };
21040    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
21041
21042    cx.set_state(indoc! {"
21043        struct Fˇoo {}
21044    "});
21045
21046    cx.update_editor(|editor, _window, cx| {
21047        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
21048        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
21049        editor.highlight_background::<DocumentHighlightRead>(
21050            &[highlight_range],
21051            |theme| theme.colors().editor_document_highlight_read_background,
21052            cx,
21053        );
21054    });
21055
21056    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
21057        .expect("Prepare rename was not started")
21058        .await
21059        .expect("Prepare rename failed");
21060
21061    let mut rename_handler =
21062        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
21063            let edit = lsp::TextEdit {
21064                range: lsp::Range {
21065                    start: lsp::Position {
21066                        line: 0,
21067                        character: 7,
21068                    },
21069                    end: lsp::Position {
21070                        line: 0,
21071                        character: 10,
21072                    },
21073                },
21074                new_text: "FooRenamed".to_string(),
21075            };
21076            Ok(Some(lsp::WorkspaceEdit::new(
21077                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
21078            )))
21079        });
21080    let rename_task = cx
21081        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
21082        .expect("Confirm rename was not started");
21083    rename_handler.next().await.unwrap();
21084    rename_task.await.expect("Confirm rename failed");
21085    cx.run_until_parked();
21086
21087    // Correct range is renamed, as `surrounding_word` is used to find it.
21088    cx.assert_editor_state(indoc! {"
21089        struct FooRenamedˇ {}
21090    "});
21091}
21092
21093#[gpui::test]
21094async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
21095    init_test(cx, |_| {});
21096    let mut cx = EditorTestContext::new(cx).await;
21097
21098    let language = Arc::new(
21099        Language::new(
21100            LanguageConfig::default(),
21101            Some(tree_sitter_html::LANGUAGE.into()),
21102        )
21103        .with_brackets_query(
21104            r#"
21105            ("<" @open "/>" @close)
21106            ("</" @open ">" @close)
21107            ("<" @open ">" @close)
21108            ("\"" @open "\"" @close)
21109            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
21110        "#,
21111        )
21112        .unwrap(),
21113    );
21114    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
21115
21116    cx.set_state(indoc! {"
21117        <span>ˇ</span>
21118    "});
21119    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21120    cx.assert_editor_state(indoc! {"
21121        <span>
21122        ˇ
21123        </span>
21124    "});
21125
21126    cx.set_state(indoc! {"
21127        <span><span></span>ˇ</span>
21128    "});
21129    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21130    cx.assert_editor_state(indoc! {"
21131        <span><span></span>
21132        ˇ</span>
21133    "});
21134
21135    cx.set_state(indoc! {"
21136        <span>ˇ
21137        </span>
21138    "});
21139    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21140    cx.assert_editor_state(indoc! {"
21141        <span>
21142        ˇ
21143        </span>
21144    "});
21145}
21146
21147#[gpui::test(iterations = 10)]
21148async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
21149    init_test(cx, |_| {});
21150
21151    let fs = FakeFs::new(cx.executor());
21152    fs.insert_tree(
21153        path!("/dir"),
21154        json!({
21155            "a.ts": "a",
21156        }),
21157    )
21158    .await;
21159
21160    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
21161    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21162    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21163
21164    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21165    language_registry.add(Arc::new(Language::new(
21166        LanguageConfig {
21167            name: "TypeScript".into(),
21168            matcher: LanguageMatcher {
21169                path_suffixes: vec!["ts".to_string()],
21170                ..Default::default()
21171            },
21172            ..Default::default()
21173        },
21174        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
21175    )));
21176    let mut fake_language_servers = language_registry.register_fake_lsp(
21177        "TypeScript",
21178        FakeLspAdapter {
21179            capabilities: lsp::ServerCapabilities {
21180                code_lens_provider: Some(lsp::CodeLensOptions {
21181                    resolve_provider: Some(true),
21182                }),
21183                execute_command_provider: Some(lsp::ExecuteCommandOptions {
21184                    commands: vec!["_the/command".to_string()],
21185                    ..lsp::ExecuteCommandOptions::default()
21186                }),
21187                ..lsp::ServerCapabilities::default()
21188            },
21189            ..FakeLspAdapter::default()
21190        },
21191    );
21192
21193    let (buffer, _handle) = project
21194        .update(cx, |p, cx| {
21195            p.open_local_buffer_with_lsp(path!("/dir/a.ts"), cx)
21196        })
21197        .await
21198        .unwrap();
21199    cx.executor().run_until_parked();
21200
21201    let fake_server = fake_language_servers.next().await.unwrap();
21202
21203    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
21204    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
21205    drop(buffer_snapshot);
21206    let actions = cx
21207        .update_window(*workspace, |_, window, cx| {
21208            project.code_actions(&buffer, anchor..anchor, window, cx)
21209        })
21210        .unwrap();
21211
21212    fake_server
21213        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
21214            Ok(Some(vec![
21215                lsp::CodeLens {
21216                    range: lsp::Range::default(),
21217                    command: Some(lsp::Command {
21218                        title: "Code lens command".to_owned(),
21219                        command: "_the/command".to_owned(),
21220                        arguments: None,
21221                    }),
21222                    data: None,
21223                },
21224                lsp::CodeLens {
21225                    range: lsp::Range::default(),
21226                    command: Some(lsp::Command {
21227                        title: "Command not in capabilities".to_owned(),
21228                        command: "not in capabilities".to_owned(),
21229                        arguments: None,
21230                    }),
21231                    data: None,
21232                },
21233                lsp::CodeLens {
21234                    range: lsp::Range {
21235                        start: lsp::Position {
21236                            line: 1,
21237                            character: 1,
21238                        },
21239                        end: lsp::Position {
21240                            line: 1,
21241                            character: 1,
21242                        },
21243                    },
21244                    command: Some(lsp::Command {
21245                        title: "Command not in range".to_owned(),
21246                        command: "_the/command".to_owned(),
21247                        arguments: None,
21248                    }),
21249                    data: None,
21250                },
21251            ]))
21252        })
21253        .next()
21254        .await;
21255
21256    let actions = actions.await.unwrap();
21257    assert_eq!(
21258        actions.len(),
21259        1,
21260        "Should have only one valid action for the 0..0 range"
21261    );
21262    let action = actions[0].clone();
21263    let apply = project.update(cx, |project, cx| {
21264        project.apply_code_action(buffer.clone(), action, true, cx)
21265    });
21266
21267    // Resolving the code action does not populate its edits. In absence of
21268    // edits, we must execute the given command.
21269    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
21270        |mut lens, _| async move {
21271            let lens_command = lens.command.as_mut().expect("should have a command");
21272            assert_eq!(lens_command.title, "Code lens command");
21273            lens_command.arguments = Some(vec![json!("the-argument")]);
21274            Ok(lens)
21275        },
21276    );
21277
21278    // While executing the command, the language server sends the editor
21279    // a `workspaceEdit` request.
21280    fake_server
21281        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
21282            let fake = fake_server.clone();
21283            move |params, _| {
21284                assert_eq!(params.command, "_the/command");
21285                let fake = fake.clone();
21286                async move {
21287                    fake.server
21288                        .request::<lsp::request::ApplyWorkspaceEdit>(
21289                            lsp::ApplyWorkspaceEditParams {
21290                                label: None,
21291                                edit: lsp::WorkspaceEdit {
21292                                    changes: Some(
21293                                        [(
21294                                            lsp::Url::from_file_path(path!("/dir/a.ts")).unwrap(),
21295                                            vec![lsp::TextEdit {
21296                                                range: lsp::Range::new(
21297                                                    lsp::Position::new(0, 0),
21298                                                    lsp::Position::new(0, 0),
21299                                                ),
21300                                                new_text: "X".into(),
21301                                            }],
21302                                        )]
21303                                        .into_iter()
21304                                        .collect(),
21305                                    ),
21306                                    ..Default::default()
21307                                },
21308                            },
21309                        )
21310                        .await
21311                        .into_response()
21312                        .unwrap();
21313                    Ok(Some(json!(null)))
21314                }
21315            }
21316        })
21317        .next()
21318        .await;
21319
21320    // Applying the code lens command returns a project transaction containing the edits
21321    // sent by the language server in its `workspaceEdit` request.
21322    let transaction = apply.await.unwrap();
21323    assert!(transaction.0.contains_key(&buffer));
21324    buffer.update(cx, |buffer, cx| {
21325        assert_eq!(buffer.text(), "Xa");
21326        buffer.undo(cx);
21327        assert_eq!(buffer.text(), "a");
21328    });
21329}
21330
21331#[gpui::test]
21332async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
21333    init_test(cx, |_| {});
21334
21335    let fs = FakeFs::new(cx.executor());
21336    let main_text = r#"fn main() {
21337println!("1");
21338println!("2");
21339println!("3");
21340println!("4");
21341println!("5");
21342}"#;
21343    let lib_text = "mod foo {}";
21344    fs.insert_tree(
21345        path!("/a"),
21346        json!({
21347            "lib.rs": lib_text,
21348            "main.rs": main_text,
21349        }),
21350    )
21351    .await;
21352
21353    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21354    let (workspace, cx) =
21355        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21356    let worktree_id = workspace.update(cx, |workspace, cx| {
21357        workspace.project().update(cx, |project, cx| {
21358            project.worktrees(cx).next().unwrap().read(cx).id()
21359        })
21360    });
21361
21362    let expected_ranges = vec![
21363        Point::new(0, 0)..Point::new(0, 0),
21364        Point::new(1, 0)..Point::new(1, 1),
21365        Point::new(2, 0)..Point::new(2, 2),
21366        Point::new(3, 0)..Point::new(3, 3),
21367    ];
21368
21369    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21370    let editor_1 = workspace
21371        .update_in(cx, |workspace, window, cx| {
21372            workspace.open_path(
21373                (worktree_id, "main.rs"),
21374                Some(pane_1.downgrade()),
21375                true,
21376                window,
21377                cx,
21378            )
21379        })
21380        .unwrap()
21381        .await
21382        .downcast::<Editor>()
21383        .unwrap();
21384    pane_1.update(cx, |pane, cx| {
21385        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21386        open_editor.update(cx, |editor, cx| {
21387            assert_eq!(
21388                editor.display_text(cx),
21389                main_text,
21390                "Original main.rs text on initial open",
21391            );
21392            assert_eq!(
21393                editor
21394                    .selections
21395                    .all::<Point>(cx)
21396                    .into_iter()
21397                    .map(|s| s.range())
21398                    .collect::<Vec<_>>(),
21399                vec![Point::zero()..Point::zero()],
21400                "Default selections on initial open",
21401            );
21402        })
21403    });
21404    editor_1.update_in(cx, |editor, window, cx| {
21405        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21406            s.select_ranges(expected_ranges.clone());
21407        });
21408    });
21409
21410    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
21411        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
21412    });
21413    let editor_2 = workspace
21414        .update_in(cx, |workspace, window, cx| {
21415            workspace.open_path(
21416                (worktree_id, "main.rs"),
21417                Some(pane_2.downgrade()),
21418                true,
21419                window,
21420                cx,
21421            )
21422        })
21423        .unwrap()
21424        .await
21425        .downcast::<Editor>()
21426        .unwrap();
21427    pane_2.update(cx, |pane, cx| {
21428        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21429        open_editor.update(cx, |editor, cx| {
21430            assert_eq!(
21431                editor.display_text(cx),
21432                main_text,
21433                "Original main.rs text on initial open in another panel",
21434            );
21435            assert_eq!(
21436                editor
21437                    .selections
21438                    .all::<Point>(cx)
21439                    .into_iter()
21440                    .map(|s| s.range())
21441                    .collect::<Vec<_>>(),
21442                vec![Point::zero()..Point::zero()],
21443                "Default selections on initial open in another panel",
21444            );
21445        })
21446    });
21447
21448    editor_2.update_in(cx, |editor, window, cx| {
21449        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
21450    });
21451
21452    let _other_editor_1 = workspace
21453        .update_in(cx, |workspace, window, cx| {
21454            workspace.open_path(
21455                (worktree_id, "lib.rs"),
21456                Some(pane_1.downgrade()),
21457                true,
21458                window,
21459                cx,
21460            )
21461        })
21462        .unwrap()
21463        .await
21464        .downcast::<Editor>()
21465        .unwrap();
21466    pane_1
21467        .update_in(cx, |pane, window, cx| {
21468            pane.close_inactive_items(&CloseInactiveItems::default(), None, window, cx)
21469        })
21470        .await
21471        .unwrap();
21472    drop(editor_1);
21473    pane_1.update(cx, |pane, cx| {
21474        pane.active_item()
21475            .unwrap()
21476            .downcast::<Editor>()
21477            .unwrap()
21478            .update(cx, |editor, cx| {
21479                assert_eq!(
21480                    editor.display_text(cx),
21481                    lib_text,
21482                    "Other file should be open and active",
21483                );
21484            });
21485        assert_eq!(pane.items().count(), 1, "No other editors should be open");
21486    });
21487
21488    let _other_editor_2 = workspace
21489        .update_in(cx, |workspace, window, cx| {
21490            workspace.open_path(
21491                (worktree_id, "lib.rs"),
21492                Some(pane_2.downgrade()),
21493                true,
21494                window,
21495                cx,
21496            )
21497        })
21498        .unwrap()
21499        .await
21500        .downcast::<Editor>()
21501        .unwrap();
21502    pane_2
21503        .update_in(cx, |pane, window, cx| {
21504            pane.close_inactive_items(&CloseInactiveItems::default(), None, window, cx)
21505        })
21506        .await
21507        .unwrap();
21508    drop(editor_2);
21509    pane_2.update(cx, |pane, cx| {
21510        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21511        open_editor.update(cx, |editor, cx| {
21512            assert_eq!(
21513                editor.display_text(cx),
21514                lib_text,
21515                "Other file should be open and active in another panel too",
21516            );
21517        });
21518        assert_eq!(
21519            pane.items().count(),
21520            1,
21521            "No other editors should be open in another pane",
21522        );
21523    });
21524
21525    let _editor_1_reopened = workspace
21526        .update_in(cx, |workspace, window, cx| {
21527            workspace.open_path(
21528                (worktree_id, "main.rs"),
21529                Some(pane_1.downgrade()),
21530                true,
21531                window,
21532                cx,
21533            )
21534        })
21535        .unwrap()
21536        .await
21537        .downcast::<Editor>()
21538        .unwrap();
21539    let _editor_2_reopened = workspace
21540        .update_in(cx, |workspace, window, cx| {
21541            workspace.open_path(
21542                (worktree_id, "main.rs"),
21543                Some(pane_2.downgrade()),
21544                true,
21545                window,
21546                cx,
21547            )
21548        })
21549        .unwrap()
21550        .await
21551        .downcast::<Editor>()
21552        .unwrap();
21553    pane_1.update(cx, |pane, cx| {
21554        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21555        open_editor.update(cx, |editor, cx| {
21556            assert_eq!(
21557                editor.display_text(cx),
21558                main_text,
21559                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
21560            );
21561            assert_eq!(
21562                editor
21563                    .selections
21564                    .all::<Point>(cx)
21565                    .into_iter()
21566                    .map(|s| s.range())
21567                    .collect::<Vec<_>>(),
21568                expected_ranges,
21569                "Previous editor in the 1st panel had selections and should get them restored on reopen",
21570            );
21571        })
21572    });
21573    pane_2.update(cx, |pane, cx| {
21574        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21575        open_editor.update(cx, |editor, cx| {
21576            assert_eq!(
21577                editor.display_text(cx),
21578                r#"fn main() {
21579⋯rintln!("1");
21580⋯intln!("2");
21581⋯ntln!("3");
21582println!("4");
21583println!("5");
21584}"#,
21585                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
21586            );
21587            assert_eq!(
21588                editor
21589                    .selections
21590                    .all::<Point>(cx)
21591                    .into_iter()
21592                    .map(|s| s.range())
21593                    .collect::<Vec<_>>(),
21594                vec![Point::zero()..Point::zero()],
21595                "Previous editor in the 2nd pane had no selections changed hence should restore none",
21596            );
21597        })
21598    });
21599}
21600
21601#[gpui::test]
21602async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
21603    init_test(cx, |_| {});
21604
21605    let fs = FakeFs::new(cx.executor());
21606    let main_text = r#"fn main() {
21607println!("1");
21608println!("2");
21609println!("3");
21610println!("4");
21611println!("5");
21612}"#;
21613    let lib_text = "mod foo {}";
21614    fs.insert_tree(
21615        path!("/a"),
21616        json!({
21617            "lib.rs": lib_text,
21618            "main.rs": main_text,
21619        }),
21620    )
21621    .await;
21622
21623    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21624    let (workspace, cx) =
21625        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21626    let worktree_id = workspace.update(cx, |workspace, cx| {
21627        workspace.project().update(cx, |project, cx| {
21628            project.worktrees(cx).next().unwrap().read(cx).id()
21629        })
21630    });
21631
21632    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21633    let editor = workspace
21634        .update_in(cx, |workspace, window, cx| {
21635            workspace.open_path(
21636                (worktree_id, "main.rs"),
21637                Some(pane.downgrade()),
21638                true,
21639                window,
21640                cx,
21641            )
21642        })
21643        .unwrap()
21644        .await
21645        .downcast::<Editor>()
21646        .unwrap();
21647    pane.update(cx, |pane, cx| {
21648        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21649        open_editor.update(cx, |editor, cx| {
21650            assert_eq!(
21651                editor.display_text(cx),
21652                main_text,
21653                "Original main.rs text on initial open",
21654            );
21655        })
21656    });
21657    editor.update_in(cx, |editor, window, cx| {
21658        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
21659    });
21660
21661    cx.update_global(|store: &mut SettingsStore, cx| {
21662        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
21663            s.restore_on_file_reopen = Some(false);
21664        });
21665    });
21666    editor.update_in(cx, |editor, window, cx| {
21667        editor.fold_ranges(
21668            vec![
21669                Point::new(1, 0)..Point::new(1, 1),
21670                Point::new(2, 0)..Point::new(2, 2),
21671                Point::new(3, 0)..Point::new(3, 3),
21672            ],
21673            false,
21674            window,
21675            cx,
21676        );
21677    });
21678    pane.update_in(cx, |pane, window, cx| {
21679        pane.close_all_items(&CloseAllItems::default(), window, cx)
21680    })
21681    .await
21682    .unwrap();
21683    pane.update(cx, |pane, _| {
21684        assert!(pane.active_item().is_none());
21685    });
21686    cx.update_global(|store: &mut SettingsStore, cx| {
21687        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
21688            s.restore_on_file_reopen = Some(true);
21689        });
21690    });
21691
21692    let _editor_reopened = workspace
21693        .update_in(cx, |workspace, window, cx| {
21694            workspace.open_path(
21695                (worktree_id, "main.rs"),
21696                Some(pane.downgrade()),
21697                true,
21698                window,
21699                cx,
21700            )
21701        })
21702        .unwrap()
21703        .await
21704        .downcast::<Editor>()
21705        .unwrap();
21706    pane.update(cx, |pane, cx| {
21707        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21708        open_editor.update(cx, |editor, cx| {
21709            assert_eq!(
21710                editor.display_text(cx),
21711                main_text,
21712                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
21713            );
21714        })
21715    });
21716}
21717
21718#[gpui::test]
21719async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
21720    struct EmptyModalView {
21721        focus_handle: gpui::FocusHandle,
21722    }
21723    impl EventEmitter<DismissEvent> for EmptyModalView {}
21724    impl Render for EmptyModalView {
21725        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
21726            div()
21727        }
21728    }
21729    impl Focusable for EmptyModalView {
21730        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
21731            self.focus_handle.clone()
21732        }
21733    }
21734    impl workspace::ModalView for EmptyModalView {}
21735    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
21736        EmptyModalView {
21737            focus_handle: cx.focus_handle(),
21738        }
21739    }
21740
21741    init_test(cx, |_| {});
21742
21743    let fs = FakeFs::new(cx.executor());
21744    let project = Project::test(fs, [], cx).await;
21745    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21746    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
21747    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21748    let editor = cx.new_window_entity(|window, cx| {
21749        Editor::new(
21750            EditorMode::full(),
21751            buffer,
21752            Some(project.clone()),
21753            window,
21754            cx,
21755        )
21756    });
21757    workspace
21758        .update(cx, |workspace, window, cx| {
21759            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
21760        })
21761        .unwrap();
21762    editor.update_in(cx, |editor, window, cx| {
21763        editor.open_context_menu(&OpenContextMenu, window, cx);
21764        assert!(editor.mouse_context_menu.is_some());
21765    });
21766    workspace
21767        .update(cx, |workspace, window, cx| {
21768            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
21769        })
21770        .unwrap();
21771    cx.read(|cx| {
21772        assert!(editor.read(cx).mouse_context_menu.is_none());
21773    });
21774}
21775
21776#[gpui::test]
21777async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
21778    init_test(cx, |_| {});
21779
21780    let fs = FakeFs::new(cx.executor());
21781    fs.insert_file(path!("/file.html"), Default::default())
21782        .await;
21783
21784    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
21785
21786    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21787    let html_language = Arc::new(Language::new(
21788        LanguageConfig {
21789            name: "HTML".into(),
21790            matcher: LanguageMatcher {
21791                path_suffixes: vec!["html".to_string()],
21792                ..LanguageMatcher::default()
21793            },
21794            brackets: BracketPairConfig {
21795                pairs: vec![BracketPair {
21796                    start: "<".into(),
21797                    end: ">".into(),
21798                    close: true,
21799                    ..Default::default()
21800                }],
21801                ..Default::default()
21802            },
21803            ..Default::default()
21804        },
21805        Some(tree_sitter_html::LANGUAGE.into()),
21806    ));
21807    language_registry.add(html_language);
21808    let mut fake_servers = language_registry.register_fake_lsp(
21809        "HTML",
21810        FakeLspAdapter {
21811            capabilities: lsp::ServerCapabilities {
21812                completion_provider: Some(lsp::CompletionOptions {
21813                    resolve_provider: Some(true),
21814                    ..Default::default()
21815                }),
21816                ..Default::default()
21817            },
21818            ..Default::default()
21819        },
21820    );
21821
21822    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21823    let cx = &mut VisualTestContext::from_window(*workspace, cx);
21824
21825    let worktree_id = workspace
21826        .update(cx, |workspace, _window, cx| {
21827            workspace.project().update(cx, |project, cx| {
21828                project.worktrees(cx).next().unwrap().read(cx).id()
21829            })
21830        })
21831        .unwrap();
21832    project
21833        .update(cx, |project, cx| {
21834            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
21835        })
21836        .await
21837        .unwrap();
21838    let editor = workspace
21839        .update(cx, |workspace, window, cx| {
21840            workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
21841        })
21842        .unwrap()
21843        .await
21844        .unwrap()
21845        .downcast::<Editor>()
21846        .unwrap();
21847
21848    let fake_server = fake_servers.next().await.unwrap();
21849    editor.update_in(cx, |editor, window, cx| {
21850        editor.set_text("<ad></ad>", window, cx);
21851        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21852            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
21853        });
21854        let Some((buffer, _)) = editor
21855            .buffer
21856            .read(cx)
21857            .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
21858        else {
21859            panic!("Failed to get buffer for selection position");
21860        };
21861        let buffer = buffer.read(cx);
21862        let buffer_id = buffer.remote_id();
21863        let opening_range =
21864            buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3));
21865        let closing_range =
21866            buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8));
21867        let mut linked_ranges = HashMap::default();
21868        linked_ranges.insert(
21869            buffer_id,
21870            vec![(opening_range.clone(), vec![closing_range.clone()])],
21871        );
21872        editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
21873    });
21874    let mut completion_handle =
21875        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
21876            Ok(Some(lsp::CompletionResponse::Array(vec![
21877                lsp::CompletionItem {
21878                    label: "head".to_string(),
21879                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21880                        lsp::InsertReplaceEdit {
21881                            new_text: "head".to_string(),
21882                            insert: lsp::Range::new(
21883                                lsp::Position::new(0, 1),
21884                                lsp::Position::new(0, 3),
21885                            ),
21886                            replace: lsp::Range::new(
21887                                lsp::Position::new(0, 1),
21888                                lsp::Position::new(0, 3),
21889                            ),
21890                        },
21891                    )),
21892                    ..Default::default()
21893                },
21894            ])))
21895        });
21896    editor.update_in(cx, |editor, window, cx| {
21897        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
21898    });
21899    cx.run_until_parked();
21900    completion_handle.next().await.unwrap();
21901    editor.update(cx, |editor, _| {
21902        assert!(
21903            editor.context_menu_visible(),
21904            "Completion menu should be visible"
21905        );
21906    });
21907    editor.update_in(cx, |editor, window, cx| {
21908        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
21909    });
21910    cx.executor().run_until_parked();
21911    editor.update(cx, |editor, cx| {
21912        assert_eq!(editor.text(cx), "<head></head>");
21913    });
21914}
21915
21916#[gpui::test]
21917async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
21918    init_test(cx, |_| {});
21919
21920    let fs = FakeFs::new(cx.executor());
21921    fs.insert_tree(
21922        path!("/root"),
21923        json!({
21924            "a": {
21925                "main.rs": "fn main() {}",
21926            },
21927            "foo": {
21928                "bar": {
21929                    "external_file.rs": "pub mod external {}",
21930                }
21931            }
21932        }),
21933    )
21934    .await;
21935
21936    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
21937    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21938    language_registry.add(rust_lang());
21939    let _fake_servers = language_registry.register_fake_lsp(
21940        "Rust",
21941        FakeLspAdapter {
21942            ..FakeLspAdapter::default()
21943        },
21944    );
21945    let (workspace, cx) =
21946        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21947    let worktree_id = workspace.update(cx, |workspace, cx| {
21948        workspace.project().update(cx, |project, cx| {
21949            project.worktrees(cx).next().unwrap().read(cx).id()
21950        })
21951    });
21952
21953    let assert_language_servers_count =
21954        |expected: usize, context: &str, cx: &mut VisualTestContext| {
21955            project.update(cx, |project, cx| {
21956                let current = project
21957                    .lsp_store()
21958                    .read(cx)
21959                    .as_local()
21960                    .unwrap()
21961                    .language_servers
21962                    .len();
21963                assert_eq!(expected, current, "{context}");
21964            });
21965        };
21966
21967    assert_language_servers_count(
21968        0,
21969        "No servers should be running before any file is open",
21970        cx,
21971    );
21972    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21973    let main_editor = workspace
21974        .update_in(cx, |workspace, window, cx| {
21975            workspace.open_path(
21976                (worktree_id, "main.rs"),
21977                Some(pane.downgrade()),
21978                true,
21979                window,
21980                cx,
21981            )
21982        })
21983        .unwrap()
21984        .await
21985        .downcast::<Editor>()
21986        .unwrap();
21987    pane.update(cx, |pane, cx| {
21988        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21989        open_editor.update(cx, |editor, cx| {
21990            assert_eq!(
21991                editor.display_text(cx),
21992                "fn main() {}",
21993                "Original main.rs text on initial open",
21994            );
21995        });
21996        assert_eq!(open_editor, main_editor);
21997    });
21998    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
21999
22000    let external_editor = workspace
22001        .update_in(cx, |workspace, window, cx| {
22002            workspace.open_abs_path(
22003                PathBuf::from("/root/foo/bar/external_file.rs"),
22004                OpenOptions::default(),
22005                window,
22006                cx,
22007            )
22008        })
22009        .await
22010        .expect("opening external file")
22011        .downcast::<Editor>()
22012        .expect("downcasted external file's open element to editor");
22013    pane.update(cx, |pane, cx| {
22014        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22015        open_editor.update(cx, |editor, cx| {
22016            assert_eq!(
22017                editor.display_text(cx),
22018                "pub mod external {}",
22019                "External file is open now",
22020            );
22021        });
22022        assert_eq!(open_editor, external_editor);
22023    });
22024    assert_language_servers_count(
22025        1,
22026        "Second, external, *.rs file should join the existing server",
22027        cx,
22028    );
22029
22030    pane.update_in(cx, |pane, window, cx| {
22031        pane.close_active_item(&CloseActiveItem::default(), window, cx)
22032    })
22033    .await
22034    .unwrap();
22035    pane.update_in(cx, |pane, window, cx| {
22036        pane.navigate_backward(window, cx);
22037    });
22038    cx.run_until_parked();
22039    pane.update(cx, |pane, cx| {
22040        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22041        open_editor.update(cx, |editor, cx| {
22042            assert_eq!(
22043                editor.display_text(cx),
22044                "pub mod external {}",
22045                "External file is open now",
22046            );
22047        });
22048    });
22049    assert_language_servers_count(
22050        1,
22051        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
22052        cx,
22053    );
22054
22055    cx.update(|_, cx| {
22056        workspace::reload(&workspace::Reload::default(), cx);
22057    });
22058    assert_language_servers_count(
22059        1,
22060        "After reloading the worktree with local and external files opened, only one project should be started",
22061        cx,
22062    );
22063}
22064
22065#[gpui::test]
22066async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
22067    init_test(cx, |_| {});
22068
22069    let mut cx = EditorTestContext::new(cx).await;
22070    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22071    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22072
22073    // test cursor move to start of each line on tab
22074    // for `if`, `elif`, `else`, `while`, `with` and `for`
22075    cx.set_state(indoc! {"
22076        def main():
22077        ˇ    for item in items:
22078        ˇ        while item.active:
22079        ˇ            if item.value > 10:
22080        ˇ                continue
22081        ˇ            elif item.value < 0:
22082        ˇ                break
22083        ˇ            else:
22084        ˇ                with item.context() as ctx:
22085        ˇ                    yield count
22086        ˇ        else:
22087        ˇ            log('while else')
22088        ˇ    else:
22089        ˇ        log('for else')
22090    "});
22091    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22092    cx.assert_editor_state(indoc! {"
22093        def main():
22094            ˇfor item in items:
22095                ˇwhile item.active:
22096                    ˇif item.value > 10:
22097                        ˇcontinue
22098                    ˇelif item.value < 0:
22099                        ˇbreak
22100                    ˇelse:
22101                        ˇwith item.context() as ctx:
22102                            ˇyield count
22103                ˇelse:
22104                    ˇlog('while else')
22105            ˇelse:
22106                ˇlog('for else')
22107    "});
22108    // test relative indent is preserved when tab
22109    // for `if`, `elif`, `else`, `while`, `with` and `for`
22110    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22111    cx.assert_editor_state(indoc! {"
22112        def main():
22113                ˇfor item in items:
22114                    ˇwhile item.active:
22115                        ˇif item.value > 10:
22116                            ˇcontinue
22117                        ˇelif item.value < 0:
22118                            ˇbreak
22119                        ˇelse:
22120                            ˇwith item.context() as ctx:
22121                                ˇyield count
22122                    ˇelse:
22123                        ˇlog('while else')
22124                ˇelse:
22125                    ˇlog('for else')
22126    "});
22127
22128    // test cursor move to start of each line on tab
22129    // for `try`, `except`, `else`, `finally`, `match` and `def`
22130    cx.set_state(indoc! {"
22131        def main():
22132        ˇ    try:
22133        ˇ        fetch()
22134        ˇ    except ValueError:
22135        ˇ        handle_error()
22136        ˇ    else:
22137        ˇ        match value:
22138        ˇ            case _:
22139        ˇ    finally:
22140        ˇ        def status():
22141        ˇ            return 0
22142    "});
22143    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22144    cx.assert_editor_state(indoc! {"
22145        def main():
22146            ˇtry:
22147                ˇfetch()
22148            ˇexcept ValueError:
22149                ˇhandle_error()
22150            ˇelse:
22151                ˇmatch value:
22152                    ˇcase _:
22153            ˇfinally:
22154                ˇdef status():
22155                    ˇreturn 0
22156    "});
22157    // test relative indent is preserved when tab
22158    // for `try`, `except`, `else`, `finally`, `match` and `def`
22159    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22160    cx.assert_editor_state(indoc! {"
22161        def main():
22162                ˇtry:
22163                    ˇfetch()
22164                ˇexcept ValueError:
22165                    ˇhandle_error()
22166                ˇelse:
22167                    ˇmatch value:
22168                        ˇcase _:
22169                ˇfinally:
22170                    ˇdef status():
22171                        ˇreturn 0
22172    "});
22173}
22174
22175#[gpui::test]
22176async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
22177    init_test(cx, |_| {});
22178
22179    let mut cx = EditorTestContext::new(cx).await;
22180    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22181    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22182
22183    // test `else` auto outdents when typed inside `if` block
22184    cx.set_state(indoc! {"
22185        def main():
22186            if i == 2:
22187                return
22188                ˇ
22189    "});
22190    cx.update_editor(|editor, window, cx| {
22191        editor.handle_input("else:", window, cx);
22192    });
22193    cx.assert_editor_state(indoc! {"
22194        def main():
22195            if i == 2:
22196                return
22197            else:ˇ
22198    "});
22199
22200    // test `except` auto outdents when typed inside `try` block
22201    cx.set_state(indoc! {"
22202        def main():
22203            try:
22204                i = 2
22205                ˇ
22206    "});
22207    cx.update_editor(|editor, window, cx| {
22208        editor.handle_input("except:", window, cx);
22209    });
22210    cx.assert_editor_state(indoc! {"
22211        def main():
22212            try:
22213                i = 2
22214            except:ˇ
22215    "});
22216
22217    // test `else` auto outdents when typed inside `except` block
22218    cx.set_state(indoc! {"
22219        def main():
22220            try:
22221                i = 2
22222            except:
22223                j = 2
22224                ˇ
22225    "});
22226    cx.update_editor(|editor, window, cx| {
22227        editor.handle_input("else:", window, cx);
22228    });
22229    cx.assert_editor_state(indoc! {"
22230        def main():
22231            try:
22232                i = 2
22233            except:
22234                j = 2
22235            else:ˇ
22236    "});
22237
22238    // test `finally` auto outdents when typed inside `else` block
22239    cx.set_state(indoc! {"
22240        def main():
22241            try:
22242                i = 2
22243            except:
22244                j = 2
22245            else:
22246                k = 2
22247                ˇ
22248    "});
22249    cx.update_editor(|editor, window, cx| {
22250        editor.handle_input("finally:", window, cx);
22251    });
22252    cx.assert_editor_state(indoc! {"
22253        def main():
22254            try:
22255                i = 2
22256            except:
22257                j = 2
22258            else:
22259                k = 2
22260            finally:ˇ
22261    "});
22262
22263    // test `else` does not outdents when typed inside `except` block right after for block
22264    cx.set_state(indoc! {"
22265        def main():
22266            try:
22267                i = 2
22268            except:
22269                for i in range(n):
22270                    pass
22271                ˇ
22272    "});
22273    cx.update_editor(|editor, window, cx| {
22274        editor.handle_input("else:", window, cx);
22275    });
22276    cx.assert_editor_state(indoc! {"
22277        def main():
22278            try:
22279                i = 2
22280            except:
22281                for i in range(n):
22282                    pass
22283                else:ˇ
22284    "});
22285
22286    // test `finally` auto outdents when typed inside `else` block right after for block
22287    cx.set_state(indoc! {"
22288        def main():
22289            try:
22290                i = 2
22291            except:
22292                j = 2
22293            else:
22294                for i in range(n):
22295                    pass
22296                ˇ
22297    "});
22298    cx.update_editor(|editor, window, cx| {
22299        editor.handle_input("finally:", window, cx);
22300    });
22301    cx.assert_editor_state(indoc! {"
22302        def main():
22303            try:
22304                i = 2
22305            except:
22306                j = 2
22307            else:
22308                for i in range(n):
22309                    pass
22310            finally:ˇ
22311    "});
22312
22313    // test `except` outdents to inner "try" block
22314    cx.set_state(indoc! {"
22315        def main():
22316            try:
22317                i = 2
22318                if i == 2:
22319                    try:
22320                        i = 3
22321                        ˇ
22322    "});
22323    cx.update_editor(|editor, window, cx| {
22324        editor.handle_input("except:", window, cx);
22325    });
22326    cx.assert_editor_state(indoc! {"
22327        def main():
22328            try:
22329                i = 2
22330                if i == 2:
22331                    try:
22332                        i = 3
22333                    except:ˇ
22334    "});
22335
22336    // test `except` outdents to outer "try" block
22337    cx.set_state(indoc! {"
22338        def main():
22339            try:
22340                i = 2
22341                if i == 2:
22342                    try:
22343                        i = 3
22344                ˇ
22345    "});
22346    cx.update_editor(|editor, window, cx| {
22347        editor.handle_input("except:", window, cx);
22348    });
22349    cx.assert_editor_state(indoc! {"
22350        def main():
22351            try:
22352                i = 2
22353                if i == 2:
22354                    try:
22355                        i = 3
22356            except:ˇ
22357    "});
22358
22359    // test `else` stays at correct indent when typed after `for` block
22360    cx.set_state(indoc! {"
22361        def main():
22362            for i in range(10):
22363                if i == 3:
22364                    break
22365            ˇ
22366    "});
22367    cx.update_editor(|editor, window, cx| {
22368        editor.handle_input("else:", window, cx);
22369    });
22370    cx.assert_editor_state(indoc! {"
22371        def main():
22372            for i in range(10):
22373                if i == 3:
22374                    break
22375            else:ˇ
22376    "});
22377
22378    // test does not outdent on typing after line with square brackets
22379    cx.set_state(indoc! {"
22380        def f() -> list[str]:
22381            ˇ
22382    "});
22383    cx.update_editor(|editor, window, cx| {
22384        editor.handle_input("a", window, cx);
22385    });
22386    cx.assert_editor_state(indoc! {"
22387        def f() -> list[str]:
2238822389    "});
22390
22391    // test does not outdent on typing : after case keyword
22392    cx.set_state(indoc! {"
22393        match 1:
22394            caseˇ
22395    "});
22396    cx.update_editor(|editor, window, cx| {
22397        editor.handle_input(":", window, cx);
22398    });
22399    cx.assert_editor_state(indoc! {"
22400        match 1:
22401            case:ˇ
22402    "});
22403}
22404
22405#[gpui::test]
22406async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
22407    init_test(cx, |_| {});
22408    update_test_language_settings(cx, |settings| {
22409        settings.defaults.extend_comment_on_newline = Some(false);
22410    });
22411    let mut cx = EditorTestContext::new(cx).await;
22412    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22413    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22414
22415    // test correct indent after newline on comment
22416    cx.set_state(indoc! {"
22417        # COMMENT:ˇ
22418    "});
22419    cx.update_editor(|editor, window, cx| {
22420        editor.newline(&Newline, window, cx);
22421    });
22422    cx.assert_editor_state(indoc! {"
22423        # COMMENT:
22424        ˇ
22425    "});
22426
22427    // test correct indent after newline in brackets
22428    cx.set_state(indoc! {"
22429        {ˇ}
22430    "});
22431    cx.update_editor(|editor, window, cx| {
22432        editor.newline(&Newline, window, cx);
22433    });
22434    cx.run_until_parked();
22435    cx.assert_editor_state(indoc! {"
22436        {
22437            ˇ
22438        }
22439    "});
22440
22441    cx.set_state(indoc! {"
22442        (ˇ)
22443    "});
22444    cx.update_editor(|editor, window, cx| {
22445        editor.newline(&Newline, window, cx);
22446    });
22447    cx.run_until_parked();
22448    cx.assert_editor_state(indoc! {"
22449        (
22450            ˇ
22451        )
22452    "});
22453
22454    // do not indent after empty lists or dictionaries
22455    cx.set_state(indoc! {"
22456        a = []ˇ
22457    "});
22458    cx.update_editor(|editor, window, cx| {
22459        editor.newline(&Newline, window, cx);
22460    });
22461    cx.run_until_parked();
22462    cx.assert_editor_state(indoc! {"
22463        a = []
22464        ˇ
22465    "});
22466}
22467
22468fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
22469    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
22470    point..point
22471}
22472
22473#[track_caller]
22474fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
22475    let (text, ranges) = marked_text_ranges(marked_text, true);
22476    assert_eq!(editor.text(cx), text);
22477    assert_eq!(
22478        editor.selections.ranges(cx),
22479        ranges,
22480        "Assert selections are {}",
22481        marked_text
22482    );
22483}
22484
22485pub fn handle_signature_help_request(
22486    cx: &mut EditorLspTestContext,
22487    mocked_response: lsp::SignatureHelp,
22488) -> impl Future<Output = ()> + use<> {
22489    let mut request =
22490        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
22491            let mocked_response = mocked_response.clone();
22492            async move { Ok(Some(mocked_response)) }
22493        });
22494
22495    async move {
22496        request.next().await;
22497    }
22498}
22499
22500#[track_caller]
22501pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
22502    cx.update_editor(|editor, _, _| {
22503        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
22504            let entries = menu.entries.borrow();
22505            let entries = entries
22506                .iter()
22507                .map(|entry| entry.string.as_str())
22508                .collect::<Vec<_>>();
22509            assert_eq!(entries, expected);
22510        } else {
22511            panic!("Expected completions menu");
22512        }
22513    });
22514}
22515
22516/// Handle completion request passing a marked string specifying where the completion
22517/// should be triggered from using '|' character, what range should be replaced, and what completions
22518/// should be returned using '<' and '>' to delimit the range.
22519///
22520/// Also see `handle_completion_request_with_insert_and_replace`.
22521#[track_caller]
22522pub fn handle_completion_request(
22523    marked_string: &str,
22524    completions: Vec<&'static str>,
22525    is_incomplete: bool,
22526    counter: Arc<AtomicUsize>,
22527    cx: &mut EditorLspTestContext,
22528) -> impl Future<Output = ()> {
22529    let complete_from_marker: TextRangeMarker = '|'.into();
22530    let replace_range_marker: TextRangeMarker = ('<', '>').into();
22531    let (_, mut marked_ranges) = marked_text_ranges_by(
22532        marked_string,
22533        vec![complete_from_marker.clone(), replace_range_marker.clone()],
22534    );
22535
22536    let complete_from_position =
22537        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
22538    let replace_range =
22539        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
22540
22541    let mut request =
22542        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
22543            let completions = completions.clone();
22544            counter.fetch_add(1, atomic::Ordering::Release);
22545            async move {
22546                assert_eq!(params.text_document_position.text_document.uri, url.clone());
22547                assert_eq!(
22548                    params.text_document_position.position,
22549                    complete_from_position
22550                );
22551                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
22552                    is_incomplete: is_incomplete,
22553                    item_defaults: None,
22554                    items: completions
22555                        .iter()
22556                        .map(|completion_text| lsp::CompletionItem {
22557                            label: completion_text.to_string(),
22558                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
22559                                range: replace_range,
22560                                new_text: completion_text.to_string(),
22561                            })),
22562                            ..Default::default()
22563                        })
22564                        .collect(),
22565                })))
22566            }
22567        });
22568
22569    async move {
22570        request.next().await;
22571    }
22572}
22573
22574/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
22575/// given instead, which also contains an `insert` range.
22576///
22577/// This function uses markers to define ranges:
22578/// - `|` marks the cursor position
22579/// - `<>` marks the replace range
22580/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
22581pub fn handle_completion_request_with_insert_and_replace(
22582    cx: &mut EditorLspTestContext,
22583    marked_string: &str,
22584    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
22585    counter: Arc<AtomicUsize>,
22586) -> impl Future<Output = ()> {
22587    let complete_from_marker: TextRangeMarker = '|'.into();
22588    let replace_range_marker: TextRangeMarker = ('<', '>').into();
22589    let insert_range_marker: TextRangeMarker = ('{', '}').into();
22590
22591    let (_, mut marked_ranges) = marked_text_ranges_by(
22592        marked_string,
22593        vec![
22594            complete_from_marker.clone(),
22595            replace_range_marker.clone(),
22596            insert_range_marker.clone(),
22597        ],
22598    );
22599
22600    let complete_from_position =
22601        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
22602    let replace_range =
22603        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
22604
22605    let insert_range = match marked_ranges.remove(&insert_range_marker) {
22606        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
22607        _ => lsp::Range {
22608            start: replace_range.start,
22609            end: complete_from_position,
22610        },
22611    };
22612
22613    let mut request =
22614        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
22615            let completions = completions.clone();
22616            counter.fetch_add(1, atomic::Ordering::Release);
22617            async move {
22618                assert_eq!(params.text_document_position.text_document.uri, url.clone());
22619                assert_eq!(
22620                    params.text_document_position.position, complete_from_position,
22621                    "marker `|` position doesn't match",
22622                );
22623                Ok(Some(lsp::CompletionResponse::Array(
22624                    completions
22625                        .iter()
22626                        .map(|(label, new_text)| lsp::CompletionItem {
22627                            label: label.to_string(),
22628                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22629                                lsp::InsertReplaceEdit {
22630                                    insert: insert_range,
22631                                    replace: replace_range,
22632                                    new_text: new_text.to_string(),
22633                                },
22634                            )),
22635                            ..Default::default()
22636                        })
22637                        .collect(),
22638                )))
22639            }
22640        });
22641
22642    async move {
22643        request.next().await;
22644    }
22645}
22646
22647fn handle_resolve_completion_request(
22648    cx: &mut EditorLspTestContext,
22649    edits: Option<Vec<(&'static str, &'static str)>>,
22650) -> impl Future<Output = ()> {
22651    let edits = edits.map(|edits| {
22652        edits
22653            .iter()
22654            .map(|(marked_string, new_text)| {
22655                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
22656                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
22657                lsp::TextEdit::new(replace_range, new_text.to_string())
22658            })
22659            .collect::<Vec<_>>()
22660    });
22661
22662    let mut request =
22663        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
22664            let edits = edits.clone();
22665            async move {
22666                Ok(lsp::CompletionItem {
22667                    additional_text_edits: edits,
22668                    ..Default::default()
22669                })
22670            }
22671        });
22672
22673    async move {
22674        request.next().await;
22675    }
22676}
22677
22678pub(crate) fn update_test_language_settings(
22679    cx: &mut TestAppContext,
22680    f: impl Fn(&mut AllLanguageSettingsContent),
22681) {
22682    cx.update(|cx| {
22683        SettingsStore::update_global(cx, |store, cx| {
22684            store.update_user_settings::<AllLanguageSettings>(cx, f);
22685        });
22686    });
22687}
22688
22689pub(crate) fn update_test_project_settings(
22690    cx: &mut TestAppContext,
22691    f: impl Fn(&mut ProjectSettings),
22692) {
22693    cx.update(|cx| {
22694        SettingsStore::update_global(cx, |store, cx| {
22695            store.update_user_settings::<ProjectSettings>(cx, f);
22696        });
22697    });
22698}
22699
22700pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
22701    cx.update(|cx| {
22702        assets::Assets.load_test_fonts(cx);
22703        let store = SettingsStore::test(cx);
22704        cx.set_global(store);
22705        theme::init(theme::LoadThemes::JustBase, cx);
22706        release_channel::init(SemanticVersion::default(), cx);
22707        client::init_settings(cx);
22708        language::init(cx);
22709        Project::init_settings(cx);
22710        workspace::init_settings(cx);
22711        crate::init(cx);
22712    });
22713
22714    update_test_language_settings(cx, f);
22715}
22716
22717#[track_caller]
22718fn assert_hunk_revert(
22719    not_reverted_text_with_selections: &str,
22720    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
22721    expected_reverted_text_with_selections: &str,
22722    base_text: &str,
22723    cx: &mut EditorLspTestContext,
22724) {
22725    cx.set_state(not_reverted_text_with_selections);
22726    cx.set_head_text(base_text);
22727    cx.executor().run_until_parked();
22728
22729    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
22730        let snapshot = editor.snapshot(window, cx);
22731        let reverted_hunk_statuses = snapshot
22732            .buffer_snapshot
22733            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
22734            .map(|hunk| hunk.status().kind)
22735            .collect::<Vec<_>>();
22736
22737        editor.git_restore(&Default::default(), window, cx);
22738        reverted_hunk_statuses
22739    });
22740    cx.executor().run_until_parked();
22741    cx.assert_editor_state(expected_reverted_text_with_selections);
22742    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
22743}
22744
22745#[gpui::test(iterations = 10)]
22746async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
22747    init_test(cx, |_| {});
22748
22749    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
22750    let counter = diagnostic_requests.clone();
22751
22752    let fs = FakeFs::new(cx.executor());
22753    fs.insert_tree(
22754        path!("/a"),
22755        json!({
22756            "first.rs": "fn main() { let a = 5; }",
22757            "second.rs": "// Test file",
22758        }),
22759    )
22760    .await;
22761
22762    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22763    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22764    let cx = &mut VisualTestContext::from_window(*workspace, cx);
22765
22766    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22767    language_registry.add(rust_lang());
22768    let mut fake_servers = language_registry.register_fake_lsp(
22769        "Rust",
22770        FakeLspAdapter {
22771            capabilities: lsp::ServerCapabilities {
22772                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
22773                    lsp::DiagnosticOptions {
22774                        identifier: None,
22775                        inter_file_dependencies: true,
22776                        workspace_diagnostics: true,
22777                        work_done_progress_options: Default::default(),
22778                    },
22779                )),
22780                ..Default::default()
22781            },
22782            ..Default::default()
22783        },
22784    );
22785
22786    let editor = workspace
22787        .update(cx, |workspace, window, cx| {
22788            workspace.open_abs_path(
22789                PathBuf::from(path!("/a/first.rs")),
22790                OpenOptions::default(),
22791                window,
22792                cx,
22793            )
22794        })
22795        .unwrap()
22796        .await
22797        .unwrap()
22798        .downcast::<Editor>()
22799        .unwrap();
22800    let fake_server = fake_servers.next().await.unwrap();
22801    let server_id = fake_server.server.server_id();
22802    let mut first_request = fake_server
22803        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
22804            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
22805            let result_id = Some(new_result_id.to_string());
22806            assert_eq!(
22807                params.text_document.uri,
22808                lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
22809            );
22810            async move {
22811                Ok(lsp::DocumentDiagnosticReportResult::Report(
22812                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
22813                        related_documents: None,
22814                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
22815                            items: Vec::new(),
22816                            result_id,
22817                        },
22818                    }),
22819                ))
22820            }
22821        });
22822
22823    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
22824        project.update(cx, |project, cx| {
22825            let buffer_id = editor
22826                .read(cx)
22827                .buffer()
22828                .read(cx)
22829                .as_singleton()
22830                .expect("created a singleton buffer")
22831                .read(cx)
22832                .remote_id();
22833            let buffer_result_id = project
22834                .lsp_store()
22835                .read(cx)
22836                .result_id(server_id, buffer_id, cx);
22837            assert_eq!(expected, buffer_result_id);
22838        });
22839    };
22840
22841    ensure_result_id(None, cx);
22842    cx.executor().advance_clock(Duration::from_millis(60));
22843    cx.executor().run_until_parked();
22844    assert_eq!(
22845        diagnostic_requests.load(atomic::Ordering::Acquire),
22846        1,
22847        "Opening file should trigger diagnostic request"
22848    );
22849    first_request
22850        .next()
22851        .await
22852        .expect("should have sent the first diagnostics pull request");
22853    ensure_result_id(Some("1".to_string()), cx);
22854
22855    // Editing should trigger diagnostics
22856    editor.update_in(cx, |editor, window, cx| {
22857        editor.handle_input("2", window, cx)
22858    });
22859    cx.executor().advance_clock(Duration::from_millis(60));
22860    cx.executor().run_until_parked();
22861    assert_eq!(
22862        diagnostic_requests.load(atomic::Ordering::Acquire),
22863        2,
22864        "Editing should trigger diagnostic request"
22865    );
22866    ensure_result_id(Some("2".to_string()), cx);
22867
22868    // Moving cursor should not trigger diagnostic request
22869    editor.update_in(cx, |editor, window, cx| {
22870        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22871            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
22872        });
22873    });
22874    cx.executor().advance_clock(Duration::from_millis(60));
22875    cx.executor().run_until_parked();
22876    assert_eq!(
22877        diagnostic_requests.load(atomic::Ordering::Acquire),
22878        2,
22879        "Cursor movement should not trigger diagnostic request"
22880    );
22881    ensure_result_id(Some("2".to_string()), cx);
22882    // Multiple rapid edits should be debounced
22883    for _ in 0..5 {
22884        editor.update_in(cx, |editor, window, cx| {
22885            editor.handle_input("x", window, cx)
22886        });
22887    }
22888    cx.executor().advance_clock(Duration::from_millis(60));
22889    cx.executor().run_until_parked();
22890
22891    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
22892    assert!(
22893        final_requests <= 4,
22894        "Multiple rapid edits should be debounced (got {final_requests} requests)",
22895    );
22896    ensure_result_id(Some(final_requests.to_string()), cx);
22897}
22898
22899#[gpui::test]
22900async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
22901    // Regression test for issue #11671
22902    // Previously, adding a cursor after moving multiple cursors would reset
22903    // the cursor count instead of adding to the existing cursors.
22904    init_test(cx, |_| {});
22905    let mut cx = EditorTestContext::new(cx).await;
22906
22907    // Create a simple buffer with cursor at start
22908    cx.set_state(indoc! {"
22909        ˇaaaa
22910        bbbb
22911        cccc
22912        dddd
22913        eeee
22914        ffff
22915        gggg
22916        hhhh"});
22917
22918    // Add 2 cursors below (so we have 3 total)
22919    cx.update_editor(|editor, window, cx| {
22920        editor.add_selection_below(&Default::default(), window, cx);
22921        editor.add_selection_below(&Default::default(), window, cx);
22922    });
22923
22924    // Verify we have 3 cursors
22925    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
22926    assert_eq!(
22927        initial_count, 3,
22928        "Should have 3 cursors after adding 2 below"
22929    );
22930
22931    // Move down one line
22932    cx.update_editor(|editor, window, cx| {
22933        editor.move_down(&MoveDown, window, cx);
22934    });
22935
22936    // Add another cursor below
22937    cx.update_editor(|editor, window, cx| {
22938        editor.add_selection_below(&Default::default(), window, cx);
22939    });
22940
22941    // Should now have 4 cursors (3 original + 1 new)
22942    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
22943    assert_eq!(
22944        final_count, 4,
22945        "Should have 4 cursors after moving and adding another"
22946    );
22947}
22948
22949#[gpui::test(iterations = 10)]
22950async fn test_document_colors(cx: &mut TestAppContext) {
22951    let expected_color = Rgba {
22952        r: 0.33,
22953        g: 0.33,
22954        b: 0.33,
22955        a: 0.33,
22956    };
22957
22958    init_test(cx, |_| {});
22959
22960    let fs = FakeFs::new(cx.executor());
22961    fs.insert_tree(
22962        path!("/a"),
22963        json!({
22964            "first.rs": "fn main() { let a = 5; }",
22965        }),
22966    )
22967    .await;
22968
22969    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22970    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22971    let cx = &mut VisualTestContext::from_window(*workspace, cx);
22972
22973    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22974    language_registry.add(rust_lang());
22975    let mut fake_servers = language_registry.register_fake_lsp(
22976        "Rust",
22977        FakeLspAdapter {
22978            capabilities: lsp::ServerCapabilities {
22979                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
22980                ..lsp::ServerCapabilities::default()
22981            },
22982            name: "rust-analyzer",
22983            ..FakeLspAdapter::default()
22984        },
22985    );
22986    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
22987        "Rust",
22988        FakeLspAdapter {
22989            capabilities: lsp::ServerCapabilities {
22990                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
22991                ..lsp::ServerCapabilities::default()
22992            },
22993            name: "not-rust-analyzer",
22994            ..FakeLspAdapter::default()
22995        },
22996    );
22997
22998    let editor = workspace
22999        .update(cx, |workspace, window, cx| {
23000            workspace.open_abs_path(
23001                PathBuf::from(path!("/a/first.rs")),
23002                OpenOptions::default(),
23003                window,
23004                cx,
23005            )
23006        })
23007        .unwrap()
23008        .await
23009        .unwrap()
23010        .downcast::<Editor>()
23011        .unwrap();
23012    let fake_language_server = fake_servers.next().await.unwrap();
23013    let fake_language_server_without_capabilities =
23014        fake_servers_without_capabilities.next().await.unwrap();
23015    let requests_made = Arc::new(AtomicUsize::new(0));
23016    let closure_requests_made = Arc::clone(&requests_made);
23017    let mut color_request_handle = fake_language_server
23018        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
23019            let requests_made = Arc::clone(&closure_requests_made);
23020            async move {
23021                assert_eq!(
23022                    params.text_document.uri,
23023                    lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
23024                );
23025                requests_made.fetch_add(1, atomic::Ordering::Release);
23026                Ok(vec![
23027                    lsp::ColorInformation {
23028                        range: lsp::Range {
23029                            start: lsp::Position {
23030                                line: 0,
23031                                character: 0,
23032                            },
23033                            end: lsp::Position {
23034                                line: 0,
23035                                character: 1,
23036                            },
23037                        },
23038                        color: lsp::Color {
23039                            red: 0.33,
23040                            green: 0.33,
23041                            blue: 0.33,
23042                            alpha: 0.33,
23043                        },
23044                    },
23045                    lsp::ColorInformation {
23046                        range: lsp::Range {
23047                            start: lsp::Position {
23048                                line: 0,
23049                                character: 0,
23050                            },
23051                            end: lsp::Position {
23052                                line: 0,
23053                                character: 1,
23054                            },
23055                        },
23056                        color: lsp::Color {
23057                            red: 0.33,
23058                            green: 0.33,
23059                            blue: 0.33,
23060                            alpha: 0.33,
23061                        },
23062                    },
23063                ])
23064            }
23065        });
23066
23067    let _handle = fake_language_server_without_capabilities
23068        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
23069            panic!("Should not be called");
23070        });
23071    cx.executor().advance_clock(Duration::from_millis(100));
23072    color_request_handle.next().await.unwrap();
23073    cx.run_until_parked();
23074    assert_eq!(
23075        1,
23076        requests_made.load(atomic::Ordering::Acquire),
23077        "Should query for colors once per editor open"
23078    );
23079    editor.update_in(cx, |editor, _, cx| {
23080        assert_eq!(
23081            vec![expected_color],
23082            extract_color_inlays(editor, cx),
23083            "Should have an initial inlay"
23084        );
23085    });
23086
23087    // opening another file in a split should not influence the LSP query counter
23088    workspace
23089        .update(cx, |workspace, window, cx| {
23090            assert_eq!(
23091                workspace.panes().len(),
23092                1,
23093                "Should have one pane with one editor"
23094            );
23095            workspace.move_item_to_pane_in_direction(
23096                &MoveItemToPaneInDirection {
23097                    direction: SplitDirection::Right,
23098                    focus: false,
23099                    clone: true,
23100                },
23101                window,
23102                cx,
23103            );
23104        })
23105        .unwrap();
23106    cx.run_until_parked();
23107    workspace
23108        .update(cx, |workspace, _, cx| {
23109            let panes = workspace.panes();
23110            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
23111            for pane in panes {
23112                let editor = pane
23113                    .read(cx)
23114                    .active_item()
23115                    .and_then(|item| item.downcast::<Editor>())
23116                    .expect("Should have opened an editor in each split");
23117                let editor_file = editor
23118                    .read(cx)
23119                    .buffer()
23120                    .read(cx)
23121                    .as_singleton()
23122                    .expect("test deals with singleton buffers")
23123                    .read(cx)
23124                    .file()
23125                    .expect("test buffese should have a file")
23126                    .path();
23127                assert_eq!(
23128                    editor_file.as_ref(),
23129                    Path::new("first.rs"),
23130                    "Both editors should be opened for the same file"
23131                )
23132            }
23133        })
23134        .unwrap();
23135
23136    cx.executor().advance_clock(Duration::from_millis(500));
23137    let save = editor.update_in(cx, |editor, window, cx| {
23138        editor.move_to_end(&MoveToEnd, window, cx);
23139        editor.handle_input("dirty", window, cx);
23140        editor.save(
23141            SaveOptions {
23142                format: true,
23143                autosave: true,
23144            },
23145            project.clone(),
23146            window,
23147            cx,
23148        )
23149    });
23150    save.await.unwrap();
23151
23152    color_request_handle.next().await.unwrap();
23153    cx.run_until_parked();
23154    assert_eq!(
23155        3,
23156        requests_made.load(atomic::Ordering::Acquire),
23157        "Should query for colors once per save and once per formatting after save"
23158    );
23159
23160    drop(editor);
23161    let close = workspace
23162        .update(cx, |workspace, window, cx| {
23163            workspace.active_pane().update(cx, |pane, cx| {
23164                pane.close_active_item(&CloseActiveItem::default(), window, cx)
23165            })
23166        })
23167        .unwrap();
23168    close.await.unwrap();
23169    let close = workspace
23170        .update(cx, |workspace, window, cx| {
23171            workspace.active_pane().update(cx, |pane, cx| {
23172                pane.close_active_item(&CloseActiveItem::default(), window, cx)
23173            })
23174        })
23175        .unwrap();
23176    close.await.unwrap();
23177    assert_eq!(
23178        3,
23179        requests_made.load(atomic::Ordering::Acquire),
23180        "After saving and closing all editors, no extra requests should be made"
23181    );
23182    workspace
23183        .update(cx, |workspace, _, cx| {
23184            assert!(
23185                workspace.active_item(cx).is_none(),
23186                "Should close all editors"
23187            )
23188        })
23189        .unwrap();
23190
23191    workspace
23192        .update(cx, |workspace, window, cx| {
23193            workspace.active_pane().update(cx, |pane, cx| {
23194                pane.navigate_backward(window, cx);
23195            })
23196        })
23197        .unwrap();
23198    cx.executor().advance_clock(Duration::from_millis(100));
23199    cx.run_until_parked();
23200    let editor = workspace
23201        .update(cx, |workspace, _, cx| {
23202            workspace
23203                .active_item(cx)
23204                .expect("Should have reopened the editor again after navigating back")
23205                .downcast::<Editor>()
23206                .expect("Should be an editor")
23207        })
23208        .unwrap();
23209    color_request_handle.next().await.unwrap();
23210    assert_eq!(
23211        3,
23212        requests_made.load(atomic::Ordering::Acquire),
23213        "Cache should be reused on buffer close and reopen"
23214    );
23215    editor.update(cx, |editor, cx| {
23216        assert_eq!(
23217            vec![expected_color],
23218            extract_color_inlays(editor, cx),
23219            "Should have an initial inlay"
23220        );
23221    });
23222}
23223
23224#[gpui::test]
23225async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
23226    init_test(cx, |_| {});
23227    let (editor, cx) = cx.add_window_view(Editor::single_line);
23228    editor.update_in(cx, |editor, window, cx| {
23229        editor.set_text("oops\n\nwow\n", window, cx)
23230    });
23231    cx.run_until_parked();
23232    editor.update(cx, |editor, cx| {
23233        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
23234    });
23235    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
23236    cx.run_until_parked();
23237    editor.update(cx, |editor, cx| {
23238        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
23239    });
23240}
23241
23242#[track_caller]
23243fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
23244    editor
23245        .all_inlays(cx)
23246        .into_iter()
23247        .filter_map(|inlay| inlay.get_color())
23248        .map(Rgba::from)
23249        .collect()
23250}