editor_tests.rs

    1use super::*;
    2use crate::{
    3    JoinLines,
    4    code_context_menus::CodeContextMenu,
    5    inline_completion_tests::FakeInlineCompletionProvider,
    6    linked_editing_ranges::LinkedEditingRanges,
    7    scroll::scroll_amount::ScrollAmount,
    8    test::{
    9        assert_text_with_selections, build_editor,
   10        editor_lsp_test_context::{EditorLspTestContext, git_commit_lang},
   11        editor_test_context::EditorTestContext,
   12        select_ranges,
   13    },
   14};
   15use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
   16use futures::StreamExt;
   17use gpui::{
   18    BackgroundExecutor, DismissEvent, Rgba, SemanticVersion, TestAppContext, UpdateGlobal,
   19    VisualTestContext, WindowBounds, WindowOptions, div,
   20};
   21use indoc::indoc;
   22use language::{
   23    BracketPairConfig,
   24    Capability::ReadWrite,
   25    DiagnosticSourceKind, FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageMatcher,
   26    LanguageName, Override, Point,
   27    language_settings::{
   28        AllLanguageSettings, AllLanguageSettingsContent, CompletionSettings, FormatterList,
   29        LanguageSettingsContent, LspInsertMode, PrettierSettings, SelectedFormatter,
   30    },
   31    tree_sitter_python,
   32};
   33use language_settings::{Formatter, IndentGuideSettings};
   34use lsp::CompletionParams;
   35use multi_buffer::{IndentGuide, PathKey};
   36use parking_lot::Mutex;
   37use pretty_assertions::{assert_eq, assert_ne};
   38use project::{
   39    FakeFs,
   40    debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
   41    project_settings::{LspSettings, ProjectSettings},
   42};
   43use serde_json::{self, json};
   44use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
   45use std::{
   46    iter,
   47    sync::atomic::{self, AtomicUsize},
   48};
   49use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
   50use text::ToPoint as _;
   51use unindent::Unindent;
   52use util::{
   53    assert_set_eq, path,
   54    test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text},
   55    uri,
   56};
   57use workspace::{
   58    CloseActiveItem, CloseAllItems, CloseOtherItems, MoveItemToPaneInDirection, NavigationEntry,
   59    OpenOptions, ViewId,
   60    item::{FollowEvent, FollowableItem, Item, ItemHandle, SaveOptions},
   61};
   62
   63#[gpui::test]
   64fn test_edit_events(cx: &mut TestAppContext) {
   65    init_test(cx, |_| {});
   66
   67    let buffer = cx.new(|cx| {
   68        let mut buffer = language::Buffer::local("123456", cx);
   69        buffer.set_group_interval(Duration::from_secs(1));
   70        buffer
   71    });
   72
   73    let events = Rc::new(RefCell::new(Vec::new()));
   74    let editor1 = cx.add_window({
   75        let events = events.clone();
   76        |window, cx| {
   77            let entity = cx.entity().clone();
   78            cx.subscribe_in(
   79                &entity,
   80                window,
   81                move |_, _, event: &EditorEvent, _, _| match event {
   82                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
   83                    EditorEvent::BufferEdited => {
   84                        events.borrow_mut().push(("editor1", "buffer edited"))
   85                    }
   86                    _ => {}
   87                },
   88            )
   89            .detach();
   90            Editor::for_buffer(buffer.clone(), None, window, cx)
   91        }
   92    });
   93
   94    let editor2 = cx.add_window({
   95        let events = events.clone();
   96        |window, cx| {
   97            cx.subscribe_in(
   98                &cx.entity().clone(),
   99                window,
  100                move |_, _, event: &EditorEvent, _, _| match event {
  101                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
  102                    EditorEvent::BufferEdited => {
  103                        events.borrow_mut().push(("editor2", "buffer edited"))
  104                    }
  105                    _ => {}
  106                },
  107            )
  108            .detach();
  109            Editor::for_buffer(buffer.clone(), None, window, cx)
  110        }
  111    });
  112
  113    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  114
  115    // Mutating editor 1 will emit an `Edited` event only for that editor.
  116    _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
  117    assert_eq!(
  118        mem::take(&mut *events.borrow_mut()),
  119        [
  120            ("editor1", "edited"),
  121            ("editor1", "buffer edited"),
  122            ("editor2", "buffer edited"),
  123        ]
  124    );
  125
  126    // Mutating editor 2 will emit an `Edited` event only for that editor.
  127    _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
  128    assert_eq!(
  129        mem::take(&mut *events.borrow_mut()),
  130        [
  131            ("editor2", "edited"),
  132            ("editor1", "buffer edited"),
  133            ("editor2", "buffer edited"),
  134        ]
  135    );
  136
  137    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  138    _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  139    assert_eq!(
  140        mem::take(&mut *events.borrow_mut()),
  141        [
  142            ("editor1", "edited"),
  143            ("editor1", "buffer edited"),
  144            ("editor2", "buffer edited"),
  145        ]
  146    );
  147
  148    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  149    _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  150    assert_eq!(
  151        mem::take(&mut *events.borrow_mut()),
  152        [
  153            ("editor1", "edited"),
  154            ("editor1", "buffer edited"),
  155            ("editor2", "buffer edited"),
  156        ]
  157    );
  158
  159    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  160    _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  161    assert_eq!(
  162        mem::take(&mut *events.borrow_mut()),
  163        [
  164            ("editor2", "edited"),
  165            ("editor1", "buffer edited"),
  166            ("editor2", "buffer edited"),
  167        ]
  168    );
  169
  170    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  171    _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  172    assert_eq!(
  173        mem::take(&mut *events.borrow_mut()),
  174        [
  175            ("editor2", "edited"),
  176            ("editor1", "buffer edited"),
  177            ("editor2", "buffer edited"),
  178        ]
  179    );
  180
  181    // No event is emitted when the mutation is a no-op.
  182    _ = editor2.update(cx, |editor, window, cx| {
  183        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  184            s.select_ranges([0..0])
  185        });
  186
  187        editor.backspace(&Backspace, window, cx);
  188    });
  189    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  190}
  191
  192#[gpui::test]
  193fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  194    init_test(cx, |_| {});
  195
  196    let mut now = Instant::now();
  197    let group_interval = Duration::from_millis(1);
  198    let buffer = cx.new(|cx| {
  199        let mut buf = language::Buffer::local("123456", cx);
  200        buf.set_group_interval(group_interval);
  201        buf
  202    });
  203    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  204    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
  205
  206    _ = editor.update(cx, |editor, window, cx| {
  207        editor.start_transaction_at(now, window, cx);
  208        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  209            s.select_ranges([2..4])
  210        });
  211
  212        editor.insert("cd", window, cx);
  213        editor.end_transaction_at(now, cx);
  214        assert_eq!(editor.text(cx), "12cd56");
  215        assert_eq!(editor.selections.ranges(cx), vec![4..4]);
  216
  217        editor.start_transaction_at(now, window, cx);
  218        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  219            s.select_ranges([4..5])
  220        });
  221        editor.insert("e", window, cx);
  222        editor.end_transaction_at(now, cx);
  223        assert_eq!(editor.text(cx), "12cde6");
  224        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  225
  226        now += group_interval + Duration::from_millis(1);
  227        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  228            s.select_ranges([2..2])
  229        });
  230
  231        // Simulate an edit in another editor
  232        buffer.update(cx, |buffer, cx| {
  233            buffer.start_transaction_at(now, cx);
  234            buffer.edit([(0..1, "a")], None, cx);
  235            buffer.edit([(1..1, "b")], None, cx);
  236            buffer.end_transaction_at(now, cx);
  237        });
  238
  239        assert_eq!(editor.text(cx), "ab2cde6");
  240        assert_eq!(editor.selections.ranges(cx), vec![3..3]);
  241
  242        // Last transaction happened past the group interval in a different editor.
  243        // Undo it individually and don't restore selections.
  244        editor.undo(&Undo, window, cx);
  245        assert_eq!(editor.text(cx), "12cde6");
  246        assert_eq!(editor.selections.ranges(cx), vec![2..2]);
  247
  248        // First two transactions happened within the group interval in this editor.
  249        // Undo them together and restore selections.
  250        editor.undo(&Undo, window, cx);
  251        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  252        assert_eq!(editor.text(cx), "123456");
  253        assert_eq!(editor.selections.ranges(cx), vec![0..0]);
  254
  255        // Redo the first two transactions together.
  256        editor.redo(&Redo, window, cx);
  257        assert_eq!(editor.text(cx), "12cde6");
  258        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  259
  260        // Redo the last transaction on its own.
  261        editor.redo(&Redo, window, cx);
  262        assert_eq!(editor.text(cx), "ab2cde6");
  263        assert_eq!(editor.selections.ranges(cx), vec![6..6]);
  264
  265        // Test empty transactions.
  266        editor.start_transaction_at(now, window, cx);
  267        editor.end_transaction_at(now, cx);
  268        editor.undo(&Undo, window, cx);
  269        assert_eq!(editor.text(cx), "12cde6");
  270    });
  271}
  272
  273#[gpui::test]
  274fn test_ime_composition(cx: &mut TestAppContext) {
  275    init_test(cx, |_| {});
  276
  277    let buffer = cx.new(|cx| {
  278        let mut buffer = language::Buffer::local("abcde", cx);
  279        // Ensure automatic grouping doesn't occur.
  280        buffer.set_group_interval(Duration::ZERO);
  281        buffer
  282    });
  283
  284    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  285    cx.add_window(|window, cx| {
  286        let mut editor = build_editor(buffer.clone(), window, cx);
  287
  288        // Start a new IME composition.
  289        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  290        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
  291        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  292        assert_eq!(editor.text(cx), "äbcde");
  293        assert_eq!(
  294            editor.marked_text_ranges(cx),
  295            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  296        );
  297
  298        // Finalize IME composition.
  299        editor.replace_text_in_range(None, "ā", window, cx);
  300        assert_eq!(editor.text(cx), "ābcde");
  301        assert_eq!(editor.marked_text_ranges(cx), None);
  302
  303        // IME composition edits are grouped and are undone/redone at once.
  304        editor.undo(&Default::default(), window, cx);
  305        assert_eq!(editor.text(cx), "abcde");
  306        assert_eq!(editor.marked_text_ranges(cx), None);
  307        editor.redo(&Default::default(), window, cx);
  308        assert_eq!(editor.text(cx), "ābcde");
  309        assert_eq!(editor.marked_text_ranges(cx), None);
  310
  311        // Start a new IME composition.
  312        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  313        assert_eq!(
  314            editor.marked_text_ranges(cx),
  315            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  316        );
  317
  318        // Undoing during an IME composition cancels it.
  319        editor.undo(&Default::default(), window, cx);
  320        assert_eq!(editor.text(cx), "ābcde");
  321        assert_eq!(editor.marked_text_ranges(cx), None);
  322
  323        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  324        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  325        assert_eq!(editor.text(cx), "ābcdè");
  326        assert_eq!(
  327            editor.marked_text_ranges(cx),
  328            Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
  329        );
  330
  331        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  332        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  333        assert_eq!(editor.text(cx), "ābcdę");
  334        assert_eq!(editor.marked_text_ranges(cx), None);
  335
  336        // Start a new IME composition with multiple cursors.
  337        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  338            s.select_ranges([
  339                OffsetUtf16(1)..OffsetUtf16(1),
  340                OffsetUtf16(3)..OffsetUtf16(3),
  341                OffsetUtf16(5)..OffsetUtf16(5),
  342            ])
  343        });
  344        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  345        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  346        assert_eq!(
  347            editor.marked_text_ranges(cx),
  348            Some(vec![
  349                OffsetUtf16(0)..OffsetUtf16(3),
  350                OffsetUtf16(4)..OffsetUtf16(7),
  351                OffsetUtf16(8)..OffsetUtf16(11)
  352            ])
  353        );
  354
  355        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  356        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  357        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  358        assert_eq!(
  359            editor.marked_text_ranges(cx),
  360            Some(vec![
  361                OffsetUtf16(1)..OffsetUtf16(2),
  362                OffsetUtf16(5)..OffsetUtf16(6),
  363                OffsetUtf16(9)..OffsetUtf16(10)
  364            ])
  365        );
  366
  367        // Finalize IME composition with multiple cursors.
  368        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  369        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  370        assert_eq!(editor.marked_text_ranges(cx), None);
  371
  372        editor
  373    });
  374}
  375
  376#[gpui::test]
  377fn test_selection_with_mouse(cx: &mut TestAppContext) {
  378    init_test(cx, |_| {});
  379
  380    let editor = cx.add_window(|window, cx| {
  381        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  382        build_editor(buffer, window, cx)
  383    });
  384
  385    _ = editor.update(cx, |editor, window, cx| {
  386        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  387    });
  388    assert_eq!(
  389        editor
  390            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  391            .unwrap(),
  392        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  393    );
  394
  395    _ = editor.update(cx, |editor, window, cx| {
  396        editor.update_selection(
  397            DisplayPoint::new(DisplayRow(3), 3),
  398            0,
  399            gpui::Point::<f32>::default(),
  400            window,
  401            cx,
  402        );
  403    });
  404
  405    assert_eq!(
  406        editor
  407            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  408            .unwrap(),
  409        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  410    );
  411
  412    _ = editor.update(cx, |editor, window, cx| {
  413        editor.update_selection(
  414            DisplayPoint::new(DisplayRow(1), 1),
  415            0,
  416            gpui::Point::<f32>::default(),
  417            window,
  418            cx,
  419        );
  420    });
  421
  422    assert_eq!(
  423        editor
  424            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  425            .unwrap(),
  426        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  427    );
  428
  429    _ = editor.update(cx, |editor, window, cx| {
  430        editor.end_selection(window, cx);
  431        editor.update_selection(
  432            DisplayPoint::new(DisplayRow(3), 3),
  433            0,
  434            gpui::Point::<f32>::default(),
  435            window,
  436            cx,
  437        );
  438    });
  439
  440    assert_eq!(
  441        editor
  442            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  443            .unwrap(),
  444        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  445    );
  446
  447    _ = editor.update(cx, |editor, window, cx| {
  448        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  449        editor.update_selection(
  450            DisplayPoint::new(DisplayRow(0), 0),
  451            0,
  452            gpui::Point::<f32>::default(),
  453            window,
  454            cx,
  455        );
  456    });
  457
  458    assert_eq!(
  459        editor
  460            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  461            .unwrap(),
  462        [
  463            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  464            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  465        ]
  466    );
  467
  468    _ = editor.update(cx, |editor, window, cx| {
  469        editor.end_selection(window, cx);
  470    });
  471
  472    assert_eq!(
  473        editor
  474            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  475            .unwrap(),
  476        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  477    );
  478}
  479
  480#[gpui::test]
  481fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  482    init_test(cx, |_| {});
  483
  484    let editor = cx.add_window(|window, cx| {
  485        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  486        build_editor(buffer, window, cx)
  487    });
  488
  489    _ = editor.update(cx, |editor, window, cx| {
  490        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  491    });
  492
  493    _ = editor.update(cx, |editor, window, cx| {
  494        editor.end_selection(window, cx);
  495    });
  496
  497    _ = editor.update(cx, |editor, window, cx| {
  498        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  499    });
  500
  501    _ = editor.update(cx, |editor, window, cx| {
  502        editor.end_selection(window, cx);
  503    });
  504
  505    assert_eq!(
  506        editor
  507            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  508            .unwrap(),
  509        [
  510            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  511            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  512        ]
  513    );
  514
  515    _ = editor.update(cx, |editor, window, cx| {
  516        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  517    });
  518
  519    _ = editor.update(cx, |editor, window, cx| {
  520        editor.end_selection(window, cx);
  521    });
  522
  523    assert_eq!(
  524        editor
  525            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  526            .unwrap(),
  527        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  528    );
  529}
  530
  531#[gpui::test]
  532fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  533    init_test(cx, |_| {});
  534
  535    let editor = cx.add_window(|window, cx| {
  536        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  537        build_editor(buffer, window, cx)
  538    });
  539
  540    _ = editor.update(cx, |editor, window, cx| {
  541        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  542        assert_eq!(
  543            editor.selections.display_ranges(cx),
  544            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  545        );
  546    });
  547
  548    _ = editor.update(cx, |editor, window, cx| {
  549        editor.update_selection(
  550            DisplayPoint::new(DisplayRow(3), 3),
  551            0,
  552            gpui::Point::<f32>::default(),
  553            window,
  554            cx,
  555        );
  556        assert_eq!(
  557            editor.selections.display_ranges(cx),
  558            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  559        );
  560    });
  561
  562    _ = editor.update(cx, |editor, window, cx| {
  563        editor.cancel(&Cancel, window, cx);
  564        editor.update_selection(
  565            DisplayPoint::new(DisplayRow(1), 1),
  566            0,
  567            gpui::Point::<f32>::default(),
  568            window,
  569            cx,
  570        );
  571        assert_eq!(
  572            editor.selections.display_ranges(cx),
  573            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  574        );
  575    });
  576}
  577
  578#[gpui::test]
  579fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  580    init_test(cx, |_| {});
  581
  582    let editor = cx.add_window(|window, cx| {
  583        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  584        build_editor(buffer, window, cx)
  585    });
  586
  587    _ = editor.update(cx, |editor, window, cx| {
  588        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  589        assert_eq!(
  590            editor.selections.display_ranges(cx),
  591            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  592        );
  593
  594        editor.move_down(&Default::default(), window, cx);
  595        assert_eq!(
  596            editor.selections.display_ranges(cx),
  597            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  598        );
  599
  600        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  601        assert_eq!(
  602            editor.selections.display_ranges(cx),
  603            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  604        );
  605
  606        editor.move_up(&Default::default(), window, cx);
  607        assert_eq!(
  608            editor.selections.display_ranges(cx),
  609            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  610        );
  611    });
  612}
  613
  614#[gpui::test]
  615fn test_clone(cx: &mut TestAppContext) {
  616    init_test(cx, |_| {});
  617
  618    let (text, selection_ranges) = marked_text_ranges(
  619        indoc! {"
  620            one
  621            two
  622            threeˇ
  623            four
  624            fiveˇ
  625        "},
  626        true,
  627    );
  628
  629    let editor = cx.add_window(|window, cx| {
  630        let buffer = MultiBuffer::build_simple(&text, cx);
  631        build_editor(buffer, window, cx)
  632    });
  633
  634    _ = editor.update(cx, |editor, window, cx| {
  635        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  636            s.select_ranges(selection_ranges.clone())
  637        });
  638        editor.fold_creases(
  639            vec![
  640                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  641                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  642            ],
  643            true,
  644            window,
  645            cx,
  646        );
  647    });
  648
  649    let cloned_editor = editor
  650        .update(cx, |editor, _, cx| {
  651            cx.open_window(Default::default(), |window, cx| {
  652                cx.new(|cx| editor.clone(window, cx))
  653            })
  654        })
  655        .unwrap()
  656        .unwrap();
  657
  658    let snapshot = editor
  659        .update(cx, |e, window, cx| e.snapshot(window, cx))
  660        .unwrap();
  661    let cloned_snapshot = cloned_editor
  662        .update(cx, |e, window, cx| e.snapshot(window, cx))
  663        .unwrap();
  664
  665    assert_eq!(
  666        cloned_editor
  667            .update(cx, |e, _, cx| e.display_text(cx))
  668            .unwrap(),
  669        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  670    );
  671    assert_eq!(
  672        cloned_snapshot
  673            .folds_in_range(0..text.len())
  674            .collect::<Vec<_>>(),
  675        snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
  676    );
  677    assert_set_eq!(
  678        cloned_editor
  679            .update(cx, |editor, _, cx| editor.selections.ranges::<Point>(cx))
  680            .unwrap(),
  681        editor
  682            .update(cx, |editor, _, cx| editor.selections.ranges(cx))
  683            .unwrap()
  684    );
  685    assert_set_eq!(
  686        cloned_editor
  687            .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
  688            .unwrap(),
  689        editor
  690            .update(cx, |e, _, cx| e.selections.display_ranges(cx))
  691            .unwrap()
  692    );
  693}
  694
  695#[gpui::test]
  696async fn test_navigation_history(cx: &mut TestAppContext) {
  697    init_test(cx, |_| {});
  698
  699    use workspace::item::Item;
  700
  701    let fs = FakeFs::new(cx.executor());
  702    let project = Project::test(fs, [], cx).await;
  703    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  704    let pane = workspace
  705        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  706        .unwrap();
  707
  708    _ = workspace.update(cx, |_v, window, cx| {
  709        cx.new(|cx| {
  710            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  711            let mut editor = build_editor(buffer.clone(), window, cx);
  712            let handle = cx.entity();
  713            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  714
  715            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  716                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  717            }
  718
  719            // Move the cursor a small distance.
  720            // Nothing is added to the navigation history.
  721            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  722                s.select_display_ranges([
  723                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  724                ])
  725            });
  726            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  727                s.select_display_ranges([
  728                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  729                ])
  730            });
  731            assert!(pop_history(&mut editor, cx).is_none());
  732
  733            // Move the cursor a large distance.
  734            // The history can jump back to the previous position.
  735            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  736                s.select_display_ranges([
  737                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  738                ])
  739            });
  740            let nav_entry = pop_history(&mut editor, cx).unwrap();
  741            editor.navigate(nav_entry.data.unwrap(), window, cx);
  742            assert_eq!(nav_entry.item.id(), cx.entity_id());
  743            assert_eq!(
  744                editor.selections.display_ranges(cx),
  745                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  746            );
  747            assert!(pop_history(&mut editor, cx).is_none());
  748
  749            // Move the cursor a small distance via the mouse.
  750            // Nothing is added to the navigation history.
  751            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  752            editor.end_selection(window, cx);
  753            assert_eq!(
  754                editor.selections.display_ranges(cx),
  755                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  756            );
  757            assert!(pop_history(&mut editor, cx).is_none());
  758
  759            // Move the cursor a large distance via the mouse.
  760            // The history can jump back to the previous position.
  761            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  762            editor.end_selection(window, cx);
  763            assert_eq!(
  764                editor.selections.display_ranges(cx),
  765                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  766            );
  767            let nav_entry = pop_history(&mut editor, cx).unwrap();
  768            editor.navigate(nav_entry.data.unwrap(), window, cx);
  769            assert_eq!(nav_entry.item.id(), cx.entity_id());
  770            assert_eq!(
  771                editor.selections.display_ranges(cx),
  772                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  773            );
  774            assert!(pop_history(&mut editor, cx).is_none());
  775
  776            // Set scroll position to check later
  777            editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), window, cx);
  778            let original_scroll_position = editor.scroll_manager.anchor();
  779
  780            // Jump to the end of the document and adjust scroll
  781            editor.move_to_end(&MoveToEnd, window, cx);
  782            editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), window, cx);
  783            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  784
  785            let nav_entry = pop_history(&mut editor, cx).unwrap();
  786            editor.navigate(nav_entry.data.unwrap(), window, cx);
  787            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  788
  789            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  790            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  791            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  792            let invalid_point = Point::new(9999, 0);
  793            editor.navigate(
  794                Box::new(NavigationData {
  795                    cursor_anchor: invalid_anchor,
  796                    cursor_position: invalid_point,
  797                    scroll_anchor: ScrollAnchor {
  798                        anchor: invalid_anchor,
  799                        offset: Default::default(),
  800                    },
  801                    scroll_top_row: invalid_point.row,
  802                }),
  803                window,
  804                cx,
  805            );
  806            assert_eq!(
  807                editor.selections.display_ranges(cx),
  808                &[editor.max_point(cx)..editor.max_point(cx)]
  809            );
  810            assert_eq!(
  811                editor.scroll_position(cx),
  812                gpui::Point::new(0., editor.max_point(cx).row().as_f32())
  813            );
  814
  815            editor
  816        })
  817    });
  818}
  819
  820#[gpui::test]
  821fn test_cancel(cx: &mut TestAppContext) {
  822    init_test(cx, |_| {});
  823
  824    let editor = cx.add_window(|window, cx| {
  825        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  826        build_editor(buffer, window, cx)
  827    });
  828
  829    _ = editor.update(cx, |editor, window, cx| {
  830        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
  831        editor.update_selection(
  832            DisplayPoint::new(DisplayRow(1), 1),
  833            0,
  834            gpui::Point::<f32>::default(),
  835            window,
  836            cx,
  837        );
  838        editor.end_selection(window, cx);
  839
  840        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
  841        editor.update_selection(
  842            DisplayPoint::new(DisplayRow(0), 3),
  843            0,
  844            gpui::Point::<f32>::default(),
  845            window,
  846            cx,
  847        );
  848        editor.end_selection(window, cx);
  849        assert_eq!(
  850            editor.selections.display_ranges(cx),
  851            [
  852                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
  853                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
  854            ]
  855        );
  856    });
  857
  858    _ = editor.update(cx, |editor, window, cx| {
  859        editor.cancel(&Cancel, window, cx);
  860        assert_eq!(
  861            editor.selections.display_ranges(cx),
  862            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
  863        );
  864    });
  865
  866    _ = editor.update(cx, |editor, window, cx| {
  867        editor.cancel(&Cancel, window, cx);
  868        assert_eq!(
  869            editor.selections.display_ranges(cx),
  870            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
  871        );
  872    });
  873}
  874
  875#[gpui::test]
  876fn test_fold_action(cx: &mut TestAppContext) {
  877    init_test(cx, |_| {});
  878
  879    let editor = cx.add_window(|window, cx| {
  880        let buffer = MultiBuffer::build_simple(
  881            &"
  882                impl Foo {
  883                    // Hello!
  884
  885                    fn a() {
  886                        1
  887                    }
  888
  889                    fn b() {
  890                        2
  891                    }
  892
  893                    fn c() {
  894                        3
  895                    }
  896                }
  897            "
  898            .unindent(),
  899            cx,
  900        );
  901        build_editor(buffer.clone(), window, cx)
  902    });
  903
  904    _ = editor.update(cx, |editor, window, cx| {
  905        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  906            s.select_display_ranges([
  907                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
  908            ]);
  909        });
  910        editor.fold(&Fold, window, cx);
  911        assert_eq!(
  912            editor.display_text(cx),
  913            "
  914                impl Foo {
  915                    // Hello!
  916
  917                    fn a() {
  918                        1
  919                    }
  920
  921                    fn b() {⋯
  922                    }
  923
  924                    fn c() {⋯
  925                    }
  926                }
  927            "
  928            .unindent(),
  929        );
  930
  931        editor.fold(&Fold, window, cx);
  932        assert_eq!(
  933            editor.display_text(cx),
  934            "
  935                impl Foo {⋯
  936                }
  937            "
  938            .unindent(),
  939        );
  940
  941        editor.unfold_lines(&UnfoldLines, window, cx);
  942        assert_eq!(
  943            editor.display_text(cx),
  944            "
  945                impl Foo {
  946                    // Hello!
  947
  948                    fn a() {
  949                        1
  950                    }
  951
  952                    fn b() {⋯
  953                    }
  954
  955                    fn c() {⋯
  956                    }
  957                }
  958            "
  959            .unindent(),
  960        );
  961
  962        editor.unfold_lines(&UnfoldLines, window, cx);
  963        assert_eq!(
  964            editor.display_text(cx),
  965            editor.buffer.read(cx).read(cx).text()
  966        );
  967    });
  968}
  969
  970#[gpui::test]
  971fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
  972    init_test(cx, |_| {});
  973
  974    let editor = cx.add_window(|window, cx| {
  975        let buffer = MultiBuffer::build_simple(
  976            &"
  977                class Foo:
  978                    # Hello!
  979
  980                    def a():
  981                        print(1)
  982
  983                    def b():
  984                        print(2)
  985
  986                    def c():
  987                        print(3)
  988            "
  989            .unindent(),
  990            cx,
  991        );
  992        build_editor(buffer.clone(), window, cx)
  993    });
  994
  995    _ = editor.update(cx, |editor, window, cx| {
  996        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  997            s.select_display_ranges([
  998                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
  999            ]);
 1000        });
 1001        editor.fold(&Fold, window, cx);
 1002        assert_eq!(
 1003            editor.display_text(cx),
 1004            "
 1005                class Foo:
 1006                    # Hello!
 1007
 1008                    def a():
 1009                        print(1)
 1010
 1011                    def b():⋯
 1012
 1013                    def c():⋯
 1014            "
 1015            .unindent(),
 1016        );
 1017
 1018        editor.fold(&Fold, window, cx);
 1019        assert_eq!(
 1020            editor.display_text(cx),
 1021            "
 1022                class Foo:⋯
 1023            "
 1024            .unindent(),
 1025        );
 1026
 1027        editor.unfold_lines(&UnfoldLines, window, cx);
 1028        assert_eq!(
 1029            editor.display_text(cx),
 1030            "
 1031                class Foo:
 1032                    # Hello!
 1033
 1034                    def a():
 1035                        print(1)
 1036
 1037                    def b():⋯
 1038
 1039                    def c():⋯
 1040            "
 1041            .unindent(),
 1042        );
 1043
 1044        editor.unfold_lines(&UnfoldLines, window, cx);
 1045        assert_eq!(
 1046            editor.display_text(cx),
 1047            editor.buffer.read(cx).read(cx).text()
 1048        );
 1049    });
 1050}
 1051
 1052#[gpui::test]
 1053fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1054    init_test(cx, |_| {});
 1055
 1056    let editor = cx.add_window(|window, cx| {
 1057        let buffer = MultiBuffer::build_simple(
 1058            &"
 1059                class Foo:
 1060                    # Hello!
 1061
 1062                    def a():
 1063                        print(1)
 1064
 1065                    def b():
 1066                        print(2)
 1067
 1068
 1069                    def c():
 1070                        print(3)
 1071
 1072
 1073            "
 1074            .unindent(),
 1075            cx,
 1076        );
 1077        build_editor(buffer.clone(), window, cx)
 1078    });
 1079
 1080    _ = editor.update(cx, |editor, window, cx| {
 1081        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1082            s.select_display_ranges([
 1083                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1084            ]);
 1085        });
 1086        editor.fold(&Fold, window, cx);
 1087        assert_eq!(
 1088            editor.display_text(cx),
 1089            "
 1090                class Foo:
 1091                    # Hello!
 1092
 1093                    def a():
 1094                        print(1)
 1095
 1096                    def b():⋯
 1097
 1098
 1099                    def c():⋯
 1100
 1101
 1102            "
 1103            .unindent(),
 1104        );
 1105
 1106        editor.fold(&Fold, window, cx);
 1107        assert_eq!(
 1108            editor.display_text(cx),
 1109            "
 1110                class Foo:⋯
 1111
 1112
 1113            "
 1114            .unindent(),
 1115        );
 1116
 1117        editor.unfold_lines(&UnfoldLines, window, cx);
 1118        assert_eq!(
 1119            editor.display_text(cx),
 1120            "
 1121                class Foo:
 1122                    # Hello!
 1123
 1124                    def a():
 1125                        print(1)
 1126
 1127                    def b():⋯
 1128
 1129
 1130                    def c():⋯
 1131
 1132
 1133            "
 1134            .unindent(),
 1135        );
 1136
 1137        editor.unfold_lines(&UnfoldLines, window, cx);
 1138        assert_eq!(
 1139            editor.display_text(cx),
 1140            editor.buffer.read(cx).read(cx).text()
 1141        );
 1142    });
 1143}
 1144
 1145#[gpui::test]
 1146fn test_fold_at_level(cx: &mut TestAppContext) {
 1147    init_test(cx, |_| {});
 1148
 1149    let editor = cx.add_window(|window, cx| {
 1150        let buffer = MultiBuffer::build_simple(
 1151            &"
 1152                class Foo:
 1153                    # Hello!
 1154
 1155                    def a():
 1156                        print(1)
 1157
 1158                    def b():
 1159                        print(2)
 1160
 1161
 1162                class Bar:
 1163                    # World!
 1164
 1165                    def a():
 1166                        print(1)
 1167
 1168                    def b():
 1169                        print(2)
 1170
 1171
 1172            "
 1173            .unindent(),
 1174            cx,
 1175        );
 1176        build_editor(buffer.clone(), window, cx)
 1177    });
 1178
 1179    _ = editor.update(cx, |editor, window, cx| {
 1180        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1181        assert_eq!(
 1182            editor.display_text(cx),
 1183            "
 1184                class Foo:
 1185                    # Hello!
 1186
 1187                    def a():⋯
 1188
 1189                    def b():⋯
 1190
 1191
 1192                class Bar:
 1193                    # World!
 1194
 1195                    def a():⋯
 1196
 1197                    def b():⋯
 1198
 1199
 1200            "
 1201            .unindent(),
 1202        );
 1203
 1204        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1205        assert_eq!(
 1206            editor.display_text(cx),
 1207            "
 1208                class Foo:⋯
 1209
 1210
 1211                class Bar:⋯
 1212
 1213
 1214            "
 1215            .unindent(),
 1216        );
 1217
 1218        editor.unfold_all(&UnfoldAll, window, cx);
 1219        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1220        assert_eq!(
 1221            editor.display_text(cx),
 1222            "
 1223                class Foo:
 1224                    # Hello!
 1225
 1226                    def a():
 1227                        print(1)
 1228
 1229                    def b():
 1230                        print(2)
 1231
 1232
 1233                class Bar:
 1234                    # World!
 1235
 1236                    def a():
 1237                        print(1)
 1238
 1239                    def b():
 1240                        print(2)
 1241
 1242
 1243            "
 1244            .unindent(),
 1245        );
 1246
 1247        assert_eq!(
 1248            editor.display_text(cx),
 1249            editor.buffer.read(cx).read(cx).text()
 1250        );
 1251    });
 1252}
 1253
 1254#[gpui::test]
 1255fn test_move_cursor(cx: &mut TestAppContext) {
 1256    init_test(cx, |_| {});
 1257
 1258    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1259    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1260
 1261    buffer.update(cx, |buffer, cx| {
 1262        buffer.edit(
 1263            vec![
 1264                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1265                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1266            ],
 1267            None,
 1268            cx,
 1269        );
 1270    });
 1271    _ = editor.update(cx, |editor, window, cx| {
 1272        assert_eq!(
 1273            editor.selections.display_ranges(cx),
 1274            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1275        );
 1276
 1277        editor.move_down(&MoveDown, window, cx);
 1278        assert_eq!(
 1279            editor.selections.display_ranges(cx),
 1280            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1281        );
 1282
 1283        editor.move_right(&MoveRight, window, cx);
 1284        assert_eq!(
 1285            editor.selections.display_ranges(cx),
 1286            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1287        );
 1288
 1289        editor.move_left(&MoveLeft, window, cx);
 1290        assert_eq!(
 1291            editor.selections.display_ranges(cx),
 1292            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1293        );
 1294
 1295        editor.move_up(&MoveUp, window, cx);
 1296        assert_eq!(
 1297            editor.selections.display_ranges(cx),
 1298            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1299        );
 1300
 1301        editor.move_to_end(&MoveToEnd, window, cx);
 1302        assert_eq!(
 1303            editor.selections.display_ranges(cx),
 1304            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1305        );
 1306
 1307        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1308        assert_eq!(
 1309            editor.selections.display_ranges(cx),
 1310            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1311        );
 1312
 1313        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1314            s.select_display_ranges([
 1315                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1316            ]);
 1317        });
 1318        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1319        assert_eq!(
 1320            editor.selections.display_ranges(cx),
 1321            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1322        );
 1323
 1324        editor.select_to_end(&SelectToEnd, window, cx);
 1325        assert_eq!(
 1326            editor.selections.display_ranges(cx),
 1327            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1328        );
 1329    });
 1330}
 1331
 1332#[gpui::test]
 1333fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1334    init_test(cx, |_| {});
 1335
 1336    let editor = cx.add_window(|window, cx| {
 1337        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1338        build_editor(buffer.clone(), window, cx)
 1339    });
 1340
 1341    assert_eq!('🟥'.len_utf8(), 4);
 1342    assert_eq!('α'.len_utf8(), 2);
 1343
 1344    _ = editor.update(cx, |editor, window, cx| {
 1345        editor.fold_creases(
 1346            vec![
 1347                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1348                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1349                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1350            ],
 1351            true,
 1352            window,
 1353            cx,
 1354        );
 1355        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1356
 1357        editor.move_right(&MoveRight, window, cx);
 1358        assert_eq!(
 1359            editor.selections.display_ranges(cx),
 1360            &[empty_range(0, "🟥".len())]
 1361        );
 1362        editor.move_right(&MoveRight, window, cx);
 1363        assert_eq!(
 1364            editor.selections.display_ranges(cx),
 1365            &[empty_range(0, "🟥🟧".len())]
 1366        );
 1367        editor.move_right(&MoveRight, window, cx);
 1368        assert_eq!(
 1369            editor.selections.display_ranges(cx),
 1370            &[empty_range(0, "🟥🟧⋯".len())]
 1371        );
 1372
 1373        editor.move_down(&MoveDown, window, cx);
 1374        assert_eq!(
 1375            editor.selections.display_ranges(cx),
 1376            &[empty_range(1, "ab⋯e".len())]
 1377        );
 1378        editor.move_left(&MoveLeft, window, cx);
 1379        assert_eq!(
 1380            editor.selections.display_ranges(cx),
 1381            &[empty_range(1, "ab⋯".len())]
 1382        );
 1383        editor.move_left(&MoveLeft, window, cx);
 1384        assert_eq!(
 1385            editor.selections.display_ranges(cx),
 1386            &[empty_range(1, "ab".len())]
 1387        );
 1388        editor.move_left(&MoveLeft, window, cx);
 1389        assert_eq!(
 1390            editor.selections.display_ranges(cx),
 1391            &[empty_range(1, "a".len())]
 1392        );
 1393
 1394        editor.move_down(&MoveDown, window, cx);
 1395        assert_eq!(
 1396            editor.selections.display_ranges(cx),
 1397            &[empty_range(2, "α".len())]
 1398        );
 1399        editor.move_right(&MoveRight, window, cx);
 1400        assert_eq!(
 1401            editor.selections.display_ranges(cx),
 1402            &[empty_range(2, "αβ".len())]
 1403        );
 1404        editor.move_right(&MoveRight, window, cx);
 1405        assert_eq!(
 1406            editor.selections.display_ranges(cx),
 1407            &[empty_range(2, "αβ⋯".len())]
 1408        );
 1409        editor.move_right(&MoveRight, window, cx);
 1410        assert_eq!(
 1411            editor.selections.display_ranges(cx),
 1412            &[empty_range(2, "αβ⋯ε".len())]
 1413        );
 1414
 1415        editor.move_up(&MoveUp, window, cx);
 1416        assert_eq!(
 1417            editor.selections.display_ranges(cx),
 1418            &[empty_range(1, "ab⋯e".len())]
 1419        );
 1420        editor.move_down(&MoveDown, window, cx);
 1421        assert_eq!(
 1422            editor.selections.display_ranges(cx),
 1423            &[empty_range(2, "αβ⋯ε".len())]
 1424        );
 1425        editor.move_up(&MoveUp, window, cx);
 1426        assert_eq!(
 1427            editor.selections.display_ranges(cx),
 1428            &[empty_range(1, "ab⋯e".len())]
 1429        );
 1430
 1431        editor.move_up(&MoveUp, window, cx);
 1432        assert_eq!(
 1433            editor.selections.display_ranges(cx),
 1434            &[empty_range(0, "🟥🟧".len())]
 1435        );
 1436        editor.move_left(&MoveLeft, window, cx);
 1437        assert_eq!(
 1438            editor.selections.display_ranges(cx),
 1439            &[empty_range(0, "🟥".len())]
 1440        );
 1441        editor.move_left(&MoveLeft, window, cx);
 1442        assert_eq!(
 1443            editor.selections.display_ranges(cx),
 1444            &[empty_range(0, "".len())]
 1445        );
 1446    });
 1447}
 1448
 1449#[gpui::test]
 1450fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1451    init_test(cx, |_| {});
 1452
 1453    let editor = cx.add_window(|window, cx| {
 1454        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1455        build_editor(buffer.clone(), window, cx)
 1456    });
 1457    _ = editor.update(cx, |editor, window, cx| {
 1458        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1459            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1460        });
 1461
 1462        // moving above start of document should move selection to start of document,
 1463        // but the next move down should still be at the original goal_x
 1464        editor.move_up(&MoveUp, window, cx);
 1465        assert_eq!(
 1466            editor.selections.display_ranges(cx),
 1467            &[empty_range(0, "".len())]
 1468        );
 1469
 1470        editor.move_down(&MoveDown, window, cx);
 1471        assert_eq!(
 1472            editor.selections.display_ranges(cx),
 1473            &[empty_range(1, "abcd".len())]
 1474        );
 1475
 1476        editor.move_down(&MoveDown, window, cx);
 1477        assert_eq!(
 1478            editor.selections.display_ranges(cx),
 1479            &[empty_range(2, "αβγ".len())]
 1480        );
 1481
 1482        editor.move_down(&MoveDown, window, cx);
 1483        assert_eq!(
 1484            editor.selections.display_ranges(cx),
 1485            &[empty_range(3, "abcd".len())]
 1486        );
 1487
 1488        editor.move_down(&MoveDown, window, cx);
 1489        assert_eq!(
 1490            editor.selections.display_ranges(cx),
 1491            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1492        );
 1493
 1494        // moving past end of document should not change goal_x
 1495        editor.move_down(&MoveDown, window, cx);
 1496        assert_eq!(
 1497            editor.selections.display_ranges(cx),
 1498            &[empty_range(5, "".len())]
 1499        );
 1500
 1501        editor.move_down(&MoveDown, window, cx);
 1502        assert_eq!(
 1503            editor.selections.display_ranges(cx),
 1504            &[empty_range(5, "".len())]
 1505        );
 1506
 1507        editor.move_up(&MoveUp, window, cx);
 1508        assert_eq!(
 1509            editor.selections.display_ranges(cx),
 1510            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1511        );
 1512
 1513        editor.move_up(&MoveUp, window, cx);
 1514        assert_eq!(
 1515            editor.selections.display_ranges(cx),
 1516            &[empty_range(3, "abcd".len())]
 1517        );
 1518
 1519        editor.move_up(&MoveUp, window, cx);
 1520        assert_eq!(
 1521            editor.selections.display_ranges(cx),
 1522            &[empty_range(2, "αβγ".len())]
 1523        );
 1524    });
 1525}
 1526
 1527#[gpui::test]
 1528fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1529    init_test(cx, |_| {});
 1530    let move_to_beg = MoveToBeginningOfLine {
 1531        stop_at_soft_wraps: true,
 1532        stop_at_indent: true,
 1533    };
 1534
 1535    let delete_to_beg = DeleteToBeginningOfLine {
 1536        stop_at_indent: false,
 1537    };
 1538
 1539    let move_to_end = MoveToEndOfLine {
 1540        stop_at_soft_wraps: true,
 1541    };
 1542
 1543    let editor = cx.add_window(|window, cx| {
 1544        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1545        build_editor(buffer, window, cx)
 1546    });
 1547    _ = editor.update(cx, |editor, window, cx| {
 1548        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1549            s.select_display_ranges([
 1550                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1551                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1552            ]);
 1553        });
 1554    });
 1555
 1556    _ = editor.update(cx, |editor, window, cx| {
 1557        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1558        assert_eq!(
 1559            editor.selections.display_ranges(cx),
 1560            &[
 1561                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1562                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1563            ]
 1564        );
 1565    });
 1566
 1567    _ = editor.update(cx, |editor, window, cx| {
 1568        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1569        assert_eq!(
 1570            editor.selections.display_ranges(cx),
 1571            &[
 1572                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1573                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1574            ]
 1575        );
 1576    });
 1577
 1578    _ = editor.update(cx, |editor, window, cx| {
 1579        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1580        assert_eq!(
 1581            editor.selections.display_ranges(cx),
 1582            &[
 1583                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1584                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1585            ]
 1586        );
 1587    });
 1588
 1589    _ = editor.update(cx, |editor, window, cx| {
 1590        editor.move_to_end_of_line(&move_to_end, window, cx);
 1591        assert_eq!(
 1592            editor.selections.display_ranges(cx),
 1593            &[
 1594                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1595                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1596            ]
 1597        );
 1598    });
 1599
 1600    // Moving to the end of line again is a no-op.
 1601    _ = editor.update(cx, |editor, window, cx| {
 1602        editor.move_to_end_of_line(&move_to_end, window, cx);
 1603        assert_eq!(
 1604            editor.selections.display_ranges(cx),
 1605            &[
 1606                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1607                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1608            ]
 1609        );
 1610    });
 1611
 1612    _ = editor.update(cx, |editor, window, cx| {
 1613        editor.move_left(&MoveLeft, window, cx);
 1614        editor.select_to_beginning_of_line(
 1615            &SelectToBeginningOfLine {
 1616                stop_at_soft_wraps: true,
 1617                stop_at_indent: true,
 1618            },
 1619            window,
 1620            cx,
 1621        );
 1622        assert_eq!(
 1623            editor.selections.display_ranges(cx),
 1624            &[
 1625                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1626                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1627            ]
 1628        );
 1629    });
 1630
 1631    _ = editor.update(cx, |editor, window, cx| {
 1632        editor.select_to_beginning_of_line(
 1633            &SelectToBeginningOfLine {
 1634                stop_at_soft_wraps: true,
 1635                stop_at_indent: true,
 1636            },
 1637            window,
 1638            cx,
 1639        );
 1640        assert_eq!(
 1641            editor.selections.display_ranges(cx),
 1642            &[
 1643                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1644                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1645            ]
 1646        );
 1647    });
 1648
 1649    _ = editor.update(cx, |editor, window, cx| {
 1650        editor.select_to_beginning_of_line(
 1651            &SelectToBeginningOfLine {
 1652                stop_at_soft_wraps: true,
 1653                stop_at_indent: true,
 1654            },
 1655            window,
 1656            cx,
 1657        );
 1658        assert_eq!(
 1659            editor.selections.display_ranges(cx),
 1660            &[
 1661                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1662                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1663            ]
 1664        );
 1665    });
 1666
 1667    _ = editor.update(cx, |editor, window, cx| {
 1668        editor.select_to_end_of_line(
 1669            &SelectToEndOfLine {
 1670                stop_at_soft_wraps: true,
 1671            },
 1672            window,
 1673            cx,
 1674        );
 1675        assert_eq!(
 1676            editor.selections.display_ranges(cx),
 1677            &[
 1678                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1679                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1680            ]
 1681        );
 1682    });
 1683
 1684    _ = editor.update(cx, |editor, window, cx| {
 1685        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1686        assert_eq!(editor.display_text(cx), "ab\n  de");
 1687        assert_eq!(
 1688            editor.selections.display_ranges(cx),
 1689            &[
 1690                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1691                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1692            ]
 1693        );
 1694    });
 1695
 1696    _ = editor.update(cx, |editor, window, cx| {
 1697        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1698        assert_eq!(editor.display_text(cx), "\n");
 1699        assert_eq!(
 1700            editor.selections.display_ranges(cx),
 1701            &[
 1702                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1703                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1704            ]
 1705        );
 1706    });
 1707}
 1708
 1709#[gpui::test]
 1710fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1711    init_test(cx, |_| {});
 1712    let move_to_beg = MoveToBeginningOfLine {
 1713        stop_at_soft_wraps: false,
 1714        stop_at_indent: false,
 1715    };
 1716
 1717    let move_to_end = MoveToEndOfLine {
 1718        stop_at_soft_wraps: false,
 1719    };
 1720
 1721    let editor = cx.add_window(|window, cx| {
 1722        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1723        build_editor(buffer, window, cx)
 1724    });
 1725
 1726    _ = editor.update(cx, |editor, window, cx| {
 1727        editor.set_wrap_width(Some(140.0.into()), cx);
 1728
 1729        // We expect the following lines after wrapping
 1730        // ```
 1731        // thequickbrownfox
 1732        // jumpedoverthelazydo
 1733        // gs
 1734        // ```
 1735        // The final `gs` was soft-wrapped onto a new line.
 1736        assert_eq!(
 1737            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1738            editor.display_text(cx),
 1739        );
 1740
 1741        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1742        // Start the cursor at the `k` on the first line
 1743        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1744            s.select_display_ranges([
 1745                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1746            ]);
 1747        });
 1748
 1749        // Moving to the beginning of the line should put us at the beginning of the line.
 1750        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1751        assert_eq!(
 1752            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1753            editor.selections.display_ranges(cx)
 1754        );
 1755
 1756        // Moving to the end of the line should put us at the end of the line.
 1757        editor.move_to_end_of_line(&move_to_end, window, cx);
 1758        assert_eq!(
 1759            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1760            editor.selections.display_ranges(cx)
 1761        );
 1762
 1763        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1764        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1765        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1766            s.select_display_ranges([
 1767                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1768            ]);
 1769        });
 1770
 1771        // Moving to the beginning of the line should put us at the start of the second line of
 1772        // display text, i.e., the `j`.
 1773        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1774        assert_eq!(
 1775            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1776            editor.selections.display_ranges(cx)
 1777        );
 1778
 1779        // Moving to the beginning of the line again should be a no-op.
 1780        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1781        assert_eq!(
 1782            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1783            editor.selections.display_ranges(cx)
 1784        );
 1785
 1786        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1787        // next display line.
 1788        editor.move_to_end_of_line(&move_to_end, window, cx);
 1789        assert_eq!(
 1790            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1791            editor.selections.display_ranges(cx)
 1792        );
 1793
 1794        // Moving to the end of the line again should be a no-op.
 1795        editor.move_to_end_of_line(&move_to_end, window, cx);
 1796        assert_eq!(
 1797            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1798            editor.selections.display_ranges(cx)
 1799        );
 1800    });
 1801}
 1802
 1803#[gpui::test]
 1804fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1805    init_test(cx, |_| {});
 1806
 1807    let move_to_beg = MoveToBeginningOfLine {
 1808        stop_at_soft_wraps: true,
 1809        stop_at_indent: true,
 1810    };
 1811
 1812    let select_to_beg = SelectToBeginningOfLine {
 1813        stop_at_soft_wraps: true,
 1814        stop_at_indent: true,
 1815    };
 1816
 1817    let delete_to_beg = DeleteToBeginningOfLine {
 1818        stop_at_indent: true,
 1819    };
 1820
 1821    let move_to_end = MoveToEndOfLine {
 1822        stop_at_soft_wraps: false,
 1823    };
 1824
 1825    let editor = cx.add_window(|window, cx| {
 1826        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1827        build_editor(buffer, window, cx)
 1828    });
 1829
 1830    _ = editor.update(cx, |editor, window, cx| {
 1831        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1832            s.select_display_ranges([
 1833                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1834                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1835            ]);
 1836        });
 1837
 1838        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 1839        // and the second cursor at the first non-whitespace character in the line.
 1840        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1841        assert_eq!(
 1842            editor.selections.display_ranges(cx),
 1843            &[
 1844                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1845                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1846            ]
 1847        );
 1848
 1849        // Moving to the beginning of the line again should be a no-op for the first cursor,
 1850        // and should move the second cursor to the beginning of the line.
 1851        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1852        assert_eq!(
 1853            editor.selections.display_ranges(cx),
 1854            &[
 1855                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1856                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1857            ]
 1858        );
 1859
 1860        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 1861        // and should move the second cursor back to the first non-whitespace character in the line.
 1862        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1863        assert_eq!(
 1864            editor.selections.display_ranges(cx),
 1865            &[
 1866                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1867                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1868            ]
 1869        );
 1870
 1871        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 1872        // and to the first non-whitespace character in the line for the second cursor.
 1873        editor.move_to_end_of_line(&move_to_end, window, cx);
 1874        editor.move_left(&MoveLeft, window, cx);
 1875        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1876        assert_eq!(
 1877            editor.selections.display_ranges(cx),
 1878            &[
 1879                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1880                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1881            ]
 1882        );
 1883
 1884        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 1885        // and should select to the beginning of the line for the second cursor.
 1886        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1887        assert_eq!(
 1888            editor.selections.display_ranges(cx),
 1889            &[
 1890                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1891                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1892            ]
 1893        );
 1894
 1895        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 1896        // and should delete to the first non-whitespace character in the line for the second cursor.
 1897        editor.move_to_end_of_line(&move_to_end, window, cx);
 1898        editor.move_left(&MoveLeft, window, cx);
 1899        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1900        assert_eq!(editor.text(cx), "c\n  f");
 1901    });
 1902}
 1903
 1904#[gpui::test]
 1905fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 1906    init_test(cx, |_| {});
 1907
 1908    let editor = cx.add_window(|window, cx| {
 1909        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 1910        build_editor(buffer, window, cx)
 1911    });
 1912    _ = editor.update(cx, |editor, window, cx| {
 1913        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1914            s.select_display_ranges([
 1915                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 1916                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 1917            ])
 1918        });
 1919        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1920        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 1921
 1922        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1923        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 1924
 1925        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1926        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1927
 1928        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1929        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1930
 1931        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1932        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 1933
 1934        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1935        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1936
 1937        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1938        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 1939
 1940        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1941        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1942
 1943        editor.move_right(&MoveRight, window, cx);
 1944        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1945        assert_selection_ranges(
 1946            "use std::«ˇs»tr::{foo, bar}\n«ˇ\n»  {baz.qux()}",
 1947            editor,
 1948            cx,
 1949        );
 1950
 1951        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1952        assert_selection_ranges(
 1953            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 1954            editor,
 1955            cx,
 1956        );
 1957
 1958        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 1959        assert_selection_ranges(
 1960            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 1961            editor,
 1962            cx,
 1963        );
 1964    });
 1965}
 1966
 1967#[gpui::test]
 1968fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 1969    init_test(cx, |_| {});
 1970
 1971    let editor = cx.add_window(|window, cx| {
 1972        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 1973        build_editor(buffer, window, cx)
 1974    });
 1975
 1976    _ = editor.update(cx, |editor, window, cx| {
 1977        editor.set_wrap_width(Some(140.0.into()), cx);
 1978        assert_eq!(
 1979            editor.display_text(cx),
 1980            "use one::{\n    two::three::\n    four::five\n};"
 1981        );
 1982
 1983        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1984            s.select_display_ranges([
 1985                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 1986            ]);
 1987        });
 1988
 1989        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1990        assert_eq!(
 1991            editor.selections.display_ranges(cx),
 1992            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 1993        );
 1994
 1995        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1996        assert_eq!(
 1997            editor.selections.display_ranges(cx),
 1998            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 1999        );
 2000
 2001        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2002        assert_eq!(
 2003            editor.selections.display_ranges(cx),
 2004            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2005        );
 2006
 2007        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2008        assert_eq!(
 2009            editor.selections.display_ranges(cx),
 2010            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2011        );
 2012
 2013        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2014        assert_eq!(
 2015            editor.selections.display_ranges(cx),
 2016            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2017        );
 2018
 2019        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2020        assert_eq!(
 2021            editor.selections.display_ranges(cx),
 2022            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2023        );
 2024    });
 2025}
 2026
 2027#[gpui::test]
 2028async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2029    init_test(cx, |_| {});
 2030    let mut cx = EditorTestContext::new(cx).await;
 2031
 2032    let line_height = cx.editor(|editor, window, _| {
 2033        editor
 2034            .style()
 2035            .unwrap()
 2036            .text
 2037            .line_height_in_pixels(window.rem_size())
 2038    });
 2039    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2040
 2041    cx.set_state(
 2042        &r#"ˇone
 2043        two
 2044
 2045        three
 2046        fourˇ
 2047        five
 2048
 2049        six"#
 2050            .unindent(),
 2051    );
 2052
 2053    cx.update_editor(|editor, window, cx| {
 2054        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2055    });
 2056    cx.assert_editor_state(
 2057        &r#"one
 2058        two
 2059        ˇ
 2060        three
 2061        four
 2062        five
 2063        ˇ
 2064        six"#
 2065            .unindent(),
 2066    );
 2067
 2068    cx.update_editor(|editor, window, cx| {
 2069        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2070    });
 2071    cx.assert_editor_state(
 2072        &r#"one
 2073        two
 2074
 2075        three
 2076        four
 2077        five
 2078        ˇ
 2079        sixˇ"#
 2080            .unindent(),
 2081    );
 2082
 2083    cx.update_editor(|editor, window, cx| {
 2084        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2085    });
 2086    cx.assert_editor_state(
 2087        &r#"one
 2088        two
 2089
 2090        three
 2091        four
 2092        five
 2093
 2094        sixˇ"#
 2095            .unindent(),
 2096    );
 2097
 2098    cx.update_editor(|editor, window, cx| {
 2099        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2100    });
 2101    cx.assert_editor_state(
 2102        &r#"one
 2103        two
 2104
 2105        three
 2106        four
 2107        five
 2108        ˇ
 2109        six"#
 2110            .unindent(),
 2111    );
 2112
 2113    cx.update_editor(|editor, window, cx| {
 2114        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2115    });
 2116    cx.assert_editor_state(
 2117        &r#"one
 2118        two
 2119        ˇ
 2120        three
 2121        four
 2122        five
 2123
 2124        six"#
 2125            .unindent(),
 2126    );
 2127
 2128    cx.update_editor(|editor, window, cx| {
 2129        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2130    });
 2131    cx.assert_editor_state(
 2132        &r#"ˇone
 2133        two
 2134
 2135        three
 2136        four
 2137        five
 2138
 2139        six"#
 2140            .unindent(),
 2141    );
 2142}
 2143
 2144#[gpui::test]
 2145async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2146    init_test(cx, |_| {});
 2147    let mut cx = EditorTestContext::new(cx).await;
 2148    let line_height = cx.editor(|editor, window, _| {
 2149        editor
 2150            .style()
 2151            .unwrap()
 2152            .text
 2153            .line_height_in_pixels(window.rem_size())
 2154    });
 2155    let window = cx.window;
 2156    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2157
 2158    cx.set_state(
 2159        r#"ˇone
 2160        two
 2161        three
 2162        four
 2163        five
 2164        six
 2165        seven
 2166        eight
 2167        nine
 2168        ten
 2169        "#,
 2170    );
 2171
 2172    cx.update_editor(|editor, window, cx| {
 2173        assert_eq!(
 2174            editor.snapshot(window, cx).scroll_position(),
 2175            gpui::Point::new(0., 0.)
 2176        );
 2177        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2178        assert_eq!(
 2179            editor.snapshot(window, cx).scroll_position(),
 2180            gpui::Point::new(0., 3.)
 2181        );
 2182        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2183        assert_eq!(
 2184            editor.snapshot(window, cx).scroll_position(),
 2185            gpui::Point::new(0., 6.)
 2186        );
 2187        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2188        assert_eq!(
 2189            editor.snapshot(window, cx).scroll_position(),
 2190            gpui::Point::new(0., 3.)
 2191        );
 2192
 2193        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2194        assert_eq!(
 2195            editor.snapshot(window, cx).scroll_position(),
 2196            gpui::Point::new(0., 1.)
 2197        );
 2198        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2199        assert_eq!(
 2200            editor.snapshot(window, cx).scroll_position(),
 2201            gpui::Point::new(0., 3.)
 2202        );
 2203    });
 2204}
 2205
 2206#[gpui::test]
 2207async fn test_autoscroll(cx: &mut TestAppContext) {
 2208    init_test(cx, |_| {});
 2209    let mut cx = EditorTestContext::new(cx).await;
 2210
 2211    let line_height = cx.update_editor(|editor, window, cx| {
 2212        editor.set_vertical_scroll_margin(2, cx);
 2213        editor
 2214            .style()
 2215            .unwrap()
 2216            .text
 2217            .line_height_in_pixels(window.rem_size())
 2218    });
 2219    let window = cx.window;
 2220    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2221
 2222    cx.set_state(
 2223        r#"ˇone
 2224            two
 2225            three
 2226            four
 2227            five
 2228            six
 2229            seven
 2230            eight
 2231            nine
 2232            ten
 2233        "#,
 2234    );
 2235    cx.update_editor(|editor, window, cx| {
 2236        assert_eq!(
 2237            editor.snapshot(window, cx).scroll_position(),
 2238            gpui::Point::new(0., 0.0)
 2239        );
 2240    });
 2241
 2242    // Add a cursor below the visible area. Since both cursors cannot fit
 2243    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2244    // allows the vertical scroll margin below that cursor.
 2245    cx.update_editor(|editor, window, cx| {
 2246        editor.change_selections(Default::default(), window, cx, |selections| {
 2247            selections.select_ranges([
 2248                Point::new(0, 0)..Point::new(0, 0),
 2249                Point::new(6, 0)..Point::new(6, 0),
 2250            ]);
 2251        })
 2252    });
 2253    cx.update_editor(|editor, window, cx| {
 2254        assert_eq!(
 2255            editor.snapshot(window, cx).scroll_position(),
 2256            gpui::Point::new(0., 3.0)
 2257        );
 2258    });
 2259
 2260    // Move down. The editor cursor scrolls down to track the newest cursor.
 2261    cx.update_editor(|editor, window, cx| {
 2262        editor.move_down(&Default::default(), window, cx);
 2263    });
 2264    cx.update_editor(|editor, window, cx| {
 2265        assert_eq!(
 2266            editor.snapshot(window, cx).scroll_position(),
 2267            gpui::Point::new(0., 4.0)
 2268        );
 2269    });
 2270
 2271    // Add a cursor above the visible area. Since both cursors fit on screen,
 2272    // the editor scrolls to show both.
 2273    cx.update_editor(|editor, window, cx| {
 2274        editor.change_selections(Default::default(), window, cx, |selections| {
 2275            selections.select_ranges([
 2276                Point::new(1, 0)..Point::new(1, 0),
 2277                Point::new(6, 0)..Point::new(6, 0),
 2278            ]);
 2279        })
 2280    });
 2281    cx.update_editor(|editor, window, cx| {
 2282        assert_eq!(
 2283            editor.snapshot(window, cx).scroll_position(),
 2284            gpui::Point::new(0., 1.0)
 2285        );
 2286    });
 2287}
 2288
 2289#[gpui::test]
 2290async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2291    init_test(cx, |_| {});
 2292    let mut cx = EditorTestContext::new(cx).await;
 2293
 2294    let line_height = cx.editor(|editor, window, _cx| {
 2295        editor
 2296            .style()
 2297            .unwrap()
 2298            .text
 2299            .line_height_in_pixels(window.rem_size())
 2300    });
 2301    let window = cx.window;
 2302    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2303    cx.set_state(
 2304        &r#"
 2305        ˇone
 2306        two
 2307        threeˇ
 2308        four
 2309        five
 2310        six
 2311        seven
 2312        eight
 2313        nine
 2314        ten
 2315        "#
 2316        .unindent(),
 2317    );
 2318
 2319    cx.update_editor(|editor, window, cx| {
 2320        editor.move_page_down(&MovePageDown::default(), window, cx)
 2321    });
 2322    cx.assert_editor_state(
 2323        &r#"
 2324        one
 2325        two
 2326        three
 2327        ˇfour
 2328        five
 2329        sixˇ
 2330        seven
 2331        eight
 2332        nine
 2333        ten
 2334        "#
 2335        .unindent(),
 2336    );
 2337
 2338    cx.update_editor(|editor, window, cx| {
 2339        editor.move_page_down(&MovePageDown::default(), window, cx)
 2340    });
 2341    cx.assert_editor_state(
 2342        &r#"
 2343        one
 2344        two
 2345        three
 2346        four
 2347        five
 2348        six
 2349        ˇseven
 2350        eight
 2351        nineˇ
 2352        ten
 2353        "#
 2354        .unindent(),
 2355    );
 2356
 2357    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2358    cx.assert_editor_state(
 2359        &r#"
 2360        one
 2361        two
 2362        three
 2363        ˇfour
 2364        five
 2365        sixˇ
 2366        seven
 2367        eight
 2368        nine
 2369        ten
 2370        "#
 2371        .unindent(),
 2372    );
 2373
 2374    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2375    cx.assert_editor_state(
 2376        &r#"
 2377        ˇone
 2378        two
 2379        threeˇ
 2380        four
 2381        five
 2382        six
 2383        seven
 2384        eight
 2385        nine
 2386        ten
 2387        "#
 2388        .unindent(),
 2389    );
 2390
 2391    // Test select collapsing
 2392    cx.update_editor(|editor, window, cx| {
 2393        editor.move_page_down(&MovePageDown::default(), window, cx);
 2394        editor.move_page_down(&MovePageDown::default(), window, cx);
 2395        editor.move_page_down(&MovePageDown::default(), window, cx);
 2396    });
 2397    cx.assert_editor_state(
 2398        &r#"
 2399        one
 2400        two
 2401        three
 2402        four
 2403        five
 2404        six
 2405        seven
 2406        eight
 2407        nine
 2408        ˇten
 2409        ˇ"#
 2410        .unindent(),
 2411    );
 2412}
 2413
 2414#[gpui::test]
 2415async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2416    init_test(cx, |_| {});
 2417    let mut cx = EditorTestContext::new(cx).await;
 2418    cx.set_state("one «two threeˇ» four");
 2419    cx.update_editor(|editor, window, cx| {
 2420        editor.delete_to_beginning_of_line(
 2421            &DeleteToBeginningOfLine {
 2422                stop_at_indent: false,
 2423            },
 2424            window,
 2425            cx,
 2426        );
 2427        assert_eq!(editor.text(cx), " four");
 2428    });
 2429}
 2430
 2431#[gpui::test]
 2432fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2433    init_test(cx, |_| {});
 2434
 2435    let editor = cx.add_window(|window, cx| {
 2436        let buffer = MultiBuffer::build_simple("one two three four", cx);
 2437        build_editor(buffer.clone(), window, cx)
 2438    });
 2439
 2440    _ = editor.update(cx, |editor, window, cx| {
 2441        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2442            s.select_display_ranges([
 2443                // an empty selection - the preceding word fragment is deleted
 2444                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2445                // characters selected - they are deleted
 2446                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
 2447            ])
 2448        });
 2449        editor.delete_to_previous_word_start(
 2450            &DeleteToPreviousWordStart {
 2451                ignore_newlines: false,
 2452            },
 2453            window,
 2454            cx,
 2455        );
 2456        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e two te four");
 2457    });
 2458
 2459    _ = editor.update(cx, |editor, window, cx| {
 2460        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2461            s.select_display_ranges([
 2462                // an empty selection - the following word fragment is deleted
 2463                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 2464                // characters selected - they are deleted
 2465                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
 2466            ])
 2467        });
 2468        editor.delete_to_next_word_end(
 2469            &DeleteToNextWordEnd {
 2470                ignore_newlines: false,
 2471            },
 2472            window,
 2473            cx,
 2474        );
 2475        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e t te our");
 2476    });
 2477}
 2478
 2479#[gpui::test]
 2480fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2481    init_test(cx, |_| {});
 2482
 2483    let editor = cx.add_window(|window, cx| {
 2484        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 2485        build_editor(buffer.clone(), window, cx)
 2486    });
 2487    let del_to_prev_word_start = DeleteToPreviousWordStart {
 2488        ignore_newlines: false,
 2489    };
 2490    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 2491        ignore_newlines: true,
 2492    };
 2493
 2494    _ = editor.update(cx, |editor, window, cx| {
 2495        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2496            s.select_display_ranges([
 2497                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 2498            ])
 2499        });
 2500        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2501        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 2502        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2503        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 2504        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2505        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 2506        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2507        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 2508        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2509        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 2510        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2511        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2512    });
 2513}
 2514
 2515#[gpui::test]
 2516fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 2517    init_test(cx, |_| {});
 2518
 2519    let editor = cx.add_window(|window, cx| {
 2520        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 2521        build_editor(buffer.clone(), window, cx)
 2522    });
 2523    let del_to_next_word_end = DeleteToNextWordEnd {
 2524        ignore_newlines: false,
 2525    };
 2526    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 2527        ignore_newlines: true,
 2528    };
 2529
 2530    _ = editor.update(cx, |editor, window, cx| {
 2531        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2532            s.select_display_ranges([
 2533                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 2534            ])
 2535        });
 2536        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2537        assert_eq!(
 2538            editor.buffer.read(cx).read(cx).text(),
 2539            "one\n   two\nthree\n   four"
 2540        );
 2541        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2542        assert_eq!(
 2543            editor.buffer.read(cx).read(cx).text(),
 2544            "\n   two\nthree\n   four"
 2545        );
 2546        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2547        assert_eq!(
 2548            editor.buffer.read(cx).read(cx).text(),
 2549            "two\nthree\n   four"
 2550        );
 2551        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2552        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 2553        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2554        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 2555        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2556        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2557    });
 2558}
 2559
 2560#[gpui::test]
 2561fn test_newline(cx: &mut TestAppContext) {
 2562    init_test(cx, |_| {});
 2563
 2564    let editor = cx.add_window(|window, cx| {
 2565        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 2566        build_editor(buffer.clone(), window, cx)
 2567    });
 2568
 2569    _ = editor.update(cx, |editor, window, cx| {
 2570        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2571            s.select_display_ranges([
 2572                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2573                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2574                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 2575            ])
 2576        });
 2577
 2578        editor.newline(&Newline, window, cx);
 2579        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 2580    });
 2581}
 2582
 2583#[gpui::test]
 2584fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 2585    init_test(cx, |_| {});
 2586
 2587    let editor = cx.add_window(|window, cx| {
 2588        let buffer = MultiBuffer::build_simple(
 2589            "
 2590                a
 2591                b(
 2592                    X
 2593                )
 2594                c(
 2595                    X
 2596                )
 2597            "
 2598            .unindent()
 2599            .as_str(),
 2600            cx,
 2601        );
 2602        let mut editor = build_editor(buffer.clone(), window, cx);
 2603        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2604            s.select_ranges([
 2605                Point::new(2, 4)..Point::new(2, 5),
 2606                Point::new(5, 4)..Point::new(5, 5),
 2607            ])
 2608        });
 2609        editor
 2610    });
 2611
 2612    _ = editor.update(cx, |editor, window, cx| {
 2613        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 2614        editor.buffer.update(cx, |buffer, cx| {
 2615            buffer.edit(
 2616                [
 2617                    (Point::new(1, 2)..Point::new(3, 0), ""),
 2618                    (Point::new(4, 2)..Point::new(6, 0), ""),
 2619                ],
 2620                None,
 2621                cx,
 2622            );
 2623            assert_eq!(
 2624                buffer.read(cx).text(),
 2625                "
 2626                    a
 2627                    b()
 2628                    c()
 2629                "
 2630                .unindent()
 2631            );
 2632        });
 2633        assert_eq!(
 2634            editor.selections.ranges(cx),
 2635            &[
 2636                Point::new(1, 2)..Point::new(1, 2),
 2637                Point::new(2, 2)..Point::new(2, 2),
 2638            ],
 2639        );
 2640
 2641        editor.newline(&Newline, window, cx);
 2642        assert_eq!(
 2643            editor.text(cx),
 2644            "
 2645                a
 2646                b(
 2647                )
 2648                c(
 2649                )
 2650            "
 2651            .unindent()
 2652        );
 2653
 2654        // The selections are moved after the inserted newlines
 2655        assert_eq!(
 2656            editor.selections.ranges(cx),
 2657            &[
 2658                Point::new(2, 0)..Point::new(2, 0),
 2659                Point::new(4, 0)..Point::new(4, 0),
 2660            ],
 2661        );
 2662    });
 2663}
 2664
 2665#[gpui::test]
 2666async fn test_newline_above(cx: &mut TestAppContext) {
 2667    init_test(cx, |settings| {
 2668        settings.defaults.tab_size = NonZeroU32::new(4)
 2669    });
 2670
 2671    let language = Arc::new(
 2672        Language::new(
 2673            LanguageConfig::default(),
 2674            Some(tree_sitter_rust::LANGUAGE.into()),
 2675        )
 2676        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2677        .unwrap(),
 2678    );
 2679
 2680    let mut cx = EditorTestContext::new(cx).await;
 2681    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2682    cx.set_state(indoc! {"
 2683        const a: ˇA = (
 2684 2685                «const_functionˇ»(ˇ),
 2686                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2687 2688        ˇ);ˇ
 2689    "});
 2690
 2691    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 2692    cx.assert_editor_state(indoc! {"
 2693        ˇ
 2694        const a: A = (
 2695            ˇ
 2696            (
 2697                ˇ
 2698                ˇ
 2699                const_function(),
 2700                ˇ
 2701                ˇ
 2702                ˇ
 2703                ˇ
 2704                something_else,
 2705                ˇ
 2706            )
 2707            ˇ
 2708            ˇ
 2709        );
 2710    "});
 2711}
 2712
 2713#[gpui::test]
 2714async fn test_newline_below(cx: &mut TestAppContext) {
 2715    init_test(cx, |settings| {
 2716        settings.defaults.tab_size = NonZeroU32::new(4)
 2717    });
 2718
 2719    let language = Arc::new(
 2720        Language::new(
 2721            LanguageConfig::default(),
 2722            Some(tree_sitter_rust::LANGUAGE.into()),
 2723        )
 2724        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2725        .unwrap(),
 2726    );
 2727
 2728    let mut cx = EditorTestContext::new(cx).await;
 2729    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2730    cx.set_state(indoc! {"
 2731        const a: ˇA = (
 2732 2733                «const_functionˇ»(ˇ),
 2734                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2735 2736        ˇ);ˇ
 2737    "});
 2738
 2739    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 2740    cx.assert_editor_state(indoc! {"
 2741        const a: A = (
 2742            ˇ
 2743            (
 2744                ˇ
 2745                const_function(),
 2746                ˇ
 2747                ˇ
 2748                something_else,
 2749                ˇ
 2750                ˇ
 2751                ˇ
 2752                ˇ
 2753            )
 2754            ˇ
 2755        );
 2756        ˇ
 2757        ˇ
 2758    "});
 2759}
 2760
 2761#[gpui::test]
 2762async fn test_newline_comments(cx: &mut TestAppContext) {
 2763    init_test(cx, |settings| {
 2764        settings.defaults.tab_size = NonZeroU32::new(4)
 2765    });
 2766
 2767    let language = Arc::new(Language::new(
 2768        LanguageConfig {
 2769            line_comments: vec!["// ".into()],
 2770            ..LanguageConfig::default()
 2771        },
 2772        None,
 2773    ));
 2774    {
 2775        let mut cx = EditorTestContext::new(cx).await;
 2776        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2777        cx.set_state(indoc! {"
 2778        // Fooˇ
 2779    "});
 2780
 2781        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2782        cx.assert_editor_state(indoc! {"
 2783        // Foo
 2784        // ˇ
 2785    "});
 2786        // Ensure that we add comment prefix when existing line contains space
 2787        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2788        cx.assert_editor_state(
 2789            indoc! {"
 2790        // Foo
 2791        //s
 2792        // ˇ
 2793    "}
 2794            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2795            .as_str(),
 2796        );
 2797        // Ensure that we add comment prefix when existing line does not contain space
 2798        cx.set_state(indoc! {"
 2799        // Foo
 2800        //ˇ
 2801    "});
 2802        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2803        cx.assert_editor_state(indoc! {"
 2804        // Foo
 2805        //
 2806        // ˇ
 2807    "});
 2808        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 2809        cx.set_state(indoc! {"
 2810        ˇ// Foo
 2811    "});
 2812        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2813        cx.assert_editor_state(indoc! {"
 2814
 2815        ˇ// Foo
 2816    "});
 2817    }
 2818    // Ensure that comment continuations can be disabled.
 2819    update_test_language_settings(cx, |settings| {
 2820        settings.defaults.extend_comment_on_newline = Some(false);
 2821    });
 2822    let mut cx = EditorTestContext::new(cx).await;
 2823    cx.set_state(indoc! {"
 2824        // Fooˇ
 2825    "});
 2826    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2827    cx.assert_editor_state(indoc! {"
 2828        // Foo
 2829        ˇ
 2830    "});
 2831}
 2832
 2833#[gpui::test]
 2834async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 2835    init_test(cx, |settings| {
 2836        settings.defaults.tab_size = NonZeroU32::new(4)
 2837    });
 2838
 2839    let language = Arc::new(Language::new(
 2840        LanguageConfig {
 2841            line_comments: vec!["// ".into(), "/// ".into()],
 2842            ..LanguageConfig::default()
 2843        },
 2844        None,
 2845    ));
 2846    {
 2847        let mut cx = EditorTestContext::new(cx).await;
 2848        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2849        cx.set_state(indoc! {"
 2850        //ˇ
 2851    "});
 2852        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2853        cx.assert_editor_state(indoc! {"
 2854        //
 2855        // ˇ
 2856    "});
 2857
 2858        cx.set_state(indoc! {"
 2859        ///ˇ
 2860    "});
 2861        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2862        cx.assert_editor_state(indoc! {"
 2863        ///
 2864        /// ˇ
 2865    "});
 2866    }
 2867}
 2868
 2869#[gpui::test]
 2870async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 2871    init_test(cx, |settings| {
 2872        settings.defaults.tab_size = NonZeroU32::new(4)
 2873    });
 2874
 2875    let language = Arc::new(
 2876        Language::new(
 2877            LanguageConfig {
 2878                documentation_comment: Some(language::BlockCommentConfig {
 2879                    start: "/**".into(),
 2880                    end: "*/".into(),
 2881                    prefix: "* ".into(),
 2882                    tab_size: 1,
 2883                }),
 2884
 2885                ..LanguageConfig::default()
 2886            },
 2887            Some(tree_sitter_rust::LANGUAGE.into()),
 2888        )
 2889        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 2890        .unwrap(),
 2891    );
 2892
 2893    {
 2894        let mut cx = EditorTestContext::new(cx).await;
 2895        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2896        cx.set_state(indoc! {"
 2897        /**ˇ
 2898    "});
 2899
 2900        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2901        cx.assert_editor_state(indoc! {"
 2902        /**
 2903         * ˇ
 2904    "});
 2905        // Ensure that if cursor is before the comment start,
 2906        // we do not actually insert a comment prefix.
 2907        cx.set_state(indoc! {"
 2908        ˇ/**
 2909    "});
 2910        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2911        cx.assert_editor_state(indoc! {"
 2912
 2913        ˇ/**
 2914    "});
 2915        // Ensure that if cursor is between it doesn't add comment prefix.
 2916        cx.set_state(indoc! {"
 2917        /*ˇ*
 2918    "});
 2919        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2920        cx.assert_editor_state(indoc! {"
 2921        /*
 2922        ˇ*
 2923    "});
 2924        // Ensure that if suffix exists on same line after cursor it adds new line.
 2925        cx.set_state(indoc! {"
 2926        /**ˇ*/
 2927    "});
 2928        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2929        cx.assert_editor_state(indoc! {"
 2930        /**
 2931         * ˇ
 2932         */
 2933    "});
 2934        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2935        cx.set_state(indoc! {"
 2936        /**ˇ */
 2937    "});
 2938        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2939        cx.assert_editor_state(indoc! {"
 2940        /**
 2941         * ˇ
 2942         */
 2943    "});
 2944        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2945        cx.set_state(indoc! {"
 2946        /** ˇ*/
 2947    "});
 2948        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2949        cx.assert_editor_state(
 2950            indoc! {"
 2951        /**s
 2952         * ˇ
 2953         */
 2954    "}
 2955            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2956            .as_str(),
 2957        );
 2958        // Ensure that delimiter space is preserved when newline on already
 2959        // spaced delimiter.
 2960        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2961        cx.assert_editor_state(
 2962            indoc! {"
 2963        /**s
 2964         *s
 2965         * ˇ
 2966         */
 2967    "}
 2968            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2969            .as_str(),
 2970        );
 2971        // Ensure that delimiter space is preserved when space is not
 2972        // on existing delimiter.
 2973        cx.set_state(indoc! {"
 2974        /**
 2975 2976         */
 2977    "});
 2978        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2979        cx.assert_editor_state(indoc! {"
 2980        /**
 2981         *
 2982         * ˇ
 2983         */
 2984    "});
 2985        // Ensure that if suffix exists on same line after cursor it
 2986        // doesn't add extra new line if prefix is not on same line.
 2987        cx.set_state(indoc! {"
 2988        /**
 2989        ˇ*/
 2990    "});
 2991        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2992        cx.assert_editor_state(indoc! {"
 2993        /**
 2994
 2995        ˇ*/
 2996    "});
 2997        // Ensure that it detects suffix after existing prefix.
 2998        cx.set_state(indoc! {"
 2999        /**ˇ/
 3000    "});
 3001        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3002        cx.assert_editor_state(indoc! {"
 3003        /**
 3004        ˇ/
 3005    "});
 3006        // Ensure that if suffix exists on same line before
 3007        // cursor it does not add comment prefix.
 3008        cx.set_state(indoc! {"
 3009        /** */ˇ
 3010    "});
 3011        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3012        cx.assert_editor_state(indoc! {"
 3013        /** */
 3014        ˇ
 3015    "});
 3016        // Ensure that if suffix exists on same line before
 3017        // cursor it does not add comment prefix.
 3018        cx.set_state(indoc! {"
 3019        /**
 3020         *
 3021         */ˇ
 3022    "});
 3023        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3024        cx.assert_editor_state(indoc! {"
 3025        /**
 3026         *
 3027         */
 3028         ˇ
 3029    "});
 3030
 3031        // Ensure that inline comment followed by code
 3032        // doesn't add comment prefix on newline
 3033        cx.set_state(indoc! {"
 3034        /** */ textˇ
 3035    "});
 3036        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3037        cx.assert_editor_state(indoc! {"
 3038        /** */ text
 3039        ˇ
 3040    "});
 3041
 3042        // Ensure that text after comment end tag
 3043        // doesn't add comment prefix on newline
 3044        cx.set_state(indoc! {"
 3045        /**
 3046         *
 3047         */ˇtext
 3048    "});
 3049        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3050        cx.assert_editor_state(indoc! {"
 3051        /**
 3052         *
 3053         */
 3054         ˇtext
 3055    "});
 3056
 3057        // Ensure if not comment block it doesn't
 3058        // add comment prefix on newline
 3059        cx.set_state(indoc! {"
 3060        * textˇ
 3061    "});
 3062        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3063        cx.assert_editor_state(indoc! {"
 3064        * text
 3065        ˇ
 3066    "});
 3067    }
 3068    // Ensure that comment continuations can be disabled.
 3069    update_test_language_settings(cx, |settings| {
 3070        settings.defaults.extend_comment_on_newline = Some(false);
 3071    });
 3072    let mut cx = EditorTestContext::new(cx).await;
 3073    cx.set_state(indoc! {"
 3074        /**ˇ
 3075    "});
 3076    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3077    cx.assert_editor_state(indoc! {"
 3078        /**
 3079        ˇ
 3080    "});
 3081}
 3082
 3083#[gpui::test]
 3084async fn test_newline_comments_with_block_comment(cx: &mut TestAppContext) {
 3085    init_test(cx, |settings| {
 3086        settings.defaults.tab_size = NonZeroU32::new(4)
 3087    });
 3088
 3089    let lua_language = Arc::new(Language::new(
 3090        LanguageConfig {
 3091            line_comments: vec!["--".into()],
 3092            block_comment: Some(language::BlockCommentConfig {
 3093                start: "--[[".into(),
 3094                prefix: "".into(),
 3095                end: "]]".into(),
 3096                tab_size: 0,
 3097            }),
 3098            ..LanguageConfig::default()
 3099        },
 3100        None,
 3101    ));
 3102
 3103    let mut cx = EditorTestContext::new(cx).await;
 3104    cx.update_buffer(|buffer, cx| buffer.set_language(Some(lua_language), cx));
 3105
 3106    // Line with line comment should extend
 3107    cx.set_state(indoc! {"
 3108        --ˇ
 3109    "});
 3110    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3111    cx.assert_editor_state(indoc! {"
 3112        --
 3113        --ˇ
 3114    "});
 3115
 3116    // Line with block comment that matches line comment should not extend
 3117    cx.set_state(indoc! {"
 3118        --[[ˇ
 3119    "});
 3120    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3121    cx.assert_editor_state(indoc! {"
 3122        --[[
 3123        ˇ
 3124    "});
 3125}
 3126
 3127#[gpui::test]
 3128fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3129    init_test(cx, |_| {});
 3130
 3131    let editor = cx.add_window(|window, cx| {
 3132        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3133        let mut editor = build_editor(buffer.clone(), window, cx);
 3134        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3135            s.select_ranges([3..4, 11..12, 19..20])
 3136        });
 3137        editor
 3138    });
 3139
 3140    _ = editor.update(cx, |editor, window, cx| {
 3141        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3142        editor.buffer.update(cx, |buffer, cx| {
 3143            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 3144            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3145        });
 3146        assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
 3147
 3148        editor.insert("Z", window, cx);
 3149        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3150
 3151        // The selections are moved after the inserted characters
 3152        assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
 3153    });
 3154}
 3155
 3156#[gpui::test]
 3157async fn test_tab(cx: &mut TestAppContext) {
 3158    init_test(cx, |settings| {
 3159        settings.defaults.tab_size = NonZeroU32::new(3)
 3160    });
 3161
 3162    let mut cx = EditorTestContext::new(cx).await;
 3163    cx.set_state(indoc! {"
 3164        ˇabˇc
 3165        ˇ🏀ˇ🏀ˇefg
 3166 3167    "});
 3168    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3169    cx.assert_editor_state(indoc! {"
 3170           ˇab ˇc
 3171           ˇ🏀  ˇ🏀  ˇefg
 3172        d  ˇ
 3173    "});
 3174
 3175    cx.set_state(indoc! {"
 3176        a
 3177        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3178    "});
 3179    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3180    cx.assert_editor_state(indoc! {"
 3181        a
 3182           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3183    "});
 3184}
 3185
 3186#[gpui::test]
 3187async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3188    init_test(cx, |_| {});
 3189
 3190    let mut cx = EditorTestContext::new(cx).await;
 3191    let language = Arc::new(
 3192        Language::new(
 3193            LanguageConfig::default(),
 3194            Some(tree_sitter_rust::LANGUAGE.into()),
 3195        )
 3196        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3197        .unwrap(),
 3198    );
 3199    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3200
 3201    // test when all cursors are not at suggested indent
 3202    // then simply move to their suggested indent location
 3203    cx.set_state(indoc! {"
 3204        const a: B = (
 3205            c(
 3206        ˇ
 3207        ˇ    )
 3208        );
 3209    "});
 3210    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3211    cx.assert_editor_state(indoc! {"
 3212        const a: B = (
 3213            c(
 3214                ˇ
 3215            ˇ)
 3216        );
 3217    "});
 3218
 3219    // test cursor already at suggested indent not moving when
 3220    // other cursors are yet to reach their suggested indents
 3221    cx.set_state(indoc! {"
 3222        ˇ
 3223        const a: B = (
 3224            c(
 3225                d(
 3226        ˇ
 3227                )
 3228        ˇ
 3229        ˇ    )
 3230        );
 3231    "});
 3232    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3233    cx.assert_editor_state(indoc! {"
 3234        ˇ
 3235        const a: B = (
 3236            c(
 3237                d(
 3238                    ˇ
 3239                )
 3240                ˇ
 3241            ˇ)
 3242        );
 3243    "});
 3244    // test when all cursors are at suggested indent then tab is inserted
 3245    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3246    cx.assert_editor_state(indoc! {"
 3247            ˇ
 3248        const a: B = (
 3249            c(
 3250                d(
 3251                        ˇ
 3252                )
 3253                    ˇ
 3254                ˇ)
 3255        );
 3256    "});
 3257
 3258    // test when current indent is less than suggested indent,
 3259    // we adjust line to match suggested indent and move cursor to it
 3260    //
 3261    // when no other cursor is at word boundary, all of them should move
 3262    cx.set_state(indoc! {"
 3263        const a: B = (
 3264            c(
 3265                d(
 3266        ˇ
 3267        ˇ   )
 3268        ˇ   )
 3269        );
 3270    "});
 3271    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3272    cx.assert_editor_state(indoc! {"
 3273        const a: B = (
 3274            c(
 3275                d(
 3276                    ˇ
 3277                ˇ)
 3278            ˇ)
 3279        );
 3280    "});
 3281
 3282    // test when current indent is less than suggested indent,
 3283    // we adjust line to match suggested indent and move cursor to it
 3284    //
 3285    // when some other cursor is at word boundary, it should not move
 3286    cx.set_state(indoc! {"
 3287        const a: B = (
 3288            c(
 3289                d(
 3290        ˇ
 3291        ˇ   )
 3292           ˇ)
 3293        );
 3294    "});
 3295    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3296    cx.assert_editor_state(indoc! {"
 3297        const a: B = (
 3298            c(
 3299                d(
 3300                    ˇ
 3301                ˇ)
 3302            ˇ)
 3303        );
 3304    "});
 3305
 3306    // test when current indent is more than suggested indent,
 3307    // we just move cursor to current indent instead of suggested indent
 3308    //
 3309    // when no other cursor is at word boundary, all of them should move
 3310    cx.set_state(indoc! {"
 3311        const a: B = (
 3312            c(
 3313                d(
 3314        ˇ
 3315        ˇ                )
 3316        ˇ   )
 3317        );
 3318    "});
 3319    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3320    cx.assert_editor_state(indoc! {"
 3321        const a: B = (
 3322            c(
 3323                d(
 3324                    ˇ
 3325                        ˇ)
 3326            ˇ)
 3327        );
 3328    "});
 3329    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3330    cx.assert_editor_state(indoc! {"
 3331        const a: B = (
 3332            c(
 3333                d(
 3334                        ˇ
 3335                            ˇ)
 3336                ˇ)
 3337        );
 3338    "});
 3339
 3340    // test when current indent is more than suggested indent,
 3341    // we just move cursor to current indent instead of suggested indent
 3342    //
 3343    // when some other cursor is at word boundary, it doesn't move
 3344    cx.set_state(indoc! {"
 3345        const a: B = (
 3346            c(
 3347                d(
 3348        ˇ
 3349        ˇ                )
 3350            ˇ)
 3351        );
 3352    "});
 3353    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3354    cx.assert_editor_state(indoc! {"
 3355        const a: B = (
 3356            c(
 3357                d(
 3358                    ˇ
 3359                        ˇ)
 3360            ˇ)
 3361        );
 3362    "});
 3363
 3364    // handle auto-indent when there are multiple cursors on the same line
 3365    cx.set_state(indoc! {"
 3366        const a: B = (
 3367            c(
 3368        ˇ    ˇ
 3369        ˇ    )
 3370        );
 3371    "});
 3372    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3373    cx.assert_editor_state(indoc! {"
 3374        const a: B = (
 3375            c(
 3376                ˇ
 3377            ˇ)
 3378        );
 3379    "});
 3380}
 3381
 3382#[gpui::test]
 3383async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 3384    init_test(cx, |settings| {
 3385        settings.defaults.tab_size = NonZeroU32::new(3)
 3386    });
 3387
 3388    let mut cx = EditorTestContext::new(cx).await;
 3389    cx.set_state(indoc! {"
 3390         ˇ
 3391        \t ˇ
 3392        \t  ˇ
 3393        \t   ˇ
 3394         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 3395    "});
 3396
 3397    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3398    cx.assert_editor_state(indoc! {"
 3399           ˇ
 3400        \t   ˇ
 3401        \t   ˇ
 3402        \t      ˇ
 3403         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 3404    "});
 3405}
 3406
 3407#[gpui::test]
 3408async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 3409    init_test(cx, |settings| {
 3410        settings.defaults.tab_size = NonZeroU32::new(4)
 3411    });
 3412
 3413    let language = Arc::new(
 3414        Language::new(
 3415            LanguageConfig::default(),
 3416            Some(tree_sitter_rust::LANGUAGE.into()),
 3417        )
 3418        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 3419        .unwrap(),
 3420    );
 3421
 3422    let mut cx = EditorTestContext::new(cx).await;
 3423    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3424    cx.set_state(indoc! {"
 3425        fn a() {
 3426            if b {
 3427        \t ˇc
 3428            }
 3429        }
 3430    "});
 3431
 3432    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3433    cx.assert_editor_state(indoc! {"
 3434        fn a() {
 3435            if b {
 3436                ˇc
 3437            }
 3438        }
 3439    "});
 3440}
 3441
 3442#[gpui::test]
 3443async fn test_indent_outdent(cx: &mut TestAppContext) {
 3444    init_test(cx, |settings| {
 3445        settings.defaults.tab_size = NonZeroU32::new(4);
 3446    });
 3447
 3448    let mut cx = EditorTestContext::new(cx).await;
 3449
 3450    cx.set_state(indoc! {"
 3451          «oneˇ» «twoˇ»
 3452        three
 3453         four
 3454    "});
 3455    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3456    cx.assert_editor_state(indoc! {"
 3457            «oneˇ» «twoˇ»
 3458        three
 3459         four
 3460    "});
 3461
 3462    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3463    cx.assert_editor_state(indoc! {"
 3464        «oneˇ» «twoˇ»
 3465        three
 3466         four
 3467    "});
 3468
 3469    // select across line ending
 3470    cx.set_state(indoc! {"
 3471        one two
 3472        t«hree
 3473        ˇ» four
 3474    "});
 3475    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3476    cx.assert_editor_state(indoc! {"
 3477        one two
 3478            t«hree
 3479        ˇ» four
 3480    "});
 3481
 3482    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3483    cx.assert_editor_state(indoc! {"
 3484        one two
 3485        t«hree
 3486        ˇ» four
 3487    "});
 3488
 3489    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3490    cx.set_state(indoc! {"
 3491        one two
 3492        ˇthree
 3493            four
 3494    "});
 3495    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3496    cx.assert_editor_state(indoc! {"
 3497        one two
 3498            ˇthree
 3499            four
 3500    "});
 3501
 3502    cx.set_state(indoc! {"
 3503        one two
 3504        ˇ    three
 3505            four
 3506    "});
 3507    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3508    cx.assert_editor_state(indoc! {"
 3509        one two
 3510        ˇthree
 3511            four
 3512    "});
 3513}
 3514
 3515#[gpui::test]
 3516async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3517    // This is a regression test for issue #33761
 3518    init_test(cx, |_| {});
 3519
 3520    let mut cx = EditorTestContext::new(cx).await;
 3521    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3522    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3523
 3524    cx.set_state(
 3525        r#"ˇ#     ingress:
 3526ˇ#         api:
 3527ˇ#             enabled: false
 3528ˇ#             pathType: Prefix
 3529ˇ#           console:
 3530ˇ#               enabled: false
 3531ˇ#               pathType: Prefix
 3532"#,
 3533    );
 3534
 3535    // Press tab to indent all lines
 3536    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3537
 3538    cx.assert_editor_state(
 3539        r#"    ˇ#     ingress:
 3540    ˇ#         api:
 3541    ˇ#             enabled: false
 3542    ˇ#             pathType: Prefix
 3543    ˇ#           console:
 3544    ˇ#               enabled: false
 3545    ˇ#               pathType: Prefix
 3546"#,
 3547    );
 3548}
 3549
 3550#[gpui::test]
 3551async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3552    // This is a test to make sure our fix for issue #33761 didn't break anything
 3553    init_test(cx, |_| {});
 3554
 3555    let mut cx = EditorTestContext::new(cx).await;
 3556    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3557    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3558
 3559    cx.set_state(
 3560        r#"ˇingress:
 3561ˇ  api:
 3562ˇ    enabled: false
 3563ˇ    pathType: Prefix
 3564"#,
 3565    );
 3566
 3567    // Press tab to indent all lines
 3568    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3569
 3570    cx.assert_editor_state(
 3571        r#"ˇingress:
 3572    ˇapi:
 3573        ˇenabled: false
 3574        ˇpathType: Prefix
 3575"#,
 3576    );
 3577}
 3578
 3579#[gpui::test]
 3580async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 3581    init_test(cx, |settings| {
 3582        settings.defaults.hard_tabs = Some(true);
 3583    });
 3584
 3585    let mut cx = EditorTestContext::new(cx).await;
 3586
 3587    // select two ranges on one line
 3588    cx.set_state(indoc! {"
 3589        «oneˇ» «twoˇ»
 3590        three
 3591        four
 3592    "});
 3593    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3594    cx.assert_editor_state(indoc! {"
 3595        \t«oneˇ» «twoˇ»
 3596        three
 3597        four
 3598    "});
 3599    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3600    cx.assert_editor_state(indoc! {"
 3601        \t\t«oneˇ» «twoˇ»
 3602        three
 3603        four
 3604    "});
 3605    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3606    cx.assert_editor_state(indoc! {"
 3607        \t«oneˇ» «twoˇ»
 3608        three
 3609        four
 3610    "});
 3611    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3612    cx.assert_editor_state(indoc! {"
 3613        «oneˇ» «twoˇ»
 3614        three
 3615        four
 3616    "});
 3617
 3618    // select across a line ending
 3619    cx.set_state(indoc! {"
 3620        one two
 3621        t«hree
 3622        ˇ»four
 3623    "});
 3624    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3625    cx.assert_editor_state(indoc! {"
 3626        one two
 3627        \tt«hree
 3628        ˇ»four
 3629    "});
 3630    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3631    cx.assert_editor_state(indoc! {"
 3632        one two
 3633        \t\tt«hree
 3634        ˇ»four
 3635    "});
 3636    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3637    cx.assert_editor_state(indoc! {"
 3638        one two
 3639        \tt«hree
 3640        ˇ»four
 3641    "});
 3642    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3643    cx.assert_editor_state(indoc! {"
 3644        one two
 3645        t«hree
 3646        ˇ»four
 3647    "});
 3648
 3649    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3650    cx.set_state(indoc! {"
 3651        one two
 3652        ˇthree
 3653        four
 3654    "});
 3655    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3656    cx.assert_editor_state(indoc! {"
 3657        one two
 3658        ˇthree
 3659        four
 3660    "});
 3661    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3662    cx.assert_editor_state(indoc! {"
 3663        one two
 3664        \tˇthree
 3665        four
 3666    "});
 3667    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3668    cx.assert_editor_state(indoc! {"
 3669        one two
 3670        ˇthree
 3671        four
 3672    "});
 3673}
 3674
 3675#[gpui::test]
 3676fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 3677    init_test(cx, |settings| {
 3678        settings.languages.0.extend([
 3679            (
 3680                "TOML".into(),
 3681                LanguageSettingsContent {
 3682                    tab_size: NonZeroU32::new(2),
 3683                    ..Default::default()
 3684                },
 3685            ),
 3686            (
 3687                "Rust".into(),
 3688                LanguageSettingsContent {
 3689                    tab_size: NonZeroU32::new(4),
 3690                    ..Default::default()
 3691                },
 3692            ),
 3693        ]);
 3694    });
 3695
 3696    let toml_language = Arc::new(Language::new(
 3697        LanguageConfig {
 3698            name: "TOML".into(),
 3699            ..Default::default()
 3700        },
 3701        None,
 3702    ));
 3703    let rust_language = Arc::new(Language::new(
 3704        LanguageConfig {
 3705            name: "Rust".into(),
 3706            ..Default::default()
 3707        },
 3708        None,
 3709    ));
 3710
 3711    let toml_buffer =
 3712        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 3713    let rust_buffer =
 3714        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 3715    let multibuffer = cx.new(|cx| {
 3716        let mut multibuffer = MultiBuffer::new(ReadWrite);
 3717        multibuffer.push_excerpts(
 3718            toml_buffer.clone(),
 3719            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 3720            cx,
 3721        );
 3722        multibuffer.push_excerpts(
 3723            rust_buffer.clone(),
 3724            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 3725            cx,
 3726        );
 3727        multibuffer
 3728    });
 3729
 3730    cx.add_window(|window, cx| {
 3731        let mut editor = build_editor(multibuffer, window, cx);
 3732
 3733        assert_eq!(
 3734            editor.text(cx),
 3735            indoc! {"
 3736                a = 1
 3737                b = 2
 3738
 3739                const c: usize = 3;
 3740            "}
 3741        );
 3742
 3743        select_ranges(
 3744            &mut editor,
 3745            indoc! {"
 3746                «aˇ» = 1
 3747                b = 2
 3748
 3749                «const c:ˇ» usize = 3;
 3750            "},
 3751            window,
 3752            cx,
 3753        );
 3754
 3755        editor.tab(&Tab, window, cx);
 3756        assert_text_with_selections(
 3757            &mut editor,
 3758            indoc! {"
 3759                  «aˇ» = 1
 3760                b = 2
 3761
 3762                    «const c:ˇ» usize = 3;
 3763            "},
 3764            cx,
 3765        );
 3766        editor.backtab(&Backtab, window, cx);
 3767        assert_text_with_selections(
 3768            &mut editor,
 3769            indoc! {"
 3770                «aˇ» = 1
 3771                b = 2
 3772
 3773                «const c:ˇ» usize = 3;
 3774            "},
 3775            cx,
 3776        );
 3777
 3778        editor
 3779    });
 3780}
 3781
 3782#[gpui::test]
 3783async fn test_backspace(cx: &mut TestAppContext) {
 3784    init_test(cx, |_| {});
 3785
 3786    let mut cx = EditorTestContext::new(cx).await;
 3787
 3788    // Basic backspace
 3789    cx.set_state(indoc! {"
 3790        onˇe two three
 3791        fou«rˇ» five six
 3792        seven «ˇeight nine
 3793        »ten
 3794    "});
 3795    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3796    cx.assert_editor_state(indoc! {"
 3797        oˇe two three
 3798        fouˇ five six
 3799        seven ˇten
 3800    "});
 3801
 3802    // Test backspace inside and around indents
 3803    cx.set_state(indoc! {"
 3804        zero
 3805            ˇone
 3806                ˇtwo
 3807            ˇ ˇ ˇ  three
 3808        ˇ  ˇ  four
 3809    "});
 3810    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3811    cx.assert_editor_state(indoc! {"
 3812        zero
 3813        ˇone
 3814            ˇtwo
 3815        ˇ  threeˇ  four
 3816    "});
 3817}
 3818
 3819#[gpui::test]
 3820async fn test_delete(cx: &mut TestAppContext) {
 3821    init_test(cx, |_| {});
 3822
 3823    let mut cx = EditorTestContext::new(cx).await;
 3824    cx.set_state(indoc! {"
 3825        onˇe two three
 3826        fou«rˇ» five six
 3827        seven «ˇeight nine
 3828        »ten
 3829    "});
 3830    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 3831    cx.assert_editor_state(indoc! {"
 3832        onˇ two three
 3833        fouˇ five six
 3834        seven ˇten
 3835    "});
 3836}
 3837
 3838#[gpui::test]
 3839fn test_delete_line(cx: &mut TestAppContext) {
 3840    init_test(cx, |_| {});
 3841
 3842    let editor = cx.add_window(|window, cx| {
 3843        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3844        build_editor(buffer, window, cx)
 3845    });
 3846    _ = editor.update(cx, |editor, window, cx| {
 3847        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3848            s.select_display_ranges([
 3849                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 3850                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 3851                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 3852            ])
 3853        });
 3854        editor.delete_line(&DeleteLine, window, cx);
 3855        assert_eq!(editor.display_text(cx), "ghi");
 3856        assert_eq!(
 3857            editor.selections.display_ranges(cx),
 3858            vec![
 3859                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 3860                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
 3861            ]
 3862        );
 3863    });
 3864
 3865    let editor = cx.add_window(|window, cx| {
 3866        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3867        build_editor(buffer, window, cx)
 3868    });
 3869    _ = editor.update(cx, |editor, window, cx| {
 3870        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3871            s.select_display_ranges([
 3872                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 3873            ])
 3874        });
 3875        editor.delete_line(&DeleteLine, window, cx);
 3876        assert_eq!(editor.display_text(cx), "ghi\n");
 3877        assert_eq!(
 3878            editor.selections.display_ranges(cx),
 3879            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 3880        );
 3881    });
 3882}
 3883
 3884#[gpui::test]
 3885fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 3886    init_test(cx, |_| {});
 3887
 3888    cx.add_window(|window, cx| {
 3889        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3890        let mut editor = build_editor(buffer.clone(), window, cx);
 3891        let buffer = buffer.read(cx).as_singleton().unwrap();
 3892
 3893        assert_eq!(
 3894            editor.selections.ranges::<Point>(cx),
 3895            &[Point::new(0, 0)..Point::new(0, 0)]
 3896        );
 3897
 3898        // When on single line, replace newline at end by space
 3899        editor.join_lines(&JoinLines, window, cx);
 3900        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3901        assert_eq!(
 3902            editor.selections.ranges::<Point>(cx),
 3903            &[Point::new(0, 3)..Point::new(0, 3)]
 3904        );
 3905
 3906        // When multiple lines are selected, remove newlines that are spanned by the selection
 3907        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3908            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 3909        });
 3910        editor.join_lines(&JoinLines, window, cx);
 3911        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 3912        assert_eq!(
 3913            editor.selections.ranges::<Point>(cx),
 3914            &[Point::new(0, 11)..Point::new(0, 11)]
 3915        );
 3916
 3917        // Undo should be transactional
 3918        editor.undo(&Undo, window, cx);
 3919        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3920        assert_eq!(
 3921            editor.selections.ranges::<Point>(cx),
 3922            &[Point::new(0, 5)..Point::new(2, 2)]
 3923        );
 3924
 3925        // When joining an empty line don't insert a space
 3926        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3927            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 3928        });
 3929        editor.join_lines(&JoinLines, window, cx);
 3930        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 3931        assert_eq!(
 3932            editor.selections.ranges::<Point>(cx),
 3933            [Point::new(2, 3)..Point::new(2, 3)]
 3934        );
 3935
 3936        // We can remove trailing newlines
 3937        editor.join_lines(&JoinLines, window, cx);
 3938        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3939        assert_eq!(
 3940            editor.selections.ranges::<Point>(cx),
 3941            [Point::new(2, 3)..Point::new(2, 3)]
 3942        );
 3943
 3944        // We don't blow up on the last line
 3945        editor.join_lines(&JoinLines, window, cx);
 3946        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3947        assert_eq!(
 3948            editor.selections.ranges::<Point>(cx),
 3949            [Point::new(2, 3)..Point::new(2, 3)]
 3950        );
 3951
 3952        // reset to test indentation
 3953        editor.buffer.update(cx, |buffer, cx| {
 3954            buffer.edit(
 3955                [
 3956                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 3957                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 3958                ],
 3959                None,
 3960                cx,
 3961            )
 3962        });
 3963
 3964        // We remove any leading spaces
 3965        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 3966        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3967            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 3968        });
 3969        editor.join_lines(&JoinLines, window, cx);
 3970        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 3971
 3972        // We don't insert a space for a line containing only spaces
 3973        editor.join_lines(&JoinLines, window, cx);
 3974        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 3975
 3976        // We ignore any leading tabs
 3977        editor.join_lines(&JoinLines, window, cx);
 3978        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 3979
 3980        editor
 3981    });
 3982}
 3983
 3984#[gpui::test]
 3985fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 3986    init_test(cx, |_| {});
 3987
 3988    cx.add_window(|window, cx| {
 3989        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3990        let mut editor = build_editor(buffer.clone(), window, cx);
 3991        let buffer = buffer.read(cx).as_singleton().unwrap();
 3992
 3993        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3994            s.select_ranges([
 3995                Point::new(0, 2)..Point::new(1, 1),
 3996                Point::new(1, 2)..Point::new(1, 2),
 3997                Point::new(3, 1)..Point::new(3, 2),
 3998            ])
 3999        });
 4000
 4001        editor.join_lines(&JoinLines, window, cx);
 4002        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 4003
 4004        assert_eq!(
 4005            editor.selections.ranges::<Point>(cx),
 4006            [
 4007                Point::new(0, 7)..Point::new(0, 7),
 4008                Point::new(1, 3)..Point::new(1, 3)
 4009            ]
 4010        );
 4011        editor
 4012    });
 4013}
 4014
 4015#[gpui::test]
 4016async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 4017    init_test(cx, |_| {});
 4018
 4019    let mut cx = EditorTestContext::new(cx).await;
 4020
 4021    let diff_base = r#"
 4022        Line 0
 4023        Line 1
 4024        Line 2
 4025        Line 3
 4026        "#
 4027    .unindent();
 4028
 4029    cx.set_state(
 4030        &r#"
 4031        ˇLine 0
 4032        Line 1
 4033        Line 2
 4034        Line 3
 4035        "#
 4036        .unindent(),
 4037    );
 4038
 4039    cx.set_head_text(&diff_base);
 4040    executor.run_until_parked();
 4041
 4042    // Join lines
 4043    cx.update_editor(|editor, window, cx| {
 4044        editor.join_lines(&JoinLines, window, cx);
 4045    });
 4046    executor.run_until_parked();
 4047
 4048    cx.assert_editor_state(
 4049        &r#"
 4050        Line 0ˇ Line 1
 4051        Line 2
 4052        Line 3
 4053        "#
 4054        .unindent(),
 4055    );
 4056    // Join again
 4057    cx.update_editor(|editor, window, cx| {
 4058        editor.join_lines(&JoinLines, window, cx);
 4059    });
 4060    executor.run_until_parked();
 4061
 4062    cx.assert_editor_state(
 4063        &r#"
 4064        Line 0 Line 1ˇ Line 2
 4065        Line 3
 4066        "#
 4067        .unindent(),
 4068    );
 4069}
 4070
 4071#[gpui::test]
 4072async fn test_custom_newlines_cause_no_false_positive_diffs(
 4073    executor: BackgroundExecutor,
 4074    cx: &mut TestAppContext,
 4075) {
 4076    init_test(cx, |_| {});
 4077    let mut cx = EditorTestContext::new(cx).await;
 4078    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 4079    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 4080    executor.run_until_parked();
 4081
 4082    cx.update_editor(|editor, window, cx| {
 4083        let snapshot = editor.snapshot(window, cx);
 4084        assert_eq!(
 4085            snapshot
 4086                .buffer_snapshot
 4087                .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
 4088                .collect::<Vec<_>>(),
 4089            Vec::new(),
 4090            "Should not have any diffs for files with custom newlines"
 4091        );
 4092    });
 4093}
 4094
 4095#[gpui::test]
 4096async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 4097    init_test(cx, |_| {});
 4098
 4099    let mut cx = EditorTestContext::new(cx).await;
 4100
 4101    // Test sort_lines_case_insensitive()
 4102    cx.set_state(indoc! {"
 4103        «z
 4104        y
 4105        x
 4106        Z
 4107        Y
 4108        Xˇ»
 4109    "});
 4110    cx.update_editor(|e, window, cx| {
 4111        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4112    });
 4113    cx.assert_editor_state(indoc! {"
 4114        «x
 4115        X
 4116        y
 4117        Y
 4118        z
 4119        Zˇ»
 4120    "});
 4121
 4122    // Test sort_lines_by_length()
 4123    //
 4124    // Demonstrates:
 4125    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 4126    // - sort is stable
 4127    cx.set_state(indoc! {"
 4128        «123
 4129        æ
 4130        12
 4131 4132        1
 4133        æˇ»
 4134    "});
 4135    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 4136    cx.assert_editor_state(indoc! {"
 4137        «æ
 4138 4139        1
 4140        æ
 4141        12
 4142        123ˇ»
 4143    "});
 4144
 4145    // Test reverse_lines()
 4146    cx.set_state(indoc! {"
 4147        «5
 4148        4
 4149        3
 4150        2
 4151        1ˇ»
 4152    "});
 4153    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4154    cx.assert_editor_state(indoc! {"
 4155        «1
 4156        2
 4157        3
 4158        4
 4159        5ˇ»
 4160    "});
 4161
 4162    // Skip testing shuffle_line()
 4163
 4164    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4165    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4166
 4167    // Don't manipulate when cursor is on single line, but expand the selection
 4168    cx.set_state(indoc! {"
 4169        ddˇdd
 4170        ccc
 4171        bb
 4172        a
 4173    "});
 4174    cx.update_editor(|e, window, cx| {
 4175        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4176    });
 4177    cx.assert_editor_state(indoc! {"
 4178        «ddddˇ»
 4179        ccc
 4180        bb
 4181        a
 4182    "});
 4183
 4184    // Basic manipulate case
 4185    // Start selection moves to column 0
 4186    // End of selection shrinks to fit shorter line
 4187    cx.set_state(indoc! {"
 4188        dd«d
 4189        ccc
 4190        bb
 4191        aaaaaˇ»
 4192    "});
 4193    cx.update_editor(|e, window, cx| {
 4194        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4195    });
 4196    cx.assert_editor_state(indoc! {"
 4197        «aaaaa
 4198        bb
 4199        ccc
 4200        dddˇ»
 4201    "});
 4202
 4203    // Manipulate case with newlines
 4204    cx.set_state(indoc! {"
 4205        dd«d
 4206        ccc
 4207
 4208        bb
 4209        aaaaa
 4210
 4211        ˇ»
 4212    "});
 4213    cx.update_editor(|e, window, cx| {
 4214        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4215    });
 4216    cx.assert_editor_state(indoc! {"
 4217        «
 4218
 4219        aaaaa
 4220        bb
 4221        ccc
 4222        dddˇ»
 4223
 4224    "});
 4225
 4226    // Adding new line
 4227    cx.set_state(indoc! {"
 4228        aa«a
 4229        bbˇ»b
 4230    "});
 4231    cx.update_editor(|e, window, cx| {
 4232        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4233    });
 4234    cx.assert_editor_state(indoc! {"
 4235        «aaa
 4236        bbb
 4237        added_lineˇ»
 4238    "});
 4239
 4240    // Removing line
 4241    cx.set_state(indoc! {"
 4242        aa«a
 4243        bbbˇ»
 4244    "});
 4245    cx.update_editor(|e, window, cx| {
 4246        e.manipulate_immutable_lines(window, cx, |lines| {
 4247            lines.pop();
 4248        })
 4249    });
 4250    cx.assert_editor_state(indoc! {"
 4251        «aaaˇ»
 4252    "});
 4253
 4254    // Removing all lines
 4255    cx.set_state(indoc! {"
 4256        aa«a
 4257        bbbˇ»
 4258    "});
 4259    cx.update_editor(|e, window, cx| {
 4260        e.manipulate_immutable_lines(window, cx, |lines| {
 4261            lines.drain(..);
 4262        })
 4263    });
 4264    cx.assert_editor_state(indoc! {"
 4265        ˇ
 4266    "});
 4267}
 4268
 4269#[gpui::test]
 4270async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4271    init_test(cx, |_| {});
 4272
 4273    let mut cx = EditorTestContext::new(cx).await;
 4274
 4275    // Consider continuous selection as single selection
 4276    cx.set_state(indoc! {"
 4277        Aaa«aa
 4278        cˇ»c«c
 4279        bb
 4280        aaaˇ»aa
 4281    "});
 4282    cx.update_editor(|e, window, cx| {
 4283        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4284    });
 4285    cx.assert_editor_state(indoc! {"
 4286        «Aaaaa
 4287        ccc
 4288        bb
 4289        aaaaaˇ»
 4290    "});
 4291
 4292    cx.set_state(indoc! {"
 4293        Aaa«aa
 4294        cˇ»c«c
 4295        bb
 4296        aaaˇ»aa
 4297    "});
 4298    cx.update_editor(|e, window, cx| {
 4299        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4300    });
 4301    cx.assert_editor_state(indoc! {"
 4302        «Aaaaa
 4303        ccc
 4304        bbˇ»
 4305    "});
 4306
 4307    // Consider non continuous selection as distinct dedup operations
 4308    cx.set_state(indoc! {"
 4309        «aaaaa
 4310        bb
 4311        aaaaa
 4312        aaaaaˇ»
 4313
 4314        aaa«aaˇ»
 4315    "});
 4316    cx.update_editor(|e, window, cx| {
 4317        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4318    });
 4319    cx.assert_editor_state(indoc! {"
 4320        «aaaaa
 4321        bbˇ»
 4322
 4323        «aaaaaˇ»
 4324    "});
 4325}
 4326
 4327#[gpui::test]
 4328async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4329    init_test(cx, |_| {});
 4330
 4331    let mut cx = EditorTestContext::new(cx).await;
 4332
 4333    cx.set_state(indoc! {"
 4334        «Aaa
 4335        aAa
 4336        Aaaˇ»
 4337    "});
 4338    cx.update_editor(|e, window, cx| {
 4339        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4340    });
 4341    cx.assert_editor_state(indoc! {"
 4342        «Aaa
 4343        aAaˇ»
 4344    "});
 4345
 4346    cx.set_state(indoc! {"
 4347        «Aaa
 4348        aAa
 4349        aaAˇ»
 4350    "});
 4351    cx.update_editor(|e, window, cx| {
 4352        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4353    });
 4354    cx.assert_editor_state(indoc! {"
 4355        «Aaaˇ»
 4356    "});
 4357}
 4358
 4359#[gpui::test]
 4360async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 4361    init_test(cx, |_| {});
 4362
 4363    let mut cx = EditorTestContext::new(cx).await;
 4364
 4365    // Manipulate with multiple selections on a single line
 4366    cx.set_state(indoc! {"
 4367        dd«dd
 4368        cˇ»c«c
 4369        bb
 4370        aaaˇ»aa
 4371    "});
 4372    cx.update_editor(|e, window, cx| {
 4373        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4374    });
 4375    cx.assert_editor_state(indoc! {"
 4376        «aaaaa
 4377        bb
 4378        ccc
 4379        ddddˇ»
 4380    "});
 4381
 4382    // Manipulate with multiple disjoin selections
 4383    cx.set_state(indoc! {"
 4384 4385        4
 4386        3
 4387        2
 4388        1ˇ»
 4389
 4390        dd«dd
 4391        ccc
 4392        bb
 4393        aaaˇ»aa
 4394    "});
 4395    cx.update_editor(|e, window, cx| {
 4396        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4397    });
 4398    cx.assert_editor_state(indoc! {"
 4399        «1
 4400        2
 4401        3
 4402        4
 4403        5ˇ»
 4404
 4405        «aaaaa
 4406        bb
 4407        ccc
 4408        ddddˇ»
 4409    "});
 4410
 4411    // Adding lines on each selection
 4412    cx.set_state(indoc! {"
 4413 4414        1ˇ»
 4415
 4416        bb«bb
 4417        aaaˇ»aa
 4418    "});
 4419    cx.update_editor(|e, window, cx| {
 4420        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 4421    });
 4422    cx.assert_editor_state(indoc! {"
 4423        «2
 4424        1
 4425        added lineˇ»
 4426
 4427        «bbbb
 4428        aaaaa
 4429        added lineˇ»
 4430    "});
 4431
 4432    // Removing lines on each selection
 4433    cx.set_state(indoc! {"
 4434 4435        1ˇ»
 4436
 4437        bb«bb
 4438        aaaˇ»aa
 4439    "});
 4440    cx.update_editor(|e, window, cx| {
 4441        e.manipulate_immutable_lines(window, cx, |lines| {
 4442            lines.pop();
 4443        })
 4444    });
 4445    cx.assert_editor_state(indoc! {"
 4446        «2ˇ»
 4447
 4448        «bbbbˇ»
 4449    "});
 4450}
 4451
 4452#[gpui::test]
 4453async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 4454    init_test(cx, |settings| {
 4455        settings.defaults.tab_size = NonZeroU32::new(3)
 4456    });
 4457
 4458    let mut cx = EditorTestContext::new(cx).await;
 4459
 4460    // MULTI SELECTION
 4461    // Ln.1 "«" tests empty lines
 4462    // Ln.9 tests just leading whitespace
 4463    cx.set_state(indoc! {"
 4464        «
 4465        abc                 // No indentationˇ»
 4466        «\tabc              // 1 tabˇ»
 4467        \t\tabc «      ˇ»   // 2 tabs
 4468        \t ab«c             // Tab followed by space
 4469         \tabc              // Space followed by tab (3 spaces should be the result)
 4470        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4471           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 4472        \t
 4473        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4474    "});
 4475    cx.update_editor(|e, window, cx| {
 4476        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4477    });
 4478    cx.assert_editor_state(
 4479        indoc! {"
 4480            «
 4481            abc                 // No indentation
 4482               abc              // 1 tab
 4483                  abc          // 2 tabs
 4484                abc             // Tab followed by space
 4485               abc              // Space followed by tab (3 spaces should be the result)
 4486                           abc   // Mixed indentation (tab conversion depends on the column)
 4487               abc         // Already space indented
 4488               ·
 4489               abc\tdef          // Only the leading tab is manipulatedˇ»
 4490        "}
 4491        .replace("·", "")
 4492        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4493    );
 4494
 4495    // Test on just a few lines, the others should remain unchanged
 4496    // Only lines (3, 5, 10, 11) should change
 4497    cx.set_state(
 4498        indoc! {"
 4499            ·
 4500            abc                 // No indentation
 4501            \tabcˇ               // 1 tab
 4502            \t\tabc             // 2 tabs
 4503            \t abcˇ              // Tab followed by space
 4504             \tabc              // Space followed by tab (3 spaces should be the result)
 4505            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4506               abc              // Already space indented
 4507            «\t
 4508            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4509        "}
 4510        .replace("·", "")
 4511        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4512    );
 4513    cx.update_editor(|e, window, cx| {
 4514        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4515    });
 4516    cx.assert_editor_state(
 4517        indoc! {"
 4518            ·
 4519            abc                 // No indentation
 4520            «   abc               // 1 tabˇ»
 4521            \t\tabc             // 2 tabs
 4522            «    abc              // Tab followed by spaceˇ»
 4523             \tabc              // Space followed by tab (3 spaces should be the result)
 4524            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4525               abc              // Already space indented
 4526            «   ·
 4527               abc\tdef          // Only the leading tab is manipulatedˇ»
 4528        "}
 4529        .replace("·", "")
 4530        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4531    );
 4532
 4533    // SINGLE SELECTION
 4534    // Ln.1 "«" tests empty lines
 4535    // Ln.9 tests just leading whitespace
 4536    cx.set_state(indoc! {"
 4537        «
 4538        abc                 // No indentation
 4539        \tabc               // 1 tab
 4540        \t\tabc             // 2 tabs
 4541        \t abc              // Tab followed by space
 4542         \tabc              // Space followed by tab (3 spaces should be the result)
 4543        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4544           abc              // Already space indented
 4545        \t
 4546        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4547    "});
 4548    cx.update_editor(|e, window, cx| {
 4549        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4550    });
 4551    cx.assert_editor_state(
 4552        indoc! {"
 4553            «
 4554            abc                 // No indentation
 4555               abc               // 1 tab
 4556                  abc             // 2 tabs
 4557                abc              // Tab followed by space
 4558               abc              // Space followed by tab (3 spaces should be the result)
 4559                           abc   // Mixed indentation (tab conversion depends on the column)
 4560               abc              // Already space indented
 4561               ·
 4562               abc\tdef          // Only the leading tab is manipulatedˇ»
 4563        "}
 4564        .replace("·", "")
 4565        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4566    );
 4567}
 4568
 4569#[gpui::test]
 4570async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 4571    init_test(cx, |settings| {
 4572        settings.defaults.tab_size = NonZeroU32::new(3)
 4573    });
 4574
 4575    let mut cx = EditorTestContext::new(cx).await;
 4576
 4577    // MULTI SELECTION
 4578    // Ln.1 "«" tests empty lines
 4579    // Ln.11 tests just leading whitespace
 4580    cx.set_state(indoc! {"
 4581        «
 4582        abˇ»ˇc                 // No indentation
 4583         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 4584          abc  «             // 2 spaces (< 3 so dont convert)
 4585           abc              // 3 spaces (convert)
 4586             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 4587        «\tˇ»\t«\tˇ»abc           // Already tab indented
 4588        «\t abc              // Tab followed by space
 4589         \tabc              // Space followed by tab (should be consumed due to tab)
 4590        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4591           \tˇ»  «\t
 4592           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 4593    "});
 4594    cx.update_editor(|e, window, cx| {
 4595        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4596    });
 4597    cx.assert_editor_state(indoc! {"
 4598        «
 4599        abc                 // No indentation
 4600         abc                // 1 space (< 3 so dont convert)
 4601          abc               // 2 spaces (< 3 so dont convert)
 4602        \tabc              // 3 spaces (convert)
 4603        \t  abc            // 5 spaces (1 tab + 2 spaces)
 4604        \t\t\tabc           // Already tab indented
 4605        \t abc              // Tab followed by space
 4606        \tabc              // Space followed by tab (should be consumed due to tab)
 4607        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4608        \t\t\t
 4609        \tabc   \t         // Only the leading spaces should be convertedˇ»
 4610    "});
 4611
 4612    // Test on just a few lines, the other should remain unchanged
 4613    // Only lines (4, 8, 11, 12) should change
 4614    cx.set_state(
 4615        indoc! {"
 4616            ·
 4617            abc                 // No indentation
 4618             abc                // 1 space (< 3 so dont convert)
 4619              abc               // 2 spaces (< 3 so dont convert)
 4620            «   abc              // 3 spaces (convert)ˇ»
 4621                 abc            // 5 spaces (1 tab + 2 spaces)
 4622            \t\t\tabc           // Already tab indented
 4623            \t abc              // Tab followed by space
 4624             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 4625               \t\t  \tabc      // Mixed indentation
 4626            \t \t  \t   \tabc   // Mixed indentation
 4627               \t  \tˇ
 4628            «   abc   \t         // Only the leading spaces should be convertedˇ»
 4629        "}
 4630        .replace("·", "")
 4631        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4632    );
 4633    cx.update_editor(|e, window, cx| {
 4634        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4635    });
 4636    cx.assert_editor_state(
 4637        indoc! {"
 4638            ·
 4639            abc                 // No indentation
 4640             abc                // 1 space (< 3 so dont convert)
 4641              abc               // 2 spaces (< 3 so dont convert)
 4642            «\tabc              // 3 spaces (convert)ˇ»
 4643                 abc            // 5 spaces (1 tab + 2 spaces)
 4644            \t\t\tabc           // Already tab indented
 4645            \t abc              // Tab followed by space
 4646            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 4647               \t\t  \tabc      // Mixed indentation
 4648            \t \t  \t   \tabc   // Mixed indentation
 4649            «\t\t\t
 4650            \tabc   \t         // Only the leading spaces should be convertedˇ»
 4651        "}
 4652        .replace("·", "")
 4653        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4654    );
 4655
 4656    // SINGLE SELECTION
 4657    // Ln.1 "«" tests empty lines
 4658    // Ln.11 tests just leading whitespace
 4659    cx.set_state(indoc! {"
 4660        «
 4661        abc                 // No indentation
 4662         abc                // 1 space (< 3 so dont convert)
 4663          abc               // 2 spaces (< 3 so dont convert)
 4664           abc              // 3 spaces (convert)
 4665             abc            // 5 spaces (1 tab + 2 spaces)
 4666        \t\t\tabc           // Already tab indented
 4667        \t abc              // Tab followed by space
 4668         \tabc              // Space followed by tab (should be consumed due to tab)
 4669        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4670           \t  \t
 4671           abc   \t         // Only the leading spaces should be convertedˇ»
 4672    "});
 4673    cx.update_editor(|e, window, cx| {
 4674        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4675    });
 4676    cx.assert_editor_state(indoc! {"
 4677        «
 4678        abc                 // No indentation
 4679         abc                // 1 space (< 3 so dont convert)
 4680          abc               // 2 spaces (< 3 so dont convert)
 4681        \tabc              // 3 spaces (convert)
 4682        \t  abc            // 5 spaces (1 tab + 2 spaces)
 4683        \t\t\tabc           // Already tab indented
 4684        \t abc              // Tab followed by space
 4685        \tabc              // Space followed by tab (should be consumed due to tab)
 4686        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4687        \t\t\t
 4688        \tabc   \t         // Only the leading spaces should be convertedˇ»
 4689    "});
 4690}
 4691
 4692#[gpui::test]
 4693async fn test_toggle_case(cx: &mut TestAppContext) {
 4694    init_test(cx, |_| {});
 4695
 4696    let mut cx = EditorTestContext::new(cx).await;
 4697
 4698    // If all lower case -> upper case
 4699    cx.set_state(indoc! {"
 4700        «hello worldˇ»
 4701    "});
 4702    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4703    cx.assert_editor_state(indoc! {"
 4704        «HELLO WORLDˇ»
 4705    "});
 4706
 4707    // If all upper case -> lower case
 4708    cx.set_state(indoc! {"
 4709        «HELLO WORLDˇ»
 4710    "});
 4711    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4712    cx.assert_editor_state(indoc! {"
 4713        «hello worldˇ»
 4714    "});
 4715
 4716    // If any upper case characters are identified -> lower case
 4717    // This matches JetBrains IDEs
 4718    cx.set_state(indoc! {"
 4719        «hEllo worldˇ»
 4720    "});
 4721    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4722    cx.assert_editor_state(indoc! {"
 4723        «hello worldˇ»
 4724    "});
 4725}
 4726
 4727#[gpui::test]
 4728async fn test_manipulate_text(cx: &mut TestAppContext) {
 4729    init_test(cx, |_| {});
 4730
 4731    let mut cx = EditorTestContext::new(cx).await;
 4732
 4733    // Test convert_to_upper_case()
 4734    cx.set_state(indoc! {"
 4735        «hello worldˇ»
 4736    "});
 4737    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4738    cx.assert_editor_state(indoc! {"
 4739        «HELLO WORLDˇ»
 4740    "});
 4741
 4742    // Test convert_to_lower_case()
 4743    cx.set_state(indoc! {"
 4744        «HELLO WORLDˇ»
 4745    "});
 4746    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 4747    cx.assert_editor_state(indoc! {"
 4748        «hello worldˇ»
 4749    "});
 4750
 4751    // Test multiple line, single selection case
 4752    cx.set_state(indoc! {"
 4753        «The quick brown
 4754        fox jumps over
 4755        the lazy dogˇ»
 4756    "});
 4757    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 4758    cx.assert_editor_state(indoc! {"
 4759        «The Quick Brown
 4760        Fox Jumps Over
 4761        The Lazy Dogˇ»
 4762    "});
 4763
 4764    // Test multiple line, single selection case
 4765    cx.set_state(indoc! {"
 4766        «The quick brown
 4767        fox jumps over
 4768        the lazy dogˇ»
 4769    "});
 4770    cx.update_editor(|e, window, cx| {
 4771        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 4772    });
 4773    cx.assert_editor_state(indoc! {"
 4774        «TheQuickBrown
 4775        FoxJumpsOver
 4776        TheLazyDogˇ»
 4777    "});
 4778
 4779    // From here on out, test more complex cases of manipulate_text()
 4780
 4781    // Test no selection case - should affect words cursors are in
 4782    // Cursor at beginning, middle, and end of word
 4783    cx.set_state(indoc! {"
 4784        ˇhello big beauˇtiful worldˇ
 4785    "});
 4786    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4787    cx.assert_editor_state(indoc! {"
 4788        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 4789    "});
 4790
 4791    // Test multiple selections on a single line and across multiple lines
 4792    cx.set_state(indoc! {"
 4793        «Theˇ» quick «brown
 4794        foxˇ» jumps «overˇ»
 4795        the «lazyˇ» dog
 4796    "});
 4797    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4798    cx.assert_editor_state(indoc! {"
 4799        «THEˇ» quick «BROWN
 4800        FOXˇ» jumps «OVERˇ»
 4801        the «LAZYˇ» dog
 4802    "});
 4803
 4804    // Test case where text length grows
 4805    cx.set_state(indoc! {"
 4806        «tschüߡ»
 4807    "});
 4808    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4809    cx.assert_editor_state(indoc! {"
 4810        «TSCHÜSSˇ»
 4811    "});
 4812
 4813    // Test to make sure we don't crash when text shrinks
 4814    cx.set_state(indoc! {"
 4815        aaa_bbbˇ
 4816    "});
 4817    cx.update_editor(|e, window, cx| {
 4818        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4819    });
 4820    cx.assert_editor_state(indoc! {"
 4821        «aaaBbbˇ»
 4822    "});
 4823
 4824    // Test to make sure we all aware of the fact that each word can grow and shrink
 4825    // Final selections should be aware of this fact
 4826    cx.set_state(indoc! {"
 4827        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 4828    "});
 4829    cx.update_editor(|e, window, cx| {
 4830        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4831    });
 4832    cx.assert_editor_state(indoc! {"
 4833        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 4834    "});
 4835
 4836    cx.set_state(indoc! {"
 4837        «hElLo, WoRld!ˇ»
 4838    "});
 4839    cx.update_editor(|e, window, cx| {
 4840        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 4841    });
 4842    cx.assert_editor_state(indoc! {"
 4843        «HeLlO, wOrLD!ˇ»
 4844    "});
 4845}
 4846
 4847#[gpui::test]
 4848fn test_duplicate_line(cx: &mut TestAppContext) {
 4849    init_test(cx, |_| {});
 4850
 4851    let editor = cx.add_window(|window, cx| {
 4852        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4853        build_editor(buffer, window, cx)
 4854    });
 4855    _ = editor.update(cx, |editor, window, cx| {
 4856        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4857            s.select_display_ranges([
 4858                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4859                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4860                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4861                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4862            ])
 4863        });
 4864        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4865        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4866        assert_eq!(
 4867            editor.selections.display_ranges(cx),
 4868            vec![
 4869                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4870                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 4871                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4872                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4873            ]
 4874        );
 4875    });
 4876
 4877    let editor = cx.add_window(|window, cx| {
 4878        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4879        build_editor(buffer, window, cx)
 4880    });
 4881    _ = editor.update(cx, |editor, window, cx| {
 4882        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4883            s.select_display_ranges([
 4884                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4885                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4886            ])
 4887        });
 4888        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4889        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4890        assert_eq!(
 4891            editor.selections.display_ranges(cx),
 4892            vec![
 4893                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 4894                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 4895            ]
 4896        );
 4897    });
 4898
 4899    // With `move_upwards` the selections stay in place, except for
 4900    // the lines inserted above them
 4901    let editor = cx.add_window(|window, cx| {
 4902        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4903        build_editor(buffer, window, cx)
 4904    });
 4905    _ = editor.update(cx, |editor, window, cx| {
 4906        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4907            s.select_display_ranges([
 4908                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4909                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4910                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4911                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4912            ])
 4913        });
 4914        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4915        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4916        assert_eq!(
 4917            editor.selections.display_ranges(cx),
 4918            vec![
 4919                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4920                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4921                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 4922                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4923            ]
 4924        );
 4925    });
 4926
 4927    let editor = cx.add_window(|window, cx| {
 4928        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4929        build_editor(buffer, window, cx)
 4930    });
 4931    _ = editor.update(cx, |editor, window, cx| {
 4932        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4933            s.select_display_ranges([
 4934                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4935                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4936            ])
 4937        });
 4938        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4939        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4940        assert_eq!(
 4941            editor.selections.display_ranges(cx),
 4942            vec![
 4943                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4944                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4945            ]
 4946        );
 4947    });
 4948
 4949    let editor = cx.add_window(|window, cx| {
 4950        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4951        build_editor(buffer, window, cx)
 4952    });
 4953    _ = editor.update(cx, |editor, window, cx| {
 4954        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4955            s.select_display_ranges([
 4956                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4957                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4958            ])
 4959        });
 4960        editor.duplicate_selection(&DuplicateSelection, window, cx);
 4961        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 4962        assert_eq!(
 4963            editor.selections.display_ranges(cx),
 4964            vec![
 4965                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4966                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 4967            ]
 4968        );
 4969    });
 4970}
 4971
 4972#[gpui::test]
 4973fn test_move_line_up_down(cx: &mut TestAppContext) {
 4974    init_test(cx, |_| {});
 4975
 4976    let editor = cx.add_window(|window, cx| {
 4977        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 4978        build_editor(buffer, window, cx)
 4979    });
 4980    _ = editor.update(cx, |editor, window, cx| {
 4981        editor.fold_creases(
 4982            vec![
 4983                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 4984                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 4985                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 4986            ],
 4987            true,
 4988            window,
 4989            cx,
 4990        );
 4991        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4992            s.select_display_ranges([
 4993                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4994                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4995                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4996                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 4997            ])
 4998        });
 4999        assert_eq!(
 5000            editor.display_text(cx),
 5001            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 5002        );
 5003
 5004        editor.move_line_up(&MoveLineUp, window, cx);
 5005        assert_eq!(
 5006            editor.display_text(cx),
 5007            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 5008        );
 5009        assert_eq!(
 5010            editor.selections.display_ranges(cx),
 5011            vec![
 5012                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5013                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5014                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5015                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5016            ]
 5017        );
 5018    });
 5019
 5020    _ = editor.update(cx, |editor, window, cx| {
 5021        editor.move_line_down(&MoveLineDown, window, cx);
 5022        assert_eq!(
 5023            editor.display_text(cx),
 5024            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 5025        );
 5026        assert_eq!(
 5027            editor.selections.display_ranges(cx),
 5028            vec![
 5029                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5030                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5031                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5032                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5033            ]
 5034        );
 5035    });
 5036
 5037    _ = editor.update(cx, |editor, window, cx| {
 5038        editor.move_line_down(&MoveLineDown, window, cx);
 5039        assert_eq!(
 5040            editor.display_text(cx),
 5041            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 5042        );
 5043        assert_eq!(
 5044            editor.selections.display_ranges(cx),
 5045            vec![
 5046                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5047                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5048                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5049                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5050            ]
 5051        );
 5052    });
 5053
 5054    _ = editor.update(cx, |editor, window, cx| {
 5055        editor.move_line_up(&MoveLineUp, window, cx);
 5056        assert_eq!(
 5057            editor.display_text(cx),
 5058            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 5059        );
 5060        assert_eq!(
 5061            editor.selections.display_ranges(cx),
 5062            vec![
 5063                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5064                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5065                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5066                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5067            ]
 5068        );
 5069    });
 5070}
 5071
 5072#[gpui::test]
 5073fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 5074    init_test(cx, |_| {});
 5075
 5076    let editor = cx.add_window(|window, cx| {
 5077        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5078        build_editor(buffer, window, cx)
 5079    });
 5080    _ = editor.update(cx, |editor, window, cx| {
 5081        let snapshot = editor.buffer.read(cx).snapshot(cx);
 5082        editor.insert_blocks(
 5083            [BlockProperties {
 5084                style: BlockStyle::Fixed,
 5085                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 5086                height: Some(1),
 5087                render: Arc::new(|_| div().into_any()),
 5088                priority: 0,
 5089            }],
 5090            Some(Autoscroll::fit()),
 5091            cx,
 5092        );
 5093        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5094            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 5095        });
 5096        editor.move_line_down(&MoveLineDown, window, cx);
 5097    });
 5098}
 5099
 5100#[gpui::test]
 5101async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 5102    init_test(cx, |_| {});
 5103
 5104    let mut cx = EditorTestContext::new(cx).await;
 5105    cx.set_state(
 5106        &"
 5107            ˇzero
 5108            one
 5109            two
 5110            three
 5111            four
 5112            five
 5113        "
 5114        .unindent(),
 5115    );
 5116
 5117    // Create a four-line block that replaces three lines of text.
 5118    cx.update_editor(|editor, window, cx| {
 5119        let snapshot = editor.snapshot(window, cx);
 5120        let snapshot = &snapshot.buffer_snapshot;
 5121        let placement = BlockPlacement::Replace(
 5122            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 5123        );
 5124        editor.insert_blocks(
 5125            [BlockProperties {
 5126                placement,
 5127                height: Some(4),
 5128                style: BlockStyle::Sticky,
 5129                render: Arc::new(|_| gpui::div().into_any_element()),
 5130                priority: 0,
 5131            }],
 5132            None,
 5133            cx,
 5134        );
 5135    });
 5136
 5137    // Move down so that the cursor touches the block.
 5138    cx.update_editor(|editor, window, cx| {
 5139        editor.move_down(&Default::default(), window, cx);
 5140    });
 5141    cx.assert_editor_state(
 5142        &"
 5143            zero
 5144            «one
 5145            two
 5146            threeˇ»
 5147            four
 5148            five
 5149        "
 5150        .unindent(),
 5151    );
 5152
 5153    // Move down past the block.
 5154    cx.update_editor(|editor, window, cx| {
 5155        editor.move_down(&Default::default(), window, cx);
 5156    });
 5157    cx.assert_editor_state(
 5158        &"
 5159            zero
 5160            one
 5161            two
 5162            three
 5163            ˇfour
 5164            five
 5165        "
 5166        .unindent(),
 5167    );
 5168}
 5169
 5170#[gpui::test]
 5171fn test_transpose(cx: &mut TestAppContext) {
 5172    init_test(cx, |_| {});
 5173
 5174    _ = cx.add_window(|window, cx| {
 5175        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 5176        editor.set_style(EditorStyle::default(), window, cx);
 5177        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5178            s.select_ranges([1..1])
 5179        });
 5180        editor.transpose(&Default::default(), window, cx);
 5181        assert_eq!(editor.text(cx), "bac");
 5182        assert_eq!(editor.selections.ranges(cx), [2..2]);
 5183
 5184        editor.transpose(&Default::default(), window, cx);
 5185        assert_eq!(editor.text(cx), "bca");
 5186        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5187
 5188        editor.transpose(&Default::default(), window, cx);
 5189        assert_eq!(editor.text(cx), "bac");
 5190        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5191
 5192        editor
 5193    });
 5194
 5195    _ = cx.add_window(|window, cx| {
 5196        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5197        editor.set_style(EditorStyle::default(), window, cx);
 5198        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5199            s.select_ranges([3..3])
 5200        });
 5201        editor.transpose(&Default::default(), window, cx);
 5202        assert_eq!(editor.text(cx), "acb\nde");
 5203        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5204
 5205        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5206            s.select_ranges([4..4])
 5207        });
 5208        editor.transpose(&Default::default(), window, cx);
 5209        assert_eq!(editor.text(cx), "acbd\ne");
 5210        assert_eq!(editor.selections.ranges(cx), [5..5]);
 5211
 5212        editor.transpose(&Default::default(), window, cx);
 5213        assert_eq!(editor.text(cx), "acbde\n");
 5214        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5215
 5216        editor.transpose(&Default::default(), window, cx);
 5217        assert_eq!(editor.text(cx), "acbd\ne");
 5218        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5219
 5220        editor
 5221    });
 5222
 5223    _ = cx.add_window(|window, cx| {
 5224        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5225        editor.set_style(EditorStyle::default(), window, cx);
 5226        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5227            s.select_ranges([1..1, 2..2, 4..4])
 5228        });
 5229        editor.transpose(&Default::default(), window, cx);
 5230        assert_eq!(editor.text(cx), "bacd\ne");
 5231        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 5232
 5233        editor.transpose(&Default::default(), window, cx);
 5234        assert_eq!(editor.text(cx), "bcade\n");
 5235        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 5236
 5237        editor.transpose(&Default::default(), window, cx);
 5238        assert_eq!(editor.text(cx), "bcda\ne");
 5239        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5240
 5241        editor.transpose(&Default::default(), window, cx);
 5242        assert_eq!(editor.text(cx), "bcade\n");
 5243        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5244
 5245        editor.transpose(&Default::default(), window, cx);
 5246        assert_eq!(editor.text(cx), "bcaed\n");
 5247        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 5248
 5249        editor
 5250    });
 5251
 5252    _ = cx.add_window(|window, cx| {
 5253        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 5254        editor.set_style(EditorStyle::default(), window, cx);
 5255        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5256            s.select_ranges([4..4])
 5257        });
 5258        editor.transpose(&Default::default(), window, cx);
 5259        assert_eq!(editor.text(cx), "🏀🍐✋");
 5260        assert_eq!(editor.selections.ranges(cx), [8..8]);
 5261
 5262        editor.transpose(&Default::default(), window, cx);
 5263        assert_eq!(editor.text(cx), "🏀✋🍐");
 5264        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5265
 5266        editor.transpose(&Default::default(), window, cx);
 5267        assert_eq!(editor.text(cx), "🏀🍐✋");
 5268        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5269
 5270        editor
 5271    });
 5272}
 5273
 5274#[gpui::test]
 5275async fn test_rewrap(cx: &mut TestAppContext) {
 5276    init_test(cx, |settings| {
 5277        settings.languages.0.extend([
 5278            (
 5279                "Markdown".into(),
 5280                LanguageSettingsContent {
 5281                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5282                    preferred_line_length: Some(40),
 5283                    ..Default::default()
 5284                },
 5285            ),
 5286            (
 5287                "Plain Text".into(),
 5288                LanguageSettingsContent {
 5289                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5290                    preferred_line_length: Some(40),
 5291                    ..Default::default()
 5292                },
 5293            ),
 5294            (
 5295                "C++".into(),
 5296                LanguageSettingsContent {
 5297                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5298                    preferred_line_length: Some(40),
 5299                    ..Default::default()
 5300                },
 5301            ),
 5302            (
 5303                "Python".into(),
 5304                LanguageSettingsContent {
 5305                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5306                    preferred_line_length: Some(40),
 5307                    ..Default::default()
 5308                },
 5309            ),
 5310            (
 5311                "Rust".into(),
 5312                LanguageSettingsContent {
 5313                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5314                    preferred_line_length: Some(40),
 5315                    ..Default::default()
 5316                },
 5317            ),
 5318        ])
 5319    });
 5320
 5321    let mut cx = EditorTestContext::new(cx).await;
 5322
 5323    let cpp_language = Arc::new(Language::new(
 5324        LanguageConfig {
 5325            name: "C++".into(),
 5326            line_comments: vec!["// ".into()],
 5327            ..LanguageConfig::default()
 5328        },
 5329        None,
 5330    ));
 5331    let python_language = Arc::new(Language::new(
 5332        LanguageConfig {
 5333            name: "Python".into(),
 5334            line_comments: vec!["# ".into()],
 5335            ..LanguageConfig::default()
 5336        },
 5337        None,
 5338    ));
 5339    let markdown_language = Arc::new(Language::new(
 5340        LanguageConfig {
 5341            name: "Markdown".into(),
 5342            rewrap_prefixes: vec![
 5343                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 5344                regex::Regex::new("[-*+]\\s+").unwrap(),
 5345            ],
 5346            ..LanguageConfig::default()
 5347        },
 5348        None,
 5349    ));
 5350    let rust_language = Arc::new(Language::new(
 5351        LanguageConfig {
 5352            name: "Rust".into(),
 5353            line_comments: vec!["// ".into(), "/// ".into()],
 5354            ..LanguageConfig::default()
 5355        },
 5356        Some(tree_sitter_rust::LANGUAGE.into()),
 5357    ));
 5358
 5359    let plaintext_language = Arc::new(Language::new(
 5360        LanguageConfig {
 5361            name: "Plain Text".into(),
 5362            ..LanguageConfig::default()
 5363        },
 5364        None,
 5365    ));
 5366
 5367    // Test basic rewrapping of a long line with a cursor
 5368    assert_rewrap(
 5369        indoc! {"
 5370            // ˇThis is a long comment that needs to be wrapped.
 5371        "},
 5372        indoc! {"
 5373            // ˇThis is a long comment that needs to
 5374            // be wrapped.
 5375        "},
 5376        cpp_language.clone(),
 5377        &mut cx,
 5378    );
 5379
 5380    // Test rewrapping a full selection
 5381    assert_rewrap(
 5382        indoc! {"
 5383            «// This selected long comment needs to be wrapped.ˇ»"
 5384        },
 5385        indoc! {"
 5386            «// This selected long comment needs to
 5387            // be wrapped.ˇ»"
 5388        },
 5389        cpp_language.clone(),
 5390        &mut cx,
 5391    );
 5392
 5393    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 5394    assert_rewrap(
 5395        indoc! {"
 5396            // ˇThis is the first line.
 5397            // Thisˇ is the second line.
 5398            // This is the thirdˇ line, all part of one paragraph.
 5399         "},
 5400        indoc! {"
 5401            // ˇThis is the first line. Thisˇ is the
 5402            // second line. This is the thirdˇ line,
 5403            // all part of one paragraph.
 5404         "},
 5405        cpp_language.clone(),
 5406        &mut cx,
 5407    );
 5408
 5409    // Test multiple cursors in different paragraphs trigger separate rewraps
 5410    assert_rewrap(
 5411        indoc! {"
 5412            // ˇThis is the first paragraph, first line.
 5413            // ˇThis is the first paragraph, second line.
 5414
 5415            // ˇThis is the second paragraph, first line.
 5416            // ˇThis is the second paragraph, second line.
 5417        "},
 5418        indoc! {"
 5419            // ˇThis is the first paragraph, first
 5420            // line. ˇThis is the first paragraph,
 5421            // second line.
 5422
 5423            // ˇThis is the second paragraph, first
 5424            // line. ˇThis is the second paragraph,
 5425            // second line.
 5426        "},
 5427        cpp_language.clone(),
 5428        &mut cx,
 5429    );
 5430
 5431    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 5432    assert_rewrap(
 5433        indoc! {"
 5434            «// A regular long long comment to be wrapped.
 5435            /// A documentation long comment to be wrapped.ˇ»
 5436          "},
 5437        indoc! {"
 5438            «// A regular long long comment to be
 5439            // wrapped.
 5440            /// A documentation long comment to be
 5441            /// wrapped.ˇ»
 5442          "},
 5443        rust_language.clone(),
 5444        &mut cx,
 5445    );
 5446
 5447    // Test that change in indentation level trigger seperate rewraps
 5448    assert_rewrap(
 5449        indoc! {"
 5450            fn foo() {
 5451                «// This is a long comment at the base indent.
 5452                    // This is a long comment at the next indent.ˇ»
 5453            }
 5454        "},
 5455        indoc! {"
 5456            fn foo() {
 5457                «// This is a long comment at the
 5458                // base indent.
 5459                    // This is a long comment at the
 5460                    // next indent.ˇ»
 5461            }
 5462        "},
 5463        rust_language.clone(),
 5464        &mut cx,
 5465    );
 5466
 5467    // Test that different comment prefix characters (e.g., '#') are handled correctly
 5468    assert_rewrap(
 5469        indoc! {"
 5470            # ˇThis is a long comment using a pound sign.
 5471        "},
 5472        indoc! {"
 5473            # ˇThis is a long comment using a pound
 5474            # sign.
 5475        "},
 5476        python_language.clone(),
 5477        &mut cx,
 5478    );
 5479
 5480    // Test rewrapping only affects comments, not code even when selected
 5481    assert_rewrap(
 5482        indoc! {"
 5483            «/// This doc comment is long and should be wrapped.
 5484            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 5485        "},
 5486        indoc! {"
 5487            «/// This doc comment is long and should
 5488            /// be wrapped.
 5489            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 5490        "},
 5491        rust_language.clone(),
 5492        &mut cx,
 5493    );
 5494
 5495    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 5496    assert_rewrap(
 5497        indoc! {"
 5498            # Header
 5499
 5500            A long long long line of markdown text to wrap.ˇ
 5501         "},
 5502        indoc! {"
 5503            # Header
 5504
 5505            A long long long line of markdown text
 5506            to wrap.ˇ
 5507         "},
 5508        markdown_language.clone(),
 5509        &mut cx,
 5510    );
 5511
 5512    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 5513    assert_rewrap(
 5514        indoc! {"
 5515            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 5516            2. This is a numbered list item that is very long and needs to be wrapped properly.
 5517            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 5518        "},
 5519        indoc! {"
 5520            «1. This is a numbered list item that is
 5521               very long and needs to be wrapped
 5522               properly.
 5523            2. This is a numbered list item that is
 5524               very long and needs to be wrapped
 5525               properly.
 5526            - This is an unordered list item that is
 5527              also very long and should not merge
 5528              with the numbered item.ˇ»
 5529        "},
 5530        markdown_language.clone(),
 5531        &mut cx,
 5532    );
 5533
 5534    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 5535    assert_rewrap(
 5536        indoc! {"
 5537            «1. This is a numbered list item that is
 5538            very long and needs to be wrapped
 5539            properly.
 5540            2. This is a numbered list item that is
 5541            very long and needs to be wrapped
 5542            properly.
 5543            - This is an unordered list item that is
 5544            also very long and should not merge with
 5545            the numbered item.ˇ»
 5546        "},
 5547        indoc! {"
 5548            «1. This is a numbered list item that is
 5549               very long and needs to be wrapped
 5550               properly.
 5551            2. This is a numbered list item that is
 5552               very long and needs to be wrapped
 5553               properly.
 5554            - This is an unordered list item that is
 5555              also very long and should not merge
 5556              with the numbered item.ˇ»
 5557        "},
 5558        markdown_language.clone(),
 5559        &mut cx,
 5560    );
 5561
 5562    // Test that rewrapping maintain indents even when they already exists.
 5563    assert_rewrap(
 5564        indoc! {"
 5565            «1. This is a numbered list
 5566               item that is very long and needs to be wrapped properly.
 5567            2. This is a numbered list
 5568               item that is very long and needs to be wrapped properly.
 5569            - This is an unordered list item that is also very long and
 5570              should not merge with the numbered item.ˇ»
 5571        "},
 5572        indoc! {"
 5573            «1. This is a numbered list item that is
 5574               very long and needs to be wrapped
 5575               properly.
 5576            2. This is a numbered list item that is
 5577               very long and needs to be wrapped
 5578               properly.
 5579            - This is an unordered list item that is
 5580              also very long and should not merge
 5581              with the numbered item.ˇ»
 5582        "},
 5583        markdown_language.clone(),
 5584        &mut cx,
 5585    );
 5586
 5587    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 5588    assert_rewrap(
 5589        indoc! {"
 5590            ˇThis is a very long line of plain text that will be wrapped.
 5591        "},
 5592        indoc! {"
 5593            ˇThis is a very long line of plain text
 5594            that will be wrapped.
 5595        "},
 5596        plaintext_language.clone(),
 5597        &mut cx,
 5598    );
 5599
 5600    // Test that non-commented code acts as a paragraph boundary within a selection
 5601    assert_rewrap(
 5602        indoc! {"
 5603               «// This is the first long comment block to be wrapped.
 5604               fn my_func(a: u32);
 5605               // This is the second long comment block to be wrapped.ˇ»
 5606           "},
 5607        indoc! {"
 5608               «// This is the first long comment block
 5609               // to be wrapped.
 5610               fn my_func(a: u32);
 5611               // This is the second long comment block
 5612               // to be wrapped.ˇ»
 5613           "},
 5614        rust_language.clone(),
 5615        &mut cx,
 5616    );
 5617
 5618    // Test rewrapping multiple selections, including ones with blank lines or tabs
 5619    assert_rewrap(
 5620        indoc! {"
 5621            «ˇThis is a very long line that will be wrapped.
 5622
 5623            This is another paragraph in the same selection.»
 5624
 5625            «\tThis is a very long indented line that will be wrapped.ˇ»
 5626         "},
 5627        indoc! {"
 5628            «ˇThis is a very long line that will be
 5629            wrapped.
 5630
 5631            This is another paragraph in the same
 5632            selection.»
 5633
 5634            «\tThis is a very long indented line
 5635            \tthat will be wrapped.ˇ»
 5636         "},
 5637        plaintext_language.clone(),
 5638        &mut cx,
 5639    );
 5640
 5641    // Test that an empty comment line acts as a paragraph boundary
 5642    assert_rewrap(
 5643        indoc! {"
 5644            // ˇThis is a long comment that will be wrapped.
 5645            //
 5646            // And this is another long comment that will also be wrapped.ˇ
 5647         "},
 5648        indoc! {"
 5649            // ˇThis is a long comment that will be
 5650            // wrapped.
 5651            //
 5652            // And this is another long comment that
 5653            // will also be wrapped.ˇ
 5654         "},
 5655        cpp_language,
 5656        &mut cx,
 5657    );
 5658
 5659    #[track_caller]
 5660    fn assert_rewrap(
 5661        unwrapped_text: &str,
 5662        wrapped_text: &str,
 5663        language: Arc<Language>,
 5664        cx: &mut EditorTestContext,
 5665    ) {
 5666        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 5667        cx.set_state(unwrapped_text);
 5668        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 5669        cx.assert_editor_state(wrapped_text);
 5670    }
 5671}
 5672
 5673#[gpui::test]
 5674async fn test_hard_wrap(cx: &mut TestAppContext) {
 5675    init_test(cx, |_| {});
 5676    let mut cx = EditorTestContext::new(cx).await;
 5677
 5678    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 5679    cx.update_editor(|editor, _, cx| {
 5680        editor.set_hard_wrap(Some(14), cx);
 5681    });
 5682
 5683    cx.set_state(indoc!(
 5684        "
 5685        one two three ˇ
 5686        "
 5687    ));
 5688    cx.simulate_input("four");
 5689    cx.run_until_parked();
 5690
 5691    cx.assert_editor_state(indoc!(
 5692        "
 5693        one two three
 5694        fourˇ
 5695        "
 5696    ));
 5697
 5698    cx.update_editor(|editor, window, cx| {
 5699        editor.newline(&Default::default(), window, cx);
 5700    });
 5701    cx.run_until_parked();
 5702    cx.assert_editor_state(indoc!(
 5703        "
 5704        one two three
 5705        four
 5706        ˇ
 5707        "
 5708    ));
 5709
 5710    cx.simulate_input("five");
 5711    cx.run_until_parked();
 5712    cx.assert_editor_state(indoc!(
 5713        "
 5714        one two three
 5715        four
 5716        fiveˇ
 5717        "
 5718    ));
 5719
 5720    cx.update_editor(|editor, window, cx| {
 5721        editor.newline(&Default::default(), window, cx);
 5722    });
 5723    cx.run_until_parked();
 5724    cx.simulate_input("# ");
 5725    cx.run_until_parked();
 5726    cx.assert_editor_state(indoc!(
 5727        "
 5728        one two three
 5729        four
 5730        five
 5731        # ˇ
 5732        "
 5733    ));
 5734
 5735    cx.update_editor(|editor, window, cx| {
 5736        editor.newline(&Default::default(), window, cx);
 5737    });
 5738    cx.run_until_parked();
 5739    cx.assert_editor_state(indoc!(
 5740        "
 5741        one two three
 5742        four
 5743        five
 5744        #\x20
 5745 5746        "
 5747    ));
 5748
 5749    cx.simulate_input(" 6");
 5750    cx.run_until_parked();
 5751    cx.assert_editor_state(indoc!(
 5752        "
 5753        one two three
 5754        four
 5755        five
 5756        #
 5757        # 6ˇ
 5758        "
 5759    ));
 5760}
 5761
 5762#[gpui::test]
 5763async fn test_clipboard(cx: &mut TestAppContext) {
 5764    init_test(cx, |_| {});
 5765
 5766    let mut cx = EditorTestContext::new(cx).await;
 5767
 5768    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 5769    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5770    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 5771
 5772    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 5773    cx.set_state("two ˇfour ˇsix ˇ");
 5774    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5775    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 5776
 5777    // Paste again but with only two cursors. Since the number of cursors doesn't
 5778    // match the number of slices in the clipboard, the entire clipboard text
 5779    // is pasted at each cursor.
 5780    cx.set_state("ˇtwo one✅ four three six five ˇ");
 5781    cx.update_editor(|e, window, cx| {
 5782        e.handle_input("( ", window, cx);
 5783        e.paste(&Paste, window, cx);
 5784        e.handle_input(") ", window, cx);
 5785    });
 5786    cx.assert_editor_state(
 5787        &([
 5788            "( one✅ ",
 5789            "three ",
 5790            "five ) ˇtwo one✅ four three six five ( one✅ ",
 5791            "three ",
 5792            "five ) ˇ",
 5793        ]
 5794        .join("\n")),
 5795    );
 5796
 5797    // Cut with three selections, one of which is full-line.
 5798    cx.set_state(indoc! {"
 5799        1«2ˇ»3
 5800        4ˇ567
 5801        «8ˇ»9"});
 5802    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5803    cx.assert_editor_state(indoc! {"
 5804        1ˇ3
 5805        ˇ9"});
 5806
 5807    // Paste with three selections, noticing how the copied selection that was full-line
 5808    // gets inserted before the second cursor.
 5809    cx.set_state(indoc! {"
 5810        1ˇ3
 5811 5812        «oˇ»ne"});
 5813    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5814    cx.assert_editor_state(indoc! {"
 5815        12ˇ3
 5816        4567
 5817 5818        8ˇne"});
 5819
 5820    // Copy with a single cursor only, which writes the whole line into the clipboard.
 5821    cx.set_state(indoc! {"
 5822        The quick brown
 5823        fox juˇmps over
 5824        the lazy dog"});
 5825    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5826    assert_eq!(
 5827        cx.read_from_clipboard()
 5828            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5829        Some("fox jumps over\n".to_string())
 5830    );
 5831
 5832    // Paste with three selections, noticing how the copied full-line selection is inserted
 5833    // before the empty selections but replaces the selection that is non-empty.
 5834    cx.set_state(indoc! {"
 5835        Tˇhe quick brown
 5836        «foˇ»x jumps over
 5837        tˇhe lazy dog"});
 5838    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5839    cx.assert_editor_state(indoc! {"
 5840        fox jumps over
 5841        Tˇhe quick brown
 5842        fox jumps over
 5843        ˇx jumps over
 5844        fox jumps over
 5845        tˇhe lazy dog"});
 5846}
 5847
 5848#[gpui::test]
 5849async fn test_copy_trim(cx: &mut TestAppContext) {
 5850    init_test(cx, |_| {});
 5851
 5852    let mut cx = EditorTestContext::new(cx).await;
 5853    cx.set_state(
 5854        r#"            «for selection in selections.iter() {
 5855            let mut start = selection.start;
 5856            let mut end = selection.end;
 5857            let is_entire_line = selection.is_empty();
 5858            if is_entire_line {
 5859                start = Point::new(start.row, 0);ˇ»
 5860                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5861            }
 5862        "#,
 5863    );
 5864    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5865    assert_eq!(
 5866        cx.read_from_clipboard()
 5867            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5868        Some(
 5869            "for selection in selections.iter() {
 5870            let mut start = selection.start;
 5871            let mut end = selection.end;
 5872            let is_entire_line = selection.is_empty();
 5873            if is_entire_line {
 5874                start = Point::new(start.row, 0);"
 5875                .to_string()
 5876        ),
 5877        "Regular copying preserves all indentation selected",
 5878    );
 5879    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5880    assert_eq!(
 5881        cx.read_from_clipboard()
 5882            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5883        Some(
 5884            "for selection in selections.iter() {
 5885let mut start = selection.start;
 5886let mut end = selection.end;
 5887let is_entire_line = selection.is_empty();
 5888if is_entire_line {
 5889    start = Point::new(start.row, 0);"
 5890                .to_string()
 5891        ),
 5892        "Copying with stripping should strip all leading whitespaces"
 5893    );
 5894
 5895    cx.set_state(
 5896        r#"       «     for selection in selections.iter() {
 5897            let mut start = selection.start;
 5898            let mut end = selection.end;
 5899            let is_entire_line = selection.is_empty();
 5900            if is_entire_line {
 5901                start = Point::new(start.row, 0);ˇ»
 5902                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5903            }
 5904        "#,
 5905    );
 5906    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5907    assert_eq!(
 5908        cx.read_from_clipboard()
 5909            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5910        Some(
 5911            "     for selection in selections.iter() {
 5912            let mut start = selection.start;
 5913            let mut end = selection.end;
 5914            let is_entire_line = selection.is_empty();
 5915            if is_entire_line {
 5916                start = Point::new(start.row, 0);"
 5917                .to_string()
 5918        ),
 5919        "Regular copying preserves all indentation selected",
 5920    );
 5921    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5922    assert_eq!(
 5923        cx.read_from_clipboard()
 5924            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5925        Some(
 5926            "for selection in selections.iter() {
 5927let mut start = selection.start;
 5928let mut end = selection.end;
 5929let is_entire_line = selection.is_empty();
 5930if is_entire_line {
 5931    start = Point::new(start.row, 0);"
 5932                .to_string()
 5933        ),
 5934        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 5935    );
 5936
 5937    cx.set_state(
 5938        r#"       «ˇ     for selection in selections.iter() {
 5939            let mut start = selection.start;
 5940            let mut end = selection.end;
 5941            let is_entire_line = selection.is_empty();
 5942            if is_entire_line {
 5943                start = Point::new(start.row, 0);»
 5944                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5945            }
 5946        "#,
 5947    );
 5948    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5949    assert_eq!(
 5950        cx.read_from_clipboard()
 5951            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5952        Some(
 5953            "     for selection in selections.iter() {
 5954            let mut start = selection.start;
 5955            let mut end = selection.end;
 5956            let is_entire_line = selection.is_empty();
 5957            if is_entire_line {
 5958                start = Point::new(start.row, 0);"
 5959                .to_string()
 5960        ),
 5961        "Regular copying for reverse selection works the same",
 5962    );
 5963    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5964    assert_eq!(
 5965        cx.read_from_clipboard()
 5966            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5967        Some(
 5968            "for selection in selections.iter() {
 5969let mut start = selection.start;
 5970let mut end = selection.end;
 5971let is_entire_line = selection.is_empty();
 5972if is_entire_line {
 5973    start = Point::new(start.row, 0);"
 5974                .to_string()
 5975        ),
 5976        "Copying with stripping for reverse selection works the same"
 5977    );
 5978
 5979    cx.set_state(
 5980        r#"            for selection «in selections.iter() {
 5981            let mut start = selection.start;
 5982            let mut end = selection.end;
 5983            let is_entire_line = selection.is_empty();
 5984            if is_entire_line {
 5985                start = Point::new(start.row, 0);ˇ»
 5986                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5987            }
 5988        "#,
 5989    );
 5990    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5991    assert_eq!(
 5992        cx.read_from_clipboard()
 5993            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5994        Some(
 5995            "in selections.iter() {
 5996            let mut start = selection.start;
 5997            let mut end = selection.end;
 5998            let is_entire_line = selection.is_empty();
 5999            if is_entire_line {
 6000                start = Point::new(start.row, 0);"
 6001                .to_string()
 6002        ),
 6003        "When selecting past the indent, the copying works as usual",
 6004    );
 6005    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6006    assert_eq!(
 6007        cx.read_from_clipboard()
 6008            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6009        Some(
 6010            "in selections.iter() {
 6011            let mut start = selection.start;
 6012            let mut end = selection.end;
 6013            let is_entire_line = selection.is_empty();
 6014            if is_entire_line {
 6015                start = Point::new(start.row, 0);"
 6016                .to_string()
 6017        ),
 6018        "When selecting past the indent, nothing is trimmed"
 6019    );
 6020
 6021    cx.set_state(
 6022        r#"            «for selection in selections.iter() {
 6023            let mut start = selection.start;
 6024
 6025            let mut end = selection.end;
 6026            let is_entire_line = selection.is_empty();
 6027            if is_entire_line {
 6028                start = Point::new(start.row, 0);
 6029ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6030            }
 6031        "#,
 6032    );
 6033    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6034    assert_eq!(
 6035        cx.read_from_clipboard()
 6036            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6037        Some(
 6038            "for selection in selections.iter() {
 6039let mut start = selection.start;
 6040
 6041let mut end = selection.end;
 6042let is_entire_line = selection.is_empty();
 6043if is_entire_line {
 6044    start = Point::new(start.row, 0);
 6045"
 6046            .to_string()
 6047        ),
 6048        "Copying with stripping should ignore empty lines"
 6049    );
 6050}
 6051
 6052#[gpui::test]
 6053async fn test_paste_multiline(cx: &mut TestAppContext) {
 6054    init_test(cx, |_| {});
 6055
 6056    let mut cx = EditorTestContext::new(cx).await;
 6057    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 6058
 6059    // Cut an indented block, without the leading whitespace.
 6060    cx.set_state(indoc! {"
 6061        const a: B = (
 6062            c(),
 6063            «d(
 6064                e,
 6065                f
 6066            )ˇ»
 6067        );
 6068    "});
 6069    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6070    cx.assert_editor_state(indoc! {"
 6071        const a: B = (
 6072            c(),
 6073            ˇ
 6074        );
 6075    "});
 6076
 6077    // Paste it at the same position.
 6078    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6079    cx.assert_editor_state(indoc! {"
 6080        const a: B = (
 6081            c(),
 6082            d(
 6083                e,
 6084                f
 6085 6086        );
 6087    "});
 6088
 6089    // Paste it at a line with a lower indent level.
 6090    cx.set_state(indoc! {"
 6091        ˇ
 6092        const a: B = (
 6093            c(),
 6094        );
 6095    "});
 6096    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6097    cx.assert_editor_state(indoc! {"
 6098        d(
 6099            e,
 6100            f
 6101 6102        const a: B = (
 6103            c(),
 6104        );
 6105    "});
 6106
 6107    // Cut an indented block, with the leading whitespace.
 6108    cx.set_state(indoc! {"
 6109        const a: B = (
 6110            c(),
 6111        «    d(
 6112                e,
 6113                f
 6114            )
 6115        ˇ»);
 6116    "});
 6117    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6118    cx.assert_editor_state(indoc! {"
 6119        const a: B = (
 6120            c(),
 6121        ˇ);
 6122    "});
 6123
 6124    // Paste it at the same position.
 6125    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6126    cx.assert_editor_state(indoc! {"
 6127        const a: B = (
 6128            c(),
 6129            d(
 6130                e,
 6131                f
 6132            )
 6133        ˇ);
 6134    "});
 6135
 6136    // Paste it at a line with a higher indent level.
 6137    cx.set_state(indoc! {"
 6138        const a: B = (
 6139            c(),
 6140            d(
 6141                e,
 6142 6143            )
 6144        );
 6145    "});
 6146    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6147    cx.assert_editor_state(indoc! {"
 6148        const a: B = (
 6149            c(),
 6150            d(
 6151                e,
 6152                f    d(
 6153                    e,
 6154                    f
 6155                )
 6156        ˇ
 6157            )
 6158        );
 6159    "});
 6160
 6161    // Copy an indented block, starting mid-line
 6162    cx.set_state(indoc! {"
 6163        const a: B = (
 6164            c(),
 6165            somethin«g(
 6166                e,
 6167                f
 6168            )ˇ»
 6169        );
 6170    "});
 6171    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6172
 6173    // Paste it on a line with a lower indent level
 6174    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 6175    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6176    cx.assert_editor_state(indoc! {"
 6177        const a: B = (
 6178            c(),
 6179            something(
 6180                e,
 6181                f
 6182            )
 6183        );
 6184        g(
 6185            e,
 6186            f
 6187"});
 6188}
 6189
 6190#[gpui::test]
 6191async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 6192    init_test(cx, |_| {});
 6193
 6194    cx.write_to_clipboard(ClipboardItem::new_string(
 6195        "    d(\n        e\n    );\n".into(),
 6196    ));
 6197
 6198    let mut cx = EditorTestContext::new(cx).await;
 6199    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 6200
 6201    cx.set_state(indoc! {"
 6202        fn a() {
 6203            b();
 6204            if c() {
 6205                ˇ
 6206            }
 6207        }
 6208    "});
 6209
 6210    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6211    cx.assert_editor_state(indoc! {"
 6212        fn a() {
 6213            b();
 6214            if c() {
 6215                d(
 6216                    e
 6217                );
 6218        ˇ
 6219            }
 6220        }
 6221    "});
 6222
 6223    cx.set_state(indoc! {"
 6224        fn a() {
 6225            b();
 6226            ˇ
 6227        }
 6228    "});
 6229
 6230    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6231    cx.assert_editor_state(indoc! {"
 6232        fn a() {
 6233            b();
 6234            d(
 6235                e
 6236            );
 6237        ˇ
 6238        }
 6239    "});
 6240}
 6241
 6242#[gpui::test]
 6243fn test_select_all(cx: &mut TestAppContext) {
 6244    init_test(cx, |_| {});
 6245
 6246    let editor = cx.add_window(|window, cx| {
 6247        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 6248        build_editor(buffer, window, cx)
 6249    });
 6250    _ = editor.update(cx, |editor, window, cx| {
 6251        editor.select_all(&SelectAll, window, cx);
 6252        assert_eq!(
 6253            editor.selections.display_ranges(cx),
 6254            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 6255        );
 6256    });
 6257}
 6258
 6259#[gpui::test]
 6260fn test_select_line(cx: &mut TestAppContext) {
 6261    init_test(cx, |_| {});
 6262
 6263    let editor = cx.add_window(|window, cx| {
 6264        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 6265        build_editor(buffer, window, cx)
 6266    });
 6267    _ = editor.update(cx, |editor, window, cx| {
 6268        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6269            s.select_display_ranges([
 6270                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6271                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6272                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6273                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 6274            ])
 6275        });
 6276        editor.select_line(&SelectLine, window, cx);
 6277        assert_eq!(
 6278            editor.selections.display_ranges(cx),
 6279            vec![
 6280                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 6281                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 6282            ]
 6283        );
 6284    });
 6285
 6286    _ = editor.update(cx, |editor, window, cx| {
 6287        editor.select_line(&SelectLine, window, cx);
 6288        assert_eq!(
 6289            editor.selections.display_ranges(cx),
 6290            vec![
 6291                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 6292                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 6293            ]
 6294        );
 6295    });
 6296
 6297    _ = editor.update(cx, |editor, window, cx| {
 6298        editor.select_line(&SelectLine, window, cx);
 6299        assert_eq!(
 6300            editor.selections.display_ranges(cx),
 6301            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 6302        );
 6303    });
 6304}
 6305
 6306#[gpui::test]
 6307async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 6308    init_test(cx, |_| {});
 6309    let mut cx = EditorTestContext::new(cx).await;
 6310
 6311    #[track_caller]
 6312    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 6313        cx.set_state(initial_state);
 6314        cx.update_editor(|e, window, cx| {
 6315            e.split_selection_into_lines(&SplitSelectionIntoLines, window, cx)
 6316        });
 6317        cx.assert_editor_state(expected_state);
 6318    }
 6319
 6320    // Selection starts and ends at the middle of lines, left-to-right
 6321    test(
 6322        &mut cx,
 6323        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 6324        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 6325    );
 6326    // Same thing, right-to-left
 6327    test(
 6328        &mut cx,
 6329        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 6330        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 6331    );
 6332
 6333    // Whole buffer, left-to-right, last line *doesn't* end with newline
 6334    test(
 6335        &mut cx,
 6336        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 6337        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 6338    );
 6339    // Same thing, right-to-left
 6340    test(
 6341        &mut cx,
 6342        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 6343        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 6344    );
 6345
 6346    // Whole buffer, left-to-right, last line ends with newline
 6347    test(
 6348        &mut cx,
 6349        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 6350        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 6351    );
 6352    // Same thing, right-to-left
 6353    test(
 6354        &mut cx,
 6355        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 6356        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 6357    );
 6358
 6359    // Starts at the end of a line, ends at the start of another
 6360    test(
 6361        &mut cx,
 6362        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 6363        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 6364    );
 6365}
 6366
 6367#[gpui::test]
 6368async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 6369    init_test(cx, |_| {});
 6370
 6371    let editor = cx.add_window(|window, cx| {
 6372        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 6373        build_editor(buffer, window, cx)
 6374    });
 6375
 6376    // setup
 6377    _ = editor.update(cx, |editor, window, cx| {
 6378        editor.fold_creases(
 6379            vec![
 6380                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 6381                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 6382                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 6383            ],
 6384            true,
 6385            window,
 6386            cx,
 6387        );
 6388        assert_eq!(
 6389            editor.display_text(cx),
 6390            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 6391        );
 6392    });
 6393
 6394    _ = editor.update(cx, |editor, window, cx| {
 6395        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6396            s.select_display_ranges([
 6397                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6398                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6399                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6400                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 6401            ])
 6402        });
 6403        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 6404        assert_eq!(
 6405            editor.display_text(cx),
 6406            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 6407        );
 6408    });
 6409    EditorTestContext::for_editor(editor, cx)
 6410        .await
 6411        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 6412
 6413    _ = editor.update(cx, |editor, window, cx| {
 6414        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6415            s.select_display_ranges([
 6416                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 6417            ])
 6418        });
 6419        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 6420        assert_eq!(
 6421            editor.display_text(cx),
 6422            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 6423        );
 6424        assert_eq!(
 6425            editor.selections.display_ranges(cx),
 6426            [
 6427                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 6428                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 6429                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 6430                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 6431                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 6432                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 6433                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 6434            ]
 6435        );
 6436    });
 6437    EditorTestContext::for_editor(editor, cx)
 6438        .await
 6439        .assert_editor_state(
 6440            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 6441        );
 6442}
 6443
 6444#[gpui::test]
 6445async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 6446    init_test(cx, |_| {});
 6447
 6448    let mut cx = EditorTestContext::new(cx).await;
 6449
 6450    cx.set_state(indoc!(
 6451        r#"abc
 6452           defˇghi
 6453
 6454           jk
 6455           nlmo
 6456           "#
 6457    ));
 6458
 6459    cx.update_editor(|editor, window, cx| {
 6460        editor.add_selection_above(&Default::default(), window, cx);
 6461    });
 6462
 6463    cx.assert_editor_state(indoc!(
 6464        r#"abcˇ
 6465           defˇghi
 6466
 6467           jk
 6468           nlmo
 6469           "#
 6470    ));
 6471
 6472    cx.update_editor(|editor, window, cx| {
 6473        editor.add_selection_above(&Default::default(), window, cx);
 6474    });
 6475
 6476    cx.assert_editor_state(indoc!(
 6477        r#"abcˇ
 6478            defˇghi
 6479
 6480            jk
 6481            nlmo
 6482            "#
 6483    ));
 6484
 6485    cx.update_editor(|editor, window, cx| {
 6486        editor.add_selection_below(&Default::default(), window, cx);
 6487    });
 6488
 6489    cx.assert_editor_state(indoc!(
 6490        r#"abc
 6491           defˇghi
 6492
 6493           jk
 6494           nlmo
 6495           "#
 6496    ));
 6497
 6498    cx.update_editor(|editor, window, cx| {
 6499        editor.undo_selection(&Default::default(), window, cx);
 6500    });
 6501
 6502    cx.assert_editor_state(indoc!(
 6503        r#"abcˇ
 6504           defˇghi
 6505
 6506           jk
 6507           nlmo
 6508           "#
 6509    ));
 6510
 6511    cx.update_editor(|editor, window, cx| {
 6512        editor.redo_selection(&Default::default(), window, cx);
 6513    });
 6514
 6515    cx.assert_editor_state(indoc!(
 6516        r#"abc
 6517           defˇghi
 6518
 6519           jk
 6520           nlmo
 6521           "#
 6522    ));
 6523
 6524    cx.update_editor(|editor, window, cx| {
 6525        editor.add_selection_below(&Default::default(), window, cx);
 6526    });
 6527
 6528    cx.assert_editor_state(indoc!(
 6529        r#"abc
 6530           defˇghi
 6531           ˇ
 6532           jk
 6533           nlmo
 6534           "#
 6535    ));
 6536
 6537    cx.update_editor(|editor, window, cx| {
 6538        editor.add_selection_below(&Default::default(), window, cx);
 6539    });
 6540
 6541    cx.assert_editor_state(indoc!(
 6542        r#"abc
 6543           defˇghi
 6544           ˇ
 6545           jkˇ
 6546           nlmo
 6547           "#
 6548    ));
 6549
 6550    cx.update_editor(|editor, window, cx| {
 6551        editor.add_selection_below(&Default::default(), window, cx);
 6552    });
 6553
 6554    cx.assert_editor_state(indoc!(
 6555        r#"abc
 6556           defˇghi
 6557           ˇ
 6558           jkˇ
 6559           nlmˇo
 6560           "#
 6561    ));
 6562
 6563    cx.update_editor(|editor, window, cx| {
 6564        editor.add_selection_below(&Default::default(), window, cx);
 6565    });
 6566
 6567    cx.assert_editor_state(indoc!(
 6568        r#"abc
 6569           defˇghi
 6570           ˇ
 6571           jkˇ
 6572           nlmˇo
 6573           ˇ"#
 6574    ));
 6575
 6576    // change selections
 6577    cx.set_state(indoc!(
 6578        r#"abc
 6579           def«ˇg»hi
 6580
 6581           jk
 6582           nlmo
 6583           "#
 6584    ));
 6585
 6586    cx.update_editor(|editor, window, cx| {
 6587        editor.add_selection_below(&Default::default(), window, cx);
 6588    });
 6589
 6590    cx.assert_editor_state(indoc!(
 6591        r#"abc
 6592           def«ˇg»hi
 6593
 6594           jk
 6595           nlm«ˇo»
 6596           "#
 6597    ));
 6598
 6599    cx.update_editor(|editor, window, cx| {
 6600        editor.add_selection_below(&Default::default(), window, cx);
 6601    });
 6602
 6603    cx.assert_editor_state(indoc!(
 6604        r#"abc
 6605           def«ˇg»hi
 6606
 6607           jk
 6608           nlm«ˇo»
 6609           "#
 6610    ));
 6611
 6612    cx.update_editor(|editor, window, cx| {
 6613        editor.add_selection_above(&Default::default(), window, cx);
 6614    });
 6615
 6616    cx.assert_editor_state(indoc!(
 6617        r#"abc
 6618           def«ˇg»hi
 6619
 6620           jk
 6621           nlmo
 6622           "#
 6623    ));
 6624
 6625    cx.update_editor(|editor, window, cx| {
 6626        editor.add_selection_above(&Default::default(), window, cx);
 6627    });
 6628
 6629    cx.assert_editor_state(indoc!(
 6630        r#"abc
 6631           def«ˇg»hi
 6632
 6633           jk
 6634           nlmo
 6635           "#
 6636    ));
 6637
 6638    // Change selections again
 6639    cx.set_state(indoc!(
 6640        r#"a«bc
 6641           defgˇ»hi
 6642
 6643           jk
 6644           nlmo
 6645           "#
 6646    ));
 6647
 6648    cx.update_editor(|editor, window, cx| {
 6649        editor.add_selection_below(&Default::default(), window, cx);
 6650    });
 6651
 6652    cx.assert_editor_state(indoc!(
 6653        r#"a«bcˇ»
 6654           d«efgˇ»hi
 6655
 6656           j«kˇ»
 6657           nlmo
 6658           "#
 6659    ));
 6660
 6661    cx.update_editor(|editor, window, cx| {
 6662        editor.add_selection_below(&Default::default(), window, cx);
 6663    });
 6664    cx.assert_editor_state(indoc!(
 6665        r#"a«bcˇ»
 6666           d«efgˇ»hi
 6667
 6668           j«kˇ»
 6669           n«lmoˇ»
 6670           "#
 6671    ));
 6672    cx.update_editor(|editor, window, cx| {
 6673        editor.add_selection_above(&Default::default(), window, cx);
 6674    });
 6675
 6676    cx.assert_editor_state(indoc!(
 6677        r#"a«bcˇ»
 6678           d«efgˇ»hi
 6679
 6680           j«kˇ»
 6681           nlmo
 6682           "#
 6683    ));
 6684
 6685    // Change selections again
 6686    cx.set_state(indoc!(
 6687        r#"abc
 6688           d«ˇefghi
 6689
 6690           jk
 6691           nlm»o
 6692           "#
 6693    ));
 6694
 6695    cx.update_editor(|editor, window, cx| {
 6696        editor.add_selection_above(&Default::default(), window, cx);
 6697    });
 6698
 6699    cx.assert_editor_state(indoc!(
 6700        r#"a«ˇbc»
 6701           d«ˇef»ghi
 6702
 6703           j«ˇk»
 6704           n«ˇlm»o
 6705           "#
 6706    ));
 6707
 6708    cx.update_editor(|editor, window, cx| {
 6709        editor.add_selection_below(&Default::default(), window, cx);
 6710    });
 6711
 6712    cx.assert_editor_state(indoc!(
 6713        r#"abc
 6714           d«ˇef»ghi
 6715
 6716           j«ˇk»
 6717           n«ˇlm»o
 6718           "#
 6719    ));
 6720}
 6721
 6722#[gpui::test]
 6723async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 6724    init_test(cx, |_| {});
 6725    let mut cx = EditorTestContext::new(cx).await;
 6726
 6727    cx.set_state(indoc!(
 6728        r#"line onˇe
 6729           liˇne two
 6730           line three
 6731           line four"#
 6732    ));
 6733
 6734    cx.update_editor(|editor, window, cx| {
 6735        editor.add_selection_below(&Default::default(), window, cx);
 6736    });
 6737
 6738    // test multiple cursors expand in the same direction
 6739    cx.assert_editor_state(indoc!(
 6740        r#"line onˇe
 6741           liˇne twˇo
 6742           liˇne three
 6743           line four"#
 6744    ));
 6745
 6746    cx.update_editor(|editor, window, cx| {
 6747        editor.add_selection_below(&Default::default(), window, cx);
 6748    });
 6749
 6750    cx.update_editor(|editor, window, cx| {
 6751        editor.add_selection_below(&Default::default(), window, cx);
 6752    });
 6753
 6754    // test multiple cursors expand below overflow
 6755    cx.assert_editor_state(indoc!(
 6756        r#"line onˇe
 6757           liˇne twˇo
 6758           liˇne thˇree
 6759           liˇne foˇur"#
 6760    ));
 6761
 6762    cx.update_editor(|editor, window, cx| {
 6763        editor.add_selection_above(&Default::default(), window, cx);
 6764    });
 6765
 6766    // test multiple cursors retrieves back correctly
 6767    cx.assert_editor_state(indoc!(
 6768        r#"line onˇe
 6769           liˇne twˇo
 6770           liˇne thˇree
 6771           line four"#
 6772    ));
 6773
 6774    cx.update_editor(|editor, window, cx| {
 6775        editor.add_selection_above(&Default::default(), window, cx);
 6776    });
 6777
 6778    cx.update_editor(|editor, window, cx| {
 6779        editor.add_selection_above(&Default::default(), window, cx);
 6780    });
 6781
 6782    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 6783    cx.assert_editor_state(indoc!(
 6784        r#"liˇne onˇe
 6785           liˇne two
 6786           line three
 6787           line four"#
 6788    ));
 6789
 6790    cx.update_editor(|editor, window, cx| {
 6791        editor.undo_selection(&Default::default(), window, cx);
 6792    });
 6793
 6794    // test undo
 6795    cx.assert_editor_state(indoc!(
 6796        r#"line onˇe
 6797           liˇne twˇo
 6798           line three
 6799           line four"#
 6800    ));
 6801
 6802    cx.update_editor(|editor, window, cx| {
 6803        editor.redo_selection(&Default::default(), window, cx);
 6804    });
 6805
 6806    // test redo
 6807    cx.assert_editor_state(indoc!(
 6808        r#"liˇne onˇe
 6809           liˇne two
 6810           line three
 6811           line four"#
 6812    ));
 6813
 6814    cx.set_state(indoc!(
 6815        r#"abcd
 6816           ef«ghˇ»
 6817           ijkl
 6818           «mˇ»nop"#
 6819    ));
 6820
 6821    cx.update_editor(|editor, window, cx| {
 6822        editor.add_selection_above(&Default::default(), window, cx);
 6823    });
 6824
 6825    // test multiple selections expand in the same direction
 6826    cx.assert_editor_state(indoc!(
 6827        r#"ab«cdˇ»
 6828           ef«ghˇ»
 6829           «iˇ»jkl
 6830           «mˇ»nop"#
 6831    ));
 6832
 6833    cx.update_editor(|editor, window, cx| {
 6834        editor.add_selection_above(&Default::default(), window, cx);
 6835    });
 6836
 6837    // test multiple selection upward overflow
 6838    cx.assert_editor_state(indoc!(
 6839        r#"ab«cdˇ»
 6840           «eˇ»f«ghˇ»
 6841           «iˇ»jkl
 6842           «mˇ»nop"#
 6843    ));
 6844
 6845    cx.update_editor(|editor, window, cx| {
 6846        editor.add_selection_below(&Default::default(), window, cx);
 6847    });
 6848
 6849    // test multiple selection retrieves back correctly
 6850    cx.assert_editor_state(indoc!(
 6851        r#"abcd
 6852           ef«ghˇ»
 6853           «iˇ»jkl
 6854           «mˇ»nop"#
 6855    ));
 6856
 6857    cx.update_editor(|editor, window, cx| {
 6858        editor.add_selection_below(&Default::default(), window, cx);
 6859    });
 6860
 6861    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 6862    cx.assert_editor_state(indoc!(
 6863        r#"abcd
 6864           ef«ghˇ»
 6865           ij«klˇ»
 6866           «mˇ»nop"#
 6867    ));
 6868
 6869    cx.update_editor(|editor, window, cx| {
 6870        editor.undo_selection(&Default::default(), window, cx);
 6871    });
 6872
 6873    // test undo
 6874    cx.assert_editor_state(indoc!(
 6875        r#"abcd
 6876           ef«ghˇ»
 6877           «iˇ»jkl
 6878           «mˇ»nop"#
 6879    ));
 6880
 6881    cx.update_editor(|editor, window, cx| {
 6882        editor.redo_selection(&Default::default(), window, cx);
 6883    });
 6884
 6885    // test redo
 6886    cx.assert_editor_state(indoc!(
 6887        r#"abcd
 6888           ef«ghˇ»
 6889           ij«klˇ»
 6890           «mˇ»nop"#
 6891    ));
 6892}
 6893
 6894#[gpui::test]
 6895async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 6896    init_test(cx, |_| {});
 6897    let mut cx = EditorTestContext::new(cx).await;
 6898
 6899    cx.set_state(indoc!(
 6900        r#"line onˇe
 6901           liˇne two
 6902           line three
 6903           line four"#
 6904    ));
 6905
 6906    cx.update_editor(|editor, window, cx| {
 6907        editor.add_selection_below(&Default::default(), window, cx);
 6908        editor.add_selection_below(&Default::default(), window, cx);
 6909        editor.add_selection_below(&Default::default(), window, cx);
 6910    });
 6911
 6912    // initial state with two multi cursor groups
 6913    cx.assert_editor_state(indoc!(
 6914        r#"line onˇe
 6915           liˇne twˇo
 6916           liˇne thˇree
 6917           liˇne foˇur"#
 6918    ));
 6919
 6920    // add single cursor in middle - simulate opt click
 6921    cx.update_editor(|editor, window, cx| {
 6922        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 6923        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 6924        editor.end_selection(window, cx);
 6925    });
 6926
 6927    cx.assert_editor_state(indoc!(
 6928        r#"line onˇe
 6929           liˇne twˇo
 6930           liˇneˇ thˇree
 6931           liˇne foˇur"#
 6932    ));
 6933
 6934    cx.update_editor(|editor, window, cx| {
 6935        editor.add_selection_above(&Default::default(), window, cx);
 6936    });
 6937
 6938    // test new added selection expands above and existing selection shrinks
 6939    cx.assert_editor_state(indoc!(
 6940        r#"line onˇe
 6941           liˇneˇ twˇo
 6942           liˇneˇ thˇree
 6943           line four"#
 6944    ));
 6945
 6946    cx.update_editor(|editor, window, cx| {
 6947        editor.add_selection_above(&Default::default(), window, cx);
 6948    });
 6949
 6950    // test new added selection expands above and existing selection shrinks
 6951    cx.assert_editor_state(indoc!(
 6952        r#"lineˇ onˇe
 6953           liˇneˇ twˇo
 6954           lineˇ three
 6955           line four"#
 6956    ));
 6957
 6958    // intial state with two selection groups
 6959    cx.set_state(indoc!(
 6960        r#"abcd
 6961           ef«ghˇ»
 6962           ijkl
 6963           «mˇ»nop"#
 6964    ));
 6965
 6966    cx.update_editor(|editor, window, cx| {
 6967        editor.add_selection_above(&Default::default(), window, cx);
 6968        editor.add_selection_above(&Default::default(), window, cx);
 6969    });
 6970
 6971    cx.assert_editor_state(indoc!(
 6972        r#"ab«cdˇ»
 6973           «eˇ»f«ghˇ»
 6974           «iˇ»jkl
 6975           «mˇ»nop"#
 6976    ));
 6977
 6978    // add single selection in middle - simulate opt drag
 6979    cx.update_editor(|editor, window, cx| {
 6980        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 6981        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 6982        editor.update_selection(
 6983            DisplayPoint::new(DisplayRow(2), 4),
 6984            0,
 6985            gpui::Point::<f32>::default(),
 6986            window,
 6987            cx,
 6988        );
 6989        editor.end_selection(window, cx);
 6990    });
 6991
 6992    cx.assert_editor_state(indoc!(
 6993        r#"ab«cdˇ»
 6994           «eˇ»f«ghˇ»
 6995           «iˇ»jk«lˇ»
 6996           «mˇ»nop"#
 6997    ));
 6998
 6999    cx.update_editor(|editor, window, cx| {
 7000        editor.add_selection_below(&Default::default(), window, cx);
 7001    });
 7002
 7003    // test new added selection expands below, others shrinks from above
 7004    cx.assert_editor_state(indoc!(
 7005        r#"abcd
 7006           ef«ghˇ»
 7007           «iˇ»jk«lˇ»
 7008           «mˇ»no«pˇ»"#
 7009    ));
 7010}
 7011
 7012#[gpui::test]
 7013async fn test_select_next(cx: &mut TestAppContext) {
 7014    init_test(cx, |_| {});
 7015
 7016    let mut cx = EditorTestContext::new(cx).await;
 7017    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7018
 7019    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7020        .unwrap();
 7021    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7022
 7023    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7024        .unwrap();
 7025    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 7026
 7027    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7028    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7029
 7030    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7031    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 7032
 7033    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7034        .unwrap();
 7035    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7036
 7037    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7038        .unwrap();
 7039    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7040
 7041    // Test selection direction should be preserved
 7042    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 7043
 7044    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7045        .unwrap();
 7046    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 7047}
 7048
 7049#[gpui::test]
 7050async fn test_select_all_matches(cx: &mut TestAppContext) {
 7051    init_test(cx, |_| {});
 7052
 7053    let mut cx = EditorTestContext::new(cx).await;
 7054
 7055    // Test caret-only selections
 7056    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7057    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7058        .unwrap();
 7059    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7060
 7061    // Test left-to-right selections
 7062    cx.set_state("abc\n«abcˇ»\nabc");
 7063    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7064        .unwrap();
 7065    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 7066
 7067    // Test right-to-left selections
 7068    cx.set_state("abc\n«ˇabc»\nabc");
 7069    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7070        .unwrap();
 7071    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 7072
 7073    // Test selecting whitespace with caret selection
 7074    cx.set_state("abc\nˇ   abc\nabc");
 7075    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7076        .unwrap();
 7077    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 7078
 7079    // Test selecting whitespace with left-to-right selection
 7080    cx.set_state("abc\n«ˇ  »abc\nabc");
 7081    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7082        .unwrap();
 7083    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 7084
 7085    // Test no matches with right-to-left selection
 7086    cx.set_state("abc\n«  ˇ»abc\nabc");
 7087    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7088        .unwrap();
 7089    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 7090
 7091    // Test with a single word and clip_at_line_ends=true (#29823)
 7092    cx.set_state("aˇbc");
 7093    cx.update_editor(|e, window, cx| {
 7094        e.set_clip_at_line_ends(true, cx);
 7095        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 7096        e.set_clip_at_line_ends(false, cx);
 7097    });
 7098    cx.assert_editor_state("«abcˇ»");
 7099}
 7100
 7101#[gpui::test]
 7102async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 7103    init_test(cx, |_| {});
 7104
 7105    let mut cx = EditorTestContext::new(cx).await;
 7106
 7107    let large_body_1 = "\nd".repeat(200);
 7108    let large_body_2 = "\ne".repeat(200);
 7109
 7110    cx.set_state(&format!(
 7111        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 7112    ));
 7113    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 7114        let scroll_position = editor.scroll_position(cx);
 7115        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 7116        scroll_position
 7117    });
 7118
 7119    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7120        .unwrap();
 7121    cx.assert_editor_state(&format!(
 7122        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 7123    ));
 7124    let scroll_position_after_selection =
 7125        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 7126    assert_eq!(
 7127        initial_scroll_position, scroll_position_after_selection,
 7128        "Scroll position should not change after selecting all matches"
 7129    );
 7130}
 7131
 7132#[gpui::test]
 7133async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 7134    init_test(cx, |_| {});
 7135
 7136    let mut cx = EditorLspTestContext::new_rust(
 7137        lsp::ServerCapabilities {
 7138            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 7139            ..Default::default()
 7140        },
 7141        cx,
 7142    )
 7143    .await;
 7144
 7145    cx.set_state(indoc! {"
 7146        line 1
 7147        line 2
 7148        linˇe 3
 7149        line 4
 7150        line 5
 7151    "});
 7152
 7153    // Make an edit
 7154    cx.update_editor(|editor, window, cx| {
 7155        editor.handle_input("X", window, cx);
 7156    });
 7157
 7158    // Move cursor to a different position
 7159    cx.update_editor(|editor, window, cx| {
 7160        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7161            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 7162        });
 7163    });
 7164
 7165    cx.assert_editor_state(indoc! {"
 7166        line 1
 7167        line 2
 7168        linXe 3
 7169        line 4
 7170        liˇne 5
 7171    "});
 7172
 7173    cx.lsp
 7174        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 7175            Ok(Some(vec![lsp::TextEdit::new(
 7176                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 7177                "PREFIX ".to_string(),
 7178            )]))
 7179        });
 7180
 7181    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 7182        .unwrap()
 7183        .await
 7184        .unwrap();
 7185
 7186    cx.assert_editor_state(indoc! {"
 7187        PREFIX line 1
 7188        line 2
 7189        linXe 3
 7190        line 4
 7191        liˇne 5
 7192    "});
 7193
 7194    // Undo formatting
 7195    cx.update_editor(|editor, window, cx| {
 7196        editor.undo(&Default::default(), window, cx);
 7197    });
 7198
 7199    // Verify cursor moved back to position after edit
 7200    cx.assert_editor_state(indoc! {"
 7201        line 1
 7202        line 2
 7203        linXˇe 3
 7204        line 4
 7205        line 5
 7206    "});
 7207}
 7208
 7209#[gpui::test]
 7210async fn test_undo_inline_completion_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 7211    init_test(cx, |_| {});
 7212
 7213    let mut cx = EditorTestContext::new(cx).await;
 7214
 7215    let provider = cx.new(|_| FakeInlineCompletionProvider::default());
 7216    cx.update_editor(|editor, window, cx| {
 7217        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 7218    });
 7219
 7220    cx.set_state(indoc! {"
 7221        line 1
 7222        line 2
 7223        linˇe 3
 7224        line 4
 7225        line 5
 7226        line 6
 7227        line 7
 7228        line 8
 7229        line 9
 7230        line 10
 7231    "});
 7232
 7233    let snapshot = cx.buffer_snapshot();
 7234    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 7235
 7236    cx.update(|_, cx| {
 7237        provider.update(cx, |provider, _| {
 7238            provider.set_inline_completion(Some(inline_completion::InlineCompletion {
 7239                id: None,
 7240                edits: vec![(edit_position..edit_position, "X".into())],
 7241                edit_preview: None,
 7242            }))
 7243        })
 7244    });
 7245
 7246    cx.update_editor(|editor, window, cx| editor.update_visible_inline_completion(window, cx));
 7247    cx.update_editor(|editor, window, cx| {
 7248        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 7249    });
 7250
 7251    cx.assert_editor_state(indoc! {"
 7252        line 1
 7253        line 2
 7254        lineXˇ 3
 7255        line 4
 7256        line 5
 7257        line 6
 7258        line 7
 7259        line 8
 7260        line 9
 7261        line 10
 7262    "});
 7263
 7264    cx.update_editor(|editor, window, cx| {
 7265        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7266            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 7267        });
 7268    });
 7269
 7270    cx.assert_editor_state(indoc! {"
 7271        line 1
 7272        line 2
 7273        lineX 3
 7274        line 4
 7275        line 5
 7276        line 6
 7277        line 7
 7278        line 8
 7279        line 9
 7280        liˇne 10
 7281    "});
 7282
 7283    cx.update_editor(|editor, window, cx| {
 7284        editor.undo(&Default::default(), window, cx);
 7285    });
 7286
 7287    cx.assert_editor_state(indoc! {"
 7288        line 1
 7289        line 2
 7290        lineˇ 3
 7291        line 4
 7292        line 5
 7293        line 6
 7294        line 7
 7295        line 8
 7296        line 9
 7297        line 10
 7298    "});
 7299}
 7300
 7301#[gpui::test]
 7302async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 7303    init_test(cx, |_| {});
 7304
 7305    let mut cx = EditorTestContext::new(cx).await;
 7306    cx.set_state(
 7307        r#"let foo = 2;
 7308lˇet foo = 2;
 7309let fooˇ = 2;
 7310let foo = 2;
 7311let foo = ˇ2;"#,
 7312    );
 7313
 7314    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7315        .unwrap();
 7316    cx.assert_editor_state(
 7317        r#"let foo = 2;
 7318«letˇ» foo = 2;
 7319let «fooˇ» = 2;
 7320let foo = 2;
 7321let foo = «2ˇ»;"#,
 7322    );
 7323
 7324    // noop for multiple selections with different contents
 7325    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7326        .unwrap();
 7327    cx.assert_editor_state(
 7328        r#"let foo = 2;
 7329«letˇ» foo = 2;
 7330let «fooˇ» = 2;
 7331let foo = 2;
 7332let foo = «2ˇ»;"#,
 7333    );
 7334
 7335    // Test last selection direction should be preserved
 7336    cx.set_state(
 7337        r#"let foo = 2;
 7338let foo = 2;
 7339let «fooˇ» = 2;
 7340let «ˇfoo» = 2;
 7341let foo = 2;"#,
 7342    );
 7343
 7344    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7345        .unwrap();
 7346    cx.assert_editor_state(
 7347        r#"let foo = 2;
 7348let foo = 2;
 7349let «fooˇ» = 2;
 7350let «ˇfoo» = 2;
 7351let «ˇfoo» = 2;"#,
 7352    );
 7353}
 7354
 7355#[gpui::test]
 7356async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 7357    init_test(cx, |_| {});
 7358
 7359    let mut cx =
 7360        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 7361
 7362    cx.assert_editor_state(indoc! {"
 7363        ˇbbb
 7364        ccc
 7365
 7366        bbb
 7367        ccc
 7368        "});
 7369    cx.dispatch_action(SelectPrevious::default());
 7370    cx.assert_editor_state(indoc! {"
 7371                «bbbˇ»
 7372                ccc
 7373
 7374                bbb
 7375                ccc
 7376                "});
 7377    cx.dispatch_action(SelectPrevious::default());
 7378    cx.assert_editor_state(indoc! {"
 7379                «bbbˇ»
 7380                ccc
 7381
 7382                «bbbˇ»
 7383                ccc
 7384                "});
 7385}
 7386
 7387#[gpui::test]
 7388async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 7389    init_test(cx, |_| {});
 7390
 7391    let mut cx = EditorTestContext::new(cx).await;
 7392    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7393
 7394    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7395        .unwrap();
 7396    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7397
 7398    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7399        .unwrap();
 7400    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 7401
 7402    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7403    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7404
 7405    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7406    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 7407
 7408    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7409        .unwrap();
 7410    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 7411
 7412    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7413        .unwrap();
 7414    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7415}
 7416
 7417#[gpui::test]
 7418async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 7419    init_test(cx, |_| {});
 7420
 7421    let mut cx = EditorTestContext::new(cx).await;
 7422    cx.set_state("");
 7423
 7424    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7425        .unwrap();
 7426    cx.assert_editor_state("«aˇ»");
 7427    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7428        .unwrap();
 7429    cx.assert_editor_state("«aˇ»");
 7430}
 7431
 7432#[gpui::test]
 7433async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 7434    init_test(cx, |_| {});
 7435
 7436    let mut cx = EditorTestContext::new(cx).await;
 7437    cx.set_state(
 7438        r#"let foo = 2;
 7439lˇet foo = 2;
 7440let fooˇ = 2;
 7441let foo = 2;
 7442let foo = ˇ2;"#,
 7443    );
 7444
 7445    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7446        .unwrap();
 7447    cx.assert_editor_state(
 7448        r#"let foo = 2;
 7449«letˇ» foo = 2;
 7450let «fooˇ» = 2;
 7451let foo = 2;
 7452let foo = «2ˇ»;"#,
 7453    );
 7454
 7455    // noop for multiple selections with different contents
 7456    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7457        .unwrap();
 7458    cx.assert_editor_state(
 7459        r#"let foo = 2;
 7460«letˇ» foo = 2;
 7461let «fooˇ» = 2;
 7462let foo = 2;
 7463let foo = «2ˇ»;"#,
 7464    );
 7465}
 7466
 7467#[gpui::test]
 7468async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 7469    init_test(cx, |_| {});
 7470
 7471    let mut cx = EditorTestContext::new(cx).await;
 7472    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 7473
 7474    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7475        .unwrap();
 7476    // selection direction is preserved
 7477    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 7478
 7479    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7480        .unwrap();
 7481    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7482
 7483    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7484    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 7485
 7486    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7487    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7488
 7489    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7490        .unwrap();
 7491    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 7492
 7493    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7494        .unwrap();
 7495    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 7496}
 7497
 7498#[gpui::test]
 7499async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 7500    init_test(cx, |_| {});
 7501
 7502    let language = Arc::new(Language::new(
 7503        LanguageConfig::default(),
 7504        Some(tree_sitter_rust::LANGUAGE.into()),
 7505    ));
 7506
 7507    let text = r#"
 7508        use mod1::mod2::{mod3, mod4};
 7509
 7510        fn fn_1(param1: bool, param2: &str) {
 7511            let var1 = "text";
 7512        }
 7513    "#
 7514    .unindent();
 7515
 7516    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7517    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7518    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7519
 7520    editor
 7521        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7522        .await;
 7523
 7524    editor.update_in(cx, |editor, window, cx| {
 7525        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7526            s.select_display_ranges([
 7527                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 7528                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 7529                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 7530            ]);
 7531        });
 7532        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7533    });
 7534    editor.update(cx, |editor, cx| {
 7535        assert_text_with_selections(
 7536            editor,
 7537            indoc! {r#"
 7538                use mod1::mod2::{mod3, «mod4ˇ»};
 7539
 7540                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7541                    let var1 = "«ˇtext»";
 7542                }
 7543            "#},
 7544            cx,
 7545        );
 7546    });
 7547
 7548    editor.update_in(cx, |editor, window, cx| {
 7549        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7550    });
 7551    editor.update(cx, |editor, cx| {
 7552        assert_text_with_selections(
 7553            editor,
 7554            indoc! {r#"
 7555                use mod1::mod2::«{mod3, mod4}ˇ»;
 7556
 7557                «ˇfn fn_1(param1: bool, param2: &str) {
 7558                    let var1 = "text";
 7559 7560            "#},
 7561            cx,
 7562        );
 7563    });
 7564
 7565    editor.update_in(cx, |editor, window, cx| {
 7566        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7567    });
 7568    assert_eq!(
 7569        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7570        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7571    );
 7572
 7573    // Trying to expand the selected syntax node one more time has no effect.
 7574    editor.update_in(cx, |editor, window, cx| {
 7575        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7576    });
 7577    assert_eq!(
 7578        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7579        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7580    );
 7581
 7582    editor.update_in(cx, |editor, window, cx| {
 7583        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7584    });
 7585    editor.update(cx, |editor, cx| {
 7586        assert_text_with_selections(
 7587            editor,
 7588            indoc! {r#"
 7589                use mod1::mod2::«{mod3, mod4}ˇ»;
 7590
 7591                «ˇfn fn_1(param1: bool, param2: &str) {
 7592                    let var1 = "text";
 7593 7594            "#},
 7595            cx,
 7596        );
 7597    });
 7598
 7599    editor.update_in(cx, |editor, window, cx| {
 7600        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7601    });
 7602    editor.update(cx, |editor, cx| {
 7603        assert_text_with_selections(
 7604            editor,
 7605            indoc! {r#"
 7606                use mod1::mod2::{mod3, «mod4ˇ»};
 7607
 7608                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7609                    let var1 = "«ˇtext»";
 7610                }
 7611            "#},
 7612            cx,
 7613        );
 7614    });
 7615
 7616    editor.update_in(cx, |editor, window, cx| {
 7617        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7618    });
 7619    editor.update(cx, |editor, cx| {
 7620        assert_text_with_selections(
 7621            editor,
 7622            indoc! {r#"
 7623                use mod1::mod2::{mod3, mo«ˇ»d4};
 7624
 7625                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7626                    let var1 = "te«ˇ»xt";
 7627                }
 7628            "#},
 7629            cx,
 7630        );
 7631    });
 7632
 7633    // Trying to shrink the selected syntax node one more time has no effect.
 7634    editor.update_in(cx, |editor, window, cx| {
 7635        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7636    });
 7637    editor.update_in(cx, |editor, _, cx| {
 7638        assert_text_with_selections(
 7639            editor,
 7640            indoc! {r#"
 7641                use mod1::mod2::{mod3, mo«ˇ»d4};
 7642
 7643                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7644                    let var1 = "te«ˇ»xt";
 7645                }
 7646            "#},
 7647            cx,
 7648        );
 7649    });
 7650
 7651    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 7652    // a fold.
 7653    editor.update_in(cx, |editor, window, cx| {
 7654        editor.fold_creases(
 7655            vec![
 7656                Crease::simple(
 7657                    Point::new(0, 21)..Point::new(0, 24),
 7658                    FoldPlaceholder::test(),
 7659                ),
 7660                Crease::simple(
 7661                    Point::new(3, 20)..Point::new(3, 22),
 7662                    FoldPlaceholder::test(),
 7663                ),
 7664            ],
 7665            true,
 7666            window,
 7667            cx,
 7668        );
 7669        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7670    });
 7671    editor.update(cx, |editor, cx| {
 7672        assert_text_with_selections(
 7673            editor,
 7674            indoc! {r#"
 7675                use mod1::mod2::«{mod3, mod4}ˇ»;
 7676
 7677                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7678                    let var1 = "«ˇtext»";
 7679                }
 7680            "#},
 7681            cx,
 7682        );
 7683    });
 7684}
 7685
 7686#[gpui::test]
 7687async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 7688    init_test(cx, |_| {});
 7689
 7690    let language = Arc::new(Language::new(
 7691        LanguageConfig::default(),
 7692        Some(tree_sitter_rust::LANGUAGE.into()),
 7693    ));
 7694
 7695    let text = "let a = 2;";
 7696
 7697    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7698    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7699    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7700
 7701    editor
 7702        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7703        .await;
 7704
 7705    // Test case 1: Cursor at end of word
 7706    editor.update_in(cx, |editor, window, cx| {
 7707        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7708            s.select_display_ranges([
 7709                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 7710            ]);
 7711        });
 7712    });
 7713    editor.update(cx, |editor, cx| {
 7714        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 7715    });
 7716    editor.update_in(cx, |editor, window, cx| {
 7717        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7718    });
 7719    editor.update(cx, |editor, cx| {
 7720        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 7721    });
 7722    editor.update_in(cx, |editor, window, cx| {
 7723        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7724    });
 7725    editor.update(cx, |editor, cx| {
 7726        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7727    });
 7728
 7729    // Test case 2: Cursor at end of statement
 7730    editor.update_in(cx, |editor, window, cx| {
 7731        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7732            s.select_display_ranges([
 7733                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 7734            ]);
 7735        });
 7736    });
 7737    editor.update(cx, |editor, cx| {
 7738        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 7739    });
 7740    editor.update_in(cx, |editor, window, cx| {
 7741        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7742    });
 7743    editor.update(cx, |editor, cx| {
 7744        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7745    });
 7746}
 7747
 7748#[gpui::test]
 7749async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 7750    init_test(cx, |_| {});
 7751
 7752    let language = Arc::new(Language::new(
 7753        LanguageConfig::default(),
 7754        Some(tree_sitter_rust::LANGUAGE.into()),
 7755    ));
 7756
 7757    let text = r#"
 7758        use mod1::mod2::{mod3, mod4};
 7759
 7760        fn fn_1(param1: bool, param2: &str) {
 7761            let var1 = "hello world";
 7762        }
 7763    "#
 7764    .unindent();
 7765
 7766    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7767    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7768    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7769
 7770    editor
 7771        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7772        .await;
 7773
 7774    // Test 1: Cursor on a letter of a string word
 7775    editor.update_in(cx, |editor, window, cx| {
 7776        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7777            s.select_display_ranges([
 7778                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 7779            ]);
 7780        });
 7781    });
 7782    editor.update_in(cx, |editor, window, cx| {
 7783        assert_text_with_selections(
 7784            editor,
 7785            indoc! {r#"
 7786                use mod1::mod2::{mod3, mod4};
 7787
 7788                fn fn_1(param1: bool, param2: &str) {
 7789                    let var1 = "hˇello world";
 7790                }
 7791            "#},
 7792            cx,
 7793        );
 7794        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7795        assert_text_with_selections(
 7796            editor,
 7797            indoc! {r#"
 7798                use mod1::mod2::{mod3, mod4};
 7799
 7800                fn fn_1(param1: bool, param2: &str) {
 7801                    let var1 = "«ˇhello» world";
 7802                }
 7803            "#},
 7804            cx,
 7805        );
 7806    });
 7807
 7808    // Test 2: Partial selection within a word
 7809    editor.update_in(cx, |editor, window, cx| {
 7810        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7811            s.select_display_ranges([
 7812                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 7813            ]);
 7814        });
 7815    });
 7816    editor.update_in(cx, |editor, window, cx| {
 7817        assert_text_with_selections(
 7818            editor,
 7819            indoc! {r#"
 7820                use mod1::mod2::{mod3, mod4};
 7821
 7822                fn fn_1(param1: bool, param2: &str) {
 7823                    let var1 = "h«elˇ»lo world";
 7824                }
 7825            "#},
 7826            cx,
 7827        );
 7828        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7829        assert_text_with_selections(
 7830            editor,
 7831            indoc! {r#"
 7832                use mod1::mod2::{mod3, mod4};
 7833
 7834                fn fn_1(param1: bool, param2: &str) {
 7835                    let var1 = "«ˇhello» world";
 7836                }
 7837            "#},
 7838            cx,
 7839        );
 7840    });
 7841
 7842    // Test 3: Complete word already selected
 7843    editor.update_in(cx, |editor, window, cx| {
 7844        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7845            s.select_display_ranges([
 7846                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 7847            ]);
 7848        });
 7849    });
 7850    editor.update_in(cx, |editor, window, cx| {
 7851        assert_text_with_selections(
 7852            editor,
 7853            indoc! {r#"
 7854                use mod1::mod2::{mod3, mod4};
 7855
 7856                fn fn_1(param1: bool, param2: &str) {
 7857                    let var1 = "«helloˇ» world";
 7858                }
 7859            "#},
 7860            cx,
 7861        );
 7862        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7863        assert_text_with_selections(
 7864            editor,
 7865            indoc! {r#"
 7866                use mod1::mod2::{mod3, mod4};
 7867
 7868                fn fn_1(param1: bool, param2: &str) {
 7869                    let var1 = "«hello worldˇ»";
 7870                }
 7871            "#},
 7872            cx,
 7873        );
 7874    });
 7875
 7876    // Test 4: Selection spanning across words
 7877    editor.update_in(cx, |editor, window, cx| {
 7878        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7879            s.select_display_ranges([
 7880                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 7881            ]);
 7882        });
 7883    });
 7884    editor.update_in(cx, |editor, window, cx| {
 7885        assert_text_with_selections(
 7886            editor,
 7887            indoc! {r#"
 7888                use mod1::mod2::{mod3, mod4};
 7889
 7890                fn fn_1(param1: bool, param2: &str) {
 7891                    let var1 = "hel«lo woˇ»rld";
 7892                }
 7893            "#},
 7894            cx,
 7895        );
 7896        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7897        assert_text_with_selections(
 7898            editor,
 7899            indoc! {r#"
 7900                use mod1::mod2::{mod3, mod4};
 7901
 7902                fn fn_1(param1: bool, param2: &str) {
 7903                    let var1 = "«ˇhello world»";
 7904                }
 7905            "#},
 7906            cx,
 7907        );
 7908    });
 7909
 7910    // Test 5: Expansion beyond string
 7911    editor.update_in(cx, |editor, window, cx| {
 7912        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7913        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7914        assert_text_with_selections(
 7915            editor,
 7916            indoc! {r#"
 7917                use mod1::mod2::{mod3, mod4};
 7918
 7919                fn fn_1(param1: bool, param2: &str) {
 7920                    «ˇlet var1 = "hello world";»
 7921                }
 7922            "#},
 7923            cx,
 7924        );
 7925    });
 7926}
 7927
 7928#[gpui::test]
 7929async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 7930    init_test(cx, |_| {});
 7931
 7932    let base_text = r#"
 7933        impl A {
 7934            // this is an uncommitted comment
 7935
 7936            fn b() {
 7937                c();
 7938            }
 7939
 7940            // this is another uncommitted comment
 7941
 7942            fn d() {
 7943                // e
 7944                // f
 7945            }
 7946        }
 7947
 7948        fn g() {
 7949            // h
 7950        }
 7951    "#
 7952    .unindent();
 7953
 7954    let text = r#"
 7955        ˇimpl A {
 7956
 7957            fn b() {
 7958                c();
 7959            }
 7960
 7961            fn d() {
 7962                // e
 7963                // f
 7964            }
 7965        }
 7966
 7967        fn g() {
 7968            // h
 7969        }
 7970    "#
 7971    .unindent();
 7972
 7973    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 7974    cx.set_state(&text);
 7975    cx.set_head_text(&base_text);
 7976    cx.update_editor(|editor, window, cx| {
 7977        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 7978    });
 7979
 7980    cx.assert_state_with_diff(
 7981        "
 7982        ˇimpl A {
 7983      -     // this is an uncommitted comment
 7984
 7985            fn b() {
 7986                c();
 7987            }
 7988
 7989      -     // this is another uncommitted comment
 7990      -
 7991            fn d() {
 7992                // e
 7993                // f
 7994            }
 7995        }
 7996
 7997        fn g() {
 7998            // h
 7999        }
 8000    "
 8001        .unindent(),
 8002    );
 8003
 8004    let expected_display_text = "
 8005        impl A {
 8006            // this is an uncommitted comment
 8007
 8008            fn b() {
 8009 8010            }
 8011
 8012            // this is another uncommitted comment
 8013
 8014            fn d() {
 8015 8016            }
 8017        }
 8018
 8019        fn g() {
 8020 8021        }
 8022        "
 8023    .unindent();
 8024
 8025    cx.update_editor(|editor, window, cx| {
 8026        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 8027        assert_eq!(editor.display_text(cx), expected_display_text);
 8028    });
 8029}
 8030
 8031#[gpui::test]
 8032async fn test_autoindent(cx: &mut TestAppContext) {
 8033    init_test(cx, |_| {});
 8034
 8035    let language = Arc::new(
 8036        Language::new(
 8037            LanguageConfig {
 8038                brackets: BracketPairConfig {
 8039                    pairs: vec![
 8040                        BracketPair {
 8041                            start: "{".to_string(),
 8042                            end: "}".to_string(),
 8043                            close: false,
 8044                            surround: false,
 8045                            newline: true,
 8046                        },
 8047                        BracketPair {
 8048                            start: "(".to_string(),
 8049                            end: ")".to_string(),
 8050                            close: false,
 8051                            surround: false,
 8052                            newline: true,
 8053                        },
 8054                    ],
 8055                    ..Default::default()
 8056                },
 8057                ..Default::default()
 8058            },
 8059            Some(tree_sitter_rust::LANGUAGE.into()),
 8060        )
 8061        .with_indents_query(
 8062            r#"
 8063                (_ "(" ")" @end) @indent
 8064                (_ "{" "}" @end) @indent
 8065            "#,
 8066        )
 8067        .unwrap(),
 8068    );
 8069
 8070    let text = "fn a() {}";
 8071
 8072    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8073    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8074    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8075    editor
 8076        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8077        .await;
 8078
 8079    editor.update_in(cx, |editor, window, cx| {
 8080        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8081            s.select_ranges([5..5, 8..8, 9..9])
 8082        });
 8083        editor.newline(&Newline, window, cx);
 8084        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 8085        assert_eq!(
 8086            editor.selections.ranges(cx),
 8087            &[
 8088                Point::new(1, 4)..Point::new(1, 4),
 8089                Point::new(3, 4)..Point::new(3, 4),
 8090                Point::new(5, 0)..Point::new(5, 0)
 8091            ]
 8092        );
 8093    });
 8094}
 8095
 8096#[gpui::test]
 8097async fn test_autoindent_selections(cx: &mut TestAppContext) {
 8098    init_test(cx, |_| {});
 8099
 8100    {
 8101        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 8102        cx.set_state(indoc! {"
 8103            impl A {
 8104
 8105                fn b() {}
 8106
 8107            «fn c() {
 8108
 8109            }ˇ»
 8110            }
 8111        "});
 8112
 8113        cx.update_editor(|editor, window, cx| {
 8114            editor.autoindent(&Default::default(), window, cx);
 8115        });
 8116
 8117        cx.assert_editor_state(indoc! {"
 8118            impl A {
 8119
 8120                fn b() {}
 8121
 8122                «fn c() {
 8123
 8124                }ˇ»
 8125            }
 8126        "});
 8127    }
 8128
 8129    {
 8130        let mut cx = EditorTestContext::new_multibuffer(
 8131            cx,
 8132            [indoc! { "
 8133                impl A {
 8134                «
 8135                // a
 8136                fn b(){}
 8137                »
 8138                «
 8139                    }
 8140                    fn c(){}
 8141                »
 8142            "}],
 8143        );
 8144
 8145        let buffer = cx.update_editor(|editor, _, cx| {
 8146            let buffer = editor.buffer().update(cx, |buffer, _| {
 8147                buffer.all_buffers().iter().next().unwrap().clone()
 8148            });
 8149            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 8150            buffer
 8151        });
 8152
 8153        cx.run_until_parked();
 8154        cx.update_editor(|editor, window, cx| {
 8155            editor.select_all(&Default::default(), window, cx);
 8156            editor.autoindent(&Default::default(), window, cx)
 8157        });
 8158        cx.run_until_parked();
 8159
 8160        cx.update(|_, cx| {
 8161            assert_eq!(
 8162                buffer.read(cx).text(),
 8163                indoc! { "
 8164                    impl A {
 8165
 8166                        // a
 8167                        fn b(){}
 8168
 8169
 8170                    }
 8171                    fn c(){}
 8172
 8173                " }
 8174            )
 8175        });
 8176    }
 8177}
 8178
 8179#[gpui::test]
 8180async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 8181    init_test(cx, |_| {});
 8182
 8183    let mut cx = EditorTestContext::new(cx).await;
 8184
 8185    let language = Arc::new(Language::new(
 8186        LanguageConfig {
 8187            brackets: BracketPairConfig {
 8188                pairs: vec![
 8189                    BracketPair {
 8190                        start: "{".to_string(),
 8191                        end: "}".to_string(),
 8192                        close: true,
 8193                        surround: true,
 8194                        newline: true,
 8195                    },
 8196                    BracketPair {
 8197                        start: "(".to_string(),
 8198                        end: ")".to_string(),
 8199                        close: true,
 8200                        surround: true,
 8201                        newline: true,
 8202                    },
 8203                    BracketPair {
 8204                        start: "/*".to_string(),
 8205                        end: " */".to_string(),
 8206                        close: true,
 8207                        surround: true,
 8208                        newline: true,
 8209                    },
 8210                    BracketPair {
 8211                        start: "[".to_string(),
 8212                        end: "]".to_string(),
 8213                        close: false,
 8214                        surround: false,
 8215                        newline: true,
 8216                    },
 8217                    BracketPair {
 8218                        start: "\"".to_string(),
 8219                        end: "\"".to_string(),
 8220                        close: true,
 8221                        surround: true,
 8222                        newline: false,
 8223                    },
 8224                    BracketPair {
 8225                        start: "<".to_string(),
 8226                        end: ">".to_string(),
 8227                        close: false,
 8228                        surround: true,
 8229                        newline: true,
 8230                    },
 8231                ],
 8232                ..Default::default()
 8233            },
 8234            autoclose_before: "})]".to_string(),
 8235            ..Default::default()
 8236        },
 8237        Some(tree_sitter_rust::LANGUAGE.into()),
 8238    ));
 8239
 8240    cx.language_registry().add(language.clone());
 8241    cx.update_buffer(|buffer, cx| {
 8242        buffer.set_language(Some(language), cx);
 8243    });
 8244
 8245    cx.set_state(
 8246        &r#"
 8247            🏀ˇ
 8248            εˇ
 8249            ❤️ˇ
 8250        "#
 8251        .unindent(),
 8252    );
 8253
 8254    // autoclose multiple nested brackets at multiple cursors
 8255    cx.update_editor(|editor, window, cx| {
 8256        editor.handle_input("{", window, cx);
 8257        editor.handle_input("{", window, cx);
 8258        editor.handle_input("{", window, cx);
 8259    });
 8260    cx.assert_editor_state(
 8261        &"
 8262            🏀{{{ˇ}}}
 8263            ε{{{ˇ}}}
 8264            ❤️{{{ˇ}}}
 8265        "
 8266        .unindent(),
 8267    );
 8268
 8269    // insert a different closing bracket
 8270    cx.update_editor(|editor, window, cx| {
 8271        editor.handle_input(")", window, cx);
 8272    });
 8273    cx.assert_editor_state(
 8274        &"
 8275            🏀{{{)ˇ}}}
 8276            ε{{{)ˇ}}}
 8277            ❤️{{{)ˇ}}}
 8278        "
 8279        .unindent(),
 8280    );
 8281
 8282    // skip over the auto-closed brackets when typing a closing bracket
 8283    cx.update_editor(|editor, window, cx| {
 8284        editor.move_right(&MoveRight, window, cx);
 8285        editor.handle_input("}", window, cx);
 8286        editor.handle_input("}", window, cx);
 8287        editor.handle_input("}", window, cx);
 8288    });
 8289    cx.assert_editor_state(
 8290        &"
 8291            🏀{{{)}}}}ˇ
 8292            ε{{{)}}}}ˇ
 8293            ❤️{{{)}}}}ˇ
 8294        "
 8295        .unindent(),
 8296    );
 8297
 8298    // autoclose multi-character pairs
 8299    cx.set_state(
 8300        &"
 8301            ˇ
 8302            ˇ
 8303        "
 8304        .unindent(),
 8305    );
 8306    cx.update_editor(|editor, window, cx| {
 8307        editor.handle_input("/", window, cx);
 8308        editor.handle_input("*", window, cx);
 8309    });
 8310    cx.assert_editor_state(
 8311        &"
 8312            /*ˇ */
 8313            /*ˇ */
 8314        "
 8315        .unindent(),
 8316    );
 8317
 8318    // one cursor autocloses a multi-character pair, one cursor
 8319    // does not autoclose.
 8320    cx.set_state(
 8321        &"
 8322 8323            ˇ
 8324        "
 8325        .unindent(),
 8326    );
 8327    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 8328    cx.assert_editor_state(
 8329        &"
 8330            /*ˇ */
 8331 8332        "
 8333        .unindent(),
 8334    );
 8335
 8336    // Don't autoclose if the next character isn't whitespace and isn't
 8337    // listed in the language's "autoclose_before" section.
 8338    cx.set_state("ˇa b");
 8339    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8340    cx.assert_editor_state("{ˇa b");
 8341
 8342    // Don't autoclose if `close` is false for the bracket pair
 8343    cx.set_state("ˇ");
 8344    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 8345    cx.assert_editor_state("");
 8346
 8347    // Surround with brackets if text is selected
 8348    cx.set_state("«aˇ» b");
 8349    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8350    cx.assert_editor_state("{«aˇ»} b");
 8351
 8352    // Autoclose when not immediately after a word character
 8353    cx.set_state("a ˇ");
 8354    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8355    cx.assert_editor_state("a \"ˇ\"");
 8356
 8357    // Autoclose pair where the start and end characters are the same
 8358    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8359    cx.assert_editor_state("a \"\"ˇ");
 8360
 8361    // Don't autoclose when immediately after a word character
 8362    cx.set_state("");
 8363    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8364    cx.assert_editor_state("a\"ˇ");
 8365
 8366    // Do autoclose when after a non-word character
 8367    cx.set_state("");
 8368    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8369    cx.assert_editor_state("{\"ˇ\"");
 8370
 8371    // Non identical pairs autoclose regardless of preceding character
 8372    cx.set_state("");
 8373    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8374    cx.assert_editor_state("a{ˇ}");
 8375
 8376    // Don't autoclose pair if autoclose is disabled
 8377    cx.set_state("ˇ");
 8378    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 8379    cx.assert_editor_state("");
 8380
 8381    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 8382    cx.set_state("«aˇ» b");
 8383    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 8384    cx.assert_editor_state("<«aˇ»> b");
 8385}
 8386
 8387#[gpui::test]
 8388async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 8389    init_test(cx, |settings| {
 8390        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 8391    });
 8392
 8393    let mut cx = EditorTestContext::new(cx).await;
 8394
 8395    let language = Arc::new(Language::new(
 8396        LanguageConfig {
 8397            brackets: BracketPairConfig {
 8398                pairs: vec![
 8399                    BracketPair {
 8400                        start: "{".to_string(),
 8401                        end: "}".to_string(),
 8402                        close: true,
 8403                        surround: true,
 8404                        newline: true,
 8405                    },
 8406                    BracketPair {
 8407                        start: "(".to_string(),
 8408                        end: ")".to_string(),
 8409                        close: true,
 8410                        surround: true,
 8411                        newline: true,
 8412                    },
 8413                    BracketPair {
 8414                        start: "[".to_string(),
 8415                        end: "]".to_string(),
 8416                        close: false,
 8417                        surround: false,
 8418                        newline: true,
 8419                    },
 8420                ],
 8421                ..Default::default()
 8422            },
 8423            autoclose_before: "})]".to_string(),
 8424            ..Default::default()
 8425        },
 8426        Some(tree_sitter_rust::LANGUAGE.into()),
 8427    ));
 8428
 8429    cx.language_registry().add(language.clone());
 8430    cx.update_buffer(|buffer, cx| {
 8431        buffer.set_language(Some(language), cx);
 8432    });
 8433
 8434    cx.set_state(
 8435        &"
 8436            ˇ
 8437            ˇ
 8438            ˇ
 8439        "
 8440        .unindent(),
 8441    );
 8442
 8443    // ensure only matching closing brackets are skipped over
 8444    cx.update_editor(|editor, window, cx| {
 8445        editor.handle_input("}", window, cx);
 8446        editor.move_left(&MoveLeft, window, cx);
 8447        editor.handle_input(")", window, cx);
 8448        editor.move_left(&MoveLeft, window, cx);
 8449    });
 8450    cx.assert_editor_state(
 8451        &"
 8452            ˇ)}
 8453            ˇ)}
 8454            ˇ)}
 8455        "
 8456        .unindent(),
 8457    );
 8458
 8459    // skip-over closing brackets at multiple cursors
 8460    cx.update_editor(|editor, window, cx| {
 8461        editor.handle_input(")", window, cx);
 8462        editor.handle_input("}", window, cx);
 8463    });
 8464    cx.assert_editor_state(
 8465        &"
 8466            )}ˇ
 8467            )}ˇ
 8468            )}ˇ
 8469        "
 8470        .unindent(),
 8471    );
 8472
 8473    // ignore non-close brackets
 8474    cx.update_editor(|editor, window, cx| {
 8475        editor.handle_input("]", window, cx);
 8476        editor.move_left(&MoveLeft, window, cx);
 8477        editor.handle_input("]", window, cx);
 8478    });
 8479    cx.assert_editor_state(
 8480        &"
 8481            )}]ˇ]
 8482            )}]ˇ]
 8483            )}]ˇ]
 8484        "
 8485        .unindent(),
 8486    );
 8487}
 8488
 8489#[gpui::test]
 8490async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
 8491    init_test(cx, |_| {});
 8492
 8493    let mut cx = EditorTestContext::new(cx).await;
 8494
 8495    let html_language = Arc::new(
 8496        Language::new(
 8497            LanguageConfig {
 8498                name: "HTML".into(),
 8499                brackets: BracketPairConfig {
 8500                    pairs: vec![
 8501                        BracketPair {
 8502                            start: "<".into(),
 8503                            end: ">".into(),
 8504                            close: true,
 8505                            ..Default::default()
 8506                        },
 8507                        BracketPair {
 8508                            start: "{".into(),
 8509                            end: "}".into(),
 8510                            close: true,
 8511                            ..Default::default()
 8512                        },
 8513                        BracketPair {
 8514                            start: "(".into(),
 8515                            end: ")".into(),
 8516                            close: true,
 8517                            ..Default::default()
 8518                        },
 8519                    ],
 8520                    ..Default::default()
 8521                },
 8522                autoclose_before: "})]>".into(),
 8523                ..Default::default()
 8524            },
 8525            Some(tree_sitter_html::LANGUAGE.into()),
 8526        )
 8527        .with_injection_query(
 8528            r#"
 8529            (script_element
 8530                (raw_text) @injection.content
 8531                (#set! injection.language "javascript"))
 8532            "#,
 8533        )
 8534        .unwrap(),
 8535    );
 8536
 8537    let javascript_language = Arc::new(Language::new(
 8538        LanguageConfig {
 8539            name: "JavaScript".into(),
 8540            brackets: BracketPairConfig {
 8541                pairs: vec![
 8542                    BracketPair {
 8543                        start: "/*".into(),
 8544                        end: " */".into(),
 8545                        close: true,
 8546                        ..Default::default()
 8547                    },
 8548                    BracketPair {
 8549                        start: "{".into(),
 8550                        end: "}".into(),
 8551                        close: true,
 8552                        ..Default::default()
 8553                    },
 8554                    BracketPair {
 8555                        start: "(".into(),
 8556                        end: ")".into(),
 8557                        close: true,
 8558                        ..Default::default()
 8559                    },
 8560                ],
 8561                ..Default::default()
 8562            },
 8563            autoclose_before: "})]>".into(),
 8564            ..Default::default()
 8565        },
 8566        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 8567    ));
 8568
 8569    cx.language_registry().add(html_language.clone());
 8570    cx.language_registry().add(javascript_language.clone());
 8571
 8572    cx.update_buffer(|buffer, cx| {
 8573        buffer.set_language(Some(html_language), cx);
 8574    });
 8575
 8576    cx.set_state(
 8577        &r#"
 8578            <body>ˇ
 8579                <script>
 8580                    var x = 1;ˇ
 8581                </script>
 8582            </body>ˇ
 8583        "#
 8584        .unindent(),
 8585    );
 8586
 8587    // Precondition: different languages are active at different locations.
 8588    cx.update_editor(|editor, window, cx| {
 8589        let snapshot = editor.snapshot(window, cx);
 8590        let cursors = editor.selections.ranges::<usize>(cx);
 8591        let languages = cursors
 8592            .iter()
 8593            .map(|c| snapshot.language_at(c.start).unwrap().name())
 8594            .collect::<Vec<_>>();
 8595        assert_eq!(
 8596            languages,
 8597            &["HTML".into(), "JavaScript".into(), "HTML".into()]
 8598        );
 8599    });
 8600
 8601    // Angle brackets autoclose in HTML, but not JavaScript.
 8602    cx.update_editor(|editor, window, cx| {
 8603        editor.handle_input("<", window, cx);
 8604        editor.handle_input("a", window, cx);
 8605    });
 8606    cx.assert_editor_state(
 8607        &r#"
 8608            <body><aˇ>
 8609                <script>
 8610                    var x = 1;<aˇ
 8611                </script>
 8612            </body><aˇ>
 8613        "#
 8614        .unindent(),
 8615    );
 8616
 8617    // Curly braces and parens autoclose in both HTML and JavaScript.
 8618    cx.update_editor(|editor, window, cx| {
 8619        editor.handle_input(" b=", window, cx);
 8620        editor.handle_input("{", window, cx);
 8621        editor.handle_input("c", window, cx);
 8622        editor.handle_input("(", window, cx);
 8623    });
 8624    cx.assert_editor_state(
 8625        &r#"
 8626            <body><a b={c(ˇ)}>
 8627                <script>
 8628                    var x = 1;<a b={c(ˇ)}
 8629                </script>
 8630            </body><a b={c(ˇ)}>
 8631        "#
 8632        .unindent(),
 8633    );
 8634
 8635    // Brackets that were already autoclosed are skipped.
 8636    cx.update_editor(|editor, window, cx| {
 8637        editor.handle_input(")", window, cx);
 8638        editor.handle_input("d", window, cx);
 8639        editor.handle_input("}", window, cx);
 8640    });
 8641    cx.assert_editor_state(
 8642        &r#"
 8643            <body><a b={c()d}ˇ>
 8644                <script>
 8645                    var x = 1;<a b={c()d}ˇ
 8646                </script>
 8647            </body><a b={c()d}ˇ>
 8648        "#
 8649        .unindent(),
 8650    );
 8651    cx.update_editor(|editor, window, cx| {
 8652        editor.handle_input(">", window, cx);
 8653    });
 8654    cx.assert_editor_state(
 8655        &r#"
 8656            <body><a b={c()d}>ˇ
 8657                <script>
 8658                    var x = 1;<a b={c()d}>ˇ
 8659                </script>
 8660            </body><a b={c()d}>ˇ
 8661        "#
 8662        .unindent(),
 8663    );
 8664
 8665    // Reset
 8666    cx.set_state(
 8667        &r#"
 8668            <body>ˇ
 8669                <script>
 8670                    var x = 1;ˇ
 8671                </script>
 8672            </body>ˇ
 8673        "#
 8674        .unindent(),
 8675    );
 8676
 8677    cx.update_editor(|editor, window, cx| {
 8678        editor.handle_input("<", window, cx);
 8679    });
 8680    cx.assert_editor_state(
 8681        &r#"
 8682            <body><ˇ>
 8683                <script>
 8684                    var x = 1;<ˇ
 8685                </script>
 8686            </body><ˇ>
 8687        "#
 8688        .unindent(),
 8689    );
 8690
 8691    // When backspacing, the closing angle brackets are removed.
 8692    cx.update_editor(|editor, window, cx| {
 8693        editor.backspace(&Backspace, window, cx);
 8694    });
 8695    cx.assert_editor_state(
 8696        &r#"
 8697            <body>ˇ
 8698                <script>
 8699                    var x = 1;ˇ
 8700                </script>
 8701            </body>ˇ
 8702        "#
 8703        .unindent(),
 8704    );
 8705
 8706    // Block comments autoclose in JavaScript, but not HTML.
 8707    cx.update_editor(|editor, window, cx| {
 8708        editor.handle_input("/", window, cx);
 8709        editor.handle_input("*", window, cx);
 8710    });
 8711    cx.assert_editor_state(
 8712        &r#"
 8713            <body>/*ˇ
 8714                <script>
 8715                    var x = 1;/*ˇ */
 8716                </script>
 8717            </body>/*ˇ
 8718        "#
 8719        .unindent(),
 8720    );
 8721}
 8722
 8723#[gpui::test]
 8724async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
 8725    init_test(cx, |_| {});
 8726
 8727    let mut cx = EditorTestContext::new(cx).await;
 8728
 8729    let rust_language = Arc::new(
 8730        Language::new(
 8731            LanguageConfig {
 8732                name: "Rust".into(),
 8733                brackets: serde_json::from_value(json!([
 8734                    { "start": "{", "end": "}", "close": true, "newline": true },
 8735                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
 8736                ]))
 8737                .unwrap(),
 8738                autoclose_before: "})]>".into(),
 8739                ..Default::default()
 8740            },
 8741            Some(tree_sitter_rust::LANGUAGE.into()),
 8742        )
 8743        .with_override_query("(string_literal) @string")
 8744        .unwrap(),
 8745    );
 8746
 8747    cx.language_registry().add(rust_language.clone());
 8748    cx.update_buffer(|buffer, cx| {
 8749        buffer.set_language(Some(rust_language), cx);
 8750    });
 8751
 8752    cx.set_state(
 8753        &r#"
 8754            let x = ˇ
 8755        "#
 8756        .unindent(),
 8757    );
 8758
 8759    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
 8760    cx.update_editor(|editor, window, cx| {
 8761        editor.handle_input("\"", window, cx);
 8762    });
 8763    cx.assert_editor_state(
 8764        &r#"
 8765            let x = "ˇ"
 8766        "#
 8767        .unindent(),
 8768    );
 8769
 8770    // Inserting another quotation mark. The cursor moves across the existing
 8771    // automatically-inserted quotation mark.
 8772    cx.update_editor(|editor, window, cx| {
 8773        editor.handle_input("\"", window, cx);
 8774    });
 8775    cx.assert_editor_state(
 8776        &r#"
 8777            let x = ""ˇ
 8778        "#
 8779        .unindent(),
 8780    );
 8781
 8782    // Reset
 8783    cx.set_state(
 8784        &r#"
 8785            let x = ˇ
 8786        "#
 8787        .unindent(),
 8788    );
 8789
 8790    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
 8791    cx.update_editor(|editor, window, cx| {
 8792        editor.handle_input("\"", window, cx);
 8793        editor.handle_input(" ", window, cx);
 8794        editor.move_left(&Default::default(), window, cx);
 8795        editor.handle_input("\\", window, cx);
 8796        editor.handle_input("\"", window, cx);
 8797    });
 8798    cx.assert_editor_state(
 8799        &r#"
 8800            let x = "\"ˇ "
 8801        "#
 8802        .unindent(),
 8803    );
 8804
 8805    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
 8806    // mark. Nothing is inserted.
 8807    cx.update_editor(|editor, window, cx| {
 8808        editor.move_right(&Default::default(), window, cx);
 8809        editor.handle_input("\"", window, cx);
 8810    });
 8811    cx.assert_editor_state(
 8812        &r#"
 8813            let x = "\" "ˇ
 8814        "#
 8815        .unindent(),
 8816    );
 8817}
 8818
 8819#[gpui::test]
 8820async fn test_surround_with_pair(cx: &mut TestAppContext) {
 8821    init_test(cx, |_| {});
 8822
 8823    let language = Arc::new(Language::new(
 8824        LanguageConfig {
 8825            brackets: BracketPairConfig {
 8826                pairs: vec![
 8827                    BracketPair {
 8828                        start: "{".to_string(),
 8829                        end: "}".to_string(),
 8830                        close: true,
 8831                        surround: true,
 8832                        newline: true,
 8833                    },
 8834                    BracketPair {
 8835                        start: "/* ".to_string(),
 8836                        end: "*/".to_string(),
 8837                        close: true,
 8838                        surround: true,
 8839                        ..Default::default()
 8840                    },
 8841                ],
 8842                ..Default::default()
 8843            },
 8844            ..Default::default()
 8845        },
 8846        Some(tree_sitter_rust::LANGUAGE.into()),
 8847    ));
 8848
 8849    let text = r#"
 8850        a
 8851        b
 8852        c
 8853    "#
 8854    .unindent();
 8855
 8856    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8857    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8858    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8859    editor
 8860        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8861        .await;
 8862
 8863    editor.update_in(cx, |editor, window, cx| {
 8864        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8865            s.select_display_ranges([
 8866                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8867                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8868                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
 8869            ])
 8870        });
 8871
 8872        editor.handle_input("{", window, cx);
 8873        editor.handle_input("{", window, cx);
 8874        editor.handle_input("{", window, cx);
 8875        assert_eq!(
 8876            editor.text(cx),
 8877            "
 8878                {{{a}}}
 8879                {{{b}}}
 8880                {{{c}}}
 8881            "
 8882            .unindent()
 8883        );
 8884        assert_eq!(
 8885            editor.selections.display_ranges(cx),
 8886            [
 8887                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
 8888                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
 8889                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
 8890            ]
 8891        );
 8892
 8893        editor.undo(&Undo, window, cx);
 8894        editor.undo(&Undo, window, cx);
 8895        editor.undo(&Undo, window, cx);
 8896        assert_eq!(
 8897            editor.text(cx),
 8898            "
 8899                a
 8900                b
 8901                c
 8902            "
 8903            .unindent()
 8904        );
 8905        assert_eq!(
 8906            editor.selections.display_ranges(cx),
 8907            [
 8908                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8909                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8910                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 8911            ]
 8912        );
 8913
 8914        // Ensure inserting the first character of a multi-byte bracket pair
 8915        // doesn't surround the selections with the bracket.
 8916        editor.handle_input("/", window, cx);
 8917        assert_eq!(
 8918            editor.text(cx),
 8919            "
 8920                /
 8921                /
 8922                /
 8923            "
 8924            .unindent()
 8925        );
 8926        assert_eq!(
 8927            editor.selections.display_ranges(cx),
 8928            [
 8929                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 8930                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 8931                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 8932            ]
 8933        );
 8934
 8935        editor.undo(&Undo, window, cx);
 8936        assert_eq!(
 8937            editor.text(cx),
 8938            "
 8939                a
 8940                b
 8941                c
 8942            "
 8943            .unindent()
 8944        );
 8945        assert_eq!(
 8946            editor.selections.display_ranges(cx),
 8947            [
 8948                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8949                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8950                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 8951            ]
 8952        );
 8953
 8954        // Ensure inserting the last character of a multi-byte bracket pair
 8955        // doesn't surround the selections with the bracket.
 8956        editor.handle_input("*", window, cx);
 8957        assert_eq!(
 8958            editor.text(cx),
 8959            "
 8960                *
 8961                *
 8962                *
 8963            "
 8964            .unindent()
 8965        );
 8966        assert_eq!(
 8967            editor.selections.display_ranges(cx),
 8968            [
 8969                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 8970                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 8971                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 8972            ]
 8973        );
 8974    });
 8975}
 8976
 8977#[gpui::test]
 8978async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
 8979    init_test(cx, |_| {});
 8980
 8981    let language = Arc::new(Language::new(
 8982        LanguageConfig {
 8983            brackets: BracketPairConfig {
 8984                pairs: vec![BracketPair {
 8985                    start: "{".to_string(),
 8986                    end: "}".to_string(),
 8987                    close: true,
 8988                    surround: true,
 8989                    newline: true,
 8990                }],
 8991                ..Default::default()
 8992            },
 8993            autoclose_before: "}".to_string(),
 8994            ..Default::default()
 8995        },
 8996        Some(tree_sitter_rust::LANGUAGE.into()),
 8997    ));
 8998
 8999    let text = r#"
 9000        a
 9001        b
 9002        c
 9003    "#
 9004    .unindent();
 9005
 9006    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9007    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9008    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9009    editor
 9010        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9011        .await;
 9012
 9013    editor.update_in(cx, |editor, window, cx| {
 9014        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9015            s.select_ranges([
 9016                Point::new(0, 1)..Point::new(0, 1),
 9017                Point::new(1, 1)..Point::new(1, 1),
 9018                Point::new(2, 1)..Point::new(2, 1),
 9019            ])
 9020        });
 9021
 9022        editor.handle_input("{", window, cx);
 9023        editor.handle_input("{", window, cx);
 9024        editor.handle_input("_", window, cx);
 9025        assert_eq!(
 9026            editor.text(cx),
 9027            "
 9028                a{{_}}
 9029                b{{_}}
 9030                c{{_}}
 9031            "
 9032            .unindent()
 9033        );
 9034        assert_eq!(
 9035            editor.selections.ranges::<Point>(cx),
 9036            [
 9037                Point::new(0, 4)..Point::new(0, 4),
 9038                Point::new(1, 4)..Point::new(1, 4),
 9039                Point::new(2, 4)..Point::new(2, 4)
 9040            ]
 9041        );
 9042
 9043        editor.backspace(&Default::default(), window, cx);
 9044        editor.backspace(&Default::default(), window, cx);
 9045        assert_eq!(
 9046            editor.text(cx),
 9047            "
 9048                a{}
 9049                b{}
 9050                c{}
 9051            "
 9052            .unindent()
 9053        );
 9054        assert_eq!(
 9055            editor.selections.ranges::<Point>(cx),
 9056            [
 9057                Point::new(0, 2)..Point::new(0, 2),
 9058                Point::new(1, 2)..Point::new(1, 2),
 9059                Point::new(2, 2)..Point::new(2, 2)
 9060            ]
 9061        );
 9062
 9063        editor.delete_to_previous_word_start(&Default::default(), window, cx);
 9064        assert_eq!(
 9065            editor.text(cx),
 9066            "
 9067                a
 9068                b
 9069                c
 9070            "
 9071            .unindent()
 9072        );
 9073        assert_eq!(
 9074            editor.selections.ranges::<Point>(cx),
 9075            [
 9076                Point::new(0, 1)..Point::new(0, 1),
 9077                Point::new(1, 1)..Point::new(1, 1),
 9078                Point::new(2, 1)..Point::new(2, 1)
 9079            ]
 9080        );
 9081    });
 9082}
 9083
 9084#[gpui::test]
 9085async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
 9086    init_test(cx, |settings| {
 9087        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 9088    });
 9089
 9090    let mut cx = EditorTestContext::new(cx).await;
 9091
 9092    let language = Arc::new(Language::new(
 9093        LanguageConfig {
 9094            brackets: BracketPairConfig {
 9095                pairs: vec![
 9096                    BracketPair {
 9097                        start: "{".to_string(),
 9098                        end: "}".to_string(),
 9099                        close: true,
 9100                        surround: true,
 9101                        newline: true,
 9102                    },
 9103                    BracketPair {
 9104                        start: "(".to_string(),
 9105                        end: ")".to_string(),
 9106                        close: true,
 9107                        surround: true,
 9108                        newline: true,
 9109                    },
 9110                    BracketPair {
 9111                        start: "[".to_string(),
 9112                        end: "]".to_string(),
 9113                        close: false,
 9114                        surround: true,
 9115                        newline: true,
 9116                    },
 9117                ],
 9118                ..Default::default()
 9119            },
 9120            autoclose_before: "})]".to_string(),
 9121            ..Default::default()
 9122        },
 9123        Some(tree_sitter_rust::LANGUAGE.into()),
 9124    ));
 9125
 9126    cx.language_registry().add(language.clone());
 9127    cx.update_buffer(|buffer, cx| {
 9128        buffer.set_language(Some(language), cx);
 9129    });
 9130
 9131    cx.set_state(
 9132        &"
 9133            {(ˇ)}
 9134            [[ˇ]]
 9135            {(ˇ)}
 9136        "
 9137        .unindent(),
 9138    );
 9139
 9140    cx.update_editor(|editor, window, cx| {
 9141        editor.backspace(&Default::default(), window, cx);
 9142        editor.backspace(&Default::default(), window, cx);
 9143    });
 9144
 9145    cx.assert_editor_state(
 9146        &"
 9147            ˇ
 9148            ˇ]]
 9149            ˇ
 9150        "
 9151        .unindent(),
 9152    );
 9153
 9154    cx.update_editor(|editor, window, cx| {
 9155        editor.handle_input("{", window, cx);
 9156        editor.handle_input("{", window, cx);
 9157        editor.move_right(&MoveRight, window, cx);
 9158        editor.move_right(&MoveRight, window, cx);
 9159        editor.move_left(&MoveLeft, window, cx);
 9160        editor.move_left(&MoveLeft, window, cx);
 9161        editor.backspace(&Default::default(), window, cx);
 9162    });
 9163
 9164    cx.assert_editor_state(
 9165        &"
 9166            {ˇ}
 9167            {ˇ}]]
 9168            {ˇ}
 9169        "
 9170        .unindent(),
 9171    );
 9172
 9173    cx.update_editor(|editor, window, cx| {
 9174        editor.backspace(&Default::default(), window, cx);
 9175    });
 9176
 9177    cx.assert_editor_state(
 9178        &"
 9179            ˇ
 9180            ˇ]]
 9181            ˇ
 9182        "
 9183        .unindent(),
 9184    );
 9185}
 9186
 9187#[gpui::test]
 9188async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
 9189    init_test(cx, |_| {});
 9190
 9191    let language = Arc::new(Language::new(
 9192        LanguageConfig::default(),
 9193        Some(tree_sitter_rust::LANGUAGE.into()),
 9194    ));
 9195
 9196    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
 9197    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9198    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9199    editor
 9200        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9201        .await;
 9202
 9203    editor.update_in(cx, |editor, window, cx| {
 9204        editor.set_auto_replace_emoji_shortcode(true);
 9205
 9206        editor.handle_input("Hello ", window, cx);
 9207        editor.handle_input(":wave", window, cx);
 9208        assert_eq!(editor.text(cx), "Hello :wave".unindent());
 9209
 9210        editor.handle_input(":", window, cx);
 9211        assert_eq!(editor.text(cx), "Hello 👋".unindent());
 9212
 9213        editor.handle_input(" :smile", window, cx);
 9214        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
 9215
 9216        editor.handle_input(":", window, cx);
 9217        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
 9218
 9219        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
 9220        editor.handle_input(":wave", window, cx);
 9221        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
 9222
 9223        editor.handle_input(":", window, cx);
 9224        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
 9225
 9226        editor.handle_input(":1", window, cx);
 9227        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
 9228
 9229        editor.handle_input(":", window, cx);
 9230        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
 9231
 9232        // Ensure shortcode does not get replaced when it is part of a word
 9233        editor.handle_input(" Test:wave", window, cx);
 9234        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
 9235
 9236        editor.handle_input(":", window, cx);
 9237        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
 9238
 9239        editor.set_auto_replace_emoji_shortcode(false);
 9240
 9241        // Ensure shortcode does not get replaced when auto replace is off
 9242        editor.handle_input(" :wave", window, cx);
 9243        assert_eq!(
 9244            editor.text(cx),
 9245            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
 9246        );
 9247
 9248        editor.handle_input(":", window, cx);
 9249        assert_eq!(
 9250            editor.text(cx),
 9251            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
 9252        );
 9253    });
 9254}
 9255
 9256#[gpui::test]
 9257async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
 9258    init_test(cx, |_| {});
 9259
 9260    let (text, insertion_ranges) = marked_text_ranges(
 9261        indoc! {"
 9262            ˇ
 9263        "},
 9264        false,
 9265    );
 9266
 9267    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
 9268    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9269
 9270    _ = editor.update_in(cx, |editor, window, cx| {
 9271        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
 9272
 9273        editor
 9274            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9275            .unwrap();
 9276
 9277        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
 9278            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
 9279            assert_eq!(editor.text(cx), expected_text);
 9280            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
 9281        }
 9282
 9283        assert(
 9284            editor,
 9285            cx,
 9286            indoc! {"
 9287            type «» =•
 9288            "},
 9289        );
 9290
 9291        assert!(editor.context_menu_visible(), "There should be a matches");
 9292    });
 9293}
 9294
 9295#[gpui::test]
 9296async fn test_snippets(cx: &mut TestAppContext) {
 9297    init_test(cx, |_| {});
 9298
 9299    let mut cx = EditorTestContext::new(cx).await;
 9300
 9301    cx.set_state(indoc! {"
 9302        a.ˇ b
 9303        a.ˇ b
 9304        a.ˇ b
 9305    "});
 9306
 9307    cx.update_editor(|editor, window, cx| {
 9308        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
 9309        let insertion_ranges = editor
 9310            .selections
 9311            .all(cx)
 9312            .iter()
 9313            .map(|s| s.range().clone())
 9314            .collect::<Vec<_>>();
 9315        editor
 9316            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9317            .unwrap();
 9318    });
 9319
 9320    cx.assert_editor_state(indoc! {"
 9321        a.f(«oneˇ», two, «threeˇ») b
 9322        a.f(«oneˇ», two, «threeˇ») b
 9323        a.f(«oneˇ», two, «threeˇ») b
 9324    "});
 9325
 9326    // Can't move earlier than the first tab stop
 9327    cx.update_editor(|editor, window, cx| {
 9328        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 9329    });
 9330    cx.assert_editor_state(indoc! {"
 9331        a.f(«oneˇ», two, «threeˇ») b
 9332        a.f(«oneˇ», two, «threeˇ») b
 9333        a.f(«oneˇ», two, «threeˇ») b
 9334    "});
 9335
 9336    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9337    cx.assert_editor_state(indoc! {"
 9338        a.f(one, «twoˇ», three) b
 9339        a.f(one, «twoˇ», three) b
 9340        a.f(one, «twoˇ», three) b
 9341    "});
 9342
 9343    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
 9344    cx.assert_editor_state(indoc! {"
 9345        a.f(«oneˇ», two, «threeˇ») b
 9346        a.f(«oneˇ», two, «threeˇ») b
 9347        a.f(«oneˇ», two, «threeˇ») b
 9348    "});
 9349
 9350    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9351    cx.assert_editor_state(indoc! {"
 9352        a.f(one, «twoˇ», three) b
 9353        a.f(one, «twoˇ», three) b
 9354        a.f(one, «twoˇ», three) b
 9355    "});
 9356    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9357    cx.assert_editor_state(indoc! {"
 9358        a.f(one, two, three)ˇ b
 9359        a.f(one, two, three)ˇ b
 9360        a.f(one, two, three)ˇ b
 9361    "});
 9362
 9363    // As soon as the last tab stop is reached, snippet state is gone
 9364    cx.update_editor(|editor, window, cx| {
 9365        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 9366    });
 9367    cx.assert_editor_state(indoc! {"
 9368        a.f(one, two, three)ˇ b
 9369        a.f(one, two, three)ˇ b
 9370        a.f(one, two, three)ˇ b
 9371    "});
 9372}
 9373
 9374#[gpui::test]
 9375async fn test_snippet_indentation(cx: &mut TestAppContext) {
 9376    init_test(cx, |_| {});
 9377
 9378    let mut cx = EditorTestContext::new(cx).await;
 9379
 9380    cx.update_editor(|editor, window, cx| {
 9381        let snippet = Snippet::parse(indoc! {"
 9382            /*
 9383             * Multiline comment with leading indentation
 9384             *
 9385             * $1
 9386             */
 9387            $0"})
 9388        .unwrap();
 9389        let insertion_ranges = editor
 9390            .selections
 9391            .all(cx)
 9392            .iter()
 9393            .map(|s| s.range().clone())
 9394            .collect::<Vec<_>>();
 9395        editor
 9396            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9397            .unwrap();
 9398    });
 9399
 9400    cx.assert_editor_state(indoc! {"
 9401        /*
 9402         * Multiline comment with leading indentation
 9403         *
 9404         * ˇ
 9405         */
 9406    "});
 9407
 9408    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9409    cx.assert_editor_state(indoc! {"
 9410        /*
 9411         * Multiline comment with leading indentation
 9412         *
 9413         *•
 9414         */
 9415        ˇ"});
 9416}
 9417
 9418#[gpui::test]
 9419async fn test_document_format_during_save(cx: &mut TestAppContext) {
 9420    init_test(cx, |_| {});
 9421
 9422    let fs = FakeFs::new(cx.executor());
 9423    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9424
 9425    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
 9426
 9427    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9428    language_registry.add(rust_lang());
 9429    let mut fake_servers = language_registry.register_fake_lsp(
 9430        "Rust",
 9431        FakeLspAdapter {
 9432            capabilities: lsp::ServerCapabilities {
 9433                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9434                ..Default::default()
 9435            },
 9436            ..Default::default()
 9437        },
 9438    );
 9439
 9440    let buffer = project
 9441        .update(cx, |project, cx| {
 9442            project.open_local_buffer(path!("/file.rs"), cx)
 9443        })
 9444        .await
 9445        .unwrap();
 9446
 9447    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9448    let (editor, cx) = cx.add_window_view(|window, cx| {
 9449        build_editor_with_project(project.clone(), buffer, window, cx)
 9450    });
 9451    editor.update_in(cx, |editor, window, cx| {
 9452        editor.set_text("one\ntwo\nthree\n", window, cx)
 9453    });
 9454    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9455
 9456    cx.executor().start_waiting();
 9457    let fake_server = fake_servers.next().await.unwrap();
 9458
 9459    {
 9460        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9461            move |params, _| async move {
 9462                assert_eq!(
 9463                    params.text_document.uri,
 9464                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9465                );
 9466                assert_eq!(params.options.tab_size, 4);
 9467                Ok(Some(vec![lsp::TextEdit::new(
 9468                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9469                    ", ".to_string(),
 9470                )]))
 9471            },
 9472        );
 9473        let save = editor
 9474            .update_in(cx, |editor, window, cx| {
 9475                editor.save(
 9476                    SaveOptions {
 9477                        format: true,
 9478                        autosave: false,
 9479                    },
 9480                    project.clone(),
 9481                    window,
 9482                    cx,
 9483                )
 9484            })
 9485            .unwrap();
 9486        cx.executor().start_waiting();
 9487        save.await;
 9488
 9489        assert_eq!(
 9490            editor.update(cx, |editor, cx| editor.text(cx)),
 9491            "one, two\nthree\n"
 9492        );
 9493        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9494    }
 9495
 9496    {
 9497        editor.update_in(cx, |editor, window, cx| {
 9498            editor.set_text("one\ntwo\nthree\n", window, cx)
 9499        });
 9500        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9501
 9502        // Ensure we can still save even if formatting hangs.
 9503        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9504            move |params, _| async move {
 9505                assert_eq!(
 9506                    params.text_document.uri,
 9507                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9508                );
 9509                futures::future::pending::<()>().await;
 9510                unreachable!()
 9511            },
 9512        );
 9513        let save = editor
 9514            .update_in(cx, |editor, window, cx| {
 9515                editor.save(
 9516                    SaveOptions {
 9517                        format: true,
 9518                        autosave: false,
 9519                    },
 9520                    project.clone(),
 9521                    window,
 9522                    cx,
 9523                )
 9524            })
 9525            .unwrap();
 9526        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9527        cx.executor().start_waiting();
 9528        save.await;
 9529        assert_eq!(
 9530            editor.update(cx, |editor, cx| editor.text(cx)),
 9531            "one\ntwo\nthree\n"
 9532        );
 9533    }
 9534
 9535    // Set rust language override and assert overridden tabsize is sent to language server
 9536    update_test_language_settings(cx, |settings| {
 9537        settings.languages.0.insert(
 9538            "Rust".into(),
 9539            LanguageSettingsContent {
 9540                tab_size: NonZeroU32::new(8),
 9541                ..Default::default()
 9542            },
 9543        );
 9544    });
 9545
 9546    {
 9547        editor.update_in(cx, |editor, window, cx| {
 9548            editor.set_text("somehting_new\n", window, cx)
 9549        });
 9550        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9551        let _formatting_request_signal = fake_server
 9552            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9553                assert_eq!(
 9554                    params.text_document.uri,
 9555                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9556                );
 9557                assert_eq!(params.options.tab_size, 8);
 9558                Ok(Some(vec![]))
 9559            });
 9560        let save = editor
 9561            .update_in(cx, |editor, window, cx| {
 9562                editor.save(
 9563                    SaveOptions {
 9564                        format: true,
 9565                        autosave: false,
 9566                    },
 9567                    project.clone(),
 9568                    window,
 9569                    cx,
 9570                )
 9571            })
 9572            .unwrap();
 9573        cx.executor().start_waiting();
 9574        save.await;
 9575    }
 9576}
 9577
 9578#[gpui::test]
 9579async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
 9580    init_test(cx, |settings| {
 9581        settings.defaults.ensure_final_newline_on_save = Some(false);
 9582    });
 9583
 9584    let fs = FakeFs::new(cx.executor());
 9585    fs.insert_file(path!("/file.txt"), "foo".into()).await;
 9586
 9587    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
 9588
 9589    let buffer = project
 9590        .update(cx, |project, cx| {
 9591            project.open_local_buffer(path!("/file.txt"), cx)
 9592        })
 9593        .await
 9594        .unwrap();
 9595
 9596    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9597    let (editor, cx) = cx.add_window_view(|window, cx| {
 9598        build_editor_with_project(project.clone(), buffer, window, cx)
 9599    });
 9600    editor.update_in(cx, |editor, window, cx| {
 9601        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 9602            s.select_ranges([0..0])
 9603        });
 9604    });
 9605    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9606
 9607    editor.update_in(cx, |editor, window, cx| {
 9608        editor.handle_input("\n", window, cx)
 9609    });
 9610    cx.run_until_parked();
 9611    save(&editor, &project, cx).await;
 9612    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
 9613
 9614    editor.update_in(cx, |editor, window, cx| {
 9615        editor.undo(&Default::default(), window, cx);
 9616    });
 9617    save(&editor, &project, cx).await;
 9618    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
 9619
 9620    editor.update_in(cx, |editor, window, cx| {
 9621        editor.redo(&Default::default(), window, cx);
 9622    });
 9623    cx.run_until_parked();
 9624    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
 9625
 9626    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
 9627        let save = editor
 9628            .update_in(cx, |editor, window, cx| {
 9629                editor.save(
 9630                    SaveOptions {
 9631                        format: true,
 9632                        autosave: false,
 9633                    },
 9634                    project.clone(),
 9635                    window,
 9636                    cx,
 9637                )
 9638            })
 9639            .unwrap();
 9640        cx.executor().start_waiting();
 9641        save.await;
 9642        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9643    }
 9644}
 9645
 9646#[gpui::test]
 9647async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
 9648    init_test(cx, |_| {});
 9649
 9650    let cols = 4;
 9651    let rows = 10;
 9652    let sample_text_1 = sample_text(rows, cols, 'a');
 9653    assert_eq!(
 9654        sample_text_1,
 9655        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
 9656    );
 9657    let sample_text_2 = sample_text(rows, cols, 'l');
 9658    assert_eq!(
 9659        sample_text_2,
 9660        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
 9661    );
 9662    let sample_text_3 = sample_text(rows, cols, 'v');
 9663    assert_eq!(
 9664        sample_text_3,
 9665        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
 9666    );
 9667
 9668    let fs = FakeFs::new(cx.executor());
 9669    fs.insert_tree(
 9670        path!("/a"),
 9671        json!({
 9672            "main.rs": sample_text_1,
 9673            "other.rs": sample_text_2,
 9674            "lib.rs": sample_text_3,
 9675        }),
 9676    )
 9677    .await;
 9678
 9679    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
 9680    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9681    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 9682
 9683    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9684    language_registry.add(rust_lang());
 9685    let mut fake_servers = language_registry.register_fake_lsp(
 9686        "Rust",
 9687        FakeLspAdapter {
 9688            capabilities: lsp::ServerCapabilities {
 9689                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9690                ..Default::default()
 9691            },
 9692            ..Default::default()
 9693        },
 9694    );
 9695
 9696    let worktree = project.update(cx, |project, cx| {
 9697        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
 9698        assert_eq!(worktrees.len(), 1);
 9699        worktrees.pop().unwrap()
 9700    });
 9701    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 9702
 9703    let buffer_1 = project
 9704        .update(cx, |project, cx| {
 9705            project.open_buffer((worktree_id, "main.rs"), cx)
 9706        })
 9707        .await
 9708        .unwrap();
 9709    let buffer_2 = project
 9710        .update(cx, |project, cx| {
 9711            project.open_buffer((worktree_id, "other.rs"), cx)
 9712        })
 9713        .await
 9714        .unwrap();
 9715    let buffer_3 = project
 9716        .update(cx, |project, cx| {
 9717            project.open_buffer((worktree_id, "lib.rs"), cx)
 9718        })
 9719        .await
 9720        .unwrap();
 9721
 9722    let multi_buffer = cx.new(|cx| {
 9723        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 9724        multi_buffer.push_excerpts(
 9725            buffer_1.clone(),
 9726            [
 9727                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9728                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9729                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9730            ],
 9731            cx,
 9732        );
 9733        multi_buffer.push_excerpts(
 9734            buffer_2.clone(),
 9735            [
 9736                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9737                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9738                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9739            ],
 9740            cx,
 9741        );
 9742        multi_buffer.push_excerpts(
 9743            buffer_3.clone(),
 9744            [
 9745                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9746                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9747                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9748            ],
 9749            cx,
 9750        );
 9751        multi_buffer
 9752    });
 9753    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
 9754        Editor::new(
 9755            EditorMode::full(),
 9756            multi_buffer,
 9757            Some(project.clone()),
 9758            window,
 9759            cx,
 9760        )
 9761    });
 9762
 9763    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 9764        editor.change_selections(
 9765            SelectionEffects::scroll(Autoscroll::Next),
 9766            window,
 9767            cx,
 9768            |s| s.select_ranges(Some(1..2)),
 9769        );
 9770        editor.insert("|one|two|three|", window, cx);
 9771    });
 9772    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 9773    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 9774        editor.change_selections(
 9775            SelectionEffects::scroll(Autoscroll::Next),
 9776            window,
 9777            cx,
 9778            |s| s.select_ranges(Some(60..70)),
 9779        );
 9780        editor.insert("|four|five|six|", window, cx);
 9781    });
 9782    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 9783
 9784    // First two buffers should be edited, but not the third one.
 9785    assert_eq!(
 9786        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 9787        "a|one|two|three|aa\nbbbb\ncccc\n\nffff\ngggg\n\njjjj\nllll\nmmmm\nnnnn|four|five|six|\nr\n\nuuuu\nvvvv\nwwww\nxxxx\n\n{{{{\n||||\n\n\u{7f}\u{7f}\u{7f}\u{7f}",
 9788    );
 9789    buffer_1.update(cx, |buffer, _| {
 9790        assert!(buffer.is_dirty());
 9791        assert_eq!(
 9792            buffer.text(),
 9793            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
 9794        )
 9795    });
 9796    buffer_2.update(cx, |buffer, _| {
 9797        assert!(buffer.is_dirty());
 9798        assert_eq!(
 9799            buffer.text(),
 9800            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
 9801        )
 9802    });
 9803    buffer_3.update(cx, |buffer, _| {
 9804        assert!(!buffer.is_dirty());
 9805        assert_eq!(buffer.text(), sample_text_3,)
 9806    });
 9807    cx.executor().run_until_parked();
 9808
 9809    cx.executor().start_waiting();
 9810    let save = multi_buffer_editor
 9811        .update_in(cx, |editor, window, cx| {
 9812            editor.save(
 9813                SaveOptions {
 9814                    format: true,
 9815                    autosave: false,
 9816                },
 9817                project.clone(),
 9818                window,
 9819                cx,
 9820            )
 9821        })
 9822        .unwrap();
 9823
 9824    let fake_server = fake_servers.next().await.unwrap();
 9825    fake_server
 9826        .server
 9827        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9828            Ok(Some(vec![lsp::TextEdit::new(
 9829                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9830                format!("[{} formatted]", params.text_document.uri),
 9831            )]))
 9832        })
 9833        .detach();
 9834    save.await;
 9835
 9836    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
 9837    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
 9838    assert_eq!(
 9839        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 9840        uri!(
 9841            "a|o[file:///a/main.rs formatted]bbbb\ncccc\n\nffff\ngggg\n\njjjj\n\nlll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|\nr\n\nuuuu\n\nvvvv\nwwww\nxxxx\n\n{{{{\n||||\n\n\u{7f}\u{7f}\u{7f}\u{7f}"
 9842        ),
 9843    );
 9844    buffer_1.update(cx, |buffer, _| {
 9845        assert!(!buffer.is_dirty());
 9846        assert_eq!(
 9847            buffer.text(),
 9848            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
 9849        )
 9850    });
 9851    buffer_2.update(cx, |buffer, _| {
 9852        assert!(!buffer.is_dirty());
 9853        assert_eq!(
 9854            buffer.text(),
 9855            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
 9856        )
 9857    });
 9858    buffer_3.update(cx, |buffer, _| {
 9859        assert!(!buffer.is_dirty());
 9860        assert_eq!(buffer.text(), sample_text_3,)
 9861    });
 9862}
 9863
 9864#[gpui::test]
 9865async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
 9866    init_test(cx, |_| {});
 9867
 9868    let fs = FakeFs::new(cx.executor());
 9869    fs.insert_tree(
 9870        path!("/dir"),
 9871        json!({
 9872            "file1.rs": "fn main() { println!(\"hello\"); }",
 9873            "file2.rs": "fn test() { println!(\"test\"); }",
 9874            "file3.rs": "fn other() { println!(\"other\"); }\n",
 9875        }),
 9876    )
 9877    .await;
 9878
 9879    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
 9880    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9881    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 9882
 9883    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9884    language_registry.add(rust_lang());
 9885
 9886    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
 9887    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 9888
 9889    // Open three buffers
 9890    let buffer_1 = project
 9891        .update(cx, |project, cx| {
 9892            project.open_buffer((worktree_id, "file1.rs"), cx)
 9893        })
 9894        .await
 9895        .unwrap();
 9896    let buffer_2 = project
 9897        .update(cx, |project, cx| {
 9898            project.open_buffer((worktree_id, "file2.rs"), cx)
 9899        })
 9900        .await
 9901        .unwrap();
 9902    let buffer_3 = project
 9903        .update(cx, |project, cx| {
 9904            project.open_buffer((worktree_id, "file3.rs"), cx)
 9905        })
 9906        .await
 9907        .unwrap();
 9908
 9909    // Create a multi-buffer with all three buffers
 9910    let multi_buffer = cx.new(|cx| {
 9911        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 9912        multi_buffer.push_excerpts(
 9913            buffer_1.clone(),
 9914            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9915            cx,
 9916        );
 9917        multi_buffer.push_excerpts(
 9918            buffer_2.clone(),
 9919            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9920            cx,
 9921        );
 9922        multi_buffer.push_excerpts(
 9923            buffer_3.clone(),
 9924            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9925            cx,
 9926        );
 9927        multi_buffer
 9928    });
 9929
 9930    let editor = cx.new_window_entity(|window, cx| {
 9931        Editor::new(
 9932            EditorMode::full(),
 9933            multi_buffer,
 9934            Some(project.clone()),
 9935            window,
 9936            cx,
 9937        )
 9938    });
 9939
 9940    // Edit only the first buffer
 9941    editor.update_in(cx, |editor, window, cx| {
 9942        editor.change_selections(
 9943            SelectionEffects::scroll(Autoscroll::Next),
 9944            window,
 9945            cx,
 9946            |s| s.select_ranges(Some(10..10)),
 9947        );
 9948        editor.insert("// edited", window, cx);
 9949    });
 9950
 9951    // Verify that only buffer 1 is dirty
 9952    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
 9953    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9954    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9955
 9956    // Get write counts after file creation (files were created with initial content)
 9957    // We expect each file to have been written once during creation
 9958    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
 9959    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
 9960    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
 9961
 9962    // Perform autosave
 9963    let save_task = editor.update_in(cx, |editor, window, cx| {
 9964        editor.save(
 9965            SaveOptions {
 9966                format: true,
 9967                autosave: true,
 9968            },
 9969            project.clone(),
 9970            window,
 9971            cx,
 9972        )
 9973    });
 9974    save_task.await.unwrap();
 9975
 9976    // Only the dirty buffer should have been saved
 9977    assert_eq!(
 9978        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
 9979        1,
 9980        "Buffer 1 was dirty, so it should have been written once during autosave"
 9981    );
 9982    assert_eq!(
 9983        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
 9984        0,
 9985        "Buffer 2 was clean, so it should not have been written during autosave"
 9986    );
 9987    assert_eq!(
 9988        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
 9989        0,
 9990        "Buffer 3 was clean, so it should not have been written during autosave"
 9991    );
 9992
 9993    // Verify buffer states after autosave
 9994    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9995    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9996    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9997
 9998    // Now perform a manual save (format = true)
 9999    let save_task = editor.update_in(cx, |editor, window, cx| {
10000        editor.save(
10001            SaveOptions {
10002                format: true,
10003                autosave: false,
10004            },
10005            project.clone(),
10006            window,
10007            cx,
10008        )
10009    });
10010    save_task.await.unwrap();
10011
10012    // During manual save, clean buffers don't get written to disk
10013    // They just get did_save called for language server notifications
10014    assert_eq!(
10015        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
10016        1,
10017        "Buffer 1 should only have been written once total (during autosave, not manual save)"
10018    );
10019    assert_eq!(
10020        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
10021        0,
10022        "Buffer 2 should not have been written at all"
10023    );
10024    assert_eq!(
10025        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
10026        0,
10027        "Buffer 3 should not have been written at all"
10028    );
10029}
10030
10031#[gpui::test]
10032async fn test_range_format_during_save(cx: &mut TestAppContext) {
10033    init_test(cx, |_| {});
10034
10035    let fs = FakeFs::new(cx.executor());
10036    fs.insert_file(path!("/file.rs"), Default::default()).await;
10037
10038    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10039
10040    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10041    language_registry.add(rust_lang());
10042    let mut fake_servers = language_registry.register_fake_lsp(
10043        "Rust",
10044        FakeLspAdapter {
10045            capabilities: lsp::ServerCapabilities {
10046                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
10047                ..Default::default()
10048            },
10049            ..Default::default()
10050        },
10051    );
10052
10053    let buffer = project
10054        .update(cx, |project, cx| {
10055            project.open_local_buffer(path!("/file.rs"), cx)
10056        })
10057        .await
10058        .unwrap();
10059
10060    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10061    let (editor, cx) = cx.add_window_view(|window, cx| {
10062        build_editor_with_project(project.clone(), buffer, window, cx)
10063    });
10064    editor.update_in(cx, |editor, window, cx| {
10065        editor.set_text("one\ntwo\nthree\n", window, cx)
10066    });
10067    assert!(cx.read(|cx| editor.is_dirty(cx)));
10068
10069    cx.executor().start_waiting();
10070    let fake_server = fake_servers.next().await.unwrap();
10071
10072    let save = editor
10073        .update_in(cx, |editor, window, cx| {
10074            editor.save(
10075                SaveOptions {
10076                    format: true,
10077                    autosave: false,
10078                },
10079                project.clone(),
10080                window,
10081                cx,
10082            )
10083        })
10084        .unwrap();
10085    fake_server
10086        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
10087            assert_eq!(
10088                params.text_document.uri,
10089                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10090            );
10091            assert_eq!(params.options.tab_size, 4);
10092            Ok(Some(vec![lsp::TextEdit::new(
10093                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10094                ", ".to_string(),
10095            )]))
10096        })
10097        .next()
10098        .await;
10099    cx.executor().start_waiting();
10100    save.await;
10101    assert_eq!(
10102        editor.update(cx, |editor, cx| editor.text(cx)),
10103        "one, two\nthree\n"
10104    );
10105    assert!(!cx.read(|cx| editor.is_dirty(cx)));
10106
10107    editor.update_in(cx, |editor, window, cx| {
10108        editor.set_text("one\ntwo\nthree\n", window, cx)
10109    });
10110    assert!(cx.read(|cx| editor.is_dirty(cx)));
10111
10112    // Ensure we can still save even if formatting hangs.
10113    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
10114        move |params, _| async move {
10115            assert_eq!(
10116                params.text_document.uri,
10117                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10118            );
10119            futures::future::pending::<()>().await;
10120            unreachable!()
10121        },
10122    );
10123    let save = editor
10124        .update_in(cx, |editor, window, cx| {
10125            editor.save(
10126                SaveOptions {
10127                    format: true,
10128                    autosave: false,
10129                },
10130                project.clone(),
10131                window,
10132                cx,
10133            )
10134        })
10135        .unwrap();
10136    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10137    cx.executor().start_waiting();
10138    save.await;
10139    assert_eq!(
10140        editor.update(cx, |editor, cx| editor.text(cx)),
10141        "one\ntwo\nthree\n"
10142    );
10143    assert!(!cx.read(|cx| editor.is_dirty(cx)));
10144
10145    // For non-dirty buffer, no formatting request should be sent
10146    let save = editor
10147        .update_in(cx, |editor, window, cx| {
10148            editor.save(
10149                SaveOptions {
10150                    format: false,
10151                    autosave: false,
10152                },
10153                project.clone(),
10154                window,
10155                cx,
10156            )
10157        })
10158        .unwrap();
10159    let _pending_format_request = fake_server
10160        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
10161            panic!("Should not be invoked");
10162        })
10163        .next();
10164    cx.executor().start_waiting();
10165    save.await;
10166
10167    // Set Rust language override and assert overridden tabsize is sent to language server
10168    update_test_language_settings(cx, |settings| {
10169        settings.languages.0.insert(
10170            "Rust".into(),
10171            LanguageSettingsContent {
10172                tab_size: NonZeroU32::new(8),
10173                ..Default::default()
10174            },
10175        );
10176    });
10177
10178    editor.update_in(cx, |editor, window, cx| {
10179        editor.set_text("somehting_new\n", window, cx)
10180    });
10181    assert!(cx.read(|cx| editor.is_dirty(cx)));
10182    let save = editor
10183        .update_in(cx, |editor, window, cx| {
10184            editor.save(
10185                SaveOptions {
10186                    format: true,
10187                    autosave: false,
10188                },
10189                project.clone(),
10190                window,
10191                cx,
10192            )
10193        })
10194        .unwrap();
10195    fake_server
10196        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
10197            assert_eq!(
10198                params.text_document.uri,
10199                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10200            );
10201            assert_eq!(params.options.tab_size, 8);
10202            Ok(Some(Vec::new()))
10203        })
10204        .next()
10205        .await;
10206    save.await;
10207}
10208
10209#[gpui::test]
10210async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
10211    init_test(cx, |settings| {
10212        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
10213            Formatter::LanguageServer { name: None },
10214        )))
10215    });
10216
10217    let fs = FakeFs::new(cx.executor());
10218    fs.insert_file(path!("/file.rs"), Default::default()).await;
10219
10220    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10221
10222    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10223    language_registry.add(Arc::new(Language::new(
10224        LanguageConfig {
10225            name: "Rust".into(),
10226            matcher: LanguageMatcher {
10227                path_suffixes: vec!["rs".to_string()],
10228                ..Default::default()
10229            },
10230            ..LanguageConfig::default()
10231        },
10232        Some(tree_sitter_rust::LANGUAGE.into()),
10233    )));
10234    update_test_language_settings(cx, |settings| {
10235        // Enable Prettier formatting for the same buffer, and ensure
10236        // LSP is called instead of Prettier.
10237        settings.defaults.prettier = Some(PrettierSettings {
10238            allowed: true,
10239            ..PrettierSettings::default()
10240        });
10241    });
10242    let mut fake_servers = language_registry.register_fake_lsp(
10243        "Rust",
10244        FakeLspAdapter {
10245            capabilities: lsp::ServerCapabilities {
10246                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10247                ..Default::default()
10248            },
10249            ..Default::default()
10250        },
10251    );
10252
10253    let buffer = project
10254        .update(cx, |project, cx| {
10255            project.open_local_buffer(path!("/file.rs"), cx)
10256        })
10257        .await
10258        .unwrap();
10259
10260    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10261    let (editor, cx) = cx.add_window_view(|window, cx| {
10262        build_editor_with_project(project.clone(), buffer, window, cx)
10263    });
10264    editor.update_in(cx, |editor, window, cx| {
10265        editor.set_text("one\ntwo\nthree\n", window, cx)
10266    });
10267
10268    cx.executor().start_waiting();
10269    let fake_server = fake_servers.next().await.unwrap();
10270
10271    let format = editor
10272        .update_in(cx, |editor, window, cx| {
10273            editor.perform_format(
10274                project.clone(),
10275                FormatTrigger::Manual,
10276                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10277                window,
10278                cx,
10279            )
10280        })
10281        .unwrap();
10282    fake_server
10283        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
10284            assert_eq!(
10285                params.text_document.uri,
10286                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10287            );
10288            assert_eq!(params.options.tab_size, 4);
10289            Ok(Some(vec![lsp::TextEdit::new(
10290                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10291                ", ".to_string(),
10292            )]))
10293        })
10294        .next()
10295        .await;
10296    cx.executor().start_waiting();
10297    format.await;
10298    assert_eq!(
10299        editor.update(cx, |editor, cx| editor.text(cx)),
10300        "one, two\nthree\n"
10301    );
10302
10303    editor.update_in(cx, |editor, window, cx| {
10304        editor.set_text("one\ntwo\nthree\n", window, cx)
10305    });
10306    // Ensure we don't lock if formatting hangs.
10307    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10308        move |params, _| async move {
10309            assert_eq!(
10310                params.text_document.uri,
10311                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10312            );
10313            futures::future::pending::<()>().await;
10314            unreachable!()
10315        },
10316    );
10317    let format = editor
10318        .update_in(cx, |editor, window, cx| {
10319            editor.perform_format(
10320                project,
10321                FormatTrigger::Manual,
10322                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10323                window,
10324                cx,
10325            )
10326        })
10327        .unwrap();
10328    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10329    cx.executor().start_waiting();
10330    format.await;
10331    assert_eq!(
10332        editor.update(cx, |editor, cx| editor.text(cx)),
10333        "one\ntwo\nthree\n"
10334    );
10335}
10336
10337#[gpui::test]
10338async fn test_multiple_formatters(cx: &mut TestAppContext) {
10339    init_test(cx, |settings| {
10340        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
10341        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
10342            Formatter::LanguageServer { name: None },
10343            Formatter::CodeActions(
10344                [
10345                    ("code-action-1".into(), true),
10346                    ("code-action-2".into(), true),
10347                ]
10348                .into_iter()
10349                .collect(),
10350            ),
10351        ])))
10352    });
10353
10354    let fs = FakeFs::new(cx.executor());
10355    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
10356        .await;
10357
10358    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10359    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10360    language_registry.add(rust_lang());
10361
10362    let mut fake_servers = language_registry.register_fake_lsp(
10363        "Rust",
10364        FakeLspAdapter {
10365            capabilities: lsp::ServerCapabilities {
10366                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10367                execute_command_provider: Some(lsp::ExecuteCommandOptions {
10368                    commands: vec!["the-command-for-code-action-1".into()],
10369                    ..Default::default()
10370                }),
10371                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10372                ..Default::default()
10373            },
10374            ..Default::default()
10375        },
10376    );
10377
10378    let buffer = project
10379        .update(cx, |project, cx| {
10380            project.open_local_buffer(path!("/file.rs"), cx)
10381        })
10382        .await
10383        .unwrap();
10384
10385    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10386    let (editor, cx) = cx.add_window_view(|window, cx| {
10387        build_editor_with_project(project.clone(), buffer, window, cx)
10388    });
10389
10390    cx.executor().start_waiting();
10391
10392    let fake_server = fake_servers.next().await.unwrap();
10393    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10394        move |_params, _| async move {
10395            Ok(Some(vec![lsp::TextEdit::new(
10396                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10397                "applied-formatting\n".to_string(),
10398            )]))
10399        },
10400    );
10401    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
10402        move |params, _| async move {
10403            assert_eq!(
10404                params.context.only,
10405                Some(vec!["code-action-1".into(), "code-action-2".into()])
10406            );
10407            let uri = lsp::Url::from_file_path(path!("/file.rs")).unwrap();
10408            Ok(Some(vec![
10409                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10410                    kind: Some("code-action-1".into()),
10411                    edit: Some(lsp::WorkspaceEdit::new(
10412                        [(
10413                            uri.clone(),
10414                            vec![lsp::TextEdit::new(
10415                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10416                                "applied-code-action-1-edit\n".to_string(),
10417                            )],
10418                        )]
10419                        .into_iter()
10420                        .collect(),
10421                    )),
10422                    command: Some(lsp::Command {
10423                        command: "the-command-for-code-action-1".into(),
10424                        ..Default::default()
10425                    }),
10426                    ..Default::default()
10427                }),
10428                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10429                    kind: Some("code-action-2".into()),
10430                    edit: Some(lsp::WorkspaceEdit::new(
10431                        [(
10432                            uri.clone(),
10433                            vec![lsp::TextEdit::new(
10434                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10435                                "applied-code-action-2-edit\n".to_string(),
10436                            )],
10437                        )]
10438                        .into_iter()
10439                        .collect(),
10440                    )),
10441                    ..Default::default()
10442                }),
10443            ]))
10444        },
10445    );
10446
10447    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
10448        move |params, _| async move { Ok(params) }
10449    });
10450
10451    let command_lock = Arc::new(futures::lock::Mutex::new(()));
10452    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
10453        let fake = fake_server.clone();
10454        let lock = command_lock.clone();
10455        move |params, _| {
10456            assert_eq!(params.command, "the-command-for-code-action-1");
10457            let fake = fake.clone();
10458            let lock = lock.clone();
10459            async move {
10460                lock.lock().await;
10461                fake.server
10462                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
10463                        label: None,
10464                        edit: lsp::WorkspaceEdit {
10465                            changes: Some(
10466                                [(
10467                                    lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
10468                                    vec![lsp::TextEdit {
10469                                        range: lsp::Range::new(
10470                                            lsp::Position::new(0, 0),
10471                                            lsp::Position::new(0, 0),
10472                                        ),
10473                                        new_text: "applied-code-action-1-command\n".into(),
10474                                    }],
10475                                )]
10476                                .into_iter()
10477                                .collect(),
10478                            ),
10479                            ..Default::default()
10480                        },
10481                    })
10482                    .await
10483                    .into_response()
10484                    .unwrap();
10485                Ok(Some(json!(null)))
10486            }
10487        }
10488    });
10489
10490    cx.executor().start_waiting();
10491    editor
10492        .update_in(cx, |editor, window, cx| {
10493            editor.perform_format(
10494                project.clone(),
10495                FormatTrigger::Manual,
10496                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10497                window,
10498                cx,
10499            )
10500        })
10501        .unwrap()
10502        .await;
10503    editor.update(cx, |editor, cx| {
10504        assert_eq!(
10505            editor.text(cx),
10506            r#"
10507                applied-code-action-2-edit
10508                applied-code-action-1-command
10509                applied-code-action-1-edit
10510                applied-formatting
10511                one
10512                two
10513                three
10514            "#
10515            .unindent()
10516        );
10517    });
10518
10519    editor.update_in(cx, |editor, window, cx| {
10520        editor.undo(&Default::default(), window, cx);
10521        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10522    });
10523
10524    // Perform a manual edit while waiting for an LSP command
10525    // that's being run as part of a formatting code action.
10526    let lock_guard = command_lock.lock().await;
10527    let format = editor
10528        .update_in(cx, |editor, window, cx| {
10529            editor.perform_format(
10530                project.clone(),
10531                FormatTrigger::Manual,
10532                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10533                window,
10534                cx,
10535            )
10536        })
10537        .unwrap();
10538    cx.run_until_parked();
10539    editor.update(cx, |editor, cx| {
10540        assert_eq!(
10541            editor.text(cx),
10542            r#"
10543                applied-code-action-1-edit
10544                applied-formatting
10545                one
10546                two
10547                three
10548            "#
10549            .unindent()
10550        );
10551
10552        editor.buffer.update(cx, |buffer, cx| {
10553            let ix = buffer.len(cx);
10554            buffer.edit([(ix..ix, "edited\n")], None, cx);
10555        });
10556    });
10557
10558    // Allow the LSP command to proceed. Because the buffer was edited,
10559    // the second code action will not be run.
10560    drop(lock_guard);
10561    format.await;
10562    editor.update_in(cx, |editor, window, cx| {
10563        assert_eq!(
10564            editor.text(cx),
10565            r#"
10566                applied-code-action-1-command
10567                applied-code-action-1-edit
10568                applied-formatting
10569                one
10570                two
10571                three
10572                edited
10573            "#
10574            .unindent()
10575        );
10576
10577        // The manual edit is undone first, because it is the last thing the user did
10578        // (even though the command completed afterwards).
10579        editor.undo(&Default::default(), window, cx);
10580        assert_eq!(
10581            editor.text(cx),
10582            r#"
10583                applied-code-action-1-command
10584                applied-code-action-1-edit
10585                applied-formatting
10586                one
10587                two
10588                three
10589            "#
10590            .unindent()
10591        );
10592
10593        // All the formatting (including the command, which completed after the manual edit)
10594        // is undone together.
10595        editor.undo(&Default::default(), window, cx);
10596        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10597    });
10598}
10599
10600#[gpui::test]
10601async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
10602    init_test(cx, |settings| {
10603        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
10604            Formatter::LanguageServer { name: None },
10605        ])))
10606    });
10607
10608    let fs = FakeFs::new(cx.executor());
10609    fs.insert_file(path!("/file.ts"), Default::default()).await;
10610
10611    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10612
10613    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10614    language_registry.add(Arc::new(Language::new(
10615        LanguageConfig {
10616            name: "TypeScript".into(),
10617            matcher: LanguageMatcher {
10618                path_suffixes: vec!["ts".to_string()],
10619                ..Default::default()
10620            },
10621            ..LanguageConfig::default()
10622        },
10623        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
10624    )));
10625    update_test_language_settings(cx, |settings| {
10626        settings.defaults.prettier = Some(PrettierSettings {
10627            allowed: true,
10628            ..PrettierSettings::default()
10629        });
10630    });
10631    let mut fake_servers = language_registry.register_fake_lsp(
10632        "TypeScript",
10633        FakeLspAdapter {
10634            capabilities: lsp::ServerCapabilities {
10635                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10636                ..Default::default()
10637            },
10638            ..Default::default()
10639        },
10640    );
10641
10642    let buffer = project
10643        .update(cx, |project, cx| {
10644            project.open_local_buffer(path!("/file.ts"), cx)
10645        })
10646        .await
10647        .unwrap();
10648
10649    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10650    let (editor, cx) = cx.add_window_view(|window, cx| {
10651        build_editor_with_project(project.clone(), buffer, window, cx)
10652    });
10653    editor.update_in(cx, |editor, window, cx| {
10654        editor.set_text(
10655            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
10656            window,
10657            cx,
10658        )
10659    });
10660
10661    cx.executor().start_waiting();
10662    let fake_server = fake_servers.next().await.unwrap();
10663
10664    let format = editor
10665        .update_in(cx, |editor, window, cx| {
10666            editor.perform_code_action_kind(
10667                project.clone(),
10668                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
10669                window,
10670                cx,
10671            )
10672        })
10673        .unwrap();
10674    fake_server
10675        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
10676            assert_eq!(
10677                params.text_document.uri,
10678                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
10679            );
10680            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
10681                lsp::CodeAction {
10682                    title: "Organize Imports".to_string(),
10683                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
10684                    edit: Some(lsp::WorkspaceEdit {
10685                        changes: Some(
10686                            [(
10687                                params.text_document.uri.clone(),
10688                                vec![lsp::TextEdit::new(
10689                                    lsp::Range::new(
10690                                        lsp::Position::new(1, 0),
10691                                        lsp::Position::new(2, 0),
10692                                    ),
10693                                    "".to_string(),
10694                                )],
10695                            )]
10696                            .into_iter()
10697                            .collect(),
10698                        ),
10699                        ..Default::default()
10700                    }),
10701                    ..Default::default()
10702                },
10703            )]))
10704        })
10705        .next()
10706        .await;
10707    cx.executor().start_waiting();
10708    format.await;
10709    assert_eq!(
10710        editor.update(cx, |editor, cx| editor.text(cx)),
10711        "import { a } from 'module';\n\nconst x = a;\n"
10712    );
10713
10714    editor.update_in(cx, |editor, window, cx| {
10715        editor.set_text(
10716            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
10717            window,
10718            cx,
10719        )
10720    });
10721    // Ensure we don't lock if code action hangs.
10722    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
10723        move |params, _| async move {
10724            assert_eq!(
10725                params.text_document.uri,
10726                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
10727            );
10728            futures::future::pending::<()>().await;
10729            unreachable!()
10730        },
10731    );
10732    let format = editor
10733        .update_in(cx, |editor, window, cx| {
10734            editor.perform_code_action_kind(
10735                project,
10736                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
10737                window,
10738                cx,
10739            )
10740        })
10741        .unwrap();
10742    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
10743    cx.executor().start_waiting();
10744    format.await;
10745    assert_eq!(
10746        editor.update(cx, |editor, cx| editor.text(cx)),
10747        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
10748    );
10749}
10750
10751#[gpui::test]
10752async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
10753    init_test(cx, |_| {});
10754
10755    let mut cx = EditorLspTestContext::new_rust(
10756        lsp::ServerCapabilities {
10757            document_formatting_provider: Some(lsp::OneOf::Left(true)),
10758            ..Default::default()
10759        },
10760        cx,
10761    )
10762    .await;
10763
10764    cx.set_state(indoc! {"
10765        one.twoˇ
10766    "});
10767
10768    // The format request takes a long time. When it completes, it inserts
10769    // a newline and an indent before the `.`
10770    cx.lsp
10771        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
10772            let executor = cx.background_executor().clone();
10773            async move {
10774                executor.timer(Duration::from_millis(100)).await;
10775                Ok(Some(vec![lsp::TextEdit {
10776                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
10777                    new_text: "\n    ".into(),
10778                }]))
10779            }
10780        });
10781
10782    // Submit a format request.
10783    let format_1 = cx
10784        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10785        .unwrap();
10786    cx.executor().run_until_parked();
10787
10788    // Submit a second format request.
10789    let format_2 = cx
10790        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10791        .unwrap();
10792    cx.executor().run_until_parked();
10793
10794    // Wait for both format requests to complete
10795    cx.executor().advance_clock(Duration::from_millis(200));
10796    cx.executor().start_waiting();
10797    format_1.await.unwrap();
10798    cx.executor().start_waiting();
10799    format_2.await.unwrap();
10800
10801    // The formatting edits only happens once.
10802    cx.assert_editor_state(indoc! {"
10803        one
10804            .twoˇ
10805    "});
10806}
10807
10808#[gpui::test]
10809async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
10810    init_test(cx, |settings| {
10811        settings.defaults.formatter = Some(SelectedFormatter::Auto)
10812    });
10813
10814    let mut cx = EditorLspTestContext::new_rust(
10815        lsp::ServerCapabilities {
10816            document_formatting_provider: Some(lsp::OneOf::Left(true)),
10817            ..Default::default()
10818        },
10819        cx,
10820    )
10821    .await;
10822
10823    // Set up a buffer white some trailing whitespace and no trailing newline.
10824    cx.set_state(
10825        &[
10826            "one ",   //
10827            "twoˇ",   //
10828            "three ", //
10829            "four",   //
10830        ]
10831        .join("\n"),
10832    );
10833
10834    // Submit a format request.
10835    let format = cx
10836        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10837        .unwrap();
10838
10839    // Record which buffer changes have been sent to the language server
10840    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
10841    cx.lsp
10842        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
10843            let buffer_changes = buffer_changes.clone();
10844            move |params, _| {
10845                buffer_changes.lock().extend(
10846                    params
10847                        .content_changes
10848                        .into_iter()
10849                        .map(|e| (e.range.unwrap(), e.text)),
10850                );
10851            }
10852        });
10853
10854    // Handle formatting requests to the language server.
10855    cx.lsp
10856        .set_request_handler::<lsp::request::Formatting, _, _>({
10857            let buffer_changes = buffer_changes.clone();
10858            move |_, _| {
10859                // When formatting is requested, trailing whitespace has already been stripped,
10860                // and the trailing newline has already been added.
10861                assert_eq!(
10862                    &buffer_changes.lock()[1..],
10863                    &[
10864                        (
10865                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
10866                            "".into()
10867                        ),
10868                        (
10869                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
10870                            "".into()
10871                        ),
10872                        (
10873                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
10874                            "\n".into()
10875                        ),
10876                    ]
10877                );
10878
10879                // Insert blank lines between each line of the buffer.
10880                async move {
10881                    Ok(Some(vec![
10882                        lsp::TextEdit {
10883                            range: lsp::Range::new(
10884                                lsp::Position::new(1, 0),
10885                                lsp::Position::new(1, 0),
10886                            ),
10887                            new_text: "\n".into(),
10888                        },
10889                        lsp::TextEdit {
10890                            range: lsp::Range::new(
10891                                lsp::Position::new(2, 0),
10892                                lsp::Position::new(2, 0),
10893                            ),
10894                            new_text: "\n".into(),
10895                        },
10896                    ]))
10897                }
10898            }
10899        });
10900
10901    // After formatting the buffer, the trailing whitespace is stripped,
10902    // a newline is appended, and the edits provided by the language server
10903    // have been applied.
10904    format.await.unwrap();
10905    cx.assert_editor_state(
10906        &[
10907            "one",   //
10908            "",      //
10909            "twoˇ",  //
10910            "",      //
10911            "three", //
10912            "four",  //
10913            "",      //
10914        ]
10915        .join("\n"),
10916    );
10917
10918    // Undoing the formatting undoes the trailing whitespace removal, the
10919    // trailing newline, and the LSP edits.
10920    cx.update_buffer(|buffer, cx| buffer.undo(cx));
10921    cx.assert_editor_state(
10922        &[
10923            "one ",   //
10924            "twoˇ",   //
10925            "three ", //
10926            "four",   //
10927        ]
10928        .join("\n"),
10929    );
10930}
10931
10932#[gpui::test]
10933async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
10934    cx: &mut TestAppContext,
10935) {
10936    init_test(cx, |_| {});
10937
10938    cx.update(|cx| {
10939        cx.update_global::<SettingsStore, _>(|settings, cx| {
10940            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10941                settings.auto_signature_help = Some(true);
10942            });
10943        });
10944    });
10945
10946    let mut cx = EditorLspTestContext::new_rust(
10947        lsp::ServerCapabilities {
10948            signature_help_provider: Some(lsp::SignatureHelpOptions {
10949                ..Default::default()
10950            }),
10951            ..Default::default()
10952        },
10953        cx,
10954    )
10955    .await;
10956
10957    let language = Language::new(
10958        LanguageConfig {
10959            name: "Rust".into(),
10960            brackets: BracketPairConfig {
10961                pairs: vec![
10962                    BracketPair {
10963                        start: "{".to_string(),
10964                        end: "}".to_string(),
10965                        close: true,
10966                        surround: true,
10967                        newline: true,
10968                    },
10969                    BracketPair {
10970                        start: "(".to_string(),
10971                        end: ")".to_string(),
10972                        close: true,
10973                        surround: true,
10974                        newline: true,
10975                    },
10976                    BracketPair {
10977                        start: "/*".to_string(),
10978                        end: " */".to_string(),
10979                        close: true,
10980                        surround: true,
10981                        newline: true,
10982                    },
10983                    BracketPair {
10984                        start: "[".to_string(),
10985                        end: "]".to_string(),
10986                        close: false,
10987                        surround: false,
10988                        newline: true,
10989                    },
10990                    BracketPair {
10991                        start: "\"".to_string(),
10992                        end: "\"".to_string(),
10993                        close: true,
10994                        surround: true,
10995                        newline: false,
10996                    },
10997                    BracketPair {
10998                        start: "<".to_string(),
10999                        end: ">".to_string(),
11000                        close: false,
11001                        surround: true,
11002                        newline: true,
11003                    },
11004                ],
11005                ..Default::default()
11006            },
11007            autoclose_before: "})]".to_string(),
11008            ..Default::default()
11009        },
11010        Some(tree_sitter_rust::LANGUAGE.into()),
11011    );
11012    let language = Arc::new(language);
11013
11014    cx.language_registry().add(language.clone());
11015    cx.update_buffer(|buffer, cx| {
11016        buffer.set_language(Some(language), cx);
11017    });
11018
11019    cx.set_state(
11020        &r#"
11021            fn main() {
11022                sampleˇ
11023            }
11024        "#
11025        .unindent(),
11026    );
11027
11028    cx.update_editor(|editor, window, cx| {
11029        editor.handle_input("(", window, cx);
11030    });
11031    cx.assert_editor_state(
11032        &"
11033            fn main() {
11034                sample(ˇ)
11035            }
11036        "
11037        .unindent(),
11038    );
11039
11040    let mocked_response = lsp::SignatureHelp {
11041        signatures: vec![lsp::SignatureInformation {
11042            label: "fn sample(param1: u8, param2: u8)".to_string(),
11043            documentation: None,
11044            parameters: Some(vec![
11045                lsp::ParameterInformation {
11046                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11047                    documentation: None,
11048                },
11049                lsp::ParameterInformation {
11050                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11051                    documentation: None,
11052                },
11053            ]),
11054            active_parameter: None,
11055        }],
11056        active_signature: Some(0),
11057        active_parameter: Some(0),
11058    };
11059    handle_signature_help_request(&mut cx, mocked_response).await;
11060
11061    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11062        .await;
11063
11064    cx.editor(|editor, _, _| {
11065        let signature_help_state = editor.signature_help_state.popover().cloned();
11066        let signature = signature_help_state.unwrap();
11067        assert_eq!(
11068            signature.signatures[signature.current_signature].label,
11069            "fn sample(param1: u8, param2: u8)"
11070        );
11071    });
11072}
11073
11074#[gpui::test]
11075async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
11076    init_test(cx, |_| {});
11077
11078    cx.update(|cx| {
11079        cx.update_global::<SettingsStore, _>(|settings, cx| {
11080            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11081                settings.auto_signature_help = Some(false);
11082                settings.show_signature_help_after_edits = Some(false);
11083            });
11084        });
11085    });
11086
11087    let mut cx = EditorLspTestContext::new_rust(
11088        lsp::ServerCapabilities {
11089            signature_help_provider: Some(lsp::SignatureHelpOptions {
11090                ..Default::default()
11091            }),
11092            ..Default::default()
11093        },
11094        cx,
11095    )
11096    .await;
11097
11098    let language = Language::new(
11099        LanguageConfig {
11100            name: "Rust".into(),
11101            brackets: BracketPairConfig {
11102                pairs: vec![
11103                    BracketPair {
11104                        start: "{".to_string(),
11105                        end: "}".to_string(),
11106                        close: true,
11107                        surround: true,
11108                        newline: true,
11109                    },
11110                    BracketPair {
11111                        start: "(".to_string(),
11112                        end: ")".to_string(),
11113                        close: true,
11114                        surround: true,
11115                        newline: true,
11116                    },
11117                    BracketPair {
11118                        start: "/*".to_string(),
11119                        end: " */".to_string(),
11120                        close: true,
11121                        surround: true,
11122                        newline: true,
11123                    },
11124                    BracketPair {
11125                        start: "[".to_string(),
11126                        end: "]".to_string(),
11127                        close: false,
11128                        surround: false,
11129                        newline: true,
11130                    },
11131                    BracketPair {
11132                        start: "\"".to_string(),
11133                        end: "\"".to_string(),
11134                        close: true,
11135                        surround: true,
11136                        newline: false,
11137                    },
11138                    BracketPair {
11139                        start: "<".to_string(),
11140                        end: ">".to_string(),
11141                        close: false,
11142                        surround: true,
11143                        newline: true,
11144                    },
11145                ],
11146                ..Default::default()
11147            },
11148            autoclose_before: "})]".to_string(),
11149            ..Default::default()
11150        },
11151        Some(tree_sitter_rust::LANGUAGE.into()),
11152    );
11153    let language = Arc::new(language);
11154
11155    cx.language_registry().add(language.clone());
11156    cx.update_buffer(|buffer, cx| {
11157        buffer.set_language(Some(language), cx);
11158    });
11159
11160    // Ensure that signature_help is not called when no signature help is enabled.
11161    cx.set_state(
11162        &r#"
11163            fn main() {
11164                sampleˇ
11165            }
11166        "#
11167        .unindent(),
11168    );
11169    cx.update_editor(|editor, window, cx| {
11170        editor.handle_input("(", window, cx);
11171    });
11172    cx.assert_editor_state(
11173        &"
11174            fn main() {
11175                sample(ˇ)
11176            }
11177        "
11178        .unindent(),
11179    );
11180    cx.editor(|editor, _, _| {
11181        assert!(editor.signature_help_state.task().is_none());
11182    });
11183
11184    let mocked_response = lsp::SignatureHelp {
11185        signatures: vec![lsp::SignatureInformation {
11186            label: "fn sample(param1: u8, param2: u8)".to_string(),
11187            documentation: None,
11188            parameters: Some(vec![
11189                lsp::ParameterInformation {
11190                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11191                    documentation: None,
11192                },
11193                lsp::ParameterInformation {
11194                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11195                    documentation: None,
11196                },
11197            ]),
11198            active_parameter: None,
11199        }],
11200        active_signature: Some(0),
11201        active_parameter: Some(0),
11202    };
11203
11204    // Ensure that signature_help is called when enabled afte edits
11205    cx.update(|_, cx| {
11206        cx.update_global::<SettingsStore, _>(|settings, cx| {
11207            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11208                settings.auto_signature_help = Some(false);
11209                settings.show_signature_help_after_edits = Some(true);
11210            });
11211        });
11212    });
11213    cx.set_state(
11214        &r#"
11215            fn main() {
11216                sampleˇ
11217            }
11218        "#
11219        .unindent(),
11220    );
11221    cx.update_editor(|editor, window, cx| {
11222        editor.handle_input("(", window, cx);
11223    });
11224    cx.assert_editor_state(
11225        &"
11226            fn main() {
11227                sample(ˇ)
11228            }
11229        "
11230        .unindent(),
11231    );
11232    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11233    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11234        .await;
11235    cx.update_editor(|editor, _, _| {
11236        let signature_help_state = editor.signature_help_state.popover().cloned();
11237        assert!(signature_help_state.is_some());
11238        let signature = signature_help_state.unwrap();
11239        assert_eq!(
11240            signature.signatures[signature.current_signature].label,
11241            "fn sample(param1: u8, param2: u8)"
11242        );
11243        editor.signature_help_state = SignatureHelpState::default();
11244    });
11245
11246    // Ensure that signature_help is called when auto signature help override is enabled
11247    cx.update(|_, cx| {
11248        cx.update_global::<SettingsStore, _>(|settings, cx| {
11249            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11250                settings.auto_signature_help = Some(true);
11251                settings.show_signature_help_after_edits = Some(false);
11252            });
11253        });
11254    });
11255    cx.set_state(
11256        &r#"
11257            fn main() {
11258                sampleˇ
11259            }
11260        "#
11261        .unindent(),
11262    );
11263    cx.update_editor(|editor, window, cx| {
11264        editor.handle_input("(", window, cx);
11265    });
11266    cx.assert_editor_state(
11267        &"
11268            fn main() {
11269                sample(ˇ)
11270            }
11271        "
11272        .unindent(),
11273    );
11274    handle_signature_help_request(&mut cx, mocked_response).await;
11275    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11276        .await;
11277    cx.editor(|editor, _, _| {
11278        let signature_help_state = editor.signature_help_state.popover().cloned();
11279        assert!(signature_help_state.is_some());
11280        let signature = signature_help_state.unwrap();
11281        assert_eq!(
11282            signature.signatures[signature.current_signature].label,
11283            "fn sample(param1: u8, param2: u8)"
11284        );
11285    });
11286}
11287
11288#[gpui::test]
11289async fn test_signature_help(cx: &mut TestAppContext) {
11290    init_test(cx, |_| {});
11291    cx.update(|cx| {
11292        cx.update_global::<SettingsStore, _>(|settings, cx| {
11293            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11294                settings.auto_signature_help = Some(true);
11295            });
11296        });
11297    });
11298
11299    let mut cx = EditorLspTestContext::new_rust(
11300        lsp::ServerCapabilities {
11301            signature_help_provider: Some(lsp::SignatureHelpOptions {
11302                ..Default::default()
11303            }),
11304            ..Default::default()
11305        },
11306        cx,
11307    )
11308    .await;
11309
11310    // A test that directly calls `show_signature_help`
11311    cx.update_editor(|editor, window, cx| {
11312        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11313    });
11314
11315    let mocked_response = lsp::SignatureHelp {
11316        signatures: vec![lsp::SignatureInformation {
11317            label: "fn sample(param1: u8, param2: u8)".to_string(),
11318            documentation: None,
11319            parameters: Some(vec![
11320                lsp::ParameterInformation {
11321                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11322                    documentation: None,
11323                },
11324                lsp::ParameterInformation {
11325                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11326                    documentation: None,
11327                },
11328            ]),
11329            active_parameter: None,
11330        }],
11331        active_signature: Some(0),
11332        active_parameter: Some(0),
11333    };
11334    handle_signature_help_request(&mut cx, mocked_response).await;
11335
11336    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11337        .await;
11338
11339    cx.editor(|editor, _, _| {
11340        let signature_help_state = editor.signature_help_state.popover().cloned();
11341        assert!(signature_help_state.is_some());
11342        let signature = signature_help_state.unwrap();
11343        assert_eq!(
11344            signature.signatures[signature.current_signature].label,
11345            "fn sample(param1: u8, param2: u8)"
11346        );
11347    });
11348
11349    // When exiting outside from inside the brackets, `signature_help` is closed.
11350    cx.set_state(indoc! {"
11351        fn main() {
11352            sample(ˇ);
11353        }
11354
11355        fn sample(param1: u8, param2: u8) {}
11356    "});
11357
11358    cx.update_editor(|editor, window, cx| {
11359        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11360            s.select_ranges([0..0])
11361        });
11362    });
11363
11364    let mocked_response = lsp::SignatureHelp {
11365        signatures: Vec::new(),
11366        active_signature: None,
11367        active_parameter: None,
11368    };
11369    handle_signature_help_request(&mut cx, mocked_response).await;
11370
11371    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11372        .await;
11373
11374    cx.editor(|editor, _, _| {
11375        assert!(!editor.signature_help_state.is_shown());
11376    });
11377
11378    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
11379    cx.set_state(indoc! {"
11380        fn main() {
11381            sample(ˇ);
11382        }
11383
11384        fn sample(param1: u8, param2: u8) {}
11385    "});
11386
11387    let mocked_response = lsp::SignatureHelp {
11388        signatures: vec![lsp::SignatureInformation {
11389            label: "fn sample(param1: u8, param2: u8)".to_string(),
11390            documentation: None,
11391            parameters: Some(vec![
11392                lsp::ParameterInformation {
11393                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11394                    documentation: None,
11395                },
11396                lsp::ParameterInformation {
11397                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11398                    documentation: None,
11399                },
11400            ]),
11401            active_parameter: None,
11402        }],
11403        active_signature: Some(0),
11404        active_parameter: Some(0),
11405    };
11406    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11407    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11408        .await;
11409    cx.editor(|editor, _, _| {
11410        assert!(editor.signature_help_state.is_shown());
11411    });
11412
11413    // Restore the popover with more parameter input
11414    cx.set_state(indoc! {"
11415        fn main() {
11416            sample(param1, param2ˇ);
11417        }
11418
11419        fn sample(param1: u8, param2: u8) {}
11420    "});
11421
11422    let mocked_response = lsp::SignatureHelp {
11423        signatures: vec![lsp::SignatureInformation {
11424            label: "fn sample(param1: u8, param2: u8)".to_string(),
11425            documentation: None,
11426            parameters: Some(vec![
11427                lsp::ParameterInformation {
11428                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11429                    documentation: None,
11430                },
11431                lsp::ParameterInformation {
11432                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11433                    documentation: None,
11434                },
11435            ]),
11436            active_parameter: None,
11437        }],
11438        active_signature: Some(0),
11439        active_parameter: Some(1),
11440    };
11441    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11442    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11443        .await;
11444
11445    // When selecting a range, the popover is gone.
11446    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
11447    cx.update_editor(|editor, window, cx| {
11448        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11449            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11450        })
11451    });
11452    cx.assert_editor_state(indoc! {"
11453        fn main() {
11454            sample(param1, «ˇparam2»);
11455        }
11456
11457        fn sample(param1: u8, param2: u8) {}
11458    "});
11459    cx.editor(|editor, _, _| {
11460        assert!(!editor.signature_help_state.is_shown());
11461    });
11462
11463    // When unselecting again, the popover is back if within the brackets.
11464    cx.update_editor(|editor, window, cx| {
11465        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11466            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11467        })
11468    });
11469    cx.assert_editor_state(indoc! {"
11470        fn main() {
11471            sample(param1, ˇparam2);
11472        }
11473
11474        fn sample(param1: u8, param2: u8) {}
11475    "});
11476    handle_signature_help_request(&mut cx, mocked_response).await;
11477    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11478        .await;
11479    cx.editor(|editor, _, _| {
11480        assert!(editor.signature_help_state.is_shown());
11481    });
11482
11483    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
11484    cx.update_editor(|editor, window, cx| {
11485        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11486            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
11487            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11488        })
11489    });
11490    cx.assert_editor_state(indoc! {"
11491        fn main() {
11492            sample(param1, ˇparam2);
11493        }
11494
11495        fn sample(param1: u8, param2: u8) {}
11496    "});
11497
11498    let mocked_response = lsp::SignatureHelp {
11499        signatures: vec![lsp::SignatureInformation {
11500            label: "fn sample(param1: u8, param2: u8)".to_string(),
11501            documentation: None,
11502            parameters: Some(vec![
11503                lsp::ParameterInformation {
11504                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11505                    documentation: None,
11506                },
11507                lsp::ParameterInformation {
11508                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11509                    documentation: None,
11510                },
11511            ]),
11512            active_parameter: None,
11513        }],
11514        active_signature: Some(0),
11515        active_parameter: Some(1),
11516    };
11517    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11518    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11519        .await;
11520    cx.update_editor(|editor, _, cx| {
11521        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
11522    });
11523    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11524        .await;
11525    cx.update_editor(|editor, window, cx| {
11526        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11527            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11528        })
11529    });
11530    cx.assert_editor_state(indoc! {"
11531        fn main() {
11532            sample(param1, «ˇparam2»);
11533        }
11534
11535        fn sample(param1: u8, param2: u8) {}
11536    "});
11537    cx.update_editor(|editor, window, cx| {
11538        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11539            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11540        })
11541    });
11542    cx.assert_editor_state(indoc! {"
11543        fn main() {
11544            sample(param1, ˇparam2);
11545        }
11546
11547        fn sample(param1: u8, param2: u8) {}
11548    "});
11549    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
11550        .await;
11551}
11552
11553#[gpui::test]
11554async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
11555    init_test(cx, |_| {});
11556
11557    let mut cx = EditorLspTestContext::new_rust(
11558        lsp::ServerCapabilities {
11559            signature_help_provider: Some(lsp::SignatureHelpOptions {
11560                ..Default::default()
11561            }),
11562            ..Default::default()
11563        },
11564        cx,
11565    )
11566    .await;
11567
11568    cx.set_state(indoc! {"
11569        fn main() {
11570            overloadedˇ
11571        }
11572    "});
11573
11574    cx.update_editor(|editor, window, cx| {
11575        editor.handle_input("(", window, cx);
11576        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11577    });
11578
11579    // Mock response with 3 signatures
11580    let mocked_response = lsp::SignatureHelp {
11581        signatures: vec![
11582            lsp::SignatureInformation {
11583                label: "fn overloaded(x: i32)".to_string(),
11584                documentation: None,
11585                parameters: Some(vec![lsp::ParameterInformation {
11586                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11587                    documentation: None,
11588                }]),
11589                active_parameter: None,
11590            },
11591            lsp::SignatureInformation {
11592                label: "fn overloaded(x: i32, y: i32)".to_string(),
11593                documentation: None,
11594                parameters: Some(vec![
11595                    lsp::ParameterInformation {
11596                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11597                        documentation: None,
11598                    },
11599                    lsp::ParameterInformation {
11600                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
11601                        documentation: None,
11602                    },
11603                ]),
11604                active_parameter: None,
11605            },
11606            lsp::SignatureInformation {
11607                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
11608                documentation: None,
11609                parameters: Some(vec![
11610                    lsp::ParameterInformation {
11611                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11612                        documentation: None,
11613                    },
11614                    lsp::ParameterInformation {
11615                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
11616                        documentation: None,
11617                    },
11618                    lsp::ParameterInformation {
11619                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
11620                        documentation: None,
11621                    },
11622                ]),
11623                active_parameter: None,
11624            },
11625        ],
11626        active_signature: Some(1),
11627        active_parameter: Some(0),
11628    };
11629    handle_signature_help_request(&mut cx, mocked_response).await;
11630
11631    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11632        .await;
11633
11634    // Verify we have multiple signatures and the right one is selected
11635    cx.editor(|editor, _, _| {
11636        let popover = editor.signature_help_state.popover().cloned().unwrap();
11637        assert_eq!(popover.signatures.len(), 3);
11638        // active_signature was 1, so that should be the current
11639        assert_eq!(popover.current_signature, 1);
11640        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
11641        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
11642        assert_eq!(
11643            popover.signatures[2].label,
11644            "fn overloaded(x: i32, y: i32, z: i32)"
11645        );
11646    });
11647
11648    // Test navigation functionality
11649    cx.update_editor(|editor, window, cx| {
11650        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
11651    });
11652
11653    cx.editor(|editor, _, _| {
11654        let popover = editor.signature_help_state.popover().cloned().unwrap();
11655        assert_eq!(popover.current_signature, 2);
11656    });
11657
11658    // Test wrap around
11659    cx.update_editor(|editor, window, cx| {
11660        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
11661    });
11662
11663    cx.editor(|editor, _, _| {
11664        let popover = editor.signature_help_state.popover().cloned().unwrap();
11665        assert_eq!(popover.current_signature, 0);
11666    });
11667
11668    // Test previous navigation
11669    cx.update_editor(|editor, window, cx| {
11670        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
11671    });
11672
11673    cx.editor(|editor, _, _| {
11674        let popover = editor.signature_help_state.popover().cloned().unwrap();
11675        assert_eq!(popover.current_signature, 2);
11676    });
11677}
11678
11679#[gpui::test]
11680async fn test_completion_mode(cx: &mut TestAppContext) {
11681    init_test(cx, |_| {});
11682    let mut cx = EditorLspTestContext::new_rust(
11683        lsp::ServerCapabilities {
11684            completion_provider: Some(lsp::CompletionOptions {
11685                resolve_provider: Some(true),
11686                ..Default::default()
11687            }),
11688            ..Default::default()
11689        },
11690        cx,
11691    )
11692    .await;
11693
11694    struct Run {
11695        run_description: &'static str,
11696        initial_state: String,
11697        buffer_marked_text: String,
11698        completion_label: &'static str,
11699        completion_text: &'static str,
11700        expected_with_insert_mode: String,
11701        expected_with_replace_mode: String,
11702        expected_with_replace_subsequence_mode: String,
11703        expected_with_replace_suffix_mode: String,
11704    }
11705
11706    let runs = [
11707        Run {
11708            run_description: "Start of word matches completion text",
11709            initial_state: "before ediˇ after".into(),
11710            buffer_marked_text: "before <edi|> after".into(),
11711            completion_label: "editor",
11712            completion_text: "editor",
11713            expected_with_insert_mode: "before editorˇ after".into(),
11714            expected_with_replace_mode: "before editorˇ after".into(),
11715            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11716            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11717        },
11718        Run {
11719            run_description: "Accept same text at the middle of the word",
11720            initial_state: "before ediˇtor after".into(),
11721            buffer_marked_text: "before <edi|tor> after".into(),
11722            completion_label: "editor",
11723            completion_text: "editor",
11724            expected_with_insert_mode: "before editorˇtor after".into(),
11725            expected_with_replace_mode: "before editorˇ after".into(),
11726            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11727            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11728        },
11729        Run {
11730            run_description: "End of word matches completion text -- cursor at end",
11731            initial_state: "before torˇ after".into(),
11732            buffer_marked_text: "before <tor|> after".into(),
11733            completion_label: "editor",
11734            completion_text: "editor",
11735            expected_with_insert_mode: "before editorˇ after".into(),
11736            expected_with_replace_mode: "before editorˇ after".into(),
11737            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11738            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11739        },
11740        Run {
11741            run_description: "End of word matches completion text -- cursor at start",
11742            initial_state: "before ˇtor after".into(),
11743            buffer_marked_text: "before <|tor> after".into(),
11744            completion_label: "editor",
11745            completion_text: "editor",
11746            expected_with_insert_mode: "before editorˇtor after".into(),
11747            expected_with_replace_mode: "before editorˇ after".into(),
11748            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11749            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11750        },
11751        Run {
11752            run_description: "Prepend text containing whitespace",
11753            initial_state: "pˇfield: bool".into(),
11754            buffer_marked_text: "<p|field>: bool".into(),
11755            completion_label: "pub ",
11756            completion_text: "pub ",
11757            expected_with_insert_mode: "pub ˇfield: bool".into(),
11758            expected_with_replace_mode: "pub ˇ: bool".into(),
11759            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
11760            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
11761        },
11762        Run {
11763            run_description: "Add element to start of list",
11764            initial_state: "[element_ˇelement_2]".into(),
11765            buffer_marked_text: "[<element_|element_2>]".into(),
11766            completion_label: "element_1",
11767            completion_text: "element_1",
11768            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
11769            expected_with_replace_mode: "[element_1ˇ]".into(),
11770            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
11771            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
11772        },
11773        Run {
11774            run_description: "Add element to start of list -- first and second elements are equal",
11775            initial_state: "[elˇelement]".into(),
11776            buffer_marked_text: "[<el|element>]".into(),
11777            completion_label: "element",
11778            completion_text: "element",
11779            expected_with_insert_mode: "[elementˇelement]".into(),
11780            expected_with_replace_mode: "[elementˇ]".into(),
11781            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
11782            expected_with_replace_suffix_mode: "[elementˇ]".into(),
11783        },
11784        Run {
11785            run_description: "Ends with matching suffix",
11786            initial_state: "SubˇError".into(),
11787            buffer_marked_text: "<Sub|Error>".into(),
11788            completion_label: "SubscriptionError",
11789            completion_text: "SubscriptionError",
11790            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
11791            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11792            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11793            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
11794        },
11795        Run {
11796            run_description: "Suffix is a subsequence -- contiguous",
11797            initial_state: "SubˇErr".into(),
11798            buffer_marked_text: "<Sub|Err>".into(),
11799            completion_label: "SubscriptionError",
11800            completion_text: "SubscriptionError",
11801            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
11802            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11803            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11804            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
11805        },
11806        Run {
11807            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
11808            initial_state: "Suˇscrirr".into(),
11809            buffer_marked_text: "<Su|scrirr>".into(),
11810            completion_label: "SubscriptionError",
11811            completion_text: "SubscriptionError",
11812            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
11813            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11814            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11815            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
11816        },
11817        Run {
11818            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
11819            initial_state: "foo(indˇix)".into(),
11820            buffer_marked_text: "foo(<ind|ix>)".into(),
11821            completion_label: "node_index",
11822            completion_text: "node_index",
11823            expected_with_insert_mode: "foo(node_indexˇix)".into(),
11824            expected_with_replace_mode: "foo(node_indexˇ)".into(),
11825            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
11826            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
11827        },
11828        Run {
11829            run_description: "Replace range ends before cursor - should extend to cursor",
11830            initial_state: "before editˇo after".into(),
11831            buffer_marked_text: "before <{ed}>it|o after".into(),
11832            completion_label: "editor",
11833            completion_text: "editor",
11834            expected_with_insert_mode: "before editorˇo after".into(),
11835            expected_with_replace_mode: "before editorˇo after".into(),
11836            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
11837            expected_with_replace_suffix_mode: "before editorˇo after".into(),
11838        },
11839        Run {
11840            run_description: "Uses label for suffix matching",
11841            initial_state: "before ediˇtor after".into(),
11842            buffer_marked_text: "before <edi|tor> after".into(),
11843            completion_label: "editor",
11844            completion_text: "editor()",
11845            expected_with_insert_mode: "before editor()ˇtor after".into(),
11846            expected_with_replace_mode: "before editor()ˇ after".into(),
11847            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
11848            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
11849        },
11850        Run {
11851            run_description: "Case insensitive subsequence and suffix matching",
11852            initial_state: "before EDiˇtoR after".into(),
11853            buffer_marked_text: "before <EDi|toR> after".into(),
11854            completion_label: "editor",
11855            completion_text: "editor",
11856            expected_with_insert_mode: "before editorˇtoR after".into(),
11857            expected_with_replace_mode: "before editorˇ after".into(),
11858            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11859            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11860        },
11861    ];
11862
11863    for run in runs {
11864        let run_variations = [
11865            (LspInsertMode::Insert, run.expected_with_insert_mode),
11866            (LspInsertMode::Replace, run.expected_with_replace_mode),
11867            (
11868                LspInsertMode::ReplaceSubsequence,
11869                run.expected_with_replace_subsequence_mode,
11870            ),
11871            (
11872                LspInsertMode::ReplaceSuffix,
11873                run.expected_with_replace_suffix_mode,
11874            ),
11875        ];
11876
11877        for (lsp_insert_mode, expected_text) in run_variations {
11878            eprintln!(
11879                "run = {:?}, mode = {lsp_insert_mode:.?}",
11880                run.run_description,
11881            );
11882
11883            update_test_language_settings(&mut cx, |settings| {
11884                settings.defaults.completions = Some(CompletionSettings {
11885                    lsp_insert_mode,
11886                    words: WordsCompletionMode::Disabled,
11887                    lsp: true,
11888                    lsp_fetch_timeout_ms: 0,
11889                });
11890            });
11891
11892            cx.set_state(&run.initial_state);
11893            cx.update_editor(|editor, window, cx| {
11894                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11895            });
11896
11897            let counter = Arc::new(AtomicUsize::new(0));
11898            handle_completion_request_with_insert_and_replace(
11899                &mut cx,
11900                &run.buffer_marked_text,
11901                vec![(run.completion_label, run.completion_text)],
11902                counter.clone(),
11903            )
11904            .await;
11905            cx.condition(|editor, _| editor.context_menu_visible())
11906                .await;
11907            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11908
11909            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11910                editor
11911                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
11912                    .unwrap()
11913            });
11914            cx.assert_editor_state(&expected_text);
11915            handle_resolve_completion_request(&mut cx, None).await;
11916            apply_additional_edits.await.unwrap();
11917        }
11918    }
11919}
11920
11921#[gpui::test]
11922async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
11923    init_test(cx, |_| {});
11924    let mut cx = EditorLspTestContext::new_rust(
11925        lsp::ServerCapabilities {
11926            completion_provider: Some(lsp::CompletionOptions {
11927                resolve_provider: Some(true),
11928                ..Default::default()
11929            }),
11930            ..Default::default()
11931        },
11932        cx,
11933    )
11934    .await;
11935
11936    let initial_state = "SubˇError";
11937    let buffer_marked_text = "<Sub|Error>";
11938    let completion_text = "SubscriptionError";
11939    let expected_with_insert_mode = "SubscriptionErrorˇError";
11940    let expected_with_replace_mode = "SubscriptionErrorˇ";
11941
11942    update_test_language_settings(&mut cx, |settings| {
11943        settings.defaults.completions = Some(CompletionSettings {
11944            words: WordsCompletionMode::Disabled,
11945            // set the opposite here to ensure that the action is overriding the default behavior
11946            lsp_insert_mode: LspInsertMode::Insert,
11947            lsp: true,
11948            lsp_fetch_timeout_ms: 0,
11949        });
11950    });
11951
11952    cx.set_state(initial_state);
11953    cx.update_editor(|editor, window, cx| {
11954        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11955    });
11956
11957    let counter = Arc::new(AtomicUsize::new(0));
11958    handle_completion_request_with_insert_and_replace(
11959        &mut cx,
11960        &buffer_marked_text,
11961        vec![(completion_text, completion_text)],
11962        counter.clone(),
11963    )
11964    .await;
11965    cx.condition(|editor, _| editor.context_menu_visible())
11966        .await;
11967    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11968
11969    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11970        editor
11971            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11972            .unwrap()
11973    });
11974    cx.assert_editor_state(&expected_with_replace_mode);
11975    handle_resolve_completion_request(&mut cx, None).await;
11976    apply_additional_edits.await.unwrap();
11977
11978    update_test_language_settings(&mut cx, |settings| {
11979        settings.defaults.completions = Some(CompletionSettings {
11980            words: WordsCompletionMode::Disabled,
11981            // set the opposite here to ensure that the action is overriding the default behavior
11982            lsp_insert_mode: LspInsertMode::Replace,
11983            lsp: true,
11984            lsp_fetch_timeout_ms: 0,
11985        });
11986    });
11987
11988    cx.set_state(initial_state);
11989    cx.update_editor(|editor, window, cx| {
11990        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11991    });
11992    handle_completion_request_with_insert_and_replace(
11993        &mut cx,
11994        &buffer_marked_text,
11995        vec![(completion_text, completion_text)],
11996        counter.clone(),
11997    )
11998    .await;
11999    cx.condition(|editor, _| editor.context_menu_visible())
12000        .await;
12001    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12002
12003    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12004        editor
12005            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
12006            .unwrap()
12007    });
12008    cx.assert_editor_state(&expected_with_insert_mode);
12009    handle_resolve_completion_request(&mut cx, None).await;
12010    apply_additional_edits.await.unwrap();
12011}
12012
12013#[gpui::test]
12014async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
12015    init_test(cx, |_| {});
12016    let mut cx = EditorLspTestContext::new_rust(
12017        lsp::ServerCapabilities {
12018            completion_provider: Some(lsp::CompletionOptions {
12019                resolve_provider: Some(true),
12020                ..Default::default()
12021            }),
12022            ..Default::default()
12023        },
12024        cx,
12025    )
12026    .await;
12027
12028    // scenario: surrounding text matches completion text
12029    let completion_text = "to_offset";
12030    let initial_state = indoc! {"
12031        1. buf.to_offˇsuffix
12032        2. buf.to_offˇsuf
12033        3. buf.to_offˇfix
12034        4. buf.to_offˇ
12035        5. into_offˇensive
12036        6. ˇsuffix
12037        7. let ˇ //
12038        8. aaˇzz
12039        9. buf.to_off«zzzzzˇ»suffix
12040        10. buf.«ˇzzzzz»suffix
12041        11. to_off«ˇzzzzz»
12042
12043        buf.to_offˇsuffix  // newest cursor
12044    "};
12045    let completion_marked_buffer = indoc! {"
12046        1. buf.to_offsuffix
12047        2. buf.to_offsuf
12048        3. buf.to_offfix
12049        4. buf.to_off
12050        5. into_offensive
12051        6. suffix
12052        7. let  //
12053        8. aazz
12054        9. buf.to_offzzzzzsuffix
12055        10. buf.zzzzzsuffix
12056        11. to_offzzzzz
12057
12058        buf.<to_off|suffix>  // newest cursor
12059    "};
12060    let expected = indoc! {"
12061        1. buf.to_offsetˇ
12062        2. buf.to_offsetˇsuf
12063        3. buf.to_offsetˇfix
12064        4. buf.to_offsetˇ
12065        5. into_offsetˇensive
12066        6. to_offsetˇsuffix
12067        7. let to_offsetˇ //
12068        8. aato_offsetˇzz
12069        9. buf.to_offsetˇ
12070        10. buf.to_offsetˇsuffix
12071        11. to_offsetˇ
12072
12073        buf.to_offsetˇ  // newest cursor
12074    "};
12075    cx.set_state(initial_state);
12076    cx.update_editor(|editor, window, cx| {
12077        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12078    });
12079    handle_completion_request_with_insert_and_replace(
12080        &mut cx,
12081        completion_marked_buffer,
12082        vec![(completion_text, completion_text)],
12083        Arc::new(AtomicUsize::new(0)),
12084    )
12085    .await;
12086    cx.condition(|editor, _| editor.context_menu_visible())
12087        .await;
12088    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12089        editor
12090            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12091            .unwrap()
12092    });
12093    cx.assert_editor_state(expected);
12094    handle_resolve_completion_request(&mut cx, None).await;
12095    apply_additional_edits.await.unwrap();
12096
12097    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
12098    let completion_text = "foo_and_bar";
12099    let initial_state = indoc! {"
12100        1. ooanbˇ
12101        2. zooanbˇ
12102        3. ooanbˇz
12103        4. zooanbˇz
12104        5. ooanˇ
12105        6. oanbˇ
12106
12107        ooanbˇ
12108    "};
12109    let completion_marked_buffer = indoc! {"
12110        1. ooanb
12111        2. zooanb
12112        3. ooanbz
12113        4. zooanbz
12114        5. ooan
12115        6. oanb
12116
12117        <ooanb|>
12118    "};
12119    let expected = indoc! {"
12120        1. foo_and_barˇ
12121        2. zfoo_and_barˇ
12122        3. foo_and_barˇz
12123        4. zfoo_and_barˇz
12124        5. ooanfoo_and_barˇ
12125        6. oanbfoo_and_barˇ
12126
12127        foo_and_barˇ
12128    "};
12129    cx.set_state(initial_state);
12130    cx.update_editor(|editor, window, cx| {
12131        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12132    });
12133    handle_completion_request_with_insert_and_replace(
12134        &mut cx,
12135        completion_marked_buffer,
12136        vec![(completion_text, completion_text)],
12137        Arc::new(AtomicUsize::new(0)),
12138    )
12139    .await;
12140    cx.condition(|editor, _| editor.context_menu_visible())
12141        .await;
12142    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12143        editor
12144            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12145            .unwrap()
12146    });
12147    cx.assert_editor_state(expected);
12148    handle_resolve_completion_request(&mut cx, None).await;
12149    apply_additional_edits.await.unwrap();
12150
12151    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
12152    // (expects the same as if it was inserted at the end)
12153    let completion_text = "foo_and_bar";
12154    let initial_state = indoc! {"
12155        1. ooˇanb
12156        2. zooˇanb
12157        3. ooˇanbz
12158        4. zooˇanbz
12159
12160        ooˇanb
12161    "};
12162    let completion_marked_buffer = indoc! {"
12163        1. ooanb
12164        2. zooanb
12165        3. ooanbz
12166        4. zooanbz
12167
12168        <oo|anb>
12169    "};
12170    let expected = indoc! {"
12171        1. foo_and_barˇ
12172        2. zfoo_and_barˇ
12173        3. foo_and_barˇz
12174        4. zfoo_and_barˇz
12175
12176        foo_and_barˇ
12177    "};
12178    cx.set_state(initial_state);
12179    cx.update_editor(|editor, window, cx| {
12180        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12181    });
12182    handle_completion_request_with_insert_and_replace(
12183        &mut cx,
12184        completion_marked_buffer,
12185        vec![(completion_text, completion_text)],
12186        Arc::new(AtomicUsize::new(0)),
12187    )
12188    .await;
12189    cx.condition(|editor, _| editor.context_menu_visible())
12190        .await;
12191    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12192        editor
12193            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12194            .unwrap()
12195    });
12196    cx.assert_editor_state(expected);
12197    handle_resolve_completion_request(&mut cx, None).await;
12198    apply_additional_edits.await.unwrap();
12199}
12200
12201// This used to crash
12202#[gpui::test]
12203async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
12204    init_test(cx, |_| {});
12205
12206    let buffer_text = indoc! {"
12207        fn main() {
12208            10.satu;
12209
12210            //
12211            // separate cursors so they open in different excerpts (manually reproducible)
12212            //
12213
12214            10.satu20;
12215        }
12216    "};
12217    let multibuffer_text_with_selections = indoc! {"
12218        fn main() {
12219            10.satuˇ;
12220
12221            //
12222
12223            //
12224
12225            10.satuˇ20;
12226        }
12227    "};
12228    let expected_multibuffer = indoc! {"
12229        fn main() {
12230            10.saturating_sub()ˇ;
12231
12232            //
12233
12234            //
12235
12236            10.saturating_sub()ˇ;
12237        }
12238    "};
12239
12240    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
12241    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
12242
12243    let fs = FakeFs::new(cx.executor());
12244    fs.insert_tree(
12245        path!("/a"),
12246        json!({
12247            "main.rs": buffer_text,
12248        }),
12249    )
12250    .await;
12251
12252    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12253    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12254    language_registry.add(rust_lang());
12255    let mut fake_servers = language_registry.register_fake_lsp(
12256        "Rust",
12257        FakeLspAdapter {
12258            capabilities: lsp::ServerCapabilities {
12259                completion_provider: Some(lsp::CompletionOptions {
12260                    resolve_provider: None,
12261                    ..lsp::CompletionOptions::default()
12262                }),
12263                ..lsp::ServerCapabilities::default()
12264            },
12265            ..FakeLspAdapter::default()
12266        },
12267    );
12268    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12269    let cx = &mut VisualTestContext::from_window(*workspace, cx);
12270    let buffer = project
12271        .update(cx, |project, cx| {
12272            project.open_local_buffer(path!("/a/main.rs"), cx)
12273        })
12274        .await
12275        .unwrap();
12276
12277    let multi_buffer = cx.new(|cx| {
12278        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
12279        multi_buffer.push_excerpts(
12280            buffer.clone(),
12281            [ExcerptRange::new(0..first_excerpt_end)],
12282            cx,
12283        );
12284        multi_buffer.push_excerpts(
12285            buffer.clone(),
12286            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
12287            cx,
12288        );
12289        multi_buffer
12290    });
12291
12292    let editor = workspace
12293        .update(cx, |_, window, cx| {
12294            cx.new(|cx| {
12295                Editor::new(
12296                    EditorMode::Full {
12297                        scale_ui_elements_with_buffer_font_size: false,
12298                        show_active_line_background: false,
12299                        sized_by_content: false,
12300                    },
12301                    multi_buffer.clone(),
12302                    Some(project.clone()),
12303                    window,
12304                    cx,
12305                )
12306            })
12307        })
12308        .unwrap();
12309
12310    let pane = workspace
12311        .update(cx, |workspace, _, _| workspace.active_pane().clone())
12312        .unwrap();
12313    pane.update_in(cx, |pane, window, cx| {
12314        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
12315    });
12316
12317    let fake_server = fake_servers.next().await.unwrap();
12318
12319    editor.update_in(cx, |editor, window, cx| {
12320        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12321            s.select_ranges([
12322                Point::new(1, 11)..Point::new(1, 11),
12323                Point::new(7, 11)..Point::new(7, 11),
12324            ])
12325        });
12326
12327        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
12328    });
12329
12330    editor.update_in(cx, |editor, window, cx| {
12331        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12332    });
12333
12334    fake_server
12335        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
12336            let completion_item = lsp::CompletionItem {
12337                label: "saturating_sub()".into(),
12338                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
12339                    lsp::InsertReplaceEdit {
12340                        new_text: "saturating_sub()".to_owned(),
12341                        insert: lsp::Range::new(
12342                            lsp::Position::new(7, 7),
12343                            lsp::Position::new(7, 11),
12344                        ),
12345                        replace: lsp::Range::new(
12346                            lsp::Position::new(7, 7),
12347                            lsp::Position::new(7, 13),
12348                        ),
12349                    },
12350                )),
12351                ..lsp::CompletionItem::default()
12352            };
12353
12354            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
12355        })
12356        .next()
12357        .await
12358        .unwrap();
12359
12360    cx.condition(&editor, |editor, _| editor.context_menu_visible())
12361        .await;
12362
12363    editor
12364        .update_in(cx, |editor, window, cx| {
12365            editor
12366                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12367                .unwrap()
12368        })
12369        .await
12370        .unwrap();
12371
12372    editor.update(cx, |editor, cx| {
12373        assert_text_with_selections(editor, expected_multibuffer, cx);
12374    })
12375}
12376
12377#[gpui::test]
12378async fn test_completion(cx: &mut TestAppContext) {
12379    init_test(cx, |_| {});
12380
12381    let mut cx = EditorLspTestContext::new_rust(
12382        lsp::ServerCapabilities {
12383            completion_provider: Some(lsp::CompletionOptions {
12384                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12385                resolve_provider: Some(true),
12386                ..Default::default()
12387            }),
12388            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12389            ..Default::default()
12390        },
12391        cx,
12392    )
12393    .await;
12394    let counter = Arc::new(AtomicUsize::new(0));
12395
12396    cx.set_state(indoc! {"
12397        oneˇ
12398        two
12399        three
12400    "});
12401    cx.simulate_keystroke(".");
12402    handle_completion_request(
12403        indoc! {"
12404            one.|<>
12405            two
12406            three
12407        "},
12408        vec!["first_completion", "second_completion"],
12409        true,
12410        counter.clone(),
12411        &mut cx,
12412    )
12413    .await;
12414    cx.condition(|editor, _| editor.context_menu_visible())
12415        .await;
12416    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12417
12418    let _handler = handle_signature_help_request(
12419        &mut cx,
12420        lsp::SignatureHelp {
12421            signatures: vec![lsp::SignatureInformation {
12422                label: "test signature".to_string(),
12423                documentation: None,
12424                parameters: Some(vec![lsp::ParameterInformation {
12425                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
12426                    documentation: None,
12427                }]),
12428                active_parameter: None,
12429            }],
12430            active_signature: None,
12431            active_parameter: None,
12432        },
12433    );
12434    cx.update_editor(|editor, window, cx| {
12435        assert!(
12436            !editor.signature_help_state.is_shown(),
12437            "No signature help was called for"
12438        );
12439        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12440    });
12441    cx.run_until_parked();
12442    cx.update_editor(|editor, _, _| {
12443        assert!(
12444            !editor.signature_help_state.is_shown(),
12445            "No signature help should be shown when completions menu is open"
12446        );
12447    });
12448
12449    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12450        editor.context_menu_next(&Default::default(), window, cx);
12451        editor
12452            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12453            .unwrap()
12454    });
12455    cx.assert_editor_state(indoc! {"
12456        one.second_completionˇ
12457        two
12458        three
12459    "});
12460
12461    handle_resolve_completion_request(
12462        &mut cx,
12463        Some(vec![
12464            (
12465                //This overlaps with the primary completion edit which is
12466                //misbehavior from the LSP spec, test that we filter it out
12467                indoc! {"
12468                    one.second_ˇcompletion
12469                    two
12470                    threeˇ
12471                "},
12472                "overlapping additional edit",
12473            ),
12474            (
12475                indoc! {"
12476                    one.second_completion
12477                    two
12478                    threeˇ
12479                "},
12480                "\nadditional edit",
12481            ),
12482        ]),
12483    )
12484    .await;
12485    apply_additional_edits.await.unwrap();
12486    cx.assert_editor_state(indoc! {"
12487        one.second_completionˇ
12488        two
12489        three
12490        additional edit
12491    "});
12492
12493    cx.set_state(indoc! {"
12494        one.second_completion
12495        twoˇ
12496        threeˇ
12497        additional edit
12498    "});
12499    cx.simulate_keystroke(" ");
12500    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12501    cx.simulate_keystroke("s");
12502    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12503
12504    cx.assert_editor_state(indoc! {"
12505        one.second_completion
12506        two sˇ
12507        three sˇ
12508        additional edit
12509    "});
12510    handle_completion_request(
12511        indoc! {"
12512            one.second_completion
12513            two s
12514            three <s|>
12515            additional edit
12516        "},
12517        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
12518        true,
12519        counter.clone(),
12520        &mut cx,
12521    )
12522    .await;
12523    cx.condition(|editor, _| editor.context_menu_visible())
12524        .await;
12525    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12526
12527    cx.simulate_keystroke("i");
12528
12529    handle_completion_request(
12530        indoc! {"
12531            one.second_completion
12532            two si
12533            three <si|>
12534            additional edit
12535        "},
12536        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
12537        true,
12538        counter.clone(),
12539        &mut cx,
12540    )
12541    .await;
12542    cx.condition(|editor, _| editor.context_menu_visible())
12543        .await;
12544    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12545
12546    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12547        editor
12548            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12549            .unwrap()
12550    });
12551    cx.assert_editor_state(indoc! {"
12552        one.second_completion
12553        two sixth_completionˇ
12554        three sixth_completionˇ
12555        additional edit
12556    "});
12557
12558    apply_additional_edits.await.unwrap();
12559
12560    update_test_language_settings(&mut cx, |settings| {
12561        settings.defaults.show_completions_on_input = Some(false);
12562    });
12563    cx.set_state("editorˇ");
12564    cx.simulate_keystroke(".");
12565    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12566    cx.simulate_keystrokes("c l o");
12567    cx.assert_editor_state("editor.cloˇ");
12568    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12569    cx.update_editor(|editor, window, cx| {
12570        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12571    });
12572    handle_completion_request(
12573        "editor.<clo|>",
12574        vec!["close", "clobber"],
12575        true,
12576        counter.clone(),
12577        &mut cx,
12578    )
12579    .await;
12580    cx.condition(|editor, _| editor.context_menu_visible())
12581        .await;
12582    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
12583
12584    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12585        editor
12586            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12587            .unwrap()
12588    });
12589    cx.assert_editor_state("editor.clobberˇ");
12590    handle_resolve_completion_request(&mut cx, None).await;
12591    apply_additional_edits.await.unwrap();
12592}
12593
12594#[gpui::test]
12595async fn test_completion_reuse(cx: &mut TestAppContext) {
12596    init_test(cx, |_| {});
12597
12598    let mut cx = EditorLspTestContext::new_rust(
12599        lsp::ServerCapabilities {
12600            completion_provider: Some(lsp::CompletionOptions {
12601                trigger_characters: Some(vec![".".to_string()]),
12602                ..Default::default()
12603            }),
12604            ..Default::default()
12605        },
12606        cx,
12607    )
12608    .await;
12609
12610    let counter = Arc::new(AtomicUsize::new(0));
12611    cx.set_state("objˇ");
12612    cx.simulate_keystroke(".");
12613
12614    // Initial completion request returns complete results
12615    let is_incomplete = false;
12616    handle_completion_request(
12617        "obj.|<>",
12618        vec!["a", "ab", "abc"],
12619        is_incomplete,
12620        counter.clone(),
12621        &mut cx,
12622    )
12623    .await;
12624    cx.run_until_parked();
12625    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12626    cx.assert_editor_state("obj.ˇ");
12627    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12628
12629    // Type "a" - filters existing completions
12630    cx.simulate_keystroke("a");
12631    cx.run_until_parked();
12632    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12633    cx.assert_editor_state("obj.aˇ");
12634    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12635
12636    // Type "b" - filters existing completions
12637    cx.simulate_keystroke("b");
12638    cx.run_until_parked();
12639    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12640    cx.assert_editor_state("obj.abˇ");
12641    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12642
12643    // Type "c" - filters existing completions
12644    cx.simulate_keystroke("c");
12645    cx.run_until_parked();
12646    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12647    cx.assert_editor_state("obj.abcˇ");
12648    check_displayed_completions(vec!["abc"], &mut cx);
12649
12650    // Backspace to delete "c" - filters existing completions
12651    cx.update_editor(|editor, window, cx| {
12652        editor.backspace(&Backspace, window, cx);
12653    });
12654    cx.run_until_parked();
12655    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12656    cx.assert_editor_state("obj.abˇ");
12657    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12658
12659    // Moving cursor to the left dismisses menu.
12660    cx.update_editor(|editor, window, cx| {
12661        editor.move_left(&MoveLeft, window, cx);
12662    });
12663    cx.run_until_parked();
12664    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12665    cx.assert_editor_state("obj.aˇb");
12666    cx.update_editor(|editor, _, _| {
12667        assert_eq!(editor.context_menu_visible(), false);
12668    });
12669
12670    // Type "b" - new request
12671    cx.simulate_keystroke("b");
12672    let is_incomplete = false;
12673    handle_completion_request(
12674        "obj.<ab|>a",
12675        vec!["ab", "abc"],
12676        is_incomplete,
12677        counter.clone(),
12678        &mut cx,
12679    )
12680    .await;
12681    cx.run_until_parked();
12682    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12683    cx.assert_editor_state("obj.abˇb");
12684    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12685
12686    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
12687    cx.update_editor(|editor, window, cx| {
12688        editor.backspace(&Backspace, window, cx);
12689    });
12690    let is_incomplete = false;
12691    handle_completion_request(
12692        "obj.<a|>b",
12693        vec!["a", "ab", "abc"],
12694        is_incomplete,
12695        counter.clone(),
12696        &mut cx,
12697    )
12698    .await;
12699    cx.run_until_parked();
12700    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12701    cx.assert_editor_state("obj.aˇb");
12702    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12703
12704    // Backspace to delete "a" - dismisses menu.
12705    cx.update_editor(|editor, window, cx| {
12706        editor.backspace(&Backspace, window, cx);
12707    });
12708    cx.run_until_parked();
12709    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12710    cx.assert_editor_state("obj.ˇb");
12711    cx.update_editor(|editor, _, _| {
12712        assert_eq!(editor.context_menu_visible(), false);
12713    });
12714}
12715
12716#[gpui::test]
12717async fn test_word_completion(cx: &mut TestAppContext) {
12718    let lsp_fetch_timeout_ms = 10;
12719    init_test(cx, |language_settings| {
12720        language_settings.defaults.completions = Some(CompletionSettings {
12721            words: WordsCompletionMode::Fallback,
12722            lsp: true,
12723            lsp_fetch_timeout_ms: 10,
12724            lsp_insert_mode: LspInsertMode::Insert,
12725        });
12726    });
12727
12728    let mut cx = EditorLspTestContext::new_rust(
12729        lsp::ServerCapabilities {
12730            completion_provider: Some(lsp::CompletionOptions {
12731                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12732                ..lsp::CompletionOptions::default()
12733            }),
12734            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12735            ..lsp::ServerCapabilities::default()
12736        },
12737        cx,
12738    )
12739    .await;
12740
12741    let throttle_completions = Arc::new(AtomicBool::new(false));
12742
12743    let lsp_throttle_completions = throttle_completions.clone();
12744    let _completion_requests_handler =
12745        cx.lsp
12746            .server
12747            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
12748                let lsp_throttle_completions = lsp_throttle_completions.clone();
12749                let cx = cx.clone();
12750                async move {
12751                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
12752                        cx.background_executor()
12753                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
12754                            .await;
12755                    }
12756                    Ok(Some(lsp::CompletionResponse::Array(vec![
12757                        lsp::CompletionItem {
12758                            label: "first".into(),
12759                            ..lsp::CompletionItem::default()
12760                        },
12761                        lsp::CompletionItem {
12762                            label: "last".into(),
12763                            ..lsp::CompletionItem::default()
12764                        },
12765                    ])))
12766                }
12767            });
12768
12769    cx.set_state(indoc! {"
12770        oneˇ
12771        two
12772        three
12773    "});
12774    cx.simulate_keystroke(".");
12775    cx.executor().run_until_parked();
12776    cx.condition(|editor, _| editor.context_menu_visible())
12777        .await;
12778    cx.update_editor(|editor, window, cx| {
12779        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12780        {
12781            assert_eq!(
12782                completion_menu_entries(&menu),
12783                &["first", "last"],
12784                "When LSP server is fast to reply, no fallback word completions are used"
12785            );
12786        } else {
12787            panic!("expected completion menu to be open");
12788        }
12789        editor.cancel(&Cancel, window, cx);
12790    });
12791    cx.executor().run_until_parked();
12792    cx.condition(|editor, _| !editor.context_menu_visible())
12793        .await;
12794
12795    throttle_completions.store(true, atomic::Ordering::Release);
12796    cx.simulate_keystroke(".");
12797    cx.executor()
12798        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
12799    cx.executor().run_until_parked();
12800    cx.condition(|editor, _| editor.context_menu_visible())
12801        .await;
12802    cx.update_editor(|editor, _, _| {
12803        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12804        {
12805            assert_eq!(completion_menu_entries(&menu), &["one", "three", "two"],
12806                "When LSP server is slow, document words can be shown instead, if configured accordingly");
12807        } else {
12808            panic!("expected completion menu to be open");
12809        }
12810    });
12811}
12812
12813#[gpui::test]
12814async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
12815    init_test(cx, |language_settings| {
12816        language_settings.defaults.completions = Some(CompletionSettings {
12817            words: WordsCompletionMode::Enabled,
12818            lsp: true,
12819            lsp_fetch_timeout_ms: 0,
12820            lsp_insert_mode: LspInsertMode::Insert,
12821        });
12822    });
12823
12824    let mut cx = EditorLspTestContext::new_rust(
12825        lsp::ServerCapabilities {
12826            completion_provider: Some(lsp::CompletionOptions {
12827                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12828                ..lsp::CompletionOptions::default()
12829            }),
12830            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12831            ..lsp::ServerCapabilities::default()
12832        },
12833        cx,
12834    )
12835    .await;
12836
12837    let _completion_requests_handler =
12838        cx.lsp
12839            .server
12840            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12841                Ok(Some(lsp::CompletionResponse::Array(vec![
12842                    lsp::CompletionItem {
12843                        label: "first".into(),
12844                        ..lsp::CompletionItem::default()
12845                    },
12846                    lsp::CompletionItem {
12847                        label: "last".into(),
12848                        ..lsp::CompletionItem::default()
12849                    },
12850                ])))
12851            });
12852
12853    cx.set_state(indoc! {"ˇ
12854        first
12855        last
12856        second
12857    "});
12858    cx.simulate_keystroke(".");
12859    cx.executor().run_until_parked();
12860    cx.condition(|editor, _| editor.context_menu_visible())
12861        .await;
12862    cx.update_editor(|editor, _, _| {
12863        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12864        {
12865            assert_eq!(
12866                completion_menu_entries(&menu),
12867                &["first", "last", "second"],
12868                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
12869            );
12870        } else {
12871            panic!("expected completion menu to be open");
12872        }
12873    });
12874}
12875
12876#[gpui::test]
12877async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
12878    init_test(cx, |language_settings| {
12879        language_settings.defaults.completions = Some(CompletionSettings {
12880            words: WordsCompletionMode::Disabled,
12881            lsp: true,
12882            lsp_fetch_timeout_ms: 0,
12883            lsp_insert_mode: LspInsertMode::Insert,
12884        });
12885    });
12886
12887    let mut cx = EditorLspTestContext::new_rust(
12888        lsp::ServerCapabilities {
12889            completion_provider: Some(lsp::CompletionOptions {
12890                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12891                ..lsp::CompletionOptions::default()
12892            }),
12893            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12894            ..lsp::ServerCapabilities::default()
12895        },
12896        cx,
12897    )
12898    .await;
12899
12900    let _completion_requests_handler =
12901        cx.lsp
12902            .server
12903            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12904                panic!("LSP completions should not be queried when dealing with word completions")
12905            });
12906
12907    cx.set_state(indoc! {"ˇ
12908        first
12909        last
12910        second
12911    "});
12912    cx.update_editor(|editor, window, cx| {
12913        editor.show_word_completions(&ShowWordCompletions, window, cx);
12914    });
12915    cx.executor().run_until_parked();
12916    cx.condition(|editor, _| editor.context_menu_visible())
12917        .await;
12918    cx.update_editor(|editor, _, _| {
12919        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12920        {
12921            assert_eq!(
12922                completion_menu_entries(&menu),
12923                &["first", "last", "second"],
12924                "`ShowWordCompletions` action should show word completions"
12925            );
12926        } else {
12927            panic!("expected completion menu to be open");
12928        }
12929    });
12930
12931    cx.simulate_keystroke("l");
12932    cx.executor().run_until_parked();
12933    cx.condition(|editor, _| editor.context_menu_visible())
12934        .await;
12935    cx.update_editor(|editor, _, _| {
12936        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12937        {
12938            assert_eq!(
12939                completion_menu_entries(&menu),
12940                &["last"],
12941                "After showing word completions, further editing should filter them and not query the LSP"
12942            );
12943        } else {
12944            panic!("expected completion menu to be open");
12945        }
12946    });
12947}
12948
12949#[gpui::test]
12950async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
12951    init_test(cx, |language_settings| {
12952        language_settings.defaults.completions = Some(CompletionSettings {
12953            words: WordsCompletionMode::Fallback,
12954            lsp: false,
12955            lsp_fetch_timeout_ms: 0,
12956            lsp_insert_mode: LspInsertMode::Insert,
12957        });
12958    });
12959
12960    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12961
12962    cx.set_state(indoc! {"ˇ
12963        0_usize
12964        let
12965        33
12966        4.5f32
12967    "});
12968    cx.update_editor(|editor, window, cx| {
12969        editor.show_completions(&ShowCompletions::default(), window, cx);
12970    });
12971    cx.executor().run_until_parked();
12972    cx.condition(|editor, _| editor.context_menu_visible())
12973        .await;
12974    cx.update_editor(|editor, window, cx| {
12975        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12976        {
12977            assert_eq!(
12978                completion_menu_entries(&menu),
12979                &["let"],
12980                "With no digits in the completion query, no digits should be in the word completions"
12981            );
12982        } else {
12983            panic!("expected completion menu to be open");
12984        }
12985        editor.cancel(&Cancel, window, cx);
12986    });
12987
12988    cx.set_state(indoc! {"12989        0_usize
12990        let
12991        3
12992        33.35f32
12993    "});
12994    cx.update_editor(|editor, window, cx| {
12995        editor.show_completions(&ShowCompletions::default(), window, cx);
12996    });
12997    cx.executor().run_until_parked();
12998    cx.condition(|editor, _| editor.context_menu_visible())
12999        .await;
13000    cx.update_editor(|editor, _, _| {
13001        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13002        {
13003            assert_eq!(completion_menu_entries(&menu), &["33", "35f32"], "The digit is in the completion query, \
13004                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
13005        } else {
13006            panic!("expected completion menu to be open");
13007        }
13008    });
13009}
13010
13011fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
13012    let position = || lsp::Position {
13013        line: params.text_document_position.position.line,
13014        character: params.text_document_position.position.character,
13015    };
13016    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13017        range: lsp::Range {
13018            start: position(),
13019            end: position(),
13020        },
13021        new_text: text.to_string(),
13022    }))
13023}
13024
13025#[gpui::test]
13026async fn test_multiline_completion(cx: &mut TestAppContext) {
13027    init_test(cx, |_| {});
13028
13029    let fs = FakeFs::new(cx.executor());
13030    fs.insert_tree(
13031        path!("/a"),
13032        json!({
13033            "main.ts": "a",
13034        }),
13035    )
13036    .await;
13037
13038    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13039    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13040    let typescript_language = Arc::new(Language::new(
13041        LanguageConfig {
13042            name: "TypeScript".into(),
13043            matcher: LanguageMatcher {
13044                path_suffixes: vec!["ts".to_string()],
13045                ..LanguageMatcher::default()
13046            },
13047            line_comments: vec!["// ".into()],
13048            ..LanguageConfig::default()
13049        },
13050        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
13051    ));
13052    language_registry.add(typescript_language.clone());
13053    let mut fake_servers = language_registry.register_fake_lsp(
13054        "TypeScript",
13055        FakeLspAdapter {
13056            capabilities: lsp::ServerCapabilities {
13057                completion_provider: Some(lsp::CompletionOptions {
13058                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13059                    ..lsp::CompletionOptions::default()
13060                }),
13061                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13062                ..lsp::ServerCapabilities::default()
13063            },
13064            // Emulate vtsls label generation
13065            label_for_completion: Some(Box::new(|item, _| {
13066                let text = if let Some(description) = item
13067                    .label_details
13068                    .as_ref()
13069                    .and_then(|label_details| label_details.description.as_ref())
13070                {
13071                    format!("{} {}", item.label, description)
13072                } else if let Some(detail) = &item.detail {
13073                    format!("{} {}", item.label, detail)
13074                } else {
13075                    item.label.clone()
13076                };
13077                let len = text.len();
13078                Some(language::CodeLabel {
13079                    text,
13080                    runs: Vec::new(),
13081                    filter_range: 0..len,
13082                })
13083            })),
13084            ..FakeLspAdapter::default()
13085        },
13086    );
13087    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13088    let cx = &mut VisualTestContext::from_window(*workspace, cx);
13089    let worktree_id = workspace
13090        .update(cx, |workspace, _window, cx| {
13091            workspace.project().update(cx, |project, cx| {
13092                project.worktrees(cx).next().unwrap().read(cx).id()
13093            })
13094        })
13095        .unwrap();
13096    let _buffer = project
13097        .update(cx, |project, cx| {
13098            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
13099        })
13100        .await
13101        .unwrap();
13102    let editor = workspace
13103        .update(cx, |workspace, window, cx| {
13104            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
13105        })
13106        .unwrap()
13107        .await
13108        .unwrap()
13109        .downcast::<Editor>()
13110        .unwrap();
13111    let fake_server = fake_servers.next().await.unwrap();
13112
13113    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
13114    let multiline_label_2 = "a\nb\nc\n";
13115    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
13116    let multiline_description = "d\ne\nf\n";
13117    let multiline_detail_2 = "g\nh\ni\n";
13118
13119    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
13120        move |params, _| async move {
13121            Ok(Some(lsp::CompletionResponse::Array(vec![
13122                lsp::CompletionItem {
13123                    label: multiline_label.to_string(),
13124                    text_edit: gen_text_edit(&params, "new_text_1"),
13125                    ..lsp::CompletionItem::default()
13126                },
13127                lsp::CompletionItem {
13128                    label: "single line label 1".to_string(),
13129                    detail: Some(multiline_detail.to_string()),
13130                    text_edit: gen_text_edit(&params, "new_text_2"),
13131                    ..lsp::CompletionItem::default()
13132                },
13133                lsp::CompletionItem {
13134                    label: "single line label 2".to_string(),
13135                    label_details: Some(lsp::CompletionItemLabelDetails {
13136                        description: Some(multiline_description.to_string()),
13137                        detail: None,
13138                    }),
13139                    text_edit: gen_text_edit(&params, "new_text_2"),
13140                    ..lsp::CompletionItem::default()
13141                },
13142                lsp::CompletionItem {
13143                    label: multiline_label_2.to_string(),
13144                    detail: Some(multiline_detail_2.to_string()),
13145                    text_edit: gen_text_edit(&params, "new_text_3"),
13146                    ..lsp::CompletionItem::default()
13147                },
13148                lsp::CompletionItem {
13149                    label: "Label with many     spaces and \t but without newlines".to_string(),
13150                    detail: Some(
13151                        "Details with many     spaces and \t but without newlines".to_string(),
13152                    ),
13153                    text_edit: gen_text_edit(&params, "new_text_4"),
13154                    ..lsp::CompletionItem::default()
13155                },
13156            ])))
13157        },
13158    );
13159
13160    editor.update_in(cx, |editor, window, cx| {
13161        cx.focus_self(window);
13162        editor.move_to_end(&MoveToEnd, window, cx);
13163        editor.handle_input(".", window, cx);
13164    });
13165    cx.run_until_parked();
13166    completion_handle.next().await.unwrap();
13167
13168    editor.update(cx, |editor, _| {
13169        assert!(editor.context_menu_visible());
13170        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13171        {
13172            let completion_labels = menu
13173                .completions
13174                .borrow()
13175                .iter()
13176                .map(|c| c.label.text.clone())
13177                .collect::<Vec<_>>();
13178            assert_eq!(
13179                completion_labels,
13180                &[
13181                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
13182                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
13183                    "single line label 2 d e f ",
13184                    "a b c g h i ",
13185                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
13186                ],
13187                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
13188            );
13189
13190            for completion in menu
13191                .completions
13192                .borrow()
13193                .iter() {
13194                    assert_eq!(
13195                        completion.label.filter_range,
13196                        0..completion.label.text.len(),
13197                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
13198                    );
13199                }
13200        } else {
13201            panic!("expected completion menu to be open");
13202        }
13203    });
13204}
13205
13206#[gpui::test]
13207async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
13208    init_test(cx, |_| {});
13209    let mut cx = EditorLspTestContext::new_rust(
13210        lsp::ServerCapabilities {
13211            completion_provider: Some(lsp::CompletionOptions {
13212                trigger_characters: Some(vec![".".to_string()]),
13213                ..Default::default()
13214            }),
13215            ..Default::default()
13216        },
13217        cx,
13218    )
13219    .await;
13220    cx.lsp
13221        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13222            Ok(Some(lsp::CompletionResponse::Array(vec![
13223                lsp::CompletionItem {
13224                    label: "first".into(),
13225                    ..Default::default()
13226                },
13227                lsp::CompletionItem {
13228                    label: "last".into(),
13229                    ..Default::default()
13230                },
13231            ])))
13232        });
13233    cx.set_state("variableˇ");
13234    cx.simulate_keystroke(".");
13235    cx.executor().run_until_parked();
13236
13237    cx.update_editor(|editor, _, _| {
13238        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13239        {
13240            assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
13241        } else {
13242            panic!("expected completion menu to be open");
13243        }
13244    });
13245
13246    cx.update_editor(|editor, window, cx| {
13247        editor.move_page_down(&MovePageDown::default(), window, cx);
13248        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13249        {
13250            assert!(
13251                menu.selected_item == 1,
13252                "expected PageDown to select the last item from the context menu"
13253            );
13254        } else {
13255            panic!("expected completion menu to stay open after PageDown");
13256        }
13257    });
13258
13259    cx.update_editor(|editor, window, cx| {
13260        editor.move_page_up(&MovePageUp::default(), window, cx);
13261        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13262        {
13263            assert!(
13264                menu.selected_item == 0,
13265                "expected PageUp to select the first item from the context menu"
13266            );
13267        } else {
13268            panic!("expected completion menu to stay open after PageUp");
13269        }
13270    });
13271}
13272
13273#[gpui::test]
13274async fn test_as_is_completions(cx: &mut TestAppContext) {
13275    init_test(cx, |_| {});
13276    let mut cx = EditorLspTestContext::new_rust(
13277        lsp::ServerCapabilities {
13278            completion_provider: Some(lsp::CompletionOptions {
13279                ..Default::default()
13280            }),
13281            ..Default::default()
13282        },
13283        cx,
13284    )
13285    .await;
13286    cx.lsp
13287        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13288            Ok(Some(lsp::CompletionResponse::Array(vec![
13289                lsp::CompletionItem {
13290                    label: "unsafe".into(),
13291                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13292                        range: lsp::Range {
13293                            start: lsp::Position {
13294                                line: 1,
13295                                character: 2,
13296                            },
13297                            end: lsp::Position {
13298                                line: 1,
13299                                character: 3,
13300                            },
13301                        },
13302                        new_text: "unsafe".to_string(),
13303                    })),
13304                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
13305                    ..Default::default()
13306                },
13307            ])))
13308        });
13309    cx.set_state("fn a() {}\n");
13310    cx.executor().run_until_parked();
13311    cx.update_editor(|editor, window, cx| {
13312        editor.show_completions(
13313            &ShowCompletions {
13314                trigger: Some("\n".into()),
13315            },
13316            window,
13317            cx,
13318        );
13319    });
13320    cx.executor().run_until_parked();
13321
13322    cx.update_editor(|editor, window, cx| {
13323        editor.confirm_completion(&Default::default(), window, cx)
13324    });
13325    cx.executor().run_until_parked();
13326    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
13327}
13328
13329#[gpui::test]
13330async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
13331    init_test(cx, |_| {});
13332
13333    let mut cx = EditorLspTestContext::new_rust(
13334        lsp::ServerCapabilities {
13335            completion_provider: Some(lsp::CompletionOptions {
13336                trigger_characters: Some(vec![".".to_string()]),
13337                resolve_provider: Some(true),
13338                ..Default::default()
13339            }),
13340            ..Default::default()
13341        },
13342        cx,
13343    )
13344    .await;
13345
13346    cx.set_state("fn main() { let a = 2ˇ; }");
13347    cx.simulate_keystroke(".");
13348    let completion_item = lsp::CompletionItem {
13349        label: "Some".into(),
13350        kind: Some(lsp::CompletionItemKind::SNIPPET),
13351        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
13352        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
13353            kind: lsp::MarkupKind::Markdown,
13354            value: "```rust\nSome(2)\n```".to_string(),
13355        })),
13356        deprecated: Some(false),
13357        sort_text: Some("Some".to_string()),
13358        filter_text: Some("Some".to_string()),
13359        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
13360        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13361            range: lsp::Range {
13362                start: lsp::Position {
13363                    line: 0,
13364                    character: 22,
13365                },
13366                end: lsp::Position {
13367                    line: 0,
13368                    character: 22,
13369                },
13370            },
13371            new_text: "Some(2)".to_string(),
13372        })),
13373        additional_text_edits: Some(vec![lsp::TextEdit {
13374            range: lsp::Range {
13375                start: lsp::Position {
13376                    line: 0,
13377                    character: 20,
13378                },
13379                end: lsp::Position {
13380                    line: 0,
13381                    character: 22,
13382                },
13383            },
13384            new_text: "".to_string(),
13385        }]),
13386        ..Default::default()
13387    };
13388
13389    let closure_completion_item = completion_item.clone();
13390    let counter = Arc::new(AtomicUsize::new(0));
13391    let counter_clone = counter.clone();
13392    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
13393        let task_completion_item = closure_completion_item.clone();
13394        counter_clone.fetch_add(1, atomic::Ordering::Release);
13395        async move {
13396            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
13397                is_incomplete: true,
13398                item_defaults: None,
13399                items: vec![task_completion_item],
13400            })))
13401        }
13402    });
13403
13404    cx.condition(|editor, _| editor.context_menu_visible())
13405        .await;
13406    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
13407    assert!(request.next().await.is_some());
13408    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13409
13410    cx.simulate_keystrokes("S o m");
13411    cx.condition(|editor, _| editor.context_menu_visible())
13412        .await;
13413    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
13414    assert!(request.next().await.is_some());
13415    assert!(request.next().await.is_some());
13416    assert!(request.next().await.is_some());
13417    request.close();
13418    assert!(request.next().await.is_none());
13419    assert_eq!(
13420        counter.load(atomic::Ordering::Acquire),
13421        4,
13422        "With the completions menu open, only one LSP request should happen per input"
13423    );
13424}
13425
13426#[gpui::test]
13427async fn test_toggle_comment(cx: &mut TestAppContext) {
13428    init_test(cx, |_| {});
13429    let mut cx = EditorTestContext::new(cx).await;
13430    let language = Arc::new(Language::new(
13431        LanguageConfig {
13432            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
13433            ..Default::default()
13434        },
13435        Some(tree_sitter_rust::LANGUAGE.into()),
13436    ));
13437    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
13438
13439    // If multiple selections intersect a line, the line is only toggled once.
13440    cx.set_state(indoc! {"
13441        fn a() {
13442            «//b();
13443            ˇ»// «c();
13444            //ˇ»  d();
13445        }
13446    "});
13447
13448    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13449
13450    cx.assert_editor_state(indoc! {"
13451        fn a() {
13452            «b();
13453            c();
13454            ˇ» d();
13455        }
13456    "});
13457
13458    // The comment prefix is inserted at the same column for every line in a
13459    // selection.
13460    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13461
13462    cx.assert_editor_state(indoc! {"
13463        fn a() {
13464            // «b();
13465            // c();
13466            ˇ»//  d();
13467        }
13468    "});
13469
13470    // If a selection ends at the beginning of a line, that line is not toggled.
13471    cx.set_selections_state(indoc! {"
13472        fn a() {
13473            // b();
13474            «// c();
13475        ˇ»    //  d();
13476        }
13477    "});
13478
13479    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13480
13481    cx.assert_editor_state(indoc! {"
13482        fn a() {
13483            // b();
13484            «c();
13485        ˇ»    //  d();
13486        }
13487    "});
13488
13489    // If a selection span a single line and is empty, the line is toggled.
13490    cx.set_state(indoc! {"
13491        fn a() {
13492            a();
13493            b();
13494        ˇ
13495        }
13496    "});
13497
13498    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13499
13500    cx.assert_editor_state(indoc! {"
13501        fn a() {
13502            a();
13503            b();
13504        //•ˇ
13505        }
13506    "});
13507
13508    // If a selection span multiple lines, empty lines are not toggled.
13509    cx.set_state(indoc! {"
13510        fn a() {
13511            «a();
13512
13513            c();ˇ»
13514        }
13515    "});
13516
13517    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13518
13519    cx.assert_editor_state(indoc! {"
13520        fn a() {
13521            // «a();
13522
13523            // c();ˇ»
13524        }
13525    "});
13526
13527    // If a selection includes multiple comment prefixes, all lines are uncommented.
13528    cx.set_state(indoc! {"
13529        fn a() {
13530            «// a();
13531            /// b();
13532            //! c();ˇ»
13533        }
13534    "});
13535
13536    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13537
13538    cx.assert_editor_state(indoc! {"
13539        fn a() {
13540            «a();
13541            b();
13542            c();ˇ»
13543        }
13544    "});
13545}
13546
13547#[gpui::test]
13548async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
13549    init_test(cx, |_| {});
13550    let mut cx = EditorTestContext::new(cx).await;
13551    let language = Arc::new(Language::new(
13552        LanguageConfig {
13553            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
13554            ..Default::default()
13555        },
13556        Some(tree_sitter_rust::LANGUAGE.into()),
13557    ));
13558    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
13559
13560    let toggle_comments = &ToggleComments {
13561        advance_downwards: false,
13562        ignore_indent: true,
13563    };
13564
13565    // If multiple selections intersect a line, the line is only toggled once.
13566    cx.set_state(indoc! {"
13567        fn a() {
13568        //    «b();
13569        //    c();
13570        //    ˇ» d();
13571        }
13572    "});
13573
13574    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13575
13576    cx.assert_editor_state(indoc! {"
13577        fn a() {
13578            «b();
13579            c();
13580            ˇ» d();
13581        }
13582    "});
13583
13584    // The comment prefix is inserted at the beginning of each line
13585    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13586
13587    cx.assert_editor_state(indoc! {"
13588        fn a() {
13589        //    «b();
13590        //    c();
13591        //    ˇ» d();
13592        }
13593    "});
13594
13595    // If a selection ends at the beginning of a line, that line is not toggled.
13596    cx.set_selections_state(indoc! {"
13597        fn a() {
13598        //    b();
13599        //    «c();
13600        ˇ»//     d();
13601        }
13602    "});
13603
13604    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13605
13606    cx.assert_editor_state(indoc! {"
13607        fn a() {
13608        //    b();
13609            «c();
13610        ˇ»//     d();
13611        }
13612    "});
13613
13614    // If a selection span a single line and is empty, the line is toggled.
13615    cx.set_state(indoc! {"
13616        fn a() {
13617            a();
13618            b();
13619        ˇ
13620        }
13621    "});
13622
13623    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13624
13625    cx.assert_editor_state(indoc! {"
13626        fn a() {
13627            a();
13628            b();
13629        //ˇ
13630        }
13631    "});
13632
13633    // If a selection span multiple lines, empty lines are not toggled.
13634    cx.set_state(indoc! {"
13635        fn a() {
13636            «a();
13637
13638            c();ˇ»
13639        }
13640    "});
13641
13642    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13643
13644    cx.assert_editor_state(indoc! {"
13645        fn a() {
13646        //    «a();
13647
13648        //    c();ˇ»
13649        }
13650    "});
13651
13652    // If a selection includes multiple comment prefixes, all lines are uncommented.
13653    cx.set_state(indoc! {"
13654        fn a() {
13655        //    «a();
13656        ///    b();
13657        //!    c();ˇ»
13658        }
13659    "});
13660
13661    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13662
13663    cx.assert_editor_state(indoc! {"
13664        fn a() {
13665            «a();
13666            b();
13667            c();ˇ»
13668        }
13669    "});
13670}
13671
13672#[gpui::test]
13673async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
13674    init_test(cx, |_| {});
13675
13676    let language = Arc::new(Language::new(
13677        LanguageConfig {
13678            line_comments: vec!["// ".into()],
13679            ..Default::default()
13680        },
13681        Some(tree_sitter_rust::LANGUAGE.into()),
13682    ));
13683
13684    let mut cx = EditorTestContext::new(cx).await;
13685
13686    cx.language_registry().add(language.clone());
13687    cx.update_buffer(|buffer, cx| {
13688        buffer.set_language(Some(language), cx);
13689    });
13690
13691    let toggle_comments = &ToggleComments {
13692        advance_downwards: true,
13693        ignore_indent: false,
13694    };
13695
13696    // Single cursor on one line -> advance
13697    // Cursor moves horizontally 3 characters as well on non-blank line
13698    cx.set_state(indoc!(
13699        "fn a() {
13700             ˇdog();
13701             cat();
13702        }"
13703    ));
13704    cx.update_editor(|editor, window, cx| {
13705        editor.toggle_comments(toggle_comments, window, cx);
13706    });
13707    cx.assert_editor_state(indoc!(
13708        "fn a() {
13709             // dog();
13710             catˇ();
13711        }"
13712    ));
13713
13714    // Single selection on one line -> don't advance
13715    cx.set_state(indoc!(
13716        "fn a() {
13717             «dog()ˇ»;
13718             cat();
13719        }"
13720    ));
13721    cx.update_editor(|editor, window, cx| {
13722        editor.toggle_comments(toggle_comments, window, cx);
13723    });
13724    cx.assert_editor_state(indoc!(
13725        "fn a() {
13726             // «dog()ˇ»;
13727             cat();
13728        }"
13729    ));
13730
13731    // Multiple cursors on one line -> advance
13732    cx.set_state(indoc!(
13733        "fn a() {
13734             ˇdˇog();
13735             cat();
13736        }"
13737    ));
13738    cx.update_editor(|editor, window, cx| {
13739        editor.toggle_comments(toggle_comments, window, cx);
13740    });
13741    cx.assert_editor_state(indoc!(
13742        "fn a() {
13743             // dog();
13744             catˇ(ˇ);
13745        }"
13746    ));
13747
13748    // Multiple cursors on one line, with selection -> don't advance
13749    cx.set_state(indoc!(
13750        "fn a() {
13751             ˇdˇog«()ˇ»;
13752             cat();
13753        }"
13754    ));
13755    cx.update_editor(|editor, window, cx| {
13756        editor.toggle_comments(toggle_comments, window, cx);
13757    });
13758    cx.assert_editor_state(indoc!(
13759        "fn a() {
13760             // ˇdˇog«()ˇ»;
13761             cat();
13762        }"
13763    ));
13764
13765    // Single cursor on one line -> advance
13766    // Cursor moves to column 0 on blank line
13767    cx.set_state(indoc!(
13768        "fn a() {
13769             ˇdog();
13770
13771             cat();
13772        }"
13773    ));
13774    cx.update_editor(|editor, window, cx| {
13775        editor.toggle_comments(toggle_comments, window, cx);
13776    });
13777    cx.assert_editor_state(indoc!(
13778        "fn a() {
13779             // dog();
13780        ˇ
13781             cat();
13782        }"
13783    ));
13784
13785    // Single cursor on one line -> advance
13786    // Cursor starts and ends at column 0
13787    cx.set_state(indoc!(
13788        "fn a() {
13789         ˇ    dog();
13790             cat();
13791        }"
13792    ));
13793    cx.update_editor(|editor, window, cx| {
13794        editor.toggle_comments(toggle_comments, window, cx);
13795    });
13796    cx.assert_editor_state(indoc!(
13797        "fn a() {
13798             // dog();
13799         ˇ    cat();
13800        }"
13801    ));
13802}
13803
13804#[gpui::test]
13805async fn test_toggle_block_comment(cx: &mut TestAppContext) {
13806    init_test(cx, |_| {});
13807
13808    let mut cx = EditorTestContext::new(cx).await;
13809
13810    let html_language = Arc::new(
13811        Language::new(
13812            LanguageConfig {
13813                name: "HTML".into(),
13814                block_comment: Some(BlockCommentConfig {
13815                    start: "<!-- ".into(),
13816                    prefix: "".into(),
13817                    end: " -->".into(),
13818                    tab_size: 0,
13819                }),
13820                ..Default::default()
13821            },
13822            Some(tree_sitter_html::LANGUAGE.into()),
13823        )
13824        .with_injection_query(
13825            r#"
13826            (script_element
13827                (raw_text) @injection.content
13828                (#set! injection.language "javascript"))
13829            "#,
13830        )
13831        .unwrap(),
13832    );
13833
13834    let javascript_language = Arc::new(Language::new(
13835        LanguageConfig {
13836            name: "JavaScript".into(),
13837            line_comments: vec!["// ".into()],
13838            ..Default::default()
13839        },
13840        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
13841    ));
13842
13843    cx.language_registry().add(html_language.clone());
13844    cx.language_registry().add(javascript_language.clone());
13845    cx.update_buffer(|buffer, cx| {
13846        buffer.set_language(Some(html_language), cx);
13847    });
13848
13849    // Toggle comments for empty selections
13850    cx.set_state(
13851        &r#"
13852            <p>A</p>ˇ
13853            <p>B</p>ˇ
13854            <p>C</p>ˇ
13855        "#
13856        .unindent(),
13857    );
13858    cx.update_editor(|editor, window, cx| {
13859        editor.toggle_comments(&ToggleComments::default(), window, cx)
13860    });
13861    cx.assert_editor_state(
13862        &r#"
13863            <!-- <p>A</p>ˇ -->
13864            <!-- <p>B</p>ˇ -->
13865            <!-- <p>C</p>ˇ -->
13866        "#
13867        .unindent(),
13868    );
13869    cx.update_editor(|editor, window, cx| {
13870        editor.toggle_comments(&ToggleComments::default(), window, cx)
13871    });
13872    cx.assert_editor_state(
13873        &r#"
13874            <p>A</p>ˇ
13875            <p>B</p>ˇ
13876            <p>C</p>ˇ
13877        "#
13878        .unindent(),
13879    );
13880
13881    // Toggle comments for mixture of empty and non-empty selections, where
13882    // multiple selections occupy a given line.
13883    cx.set_state(
13884        &r#"
13885            <p>A«</p>
13886            <p>ˇ»B</p>ˇ
13887            <p>C«</p>
13888            <p>ˇ»D</p>ˇ
13889        "#
13890        .unindent(),
13891    );
13892
13893    cx.update_editor(|editor, window, cx| {
13894        editor.toggle_comments(&ToggleComments::default(), window, cx)
13895    });
13896    cx.assert_editor_state(
13897        &r#"
13898            <!-- <p>A«</p>
13899            <p>ˇ»B</p>ˇ -->
13900            <!-- <p>C«</p>
13901            <p>ˇ»D</p>ˇ -->
13902        "#
13903        .unindent(),
13904    );
13905    cx.update_editor(|editor, window, cx| {
13906        editor.toggle_comments(&ToggleComments::default(), window, cx)
13907    });
13908    cx.assert_editor_state(
13909        &r#"
13910            <p>A«</p>
13911            <p>ˇ»B</p>ˇ
13912            <p>C«</p>
13913            <p>ˇ»D</p>ˇ
13914        "#
13915        .unindent(),
13916    );
13917
13918    // Toggle comments when different languages are active for different
13919    // selections.
13920    cx.set_state(
13921        &r#"
13922            ˇ<script>
13923                ˇvar x = new Y();
13924            ˇ</script>
13925        "#
13926        .unindent(),
13927    );
13928    cx.executor().run_until_parked();
13929    cx.update_editor(|editor, window, cx| {
13930        editor.toggle_comments(&ToggleComments::default(), window, cx)
13931    });
13932    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
13933    // Uncommenting and commenting from this position brings in even more wrong artifacts.
13934    cx.assert_editor_state(
13935        &r#"
13936            <!-- ˇ<script> -->
13937                // ˇvar x = new Y();
13938            <!-- ˇ</script> -->
13939        "#
13940        .unindent(),
13941    );
13942}
13943
13944#[gpui::test]
13945fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
13946    init_test(cx, |_| {});
13947
13948    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
13949    let multibuffer = cx.new(|cx| {
13950        let mut multibuffer = MultiBuffer::new(ReadWrite);
13951        multibuffer.push_excerpts(
13952            buffer.clone(),
13953            [
13954                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
13955                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
13956            ],
13957            cx,
13958        );
13959        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
13960        multibuffer
13961    });
13962
13963    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
13964    editor.update_in(cx, |editor, window, cx| {
13965        assert_eq!(editor.text(cx), "aaaa\nbbbb");
13966        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13967            s.select_ranges([
13968                Point::new(0, 0)..Point::new(0, 0),
13969                Point::new(1, 0)..Point::new(1, 0),
13970            ])
13971        });
13972
13973        editor.handle_input("X", window, cx);
13974        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
13975        assert_eq!(
13976            editor.selections.ranges(cx),
13977            [
13978                Point::new(0, 1)..Point::new(0, 1),
13979                Point::new(1, 1)..Point::new(1, 1),
13980            ]
13981        );
13982
13983        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
13984        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13985            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
13986        });
13987        editor.backspace(&Default::default(), window, cx);
13988        assert_eq!(editor.text(cx), "Xa\nbbb");
13989        assert_eq!(
13990            editor.selections.ranges(cx),
13991            [Point::new(1, 0)..Point::new(1, 0)]
13992        );
13993
13994        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13995            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
13996        });
13997        editor.backspace(&Default::default(), window, cx);
13998        assert_eq!(editor.text(cx), "X\nbb");
13999        assert_eq!(
14000            editor.selections.ranges(cx),
14001            [Point::new(0, 1)..Point::new(0, 1)]
14002        );
14003    });
14004}
14005
14006#[gpui::test]
14007fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
14008    init_test(cx, |_| {});
14009
14010    let markers = vec![('[', ']').into(), ('(', ')').into()];
14011    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
14012        indoc! {"
14013            [aaaa
14014            (bbbb]
14015            cccc)",
14016        },
14017        markers.clone(),
14018    );
14019    let excerpt_ranges = markers.into_iter().map(|marker| {
14020        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
14021        ExcerptRange::new(context.clone())
14022    });
14023    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
14024    let multibuffer = cx.new(|cx| {
14025        let mut multibuffer = MultiBuffer::new(ReadWrite);
14026        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
14027        multibuffer
14028    });
14029
14030    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
14031    editor.update_in(cx, |editor, window, cx| {
14032        let (expected_text, selection_ranges) = marked_text_ranges(
14033            indoc! {"
14034                aaaa
14035                bˇbbb
14036                bˇbbˇb
14037                cccc"
14038            },
14039            true,
14040        );
14041        assert_eq!(editor.text(cx), expected_text);
14042        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14043            s.select_ranges(selection_ranges)
14044        });
14045
14046        editor.handle_input("X", window, cx);
14047
14048        let (expected_text, expected_selections) = marked_text_ranges(
14049            indoc! {"
14050                aaaa
14051                bXˇbbXb
14052                bXˇbbXˇb
14053                cccc"
14054            },
14055            false,
14056        );
14057        assert_eq!(editor.text(cx), expected_text);
14058        assert_eq!(editor.selections.ranges(cx), expected_selections);
14059
14060        editor.newline(&Newline, window, cx);
14061        let (expected_text, expected_selections) = marked_text_ranges(
14062            indoc! {"
14063                aaaa
14064                bX
14065                ˇbbX
14066                b
14067                bX
14068                ˇbbX
14069                ˇb
14070                cccc"
14071            },
14072            false,
14073        );
14074        assert_eq!(editor.text(cx), expected_text);
14075        assert_eq!(editor.selections.ranges(cx), expected_selections);
14076    });
14077}
14078
14079#[gpui::test]
14080fn test_refresh_selections(cx: &mut TestAppContext) {
14081    init_test(cx, |_| {});
14082
14083    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
14084    let mut excerpt1_id = None;
14085    let multibuffer = cx.new(|cx| {
14086        let mut multibuffer = MultiBuffer::new(ReadWrite);
14087        excerpt1_id = multibuffer
14088            .push_excerpts(
14089                buffer.clone(),
14090                [
14091                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
14092                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
14093                ],
14094                cx,
14095            )
14096            .into_iter()
14097            .next();
14098        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
14099        multibuffer
14100    });
14101
14102    let editor = cx.add_window(|window, cx| {
14103        let mut editor = build_editor(multibuffer.clone(), window, cx);
14104        let snapshot = editor.snapshot(window, cx);
14105        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14106            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
14107        });
14108        editor.begin_selection(
14109            Point::new(2, 1).to_display_point(&snapshot),
14110            true,
14111            1,
14112            window,
14113            cx,
14114        );
14115        assert_eq!(
14116            editor.selections.ranges(cx),
14117            [
14118                Point::new(1, 3)..Point::new(1, 3),
14119                Point::new(2, 1)..Point::new(2, 1),
14120            ]
14121        );
14122        editor
14123    });
14124
14125    // Refreshing selections is a no-op when excerpts haven't changed.
14126    _ = editor.update(cx, |editor, window, cx| {
14127        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14128        assert_eq!(
14129            editor.selections.ranges(cx),
14130            [
14131                Point::new(1, 3)..Point::new(1, 3),
14132                Point::new(2, 1)..Point::new(2, 1),
14133            ]
14134        );
14135    });
14136
14137    multibuffer.update(cx, |multibuffer, cx| {
14138        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
14139    });
14140    _ = editor.update(cx, |editor, window, cx| {
14141        // Removing an excerpt causes the first selection to become degenerate.
14142        assert_eq!(
14143            editor.selections.ranges(cx),
14144            [
14145                Point::new(0, 0)..Point::new(0, 0),
14146                Point::new(0, 1)..Point::new(0, 1)
14147            ]
14148        );
14149
14150        // Refreshing selections will relocate the first selection to the original buffer
14151        // location.
14152        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14153        assert_eq!(
14154            editor.selections.ranges(cx),
14155            [
14156                Point::new(0, 1)..Point::new(0, 1),
14157                Point::new(0, 3)..Point::new(0, 3)
14158            ]
14159        );
14160        assert!(editor.selections.pending_anchor().is_some());
14161    });
14162}
14163
14164#[gpui::test]
14165fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
14166    init_test(cx, |_| {});
14167
14168    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
14169    let mut excerpt1_id = None;
14170    let multibuffer = cx.new(|cx| {
14171        let mut multibuffer = MultiBuffer::new(ReadWrite);
14172        excerpt1_id = multibuffer
14173            .push_excerpts(
14174                buffer.clone(),
14175                [
14176                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
14177                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
14178                ],
14179                cx,
14180            )
14181            .into_iter()
14182            .next();
14183        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
14184        multibuffer
14185    });
14186
14187    let editor = cx.add_window(|window, cx| {
14188        let mut editor = build_editor(multibuffer.clone(), window, cx);
14189        let snapshot = editor.snapshot(window, cx);
14190        editor.begin_selection(
14191            Point::new(1, 3).to_display_point(&snapshot),
14192            false,
14193            1,
14194            window,
14195            cx,
14196        );
14197        assert_eq!(
14198            editor.selections.ranges(cx),
14199            [Point::new(1, 3)..Point::new(1, 3)]
14200        );
14201        editor
14202    });
14203
14204    multibuffer.update(cx, |multibuffer, cx| {
14205        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
14206    });
14207    _ = editor.update(cx, |editor, window, cx| {
14208        assert_eq!(
14209            editor.selections.ranges(cx),
14210            [Point::new(0, 0)..Point::new(0, 0)]
14211        );
14212
14213        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
14214        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14215        assert_eq!(
14216            editor.selections.ranges(cx),
14217            [Point::new(0, 3)..Point::new(0, 3)]
14218        );
14219        assert!(editor.selections.pending_anchor().is_some());
14220    });
14221}
14222
14223#[gpui::test]
14224async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
14225    init_test(cx, |_| {});
14226
14227    let language = Arc::new(
14228        Language::new(
14229            LanguageConfig {
14230                brackets: BracketPairConfig {
14231                    pairs: vec![
14232                        BracketPair {
14233                            start: "{".to_string(),
14234                            end: "}".to_string(),
14235                            close: true,
14236                            surround: true,
14237                            newline: true,
14238                        },
14239                        BracketPair {
14240                            start: "/* ".to_string(),
14241                            end: " */".to_string(),
14242                            close: true,
14243                            surround: true,
14244                            newline: true,
14245                        },
14246                    ],
14247                    ..Default::default()
14248                },
14249                ..Default::default()
14250            },
14251            Some(tree_sitter_rust::LANGUAGE.into()),
14252        )
14253        .with_indents_query("")
14254        .unwrap(),
14255    );
14256
14257    let text = concat!(
14258        "{   }\n",     //
14259        "  x\n",       //
14260        "  /*   */\n", //
14261        "x\n",         //
14262        "{{} }\n",     //
14263    );
14264
14265    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
14266    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
14267    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
14268    editor
14269        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
14270        .await;
14271
14272    editor.update_in(cx, |editor, window, cx| {
14273        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14274            s.select_display_ranges([
14275                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
14276                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
14277                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
14278            ])
14279        });
14280        editor.newline(&Newline, window, cx);
14281
14282        assert_eq!(
14283            editor.buffer().read(cx).read(cx).text(),
14284            concat!(
14285                "{ \n",    // Suppress rustfmt
14286                "\n",      //
14287                "}\n",     //
14288                "  x\n",   //
14289                "  /* \n", //
14290                "  \n",    //
14291                "  */\n",  //
14292                "x\n",     //
14293                "{{} \n",  //
14294                "}\n",     //
14295            )
14296        );
14297    });
14298}
14299
14300#[gpui::test]
14301fn test_highlighted_ranges(cx: &mut TestAppContext) {
14302    init_test(cx, |_| {});
14303
14304    let editor = cx.add_window(|window, cx| {
14305        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
14306        build_editor(buffer.clone(), window, cx)
14307    });
14308
14309    _ = editor.update(cx, |editor, window, cx| {
14310        struct Type1;
14311        struct Type2;
14312
14313        let buffer = editor.buffer.read(cx).snapshot(cx);
14314
14315        let anchor_range =
14316            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
14317
14318        editor.highlight_background::<Type1>(
14319            &[
14320                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
14321                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
14322                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
14323                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
14324            ],
14325            |_| Hsla::red(),
14326            cx,
14327        );
14328        editor.highlight_background::<Type2>(
14329            &[
14330                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
14331                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
14332                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
14333                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
14334            ],
14335            |_| Hsla::green(),
14336            cx,
14337        );
14338
14339        let snapshot = editor.snapshot(window, cx);
14340        let mut highlighted_ranges = editor.background_highlights_in_range(
14341            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
14342            &snapshot,
14343            cx.theme(),
14344        );
14345        // Enforce a consistent ordering based on color without relying on the ordering of the
14346        // highlight's `TypeId` which is non-executor.
14347        highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
14348        assert_eq!(
14349            highlighted_ranges,
14350            &[
14351                (
14352                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
14353                    Hsla::red(),
14354                ),
14355                (
14356                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
14357                    Hsla::red(),
14358                ),
14359                (
14360                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
14361                    Hsla::green(),
14362                ),
14363                (
14364                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
14365                    Hsla::green(),
14366                ),
14367            ]
14368        );
14369        assert_eq!(
14370            editor.background_highlights_in_range(
14371                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
14372                &snapshot,
14373                cx.theme(),
14374            ),
14375            &[(
14376                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
14377                Hsla::red(),
14378            )]
14379        );
14380    });
14381}
14382
14383#[gpui::test]
14384async fn test_following(cx: &mut TestAppContext) {
14385    init_test(cx, |_| {});
14386
14387    let fs = FakeFs::new(cx.executor());
14388    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
14389
14390    let buffer = project.update(cx, |project, cx| {
14391        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
14392        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
14393    });
14394    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
14395    let follower = cx.update(|cx| {
14396        cx.open_window(
14397            WindowOptions {
14398                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
14399                    gpui::Point::new(px(0.), px(0.)),
14400                    gpui::Point::new(px(10.), px(80.)),
14401                ))),
14402                ..Default::default()
14403            },
14404            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
14405        )
14406        .unwrap()
14407    });
14408
14409    let is_still_following = Rc::new(RefCell::new(true));
14410    let follower_edit_event_count = Rc::new(RefCell::new(0));
14411    let pending_update = Rc::new(RefCell::new(None));
14412    let leader_entity = leader.root(cx).unwrap();
14413    let follower_entity = follower.root(cx).unwrap();
14414    _ = follower.update(cx, {
14415        let update = pending_update.clone();
14416        let is_still_following = is_still_following.clone();
14417        let follower_edit_event_count = follower_edit_event_count.clone();
14418        |_, window, cx| {
14419            cx.subscribe_in(
14420                &leader_entity,
14421                window,
14422                move |_, leader, event, window, cx| {
14423                    leader.read(cx).add_event_to_update_proto(
14424                        event,
14425                        &mut update.borrow_mut(),
14426                        window,
14427                        cx,
14428                    );
14429                },
14430            )
14431            .detach();
14432
14433            cx.subscribe_in(
14434                &follower_entity,
14435                window,
14436                move |_, _, event: &EditorEvent, _window, _cx| {
14437                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
14438                        *is_still_following.borrow_mut() = false;
14439                    }
14440
14441                    if let EditorEvent::BufferEdited = event {
14442                        *follower_edit_event_count.borrow_mut() += 1;
14443                    }
14444                },
14445            )
14446            .detach();
14447        }
14448    });
14449
14450    // Update the selections only
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    });
14456    follower
14457        .update(cx, |follower, window, cx| {
14458            follower.apply_update_proto(
14459                &project,
14460                pending_update.borrow_mut().take().unwrap(),
14461                window,
14462                cx,
14463            )
14464        })
14465        .unwrap()
14466        .await
14467        .unwrap();
14468    _ = follower.update(cx, |follower, _, cx| {
14469        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
14470    });
14471    assert!(*is_still_following.borrow());
14472    assert_eq!(*follower_edit_event_count.borrow(), 0);
14473
14474    // Update the scroll position only
14475    _ = leader.update(cx, |leader, window, cx| {
14476        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), 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    assert_eq!(
14491        follower
14492            .update(cx, |follower, _, cx| follower.scroll_position(cx))
14493            .unwrap(),
14494        gpui::Point::new(1.5, 3.5)
14495    );
14496    assert!(*is_still_following.borrow());
14497    assert_eq!(*follower_edit_event_count.borrow(), 0);
14498
14499    // Update the selections and scroll position. The follower's scroll position is updated
14500    // via autoscroll, not via the leader's exact scroll position.
14501    _ = leader.update(cx, |leader, window, cx| {
14502        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14503            s.select_ranges([0..0])
14504        });
14505        leader.request_autoscroll(Autoscroll::newest(), cx);
14506        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
14507    });
14508    follower
14509        .update(cx, |follower, window, cx| {
14510            follower.apply_update_proto(
14511                &project,
14512                pending_update.borrow_mut().take().unwrap(),
14513                window,
14514                cx,
14515            )
14516        })
14517        .unwrap()
14518        .await
14519        .unwrap();
14520    _ = follower.update(cx, |follower, _, cx| {
14521        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
14522        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
14523    });
14524    assert!(*is_still_following.borrow());
14525
14526    // Creating a pending selection that precedes another selection
14527    _ = leader.update(cx, |leader, window, cx| {
14528        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14529            s.select_ranges([1..1])
14530        });
14531        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
14532    });
14533    follower
14534        .update(cx, |follower, window, cx| {
14535            follower.apply_update_proto(
14536                &project,
14537                pending_update.borrow_mut().take().unwrap(),
14538                window,
14539                cx,
14540            )
14541        })
14542        .unwrap()
14543        .await
14544        .unwrap();
14545    _ = follower.update(cx, |follower, _, cx| {
14546        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
14547    });
14548    assert!(*is_still_following.borrow());
14549
14550    // Extend the pending selection so that it surrounds another selection
14551    _ = leader.update(cx, |leader, window, cx| {
14552        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
14553    });
14554    follower
14555        .update(cx, |follower, window, cx| {
14556            follower.apply_update_proto(
14557                &project,
14558                pending_update.borrow_mut().take().unwrap(),
14559                window,
14560                cx,
14561            )
14562        })
14563        .unwrap()
14564        .await
14565        .unwrap();
14566    _ = follower.update(cx, |follower, _, cx| {
14567        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
14568    });
14569
14570    // Scrolling locally breaks the follow
14571    _ = follower.update(cx, |follower, window, cx| {
14572        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
14573        follower.set_scroll_anchor(
14574            ScrollAnchor {
14575                anchor: top_anchor,
14576                offset: gpui::Point::new(0.0, 0.5),
14577            },
14578            window,
14579            cx,
14580        );
14581    });
14582    assert!(!(*is_still_following.borrow()));
14583}
14584
14585#[gpui::test]
14586async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
14587    init_test(cx, |_| {});
14588
14589    let fs = FakeFs::new(cx.executor());
14590    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
14591    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14592    let pane = workspace
14593        .update(cx, |workspace, _, _| workspace.active_pane().clone())
14594        .unwrap();
14595
14596    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14597
14598    let leader = pane.update_in(cx, |_, window, cx| {
14599        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
14600        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
14601    });
14602
14603    // Start following the editor when it has no excerpts.
14604    let mut state_message =
14605        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
14606    let workspace_entity = workspace.root(cx).unwrap();
14607    let follower_1 = cx
14608        .update_window(*workspace.deref(), |_, window, cx| {
14609            Editor::from_state_proto(
14610                workspace_entity,
14611                ViewId {
14612                    creator: CollaboratorId::PeerId(PeerId::default()),
14613                    id: 0,
14614                },
14615                &mut state_message,
14616                window,
14617                cx,
14618            )
14619        })
14620        .unwrap()
14621        .unwrap()
14622        .await
14623        .unwrap();
14624
14625    let update_message = Rc::new(RefCell::new(None));
14626    follower_1.update_in(cx, {
14627        let update = update_message.clone();
14628        |_, window, cx| {
14629            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
14630                leader.read(cx).add_event_to_update_proto(
14631                    event,
14632                    &mut update.borrow_mut(),
14633                    window,
14634                    cx,
14635                );
14636            })
14637            .detach();
14638        }
14639    });
14640
14641    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
14642        (
14643            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
14644            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
14645        )
14646    });
14647
14648    // Insert some excerpts.
14649    leader.update(cx, |leader, cx| {
14650        leader.buffer.update(cx, |multibuffer, cx| {
14651            multibuffer.set_excerpts_for_path(
14652                PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
14653                buffer_1.clone(),
14654                vec![
14655                    Point::row_range(0..3),
14656                    Point::row_range(1..6),
14657                    Point::row_range(12..15),
14658                ],
14659                0,
14660                cx,
14661            );
14662            multibuffer.set_excerpts_for_path(
14663                PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
14664                buffer_2.clone(),
14665                vec![Point::row_range(0..6), Point::row_range(8..12)],
14666                0,
14667                cx,
14668            );
14669        });
14670    });
14671
14672    // Apply the update of adding the excerpts.
14673    follower_1
14674        .update_in(cx, |follower, window, cx| {
14675            follower.apply_update_proto(
14676                &project,
14677                update_message.borrow().clone().unwrap(),
14678                window,
14679                cx,
14680            )
14681        })
14682        .await
14683        .unwrap();
14684    assert_eq!(
14685        follower_1.update(cx, |editor, cx| editor.text(cx)),
14686        leader.update(cx, |editor, cx| editor.text(cx))
14687    );
14688    update_message.borrow_mut().take();
14689
14690    // Start following separately after it already has excerpts.
14691    let mut state_message =
14692        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
14693    let workspace_entity = workspace.root(cx).unwrap();
14694    let follower_2 = cx
14695        .update_window(*workspace.deref(), |_, window, cx| {
14696            Editor::from_state_proto(
14697                workspace_entity,
14698                ViewId {
14699                    creator: CollaboratorId::PeerId(PeerId::default()),
14700                    id: 0,
14701                },
14702                &mut state_message,
14703                window,
14704                cx,
14705            )
14706        })
14707        .unwrap()
14708        .unwrap()
14709        .await
14710        .unwrap();
14711    assert_eq!(
14712        follower_2.update(cx, |editor, cx| editor.text(cx)),
14713        leader.update(cx, |editor, cx| editor.text(cx))
14714    );
14715
14716    // Remove some excerpts.
14717    leader.update(cx, |leader, cx| {
14718        leader.buffer.update(cx, |multibuffer, cx| {
14719            let excerpt_ids = multibuffer.excerpt_ids();
14720            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
14721            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
14722        });
14723    });
14724
14725    // Apply the update of removing the excerpts.
14726    follower_1
14727        .update_in(cx, |follower, window, cx| {
14728            follower.apply_update_proto(
14729                &project,
14730                update_message.borrow().clone().unwrap(),
14731                window,
14732                cx,
14733            )
14734        })
14735        .await
14736        .unwrap();
14737    follower_2
14738        .update_in(cx, |follower, window, cx| {
14739            follower.apply_update_proto(
14740                &project,
14741                update_message.borrow().clone().unwrap(),
14742                window,
14743                cx,
14744            )
14745        })
14746        .await
14747        .unwrap();
14748    update_message.borrow_mut().take();
14749    assert_eq!(
14750        follower_1.update(cx, |editor, cx| editor.text(cx)),
14751        leader.update(cx, |editor, cx| editor.text(cx))
14752    );
14753}
14754
14755#[gpui::test]
14756async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14757    init_test(cx, |_| {});
14758
14759    let mut cx = EditorTestContext::new(cx).await;
14760    let lsp_store =
14761        cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
14762
14763    cx.set_state(indoc! {"
14764        ˇfn func(abc def: i32) -> u32 {
14765        }
14766    "});
14767
14768    cx.update(|_, cx| {
14769        lsp_store.update(cx, |lsp_store, cx| {
14770            lsp_store
14771                .update_diagnostics(
14772                    LanguageServerId(0),
14773                    lsp::PublishDiagnosticsParams {
14774                        uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
14775                        version: None,
14776                        diagnostics: vec![
14777                            lsp::Diagnostic {
14778                                range: lsp::Range::new(
14779                                    lsp::Position::new(0, 11),
14780                                    lsp::Position::new(0, 12),
14781                                ),
14782                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14783                                ..Default::default()
14784                            },
14785                            lsp::Diagnostic {
14786                                range: lsp::Range::new(
14787                                    lsp::Position::new(0, 12),
14788                                    lsp::Position::new(0, 15),
14789                                ),
14790                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14791                                ..Default::default()
14792                            },
14793                            lsp::Diagnostic {
14794                                range: lsp::Range::new(
14795                                    lsp::Position::new(0, 25),
14796                                    lsp::Position::new(0, 28),
14797                                ),
14798                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14799                                ..Default::default()
14800                            },
14801                        ],
14802                    },
14803                    None,
14804                    DiagnosticSourceKind::Pushed,
14805                    &[],
14806                    cx,
14807                )
14808                .unwrap()
14809        });
14810    });
14811
14812    executor.run_until_parked();
14813
14814    cx.update_editor(|editor, window, cx| {
14815        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
14816    });
14817
14818    cx.assert_editor_state(indoc! {"
14819        fn func(abc def: i32) -> ˇu32 {
14820        }
14821    "});
14822
14823    cx.update_editor(|editor, window, cx| {
14824        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
14825    });
14826
14827    cx.assert_editor_state(indoc! {"
14828        fn func(abc ˇdef: i32) -> u32 {
14829        }
14830    "});
14831
14832    cx.update_editor(|editor, window, cx| {
14833        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
14834    });
14835
14836    cx.assert_editor_state(indoc! {"
14837        fn func(abcˇ def: i32) -> u32 {
14838        }
14839    "});
14840
14841    cx.update_editor(|editor, window, cx| {
14842        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
14843    });
14844
14845    cx.assert_editor_state(indoc! {"
14846        fn func(abc def: i32) -> ˇu32 {
14847        }
14848    "});
14849}
14850
14851#[gpui::test]
14852async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14853    init_test(cx, |_| {});
14854
14855    let mut cx = EditorTestContext::new(cx).await;
14856
14857    let diff_base = r#"
14858        use some::mod;
14859
14860        const A: u32 = 42;
14861
14862        fn main() {
14863            println!("hello");
14864
14865            println!("world");
14866        }
14867        "#
14868    .unindent();
14869
14870    // Edits are modified, removed, modified, added
14871    cx.set_state(
14872        &r#"
14873        use some::modified;
14874
14875        ˇ
14876        fn main() {
14877            println!("hello there");
14878
14879            println!("around the");
14880            println!("world");
14881        }
14882        "#
14883        .unindent(),
14884    );
14885
14886    cx.set_head_text(&diff_base);
14887    executor.run_until_parked();
14888
14889    cx.update_editor(|editor, window, cx| {
14890        //Wrap around the bottom of the buffer
14891        for _ in 0..3 {
14892            editor.go_to_next_hunk(&GoToHunk, window, cx);
14893        }
14894    });
14895
14896    cx.assert_editor_state(
14897        &r#"
14898        ˇuse some::modified;
14899
14900
14901        fn main() {
14902            println!("hello there");
14903
14904            println!("around the");
14905            println!("world");
14906        }
14907        "#
14908        .unindent(),
14909    );
14910
14911    cx.update_editor(|editor, window, cx| {
14912        //Wrap around the top of the buffer
14913        for _ in 0..2 {
14914            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14915        }
14916    });
14917
14918    cx.assert_editor_state(
14919        &r#"
14920        use some::modified;
14921
14922
14923        fn main() {
14924        ˇ    println!("hello there");
14925
14926            println!("around the");
14927            println!("world");
14928        }
14929        "#
14930        .unindent(),
14931    );
14932
14933    cx.update_editor(|editor, window, cx| {
14934        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14935    });
14936
14937    cx.assert_editor_state(
14938        &r#"
14939        use some::modified;
14940
14941        ˇ
14942        fn main() {
14943            println!("hello there");
14944
14945            println!("around the");
14946            println!("world");
14947        }
14948        "#
14949        .unindent(),
14950    );
14951
14952    cx.update_editor(|editor, window, cx| {
14953        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14954    });
14955
14956    cx.assert_editor_state(
14957        &r#"
14958        ˇuse some::modified;
14959
14960
14961        fn main() {
14962            println!("hello there");
14963
14964            println!("around the");
14965            println!("world");
14966        }
14967        "#
14968        .unindent(),
14969    );
14970
14971    cx.update_editor(|editor, window, cx| {
14972        for _ in 0..2 {
14973            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14974        }
14975    });
14976
14977    cx.assert_editor_state(
14978        &r#"
14979        use some::modified;
14980
14981
14982        fn main() {
14983        ˇ    println!("hello there");
14984
14985            println!("around the");
14986            println!("world");
14987        }
14988        "#
14989        .unindent(),
14990    );
14991
14992    cx.update_editor(|editor, window, cx| {
14993        editor.fold(&Fold, window, cx);
14994    });
14995
14996    cx.update_editor(|editor, window, cx| {
14997        editor.go_to_next_hunk(&GoToHunk, window, cx);
14998    });
14999
15000    cx.assert_editor_state(
15001        &r#"
15002        ˇuse some::modified;
15003
15004
15005        fn main() {
15006            println!("hello there");
15007
15008            println!("around the");
15009            println!("world");
15010        }
15011        "#
15012        .unindent(),
15013    );
15014}
15015
15016#[test]
15017fn test_split_words() {
15018    fn split(text: &str) -> Vec<&str> {
15019        split_words(text).collect()
15020    }
15021
15022    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
15023    assert_eq!(split("hello_world"), &["hello_", "world"]);
15024    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
15025    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
15026    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
15027    assert_eq!(split("helloworld"), &["helloworld"]);
15028
15029    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
15030}
15031
15032#[gpui::test]
15033async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
15034    init_test(cx, |_| {});
15035
15036    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
15037    let mut assert = |before, after| {
15038        let _state_context = cx.set_state(before);
15039        cx.run_until_parked();
15040        cx.update_editor(|editor, window, cx| {
15041            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
15042        });
15043        cx.run_until_parked();
15044        cx.assert_editor_state(after);
15045    };
15046
15047    // Outside bracket jumps to outside of matching bracket
15048    assert("console.logˇ(var);", "console.log(var)ˇ;");
15049    assert("console.log(var)ˇ;", "console.logˇ(var);");
15050
15051    // Inside bracket jumps to inside of matching bracket
15052    assert("console.log(ˇvar);", "console.log(varˇ);");
15053    assert("console.log(varˇ);", "console.log(ˇvar);");
15054
15055    // When outside a bracket and inside, favor jumping to the inside bracket
15056    assert(
15057        "console.log('foo', [1, 2, 3]ˇ);",
15058        "console.log(ˇ'foo', [1, 2, 3]);",
15059    );
15060    assert(
15061        "console.log(ˇ'foo', [1, 2, 3]);",
15062        "console.log('foo', [1, 2, 3]ˇ);",
15063    );
15064
15065    // Bias forward if two options are equally likely
15066    assert(
15067        "let result = curried_fun()ˇ();",
15068        "let result = curried_fun()()ˇ;",
15069    );
15070
15071    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
15072    assert(
15073        indoc! {"
15074            function test() {
15075                console.log('test')ˇ
15076            }"},
15077        indoc! {"
15078            function test() {
15079                console.logˇ('test')
15080            }"},
15081    );
15082}
15083
15084#[gpui::test]
15085async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
15086    init_test(cx, |_| {});
15087
15088    let fs = FakeFs::new(cx.executor());
15089    fs.insert_tree(
15090        path!("/a"),
15091        json!({
15092            "main.rs": "fn main() { let a = 5; }",
15093            "other.rs": "// Test file",
15094        }),
15095    )
15096    .await;
15097    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15098
15099    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15100    language_registry.add(Arc::new(Language::new(
15101        LanguageConfig {
15102            name: "Rust".into(),
15103            matcher: LanguageMatcher {
15104                path_suffixes: vec!["rs".to_string()],
15105                ..Default::default()
15106            },
15107            brackets: BracketPairConfig {
15108                pairs: vec![BracketPair {
15109                    start: "{".to_string(),
15110                    end: "}".to_string(),
15111                    close: true,
15112                    surround: true,
15113                    newline: true,
15114                }],
15115                disabled_scopes_by_bracket_ix: Vec::new(),
15116            },
15117            ..Default::default()
15118        },
15119        Some(tree_sitter_rust::LANGUAGE.into()),
15120    )));
15121    let mut fake_servers = language_registry.register_fake_lsp(
15122        "Rust",
15123        FakeLspAdapter {
15124            capabilities: lsp::ServerCapabilities {
15125                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
15126                    first_trigger_character: "{".to_string(),
15127                    more_trigger_character: None,
15128                }),
15129                ..Default::default()
15130            },
15131            ..Default::default()
15132        },
15133    );
15134
15135    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15136
15137    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15138
15139    let worktree_id = workspace
15140        .update(cx, |workspace, _, cx| {
15141            workspace.project().update(cx, |project, cx| {
15142                project.worktrees(cx).next().unwrap().read(cx).id()
15143            })
15144        })
15145        .unwrap();
15146
15147    let buffer = project
15148        .update(cx, |project, cx| {
15149            project.open_local_buffer(path!("/a/main.rs"), cx)
15150        })
15151        .await
15152        .unwrap();
15153    let editor_handle = workspace
15154        .update(cx, |workspace, window, cx| {
15155            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
15156        })
15157        .unwrap()
15158        .await
15159        .unwrap()
15160        .downcast::<Editor>()
15161        .unwrap();
15162
15163    cx.executor().start_waiting();
15164    let fake_server = fake_servers.next().await.unwrap();
15165
15166    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
15167        |params, _| async move {
15168            assert_eq!(
15169                params.text_document_position.text_document.uri,
15170                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
15171            );
15172            assert_eq!(
15173                params.text_document_position.position,
15174                lsp::Position::new(0, 21),
15175            );
15176
15177            Ok(Some(vec![lsp::TextEdit {
15178                new_text: "]".to_string(),
15179                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15180            }]))
15181        },
15182    );
15183
15184    editor_handle.update_in(cx, |editor, window, cx| {
15185        window.focus(&editor.focus_handle(cx));
15186        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15187            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
15188        });
15189        editor.handle_input("{", window, cx);
15190    });
15191
15192    cx.executor().run_until_parked();
15193
15194    buffer.update(cx, |buffer, _| {
15195        assert_eq!(
15196            buffer.text(),
15197            "fn main() { let a = {5}; }",
15198            "No extra braces from on type formatting should appear in the buffer"
15199        )
15200    });
15201}
15202
15203#[gpui::test(iterations = 20, seeds(31))]
15204async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
15205    init_test(cx, |_| {});
15206
15207    let mut cx = EditorLspTestContext::new_rust(
15208        lsp::ServerCapabilities {
15209            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
15210                first_trigger_character: ".".to_string(),
15211                more_trigger_character: None,
15212            }),
15213            ..Default::default()
15214        },
15215        cx,
15216    )
15217    .await;
15218
15219    cx.update_buffer(|buffer, _| {
15220        // This causes autoindent to be async.
15221        buffer.set_sync_parse_timeout(Duration::ZERO)
15222    });
15223
15224    cx.set_state("fn c() {\n    d()ˇ\n}\n");
15225    cx.simulate_keystroke("\n");
15226    cx.run_until_parked();
15227
15228    let buffer_cloned =
15229        cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap().clone());
15230    let mut request =
15231        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
15232            let buffer_cloned = buffer_cloned.clone();
15233            async move {
15234                buffer_cloned.update(&mut cx, |buffer, _| {
15235                    assert_eq!(
15236                        buffer.text(),
15237                        "fn c() {\n    d()\n        .\n}\n",
15238                        "OnTypeFormatting should triggered after autoindent applied"
15239                    )
15240                })?;
15241
15242                Ok(Some(vec![]))
15243            }
15244        });
15245
15246    cx.simulate_keystroke(".");
15247    cx.run_until_parked();
15248
15249    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
15250    assert!(request.next().await.is_some());
15251    request.close();
15252    assert!(request.next().await.is_none());
15253}
15254
15255#[gpui::test]
15256async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
15257    init_test(cx, |_| {});
15258
15259    let fs = FakeFs::new(cx.executor());
15260    fs.insert_tree(
15261        path!("/a"),
15262        json!({
15263            "main.rs": "fn main() { let a = 5; }",
15264            "other.rs": "// Test file",
15265        }),
15266    )
15267    .await;
15268
15269    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15270
15271    let server_restarts = Arc::new(AtomicUsize::new(0));
15272    let closure_restarts = Arc::clone(&server_restarts);
15273    let language_server_name = "test language server";
15274    let language_name: LanguageName = "Rust".into();
15275
15276    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15277    language_registry.add(Arc::new(Language::new(
15278        LanguageConfig {
15279            name: language_name.clone(),
15280            matcher: LanguageMatcher {
15281                path_suffixes: vec!["rs".to_string()],
15282                ..Default::default()
15283            },
15284            ..Default::default()
15285        },
15286        Some(tree_sitter_rust::LANGUAGE.into()),
15287    )));
15288    let mut fake_servers = language_registry.register_fake_lsp(
15289        "Rust",
15290        FakeLspAdapter {
15291            name: language_server_name,
15292            initialization_options: Some(json!({
15293                "testOptionValue": true
15294            })),
15295            initializer: Some(Box::new(move |fake_server| {
15296                let task_restarts = Arc::clone(&closure_restarts);
15297                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
15298                    task_restarts.fetch_add(1, atomic::Ordering::Release);
15299                    futures::future::ready(Ok(()))
15300                });
15301            })),
15302            ..Default::default()
15303        },
15304    );
15305
15306    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15307    let _buffer = project
15308        .update(cx, |project, cx| {
15309            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
15310        })
15311        .await
15312        .unwrap();
15313    let _fake_server = fake_servers.next().await.unwrap();
15314    update_test_language_settings(cx, |language_settings| {
15315        language_settings.languages.0.insert(
15316            language_name.clone(),
15317            LanguageSettingsContent {
15318                tab_size: NonZeroU32::new(8),
15319                ..Default::default()
15320            },
15321        );
15322    });
15323    cx.executor().run_until_parked();
15324    assert_eq!(
15325        server_restarts.load(atomic::Ordering::Acquire),
15326        0,
15327        "Should not restart LSP server on an unrelated change"
15328    );
15329
15330    update_test_project_settings(cx, |project_settings| {
15331        project_settings.lsp.insert(
15332            "Some other server name".into(),
15333            LspSettings {
15334                binary: None,
15335                settings: None,
15336                initialization_options: Some(json!({
15337                    "some other init value": false
15338                })),
15339                enable_lsp_tasks: false,
15340            },
15341        );
15342    });
15343    cx.executor().run_until_parked();
15344    assert_eq!(
15345        server_restarts.load(atomic::Ordering::Acquire),
15346        0,
15347        "Should not restart LSP server on an unrelated LSP settings change"
15348    );
15349
15350    update_test_project_settings(cx, |project_settings| {
15351        project_settings.lsp.insert(
15352            language_server_name.into(),
15353            LspSettings {
15354                binary: None,
15355                settings: None,
15356                initialization_options: Some(json!({
15357                    "anotherInitValue": false
15358                })),
15359                enable_lsp_tasks: false,
15360            },
15361        );
15362    });
15363    cx.executor().run_until_parked();
15364    assert_eq!(
15365        server_restarts.load(atomic::Ordering::Acquire),
15366        1,
15367        "Should restart LSP server on a related LSP settings change"
15368    );
15369
15370    update_test_project_settings(cx, |project_settings| {
15371        project_settings.lsp.insert(
15372            language_server_name.into(),
15373            LspSettings {
15374                binary: None,
15375                settings: None,
15376                initialization_options: Some(json!({
15377                    "anotherInitValue": false
15378                })),
15379                enable_lsp_tasks: false,
15380            },
15381        );
15382    });
15383    cx.executor().run_until_parked();
15384    assert_eq!(
15385        server_restarts.load(atomic::Ordering::Acquire),
15386        1,
15387        "Should not restart LSP server on a related LSP settings change that is the same"
15388    );
15389
15390    update_test_project_settings(cx, |project_settings| {
15391        project_settings.lsp.insert(
15392            language_server_name.into(),
15393            LspSettings {
15394                binary: None,
15395                settings: None,
15396                initialization_options: None,
15397                enable_lsp_tasks: false,
15398            },
15399        );
15400    });
15401    cx.executor().run_until_parked();
15402    assert_eq!(
15403        server_restarts.load(atomic::Ordering::Acquire),
15404        2,
15405        "Should restart LSP server on another related LSP settings change"
15406    );
15407}
15408
15409#[gpui::test]
15410async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
15411    init_test(cx, |_| {});
15412
15413    let mut cx = EditorLspTestContext::new_rust(
15414        lsp::ServerCapabilities {
15415            completion_provider: Some(lsp::CompletionOptions {
15416                trigger_characters: Some(vec![".".to_string()]),
15417                resolve_provider: Some(true),
15418                ..Default::default()
15419            }),
15420            ..Default::default()
15421        },
15422        cx,
15423    )
15424    .await;
15425
15426    cx.set_state("fn main() { let a = 2ˇ; }");
15427    cx.simulate_keystroke(".");
15428    let completion_item = lsp::CompletionItem {
15429        label: "some".into(),
15430        kind: Some(lsp::CompletionItemKind::SNIPPET),
15431        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15432        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15433            kind: lsp::MarkupKind::Markdown,
15434            value: "```rust\nSome(2)\n```".to_string(),
15435        })),
15436        deprecated: Some(false),
15437        sort_text: Some("fffffff2".to_string()),
15438        filter_text: Some("some".to_string()),
15439        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15440        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15441            range: lsp::Range {
15442                start: lsp::Position {
15443                    line: 0,
15444                    character: 22,
15445                },
15446                end: lsp::Position {
15447                    line: 0,
15448                    character: 22,
15449                },
15450            },
15451            new_text: "Some(2)".to_string(),
15452        })),
15453        additional_text_edits: Some(vec![lsp::TextEdit {
15454            range: lsp::Range {
15455                start: lsp::Position {
15456                    line: 0,
15457                    character: 20,
15458                },
15459                end: lsp::Position {
15460                    line: 0,
15461                    character: 22,
15462                },
15463            },
15464            new_text: "".to_string(),
15465        }]),
15466        ..Default::default()
15467    };
15468
15469    let closure_completion_item = completion_item.clone();
15470    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15471        let task_completion_item = closure_completion_item.clone();
15472        async move {
15473            Ok(Some(lsp::CompletionResponse::Array(vec![
15474                task_completion_item,
15475            ])))
15476        }
15477    });
15478
15479    request.next().await;
15480
15481    cx.condition(|editor, _| editor.context_menu_visible())
15482        .await;
15483    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15484        editor
15485            .confirm_completion(&ConfirmCompletion::default(), window, cx)
15486            .unwrap()
15487    });
15488    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
15489
15490    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
15491        let task_completion_item = completion_item.clone();
15492        async move { Ok(task_completion_item) }
15493    })
15494    .next()
15495    .await
15496    .unwrap();
15497    apply_additional_edits.await.unwrap();
15498    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
15499}
15500
15501#[gpui::test]
15502async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
15503    init_test(cx, |_| {});
15504
15505    let mut cx = EditorLspTestContext::new_rust(
15506        lsp::ServerCapabilities {
15507            completion_provider: Some(lsp::CompletionOptions {
15508                trigger_characters: Some(vec![".".to_string()]),
15509                resolve_provider: Some(true),
15510                ..Default::default()
15511            }),
15512            ..Default::default()
15513        },
15514        cx,
15515    )
15516    .await;
15517
15518    cx.set_state("fn main() { let a = 2ˇ; }");
15519    cx.simulate_keystroke(".");
15520
15521    let item1 = lsp::CompletionItem {
15522        label: "method id()".to_string(),
15523        filter_text: Some("id".to_string()),
15524        detail: None,
15525        documentation: None,
15526        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15527            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15528            new_text: ".id".to_string(),
15529        })),
15530        ..lsp::CompletionItem::default()
15531    };
15532
15533    let item2 = lsp::CompletionItem {
15534        label: "other".to_string(),
15535        filter_text: Some("other".to_string()),
15536        detail: None,
15537        documentation: None,
15538        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15539            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15540            new_text: ".other".to_string(),
15541        })),
15542        ..lsp::CompletionItem::default()
15543    };
15544
15545    let item1 = item1.clone();
15546    cx.set_request_handler::<lsp::request::Completion, _, _>({
15547        let item1 = item1.clone();
15548        move |_, _, _| {
15549            let item1 = item1.clone();
15550            let item2 = item2.clone();
15551            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
15552        }
15553    })
15554    .next()
15555    .await;
15556
15557    cx.condition(|editor, _| editor.context_menu_visible())
15558        .await;
15559    cx.update_editor(|editor, _, _| {
15560        let context_menu = editor.context_menu.borrow_mut();
15561        let context_menu = context_menu
15562            .as_ref()
15563            .expect("Should have the context menu deployed");
15564        match context_menu {
15565            CodeContextMenu::Completions(completions_menu) => {
15566                let completions = completions_menu.completions.borrow_mut();
15567                assert_eq!(
15568                    completions
15569                        .iter()
15570                        .map(|completion| &completion.label.text)
15571                        .collect::<Vec<_>>(),
15572                    vec!["method id()", "other"]
15573                )
15574            }
15575            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15576        }
15577    });
15578
15579    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
15580        let item1 = item1.clone();
15581        move |_, item_to_resolve, _| {
15582            let item1 = item1.clone();
15583            async move {
15584                if item1 == item_to_resolve {
15585                    Ok(lsp::CompletionItem {
15586                        label: "method id()".to_string(),
15587                        filter_text: Some("id".to_string()),
15588                        detail: Some("Now resolved!".to_string()),
15589                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
15590                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15591                            range: lsp::Range::new(
15592                                lsp::Position::new(0, 22),
15593                                lsp::Position::new(0, 22),
15594                            ),
15595                            new_text: ".id".to_string(),
15596                        })),
15597                        ..lsp::CompletionItem::default()
15598                    })
15599                } else {
15600                    Ok(item_to_resolve)
15601                }
15602            }
15603        }
15604    })
15605    .next()
15606    .await
15607    .unwrap();
15608    cx.run_until_parked();
15609
15610    cx.update_editor(|editor, window, cx| {
15611        editor.context_menu_next(&Default::default(), window, cx);
15612    });
15613
15614    cx.update_editor(|editor, _, _| {
15615        let context_menu = editor.context_menu.borrow_mut();
15616        let context_menu = context_menu
15617            .as_ref()
15618            .expect("Should have the context menu deployed");
15619        match context_menu {
15620            CodeContextMenu::Completions(completions_menu) => {
15621                let completions = completions_menu.completions.borrow_mut();
15622                assert_eq!(
15623                    completions
15624                        .iter()
15625                        .map(|completion| &completion.label.text)
15626                        .collect::<Vec<_>>(),
15627                    vec!["method id() Now resolved!", "other"],
15628                    "Should update first completion label, but not second as the filter text did not match."
15629                );
15630            }
15631            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15632        }
15633    });
15634}
15635
15636#[gpui::test]
15637async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
15638    init_test(cx, |_| {});
15639    let mut cx = EditorLspTestContext::new_rust(
15640        lsp::ServerCapabilities {
15641            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
15642            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
15643            completion_provider: Some(lsp::CompletionOptions {
15644                resolve_provider: Some(true),
15645                ..Default::default()
15646            }),
15647            ..Default::default()
15648        },
15649        cx,
15650    )
15651    .await;
15652    cx.set_state(indoc! {"
15653        struct TestStruct {
15654            field: i32
15655        }
15656
15657        fn mainˇ() {
15658            let unused_var = 42;
15659            let test_struct = TestStruct { field: 42 };
15660        }
15661    "});
15662    let symbol_range = cx.lsp_range(indoc! {"
15663        struct TestStruct {
15664            field: i32
15665        }
15666
15667        «fn main»() {
15668            let unused_var = 42;
15669            let test_struct = TestStruct { field: 42 };
15670        }
15671    "});
15672    let mut hover_requests =
15673        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
15674            Ok(Some(lsp::Hover {
15675                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
15676                    kind: lsp::MarkupKind::Markdown,
15677                    value: "Function documentation".to_string(),
15678                }),
15679                range: Some(symbol_range),
15680            }))
15681        });
15682
15683    // Case 1: Test that code action menu hide hover popover
15684    cx.dispatch_action(Hover);
15685    hover_requests.next().await;
15686    cx.condition(|editor, _| editor.hover_state.visible()).await;
15687    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
15688        move |_, _, _| async move {
15689            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
15690                lsp::CodeAction {
15691                    title: "Remove unused variable".to_string(),
15692                    kind: Some(CodeActionKind::QUICKFIX),
15693                    edit: Some(lsp::WorkspaceEdit {
15694                        changes: Some(
15695                            [(
15696                                lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
15697                                vec![lsp::TextEdit {
15698                                    range: lsp::Range::new(
15699                                        lsp::Position::new(5, 4),
15700                                        lsp::Position::new(5, 27),
15701                                    ),
15702                                    new_text: "".to_string(),
15703                                }],
15704                            )]
15705                            .into_iter()
15706                            .collect(),
15707                        ),
15708                        ..Default::default()
15709                    }),
15710                    ..Default::default()
15711                },
15712            )]))
15713        },
15714    );
15715    cx.update_editor(|editor, window, cx| {
15716        editor.toggle_code_actions(
15717            &ToggleCodeActions {
15718                deployed_from: None,
15719                quick_launch: false,
15720            },
15721            window,
15722            cx,
15723        );
15724    });
15725    code_action_requests.next().await;
15726    cx.run_until_parked();
15727    cx.condition(|editor, _| editor.context_menu_visible())
15728        .await;
15729    cx.update_editor(|editor, _, _| {
15730        assert!(
15731            !editor.hover_state.visible(),
15732            "Hover popover should be hidden when code action menu is shown"
15733        );
15734        // Hide code actions
15735        editor.context_menu.take();
15736    });
15737
15738    // Case 2: Test that code completions hide hover popover
15739    cx.dispatch_action(Hover);
15740    hover_requests.next().await;
15741    cx.condition(|editor, _| editor.hover_state.visible()).await;
15742    let counter = Arc::new(AtomicUsize::new(0));
15743    let mut completion_requests =
15744        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15745            let counter = counter.clone();
15746            async move {
15747                counter.fetch_add(1, atomic::Ordering::Release);
15748                Ok(Some(lsp::CompletionResponse::Array(vec![
15749                    lsp::CompletionItem {
15750                        label: "main".into(),
15751                        kind: Some(lsp::CompletionItemKind::FUNCTION),
15752                        detail: Some("() -> ()".to_string()),
15753                        ..Default::default()
15754                    },
15755                    lsp::CompletionItem {
15756                        label: "TestStruct".into(),
15757                        kind: Some(lsp::CompletionItemKind::STRUCT),
15758                        detail: Some("struct TestStruct".to_string()),
15759                        ..Default::default()
15760                    },
15761                ])))
15762            }
15763        });
15764    cx.update_editor(|editor, window, cx| {
15765        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15766    });
15767    completion_requests.next().await;
15768    cx.condition(|editor, _| editor.context_menu_visible())
15769        .await;
15770    cx.update_editor(|editor, _, _| {
15771        assert!(
15772            !editor.hover_state.visible(),
15773            "Hover popover should be hidden when completion menu is shown"
15774        );
15775    });
15776}
15777
15778#[gpui::test]
15779async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
15780    init_test(cx, |_| {});
15781
15782    let mut cx = EditorLspTestContext::new_rust(
15783        lsp::ServerCapabilities {
15784            completion_provider: Some(lsp::CompletionOptions {
15785                trigger_characters: Some(vec![".".to_string()]),
15786                resolve_provider: Some(true),
15787                ..Default::default()
15788            }),
15789            ..Default::default()
15790        },
15791        cx,
15792    )
15793    .await;
15794
15795    cx.set_state("fn main() { let a = 2ˇ; }");
15796    cx.simulate_keystroke(".");
15797
15798    let unresolved_item_1 = lsp::CompletionItem {
15799        label: "id".to_string(),
15800        filter_text: Some("id".to_string()),
15801        detail: None,
15802        documentation: None,
15803        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15804            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15805            new_text: ".id".to_string(),
15806        })),
15807        ..lsp::CompletionItem::default()
15808    };
15809    let resolved_item_1 = lsp::CompletionItem {
15810        additional_text_edits: Some(vec![lsp::TextEdit {
15811            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
15812            new_text: "!!".to_string(),
15813        }]),
15814        ..unresolved_item_1.clone()
15815    };
15816    let unresolved_item_2 = lsp::CompletionItem {
15817        label: "other".to_string(),
15818        filter_text: Some("other".to_string()),
15819        detail: None,
15820        documentation: None,
15821        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15822            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15823            new_text: ".other".to_string(),
15824        })),
15825        ..lsp::CompletionItem::default()
15826    };
15827    let resolved_item_2 = lsp::CompletionItem {
15828        additional_text_edits: Some(vec![lsp::TextEdit {
15829            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
15830            new_text: "??".to_string(),
15831        }]),
15832        ..unresolved_item_2.clone()
15833    };
15834
15835    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
15836    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
15837    cx.lsp
15838        .server
15839        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
15840            let unresolved_item_1 = unresolved_item_1.clone();
15841            let resolved_item_1 = resolved_item_1.clone();
15842            let unresolved_item_2 = unresolved_item_2.clone();
15843            let resolved_item_2 = resolved_item_2.clone();
15844            let resolve_requests_1 = resolve_requests_1.clone();
15845            let resolve_requests_2 = resolve_requests_2.clone();
15846            move |unresolved_request, _| {
15847                let unresolved_item_1 = unresolved_item_1.clone();
15848                let resolved_item_1 = resolved_item_1.clone();
15849                let unresolved_item_2 = unresolved_item_2.clone();
15850                let resolved_item_2 = resolved_item_2.clone();
15851                let resolve_requests_1 = resolve_requests_1.clone();
15852                let resolve_requests_2 = resolve_requests_2.clone();
15853                async move {
15854                    if unresolved_request == unresolved_item_1 {
15855                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
15856                        Ok(resolved_item_1.clone())
15857                    } else if unresolved_request == unresolved_item_2 {
15858                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
15859                        Ok(resolved_item_2.clone())
15860                    } else {
15861                        panic!("Unexpected completion item {unresolved_request:?}")
15862                    }
15863                }
15864            }
15865        })
15866        .detach();
15867
15868    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15869        let unresolved_item_1 = unresolved_item_1.clone();
15870        let unresolved_item_2 = unresolved_item_2.clone();
15871        async move {
15872            Ok(Some(lsp::CompletionResponse::Array(vec![
15873                unresolved_item_1,
15874                unresolved_item_2,
15875            ])))
15876        }
15877    })
15878    .next()
15879    .await;
15880
15881    cx.condition(|editor, _| editor.context_menu_visible())
15882        .await;
15883    cx.update_editor(|editor, _, _| {
15884        let context_menu = editor.context_menu.borrow_mut();
15885        let context_menu = context_menu
15886            .as_ref()
15887            .expect("Should have the context menu deployed");
15888        match context_menu {
15889            CodeContextMenu::Completions(completions_menu) => {
15890                let completions = completions_menu.completions.borrow_mut();
15891                assert_eq!(
15892                    completions
15893                        .iter()
15894                        .map(|completion| &completion.label.text)
15895                        .collect::<Vec<_>>(),
15896                    vec!["id", "other"]
15897                )
15898            }
15899            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15900        }
15901    });
15902    cx.run_until_parked();
15903
15904    cx.update_editor(|editor, window, cx| {
15905        editor.context_menu_next(&ContextMenuNext, window, cx);
15906    });
15907    cx.run_until_parked();
15908    cx.update_editor(|editor, window, cx| {
15909        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
15910    });
15911    cx.run_until_parked();
15912    cx.update_editor(|editor, window, cx| {
15913        editor.context_menu_next(&ContextMenuNext, window, cx);
15914    });
15915    cx.run_until_parked();
15916    cx.update_editor(|editor, window, cx| {
15917        editor
15918            .compose_completion(&ComposeCompletion::default(), window, cx)
15919            .expect("No task returned")
15920    })
15921    .await
15922    .expect("Completion failed");
15923    cx.run_until_parked();
15924
15925    cx.update_editor(|editor, _, cx| {
15926        assert_eq!(
15927            resolve_requests_1.load(atomic::Ordering::Acquire),
15928            1,
15929            "Should always resolve once despite multiple selections"
15930        );
15931        assert_eq!(
15932            resolve_requests_2.load(atomic::Ordering::Acquire),
15933            1,
15934            "Should always resolve once after multiple selections and applying the completion"
15935        );
15936        assert_eq!(
15937            editor.text(cx),
15938            "fn main() { let a = ??.other; }",
15939            "Should use resolved data when applying the completion"
15940        );
15941    });
15942}
15943
15944#[gpui::test]
15945async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
15946    init_test(cx, |_| {});
15947
15948    let item_0 = lsp::CompletionItem {
15949        label: "abs".into(),
15950        insert_text: Some("abs".into()),
15951        data: Some(json!({ "very": "special"})),
15952        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
15953        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
15954            lsp::InsertReplaceEdit {
15955                new_text: "abs".to_string(),
15956                insert: lsp::Range::default(),
15957                replace: lsp::Range::default(),
15958            },
15959        )),
15960        ..lsp::CompletionItem::default()
15961    };
15962    let items = iter::once(item_0.clone())
15963        .chain((11..51).map(|i| lsp::CompletionItem {
15964            label: format!("item_{}", i),
15965            insert_text: Some(format!("item_{}", i)),
15966            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15967            ..lsp::CompletionItem::default()
15968        }))
15969        .collect::<Vec<_>>();
15970
15971    let default_commit_characters = vec!["?".to_string()];
15972    let default_data = json!({ "default": "data"});
15973    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
15974    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
15975    let default_edit_range = lsp::Range {
15976        start: lsp::Position {
15977            line: 0,
15978            character: 5,
15979        },
15980        end: lsp::Position {
15981            line: 0,
15982            character: 5,
15983        },
15984    };
15985
15986    let mut cx = EditorLspTestContext::new_rust(
15987        lsp::ServerCapabilities {
15988            completion_provider: Some(lsp::CompletionOptions {
15989                trigger_characters: Some(vec![".".to_string()]),
15990                resolve_provider: Some(true),
15991                ..Default::default()
15992            }),
15993            ..Default::default()
15994        },
15995        cx,
15996    )
15997    .await;
15998
15999    cx.set_state("fn main() { let a = 2ˇ; }");
16000    cx.simulate_keystroke(".");
16001
16002    let completion_data = default_data.clone();
16003    let completion_characters = default_commit_characters.clone();
16004    let completion_items = items.clone();
16005    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16006        let default_data = completion_data.clone();
16007        let default_commit_characters = completion_characters.clone();
16008        let items = completion_items.clone();
16009        async move {
16010            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16011                items,
16012                item_defaults: Some(lsp::CompletionListItemDefaults {
16013                    data: Some(default_data.clone()),
16014                    commit_characters: Some(default_commit_characters.clone()),
16015                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
16016                        default_edit_range,
16017                    )),
16018                    insert_text_format: Some(default_insert_text_format),
16019                    insert_text_mode: Some(default_insert_text_mode),
16020                }),
16021                ..lsp::CompletionList::default()
16022            })))
16023        }
16024    })
16025    .next()
16026    .await;
16027
16028    let resolved_items = Arc::new(Mutex::new(Vec::new()));
16029    cx.lsp
16030        .server
16031        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
16032            let closure_resolved_items = resolved_items.clone();
16033            move |item_to_resolve, _| {
16034                let closure_resolved_items = closure_resolved_items.clone();
16035                async move {
16036                    closure_resolved_items.lock().push(item_to_resolve.clone());
16037                    Ok(item_to_resolve)
16038                }
16039            }
16040        })
16041        .detach();
16042
16043    cx.condition(|editor, _| editor.context_menu_visible())
16044        .await;
16045    cx.run_until_parked();
16046    cx.update_editor(|editor, _, _| {
16047        let menu = editor.context_menu.borrow_mut();
16048        match menu.as_ref().expect("should have the completions menu") {
16049            CodeContextMenu::Completions(completions_menu) => {
16050                assert_eq!(
16051                    completions_menu
16052                        .entries
16053                        .borrow()
16054                        .iter()
16055                        .map(|mat| mat.string.clone())
16056                        .collect::<Vec<String>>(),
16057                    items
16058                        .iter()
16059                        .map(|completion| completion.label.clone())
16060                        .collect::<Vec<String>>()
16061                );
16062            }
16063            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
16064        }
16065    });
16066    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
16067    // with 4 from the end.
16068    assert_eq!(
16069        *resolved_items.lock(),
16070        [&items[0..16], &items[items.len() - 4..items.len()]]
16071            .concat()
16072            .iter()
16073            .cloned()
16074            .map(|mut item| {
16075                if item.data.is_none() {
16076                    item.data = Some(default_data.clone());
16077                }
16078                item
16079            })
16080            .collect::<Vec<lsp::CompletionItem>>(),
16081        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
16082    );
16083    resolved_items.lock().clear();
16084
16085    cx.update_editor(|editor, window, cx| {
16086        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
16087    });
16088    cx.run_until_parked();
16089    // Completions that have already been resolved are skipped.
16090    assert_eq!(
16091        *resolved_items.lock(),
16092        items[items.len() - 17..items.len() - 4]
16093            .iter()
16094            .cloned()
16095            .map(|mut item| {
16096                if item.data.is_none() {
16097                    item.data = Some(default_data.clone());
16098                }
16099                item
16100            })
16101            .collect::<Vec<lsp::CompletionItem>>()
16102    );
16103    resolved_items.lock().clear();
16104}
16105
16106#[gpui::test]
16107async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
16108    init_test(cx, |_| {});
16109
16110    let mut cx = EditorLspTestContext::new(
16111        Language::new(
16112            LanguageConfig {
16113                matcher: LanguageMatcher {
16114                    path_suffixes: vec!["jsx".into()],
16115                    ..Default::default()
16116                },
16117                overrides: [(
16118                    "element".into(),
16119                    LanguageConfigOverride {
16120                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
16121                        ..Default::default()
16122                    },
16123                )]
16124                .into_iter()
16125                .collect(),
16126                ..Default::default()
16127            },
16128            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
16129        )
16130        .with_override_query("(jsx_self_closing_element) @element")
16131        .unwrap(),
16132        lsp::ServerCapabilities {
16133            completion_provider: Some(lsp::CompletionOptions {
16134                trigger_characters: Some(vec![":".to_string()]),
16135                ..Default::default()
16136            }),
16137            ..Default::default()
16138        },
16139        cx,
16140    )
16141    .await;
16142
16143    cx.lsp
16144        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16145            Ok(Some(lsp::CompletionResponse::Array(vec![
16146                lsp::CompletionItem {
16147                    label: "bg-blue".into(),
16148                    ..Default::default()
16149                },
16150                lsp::CompletionItem {
16151                    label: "bg-red".into(),
16152                    ..Default::default()
16153                },
16154                lsp::CompletionItem {
16155                    label: "bg-yellow".into(),
16156                    ..Default::default()
16157                },
16158            ])))
16159        });
16160
16161    cx.set_state(r#"<p class="bgˇ" />"#);
16162
16163    // Trigger completion when typing a dash, because the dash is an extra
16164    // word character in the 'element' scope, which contains the cursor.
16165    cx.simulate_keystroke("-");
16166    cx.executor().run_until_parked();
16167    cx.update_editor(|editor, _, _| {
16168        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16169        {
16170            assert_eq!(
16171                completion_menu_entries(&menu),
16172                &["bg-blue", "bg-red", "bg-yellow"]
16173            );
16174        } else {
16175            panic!("expected completion menu to be open");
16176        }
16177    });
16178
16179    cx.simulate_keystroke("l");
16180    cx.executor().run_until_parked();
16181    cx.update_editor(|editor, _, _| {
16182        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16183        {
16184            assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
16185        } else {
16186            panic!("expected completion menu to be open");
16187        }
16188    });
16189
16190    // When filtering completions, consider the character after the '-' to
16191    // be the start of a subword.
16192    cx.set_state(r#"<p class="yelˇ" />"#);
16193    cx.simulate_keystroke("l");
16194    cx.executor().run_until_parked();
16195    cx.update_editor(|editor, _, _| {
16196        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16197        {
16198            assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
16199        } else {
16200            panic!("expected completion menu to be open");
16201        }
16202    });
16203}
16204
16205fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
16206    let entries = menu.entries.borrow();
16207    entries.iter().map(|mat| mat.string.clone()).collect()
16208}
16209
16210#[gpui::test]
16211async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
16212    init_test(cx, |settings| {
16213        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
16214            Formatter::Prettier,
16215        )))
16216    });
16217
16218    let fs = FakeFs::new(cx.executor());
16219    fs.insert_file(path!("/file.ts"), Default::default()).await;
16220
16221    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
16222    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16223
16224    language_registry.add(Arc::new(Language::new(
16225        LanguageConfig {
16226            name: "TypeScript".into(),
16227            matcher: LanguageMatcher {
16228                path_suffixes: vec!["ts".to_string()],
16229                ..Default::default()
16230            },
16231            ..Default::default()
16232        },
16233        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
16234    )));
16235    update_test_language_settings(cx, |settings| {
16236        settings.defaults.prettier = Some(PrettierSettings {
16237            allowed: true,
16238            ..PrettierSettings::default()
16239        });
16240    });
16241
16242    let test_plugin = "test_plugin";
16243    let _ = language_registry.register_fake_lsp(
16244        "TypeScript",
16245        FakeLspAdapter {
16246            prettier_plugins: vec![test_plugin],
16247            ..Default::default()
16248        },
16249    );
16250
16251    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
16252    let buffer = project
16253        .update(cx, |project, cx| {
16254            project.open_local_buffer(path!("/file.ts"), cx)
16255        })
16256        .await
16257        .unwrap();
16258
16259    let buffer_text = "one\ntwo\nthree\n";
16260    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16261    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16262    editor.update_in(cx, |editor, window, cx| {
16263        editor.set_text(buffer_text, window, cx)
16264    });
16265
16266    editor
16267        .update_in(cx, |editor, window, cx| {
16268            editor.perform_format(
16269                project.clone(),
16270                FormatTrigger::Manual,
16271                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
16272                window,
16273                cx,
16274            )
16275        })
16276        .unwrap()
16277        .await;
16278    assert_eq!(
16279        editor.update(cx, |editor, cx| editor.text(cx)),
16280        buffer_text.to_string() + prettier_format_suffix,
16281        "Test prettier formatting was not applied to the original buffer text",
16282    );
16283
16284    update_test_language_settings(cx, |settings| {
16285        settings.defaults.formatter = Some(SelectedFormatter::Auto)
16286    });
16287    let format = editor.update_in(cx, |editor, window, cx| {
16288        editor.perform_format(
16289            project.clone(),
16290            FormatTrigger::Manual,
16291            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
16292            window,
16293            cx,
16294        )
16295    });
16296    format.await.unwrap();
16297    assert_eq!(
16298        editor.update(cx, |editor, cx| editor.text(cx)),
16299        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
16300        "Autoformatting (via test prettier) was not applied to the original buffer text",
16301    );
16302}
16303
16304#[gpui::test]
16305async fn test_addition_reverts(cx: &mut TestAppContext) {
16306    init_test(cx, |_| {});
16307    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16308    let base_text = indoc! {r#"
16309        struct Row;
16310        struct Row1;
16311        struct Row2;
16312
16313        struct Row4;
16314        struct Row5;
16315        struct Row6;
16316
16317        struct Row8;
16318        struct Row9;
16319        struct Row10;"#};
16320
16321    // When addition hunks are not adjacent to carets, no hunk revert is performed
16322    assert_hunk_revert(
16323        indoc! {r#"struct Row;
16324                   struct Row1;
16325                   struct Row1.1;
16326                   struct Row1.2;
16327                   struct Row2;ˇ
16328
16329                   struct Row4;
16330                   struct Row5;
16331                   struct Row6;
16332
16333                   struct Row8;
16334                   ˇstruct Row9;
16335                   struct Row9.1;
16336                   struct Row9.2;
16337                   struct Row9.3;
16338                   struct Row10;"#},
16339        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
16340        indoc! {r#"struct Row;
16341                   struct Row1;
16342                   struct Row1.1;
16343                   struct Row1.2;
16344                   struct Row2;ˇ
16345
16346                   struct Row4;
16347                   struct Row5;
16348                   struct Row6;
16349
16350                   struct Row8;
16351                   ˇstruct Row9;
16352                   struct Row9.1;
16353                   struct Row9.2;
16354                   struct Row9.3;
16355                   struct Row10;"#},
16356        base_text,
16357        &mut cx,
16358    );
16359    // Same for selections
16360    assert_hunk_revert(
16361        indoc! {r#"struct Row;
16362                   struct Row1;
16363                   struct Row2;
16364                   struct Row2.1;
16365                   struct Row2.2;
16366                   «ˇ
16367                   struct Row4;
16368                   struct» Row5;
16369                   «struct Row6;
16370                   ˇ»
16371                   struct Row9.1;
16372                   struct Row9.2;
16373                   struct Row9.3;
16374                   struct Row8;
16375                   struct Row9;
16376                   struct Row10;"#},
16377        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
16378        indoc! {r#"struct Row;
16379                   struct Row1;
16380                   struct Row2;
16381                   struct Row2.1;
16382                   struct Row2.2;
16383                   «ˇ
16384                   struct Row4;
16385                   struct» Row5;
16386                   «struct Row6;
16387                   ˇ»
16388                   struct Row9.1;
16389                   struct Row9.2;
16390                   struct Row9.3;
16391                   struct Row8;
16392                   struct Row9;
16393                   struct Row10;"#},
16394        base_text,
16395        &mut cx,
16396    );
16397
16398    // When carets and selections intersect the addition hunks, those are reverted.
16399    // Adjacent carets got merged.
16400    assert_hunk_revert(
16401        indoc! {r#"struct Row;
16402                   ˇ// something on the top
16403                   struct Row1;
16404                   struct Row2;
16405                   struct Roˇw3.1;
16406                   struct Row2.2;
16407                   struct Row2.3;ˇ
16408
16409                   struct Row4;
16410                   struct ˇRow5.1;
16411                   struct Row5.2;
16412                   struct «Rowˇ»5.3;
16413                   struct Row5;
16414                   struct Row6;
16415                   ˇ
16416                   struct Row9.1;
16417                   struct «Rowˇ»9.2;
16418                   struct «ˇRow»9.3;
16419                   struct Row8;
16420                   struct Row9;
16421                   «ˇ// something on bottom»
16422                   struct Row10;"#},
16423        vec![
16424            DiffHunkStatusKind::Added,
16425            DiffHunkStatusKind::Added,
16426            DiffHunkStatusKind::Added,
16427            DiffHunkStatusKind::Added,
16428            DiffHunkStatusKind::Added,
16429        ],
16430        indoc! {r#"struct Row;
16431                   ˇstruct Row1;
16432                   struct Row2;
16433                   ˇ
16434                   struct Row4;
16435                   ˇstruct Row5;
16436                   struct Row6;
16437                   ˇ
16438                   ˇstruct Row8;
16439                   struct Row9;
16440                   ˇstruct Row10;"#},
16441        base_text,
16442        &mut cx,
16443    );
16444}
16445
16446#[gpui::test]
16447async fn test_modification_reverts(cx: &mut TestAppContext) {
16448    init_test(cx, |_| {});
16449    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16450    let base_text = indoc! {r#"
16451        struct Row;
16452        struct Row1;
16453        struct Row2;
16454
16455        struct Row4;
16456        struct Row5;
16457        struct Row6;
16458
16459        struct Row8;
16460        struct Row9;
16461        struct Row10;"#};
16462
16463    // Modification hunks behave the same as the addition ones.
16464    assert_hunk_revert(
16465        indoc! {r#"struct Row;
16466                   struct Row1;
16467                   struct Row33;
16468                   ˇ
16469                   struct Row4;
16470                   struct Row5;
16471                   struct Row6;
16472                   ˇ
16473                   struct Row99;
16474                   struct Row9;
16475                   struct Row10;"#},
16476        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
16477        indoc! {r#"struct Row;
16478                   struct Row1;
16479                   struct Row33;
16480                   ˇ
16481                   struct Row4;
16482                   struct Row5;
16483                   struct Row6;
16484                   ˇ
16485                   struct Row99;
16486                   struct Row9;
16487                   struct Row10;"#},
16488        base_text,
16489        &mut cx,
16490    );
16491    assert_hunk_revert(
16492        indoc! {r#"struct Row;
16493                   struct Row1;
16494                   struct Row33;
16495                   «ˇ
16496                   struct Row4;
16497                   struct» Row5;
16498                   «struct Row6;
16499                   ˇ»
16500                   struct Row99;
16501                   struct Row9;
16502                   struct Row10;"#},
16503        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
16504        indoc! {r#"struct Row;
16505                   struct Row1;
16506                   struct Row33;
16507                   «ˇ
16508                   struct Row4;
16509                   struct» Row5;
16510                   «struct Row6;
16511                   ˇ»
16512                   struct Row99;
16513                   struct Row9;
16514                   struct Row10;"#},
16515        base_text,
16516        &mut cx,
16517    );
16518
16519    assert_hunk_revert(
16520        indoc! {r#"ˇstruct Row1.1;
16521                   struct Row1;
16522                   «ˇstr»uct Row22;
16523
16524                   struct ˇRow44;
16525                   struct Row5;
16526                   struct «Rˇ»ow66;ˇ
16527
16528                   «struˇ»ct Row88;
16529                   struct Row9;
16530                   struct Row1011;ˇ"#},
16531        vec![
16532            DiffHunkStatusKind::Modified,
16533            DiffHunkStatusKind::Modified,
16534            DiffHunkStatusKind::Modified,
16535            DiffHunkStatusKind::Modified,
16536            DiffHunkStatusKind::Modified,
16537            DiffHunkStatusKind::Modified,
16538        ],
16539        indoc! {r#"struct Row;
16540                   ˇstruct Row1;
16541                   struct Row2;
16542                   ˇ
16543                   struct Row4;
16544                   ˇstruct Row5;
16545                   struct Row6;
16546                   ˇ
16547                   struct Row8;
16548                   ˇstruct Row9;
16549                   struct Row10;ˇ"#},
16550        base_text,
16551        &mut cx,
16552    );
16553}
16554
16555#[gpui::test]
16556async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
16557    init_test(cx, |_| {});
16558    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16559    let base_text = indoc! {r#"
16560        one
16561
16562        two
16563        three
16564        "#};
16565
16566    cx.set_head_text(base_text);
16567    cx.set_state("\nˇ\n");
16568    cx.executor().run_until_parked();
16569    cx.update_editor(|editor, _window, cx| {
16570        editor.expand_selected_diff_hunks(cx);
16571    });
16572    cx.executor().run_until_parked();
16573    cx.update_editor(|editor, window, cx| {
16574        editor.backspace(&Default::default(), window, cx);
16575    });
16576    cx.run_until_parked();
16577    cx.assert_state_with_diff(
16578        indoc! {r#"
16579
16580        - two
16581        - threeˇ
16582        +
16583        "#}
16584        .to_string(),
16585    );
16586}
16587
16588#[gpui::test]
16589async fn test_deletion_reverts(cx: &mut TestAppContext) {
16590    init_test(cx, |_| {});
16591    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16592    let base_text = indoc! {r#"struct Row;
16593struct Row1;
16594struct Row2;
16595
16596struct Row4;
16597struct Row5;
16598struct Row6;
16599
16600struct Row8;
16601struct Row9;
16602struct Row10;"#};
16603
16604    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
16605    assert_hunk_revert(
16606        indoc! {r#"struct Row;
16607                   struct Row2;
16608
16609                   ˇstruct Row4;
16610                   struct Row5;
16611                   struct Row6;
16612                   ˇ
16613                   struct Row8;
16614                   struct Row10;"#},
16615        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16616        indoc! {r#"struct Row;
16617                   struct Row2;
16618
16619                   ˇstruct Row4;
16620                   struct Row5;
16621                   struct Row6;
16622                   ˇ
16623                   struct Row8;
16624                   struct Row10;"#},
16625        base_text,
16626        &mut cx,
16627    );
16628    assert_hunk_revert(
16629        indoc! {r#"struct Row;
16630                   struct Row2;
16631
16632                   «ˇstruct Row4;
16633                   struct» Row5;
16634                   «struct Row6;
16635                   ˇ»
16636                   struct Row8;
16637                   struct Row10;"#},
16638        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16639        indoc! {r#"struct Row;
16640                   struct Row2;
16641
16642                   «ˇstruct Row4;
16643                   struct» Row5;
16644                   «struct Row6;
16645                   ˇ»
16646                   struct Row8;
16647                   struct Row10;"#},
16648        base_text,
16649        &mut cx,
16650    );
16651
16652    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
16653    assert_hunk_revert(
16654        indoc! {r#"struct Row;
16655                   ˇstruct Row2;
16656
16657                   struct Row4;
16658                   struct Row5;
16659                   struct Row6;
16660
16661                   struct Row8;ˇ
16662                   struct Row10;"#},
16663        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16664        indoc! {r#"struct Row;
16665                   struct Row1;
16666                   ˇstruct Row2;
16667
16668                   struct Row4;
16669                   struct Row5;
16670                   struct Row6;
16671
16672                   struct Row8;ˇ
16673                   struct Row9;
16674                   struct Row10;"#},
16675        base_text,
16676        &mut cx,
16677    );
16678    assert_hunk_revert(
16679        indoc! {r#"struct Row;
16680                   struct Row2«ˇ;
16681                   struct Row4;
16682                   struct» Row5;
16683                   «struct Row6;
16684
16685                   struct Row8;ˇ»
16686                   struct Row10;"#},
16687        vec![
16688            DiffHunkStatusKind::Deleted,
16689            DiffHunkStatusKind::Deleted,
16690            DiffHunkStatusKind::Deleted,
16691        ],
16692        indoc! {r#"struct Row;
16693                   struct Row1;
16694                   struct Row2«ˇ;
16695
16696                   struct Row4;
16697                   struct» Row5;
16698                   «struct Row6;
16699
16700                   struct Row8;ˇ»
16701                   struct Row9;
16702                   struct Row10;"#},
16703        base_text,
16704        &mut cx,
16705    );
16706}
16707
16708#[gpui::test]
16709async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
16710    init_test(cx, |_| {});
16711
16712    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
16713    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
16714    let base_text_3 =
16715        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
16716
16717    let text_1 = edit_first_char_of_every_line(base_text_1);
16718    let text_2 = edit_first_char_of_every_line(base_text_2);
16719    let text_3 = edit_first_char_of_every_line(base_text_3);
16720
16721    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
16722    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
16723    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
16724
16725    let multibuffer = cx.new(|cx| {
16726        let mut multibuffer = MultiBuffer::new(ReadWrite);
16727        multibuffer.push_excerpts(
16728            buffer_1.clone(),
16729            [
16730                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16731                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16732                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16733            ],
16734            cx,
16735        );
16736        multibuffer.push_excerpts(
16737            buffer_2.clone(),
16738            [
16739                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16740                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16741                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16742            ],
16743            cx,
16744        );
16745        multibuffer.push_excerpts(
16746            buffer_3.clone(),
16747            [
16748                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16749                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16750                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16751            ],
16752            cx,
16753        );
16754        multibuffer
16755    });
16756
16757    let fs = FakeFs::new(cx.executor());
16758    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
16759    let (editor, cx) = cx
16760        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
16761    editor.update_in(cx, |editor, _window, cx| {
16762        for (buffer, diff_base) in [
16763            (buffer_1.clone(), base_text_1),
16764            (buffer_2.clone(), base_text_2),
16765            (buffer_3.clone(), base_text_3),
16766        ] {
16767            let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
16768            editor
16769                .buffer
16770                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
16771        }
16772    });
16773    cx.executor().run_until_parked();
16774
16775    editor.update_in(cx, |editor, window, cx| {
16776        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}");
16777        editor.select_all(&SelectAll, window, cx);
16778        editor.git_restore(&Default::default(), window, cx);
16779    });
16780    cx.executor().run_until_parked();
16781
16782    // When all ranges are selected, all buffer hunks are reverted.
16783    editor.update(cx, |editor, cx| {
16784        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");
16785    });
16786    buffer_1.update(cx, |buffer, _| {
16787        assert_eq!(buffer.text(), base_text_1);
16788    });
16789    buffer_2.update(cx, |buffer, _| {
16790        assert_eq!(buffer.text(), base_text_2);
16791    });
16792    buffer_3.update(cx, |buffer, _| {
16793        assert_eq!(buffer.text(), base_text_3);
16794    });
16795
16796    editor.update_in(cx, |editor, window, cx| {
16797        editor.undo(&Default::default(), window, cx);
16798    });
16799
16800    editor.update_in(cx, |editor, window, cx| {
16801        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16802            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
16803        });
16804        editor.git_restore(&Default::default(), window, cx);
16805    });
16806
16807    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
16808    // but not affect buffer_2 and its related excerpts.
16809    editor.update(cx, |editor, cx| {
16810        assert_eq!(
16811            editor.text(cx),
16812            "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}"
16813        );
16814    });
16815    buffer_1.update(cx, |buffer, _| {
16816        assert_eq!(buffer.text(), base_text_1);
16817    });
16818    buffer_2.update(cx, |buffer, _| {
16819        assert_eq!(
16820            buffer.text(),
16821            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
16822        );
16823    });
16824    buffer_3.update(cx, |buffer, _| {
16825        assert_eq!(
16826            buffer.text(),
16827            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
16828        );
16829    });
16830
16831    fn edit_first_char_of_every_line(text: &str) -> String {
16832        text.split('\n')
16833            .map(|line| format!("X{}", &line[1..]))
16834            .collect::<Vec<_>>()
16835            .join("\n")
16836    }
16837}
16838
16839#[gpui::test]
16840async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
16841    init_test(cx, |_| {});
16842
16843    let cols = 4;
16844    let rows = 10;
16845    let sample_text_1 = sample_text(rows, cols, 'a');
16846    assert_eq!(
16847        sample_text_1,
16848        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
16849    );
16850    let sample_text_2 = sample_text(rows, cols, 'l');
16851    assert_eq!(
16852        sample_text_2,
16853        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
16854    );
16855    let sample_text_3 = sample_text(rows, cols, 'v');
16856    assert_eq!(
16857        sample_text_3,
16858        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
16859    );
16860
16861    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
16862    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
16863    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
16864
16865    let multi_buffer = cx.new(|cx| {
16866        let mut multibuffer = MultiBuffer::new(ReadWrite);
16867        multibuffer.push_excerpts(
16868            buffer_1.clone(),
16869            [
16870                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16871                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16872                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16873            ],
16874            cx,
16875        );
16876        multibuffer.push_excerpts(
16877            buffer_2.clone(),
16878            [
16879                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16880                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16881                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16882            ],
16883            cx,
16884        );
16885        multibuffer.push_excerpts(
16886            buffer_3.clone(),
16887            [
16888                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16889                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16890                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16891            ],
16892            cx,
16893        );
16894        multibuffer
16895    });
16896
16897    let fs = FakeFs::new(cx.executor());
16898    fs.insert_tree(
16899        "/a",
16900        json!({
16901            "main.rs": sample_text_1,
16902            "other.rs": sample_text_2,
16903            "lib.rs": sample_text_3,
16904        }),
16905    )
16906    .await;
16907    let project = Project::test(fs, ["/a".as_ref()], cx).await;
16908    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16909    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16910    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16911        Editor::new(
16912            EditorMode::full(),
16913            multi_buffer,
16914            Some(project.clone()),
16915            window,
16916            cx,
16917        )
16918    });
16919    let multibuffer_item_id = workspace
16920        .update(cx, |workspace, window, cx| {
16921            assert!(
16922                workspace.active_item(cx).is_none(),
16923                "active item should be None before the first item is added"
16924            );
16925            workspace.add_item_to_active_pane(
16926                Box::new(multi_buffer_editor.clone()),
16927                None,
16928                true,
16929                window,
16930                cx,
16931            );
16932            let active_item = workspace
16933                .active_item(cx)
16934                .expect("should have an active item after adding the multi buffer");
16935            assert!(
16936                !active_item.is_singleton(cx),
16937                "A multi buffer was expected to active after adding"
16938            );
16939            active_item.item_id()
16940        })
16941        .unwrap();
16942    cx.executor().run_until_parked();
16943
16944    multi_buffer_editor.update_in(cx, |editor, window, cx| {
16945        editor.change_selections(
16946            SelectionEffects::scroll(Autoscroll::Next),
16947            window,
16948            cx,
16949            |s| s.select_ranges(Some(1..2)),
16950        );
16951        editor.open_excerpts(&OpenExcerpts, window, cx);
16952    });
16953    cx.executor().run_until_parked();
16954    let first_item_id = workspace
16955        .update(cx, |workspace, window, cx| {
16956            let active_item = workspace
16957                .active_item(cx)
16958                .expect("should have an active item after navigating into the 1st buffer");
16959            let first_item_id = active_item.item_id();
16960            assert_ne!(
16961                first_item_id, multibuffer_item_id,
16962                "Should navigate into the 1st buffer and activate it"
16963            );
16964            assert!(
16965                active_item.is_singleton(cx),
16966                "New active item should be a singleton buffer"
16967            );
16968            assert_eq!(
16969                active_item
16970                    .act_as::<Editor>(cx)
16971                    .expect("should have navigated into an editor for the 1st buffer")
16972                    .read(cx)
16973                    .text(cx),
16974                sample_text_1
16975            );
16976
16977            workspace
16978                .go_back(workspace.active_pane().downgrade(), window, cx)
16979                .detach_and_log_err(cx);
16980
16981            first_item_id
16982        })
16983        .unwrap();
16984    cx.executor().run_until_parked();
16985    workspace
16986        .update(cx, |workspace, _, cx| {
16987            let active_item = workspace
16988                .active_item(cx)
16989                .expect("should have an active item after navigating back");
16990            assert_eq!(
16991                active_item.item_id(),
16992                multibuffer_item_id,
16993                "Should navigate back to the multi buffer"
16994            );
16995            assert!(!active_item.is_singleton(cx));
16996        })
16997        .unwrap();
16998
16999    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17000        editor.change_selections(
17001            SelectionEffects::scroll(Autoscroll::Next),
17002            window,
17003            cx,
17004            |s| s.select_ranges(Some(39..40)),
17005        );
17006        editor.open_excerpts(&OpenExcerpts, window, cx);
17007    });
17008    cx.executor().run_until_parked();
17009    let second_item_id = workspace
17010        .update(cx, |workspace, window, cx| {
17011            let active_item = workspace
17012                .active_item(cx)
17013                .expect("should have an active item after navigating into the 2nd buffer");
17014            let second_item_id = active_item.item_id();
17015            assert_ne!(
17016                second_item_id, multibuffer_item_id,
17017                "Should navigate away from the multibuffer"
17018            );
17019            assert_ne!(
17020                second_item_id, first_item_id,
17021                "Should navigate into the 2nd buffer and activate it"
17022            );
17023            assert!(
17024                active_item.is_singleton(cx),
17025                "New active item should be a singleton buffer"
17026            );
17027            assert_eq!(
17028                active_item
17029                    .act_as::<Editor>(cx)
17030                    .expect("should have navigated into an editor")
17031                    .read(cx)
17032                    .text(cx),
17033                sample_text_2
17034            );
17035
17036            workspace
17037                .go_back(workspace.active_pane().downgrade(), window, cx)
17038                .detach_and_log_err(cx);
17039
17040            second_item_id
17041        })
17042        .unwrap();
17043    cx.executor().run_until_parked();
17044    workspace
17045        .update(cx, |workspace, _, cx| {
17046            let active_item = workspace
17047                .active_item(cx)
17048                .expect("should have an active item after navigating back from the 2nd buffer");
17049            assert_eq!(
17050                active_item.item_id(),
17051                multibuffer_item_id,
17052                "Should navigate back from the 2nd buffer to the multi buffer"
17053            );
17054            assert!(!active_item.is_singleton(cx));
17055        })
17056        .unwrap();
17057
17058    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17059        editor.change_selections(
17060            SelectionEffects::scroll(Autoscroll::Next),
17061            window,
17062            cx,
17063            |s| s.select_ranges(Some(70..70)),
17064        );
17065        editor.open_excerpts(&OpenExcerpts, window, cx);
17066    });
17067    cx.executor().run_until_parked();
17068    workspace
17069        .update(cx, |workspace, window, cx| {
17070            let active_item = workspace
17071                .active_item(cx)
17072                .expect("should have an active item after navigating into the 3rd buffer");
17073            let third_item_id = active_item.item_id();
17074            assert_ne!(
17075                third_item_id, multibuffer_item_id,
17076                "Should navigate into the 3rd buffer and activate it"
17077            );
17078            assert_ne!(third_item_id, first_item_id);
17079            assert_ne!(third_item_id, second_item_id);
17080            assert!(
17081                active_item.is_singleton(cx),
17082                "New active item should be a singleton buffer"
17083            );
17084            assert_eq!(
17085                active_item
17086                    .act_as::<Editor>(cx)
17087                    .expect("should have navigated into an editor")
17088                    .read(cx)
17089                    .text(cx),
17090                sample_text_3
17091            );
17092
17093            workspace
17094                .go_back(workspace.active_pane().downgrade(), window, cx)
17095                .detach_and_log_err(cx);
17096        })
17097        .unwrap();
17098    cx.executor().run_until_parked();
17099    workspace
17100        .update(cx, |workspace, _, cx| {
17101            let active_item = workspace
17102                .active_item(cx)
17103                .expect("should have an active item after navigating back from the 3rd buffer");
17104            assert_eq!(
17105                active_item.item_id(),
17106                multibuffer_item_id,
17107                "Should navigate back from the 3rd buffer to the multi buffer"
17108            );
17109            assert!(!active_item.is_singleton(cx));
17110        })
17111        .unwrap();
17112}
17113
17114#[gpui::test]
17115async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17116    init_test(cx, |_| {});
17117
17118    let mut cx = EditorTestContext::new(cx).await;
17119
17120    let diff_base = r#"
17121        use some::mod;
17122
17123        const A: u32 = 42;
17124
17125        fn main() {
17126            println!("hello");
17127
17128            println!("world");
17129        }
17130        "#
17131    .unindent();
17132
17133    cx.set_state(
17134        &r#"
17135        use some::modified;
17136
17137        ˇ
17138        fn main() {
17139            println!("hello there");
17140
17141            println!("around the");
17142            println!("world");
17143        }
17144        "#
17145        .unindent(),
17146    );
17147
17148    cx.set_head_text(&diff_base);
17149    executor.run_until_parked();
17150
17151    cx.update_editor(|editor, window, cx| {
17152        editor.go_to_next_hunk(&GoToHunk, window, cx);
17153        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17154    });
17155    executor.run_until_parked();
17156    cx.assert_state_with_diff(
17157        r#"
17158          use some::modified;
17159
17160
17161          fn main() {
17162        -     println!("hello");
17163        + ˇ    println!("hello there");
17164
17165              println!("around the");
17166              println!("world");
17167          }
17168        "#
17169        .unindent(),
17170    );
17171
17172    cx.update_editor(|editor, window, cx| {
17173        for _ in 0..2 {
17174            editor.go_to_next_hunk(&GoToHunk, window, cx);
17175            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17176        }
17177    });
17178    executor.run_until_parked();
17179    cx.assert_state_with_diff(
17180        r#"
17181        - use some::mod;
17182        + ˇuse some::modified;
17183
17184
17185          fn main() {
17186        -     println!("hello");
17187        +     println!("hello there");
17188
17189        +     println!("around the");
17190              println!("world");
17191          }
17192        "#
17193        .unindent(),
17194    );
17195
17196    cx.update_editor(|editor, window, cx| {
17197        editor.go_to_next_hunk(&GoToHunk, window, cx);
17198        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17199    });
17200    executor.run_until_parked();
17201    cx.assert_state_with_diff(
17202        r#"
17203        - use some::mod;
17204        + use some::modified;
17205
17206        - const A: u32 = 42;
17207          ˇ
17208          fn main() {
17209        -     println!("hello");
17210        +     println!("hello there");
17211
17212        +     println!("around the");
17213              println!("world");
17214          }
17215        "#
17216        .unindent(),
17217    );
17218
17219    cx.update_editor(|editor, window, cx| {
17220        editor.cancel(&Cancel, window, cx);
17221    });
17222
17223    cx.assert_state_with_diff(
17224        r#"
17225          use some::modified;
17226
17227          ˇ
17228          fn main() {
17229              println!("hello there");
17230
17231              println!("around the");
17232              println!("world");
17233          }
17234        "#
17235        .unindent(),
17236    );
17237}
17238
17239#[gpui::test]
17240async fn test_diff_base_change_with_expanded_diff_hunks(
17241    executor: BackgroundExecutor,
17242    cx: &mut TestAppContext,
17243) {
17244    init_test(cx, |_| {});
17245
17246    let mut cx = EditorTestContext::new(cx).await;
17247
17248    let diff_base = r#"
17249        use some::mod1;
17250        use some::mod2;
17251
17252        const A: u32 = 42;
17253        const B: u32 = 42;
17254        const C: u32 = 42;
17255
17256        fn main() {
17257            println!("hello");
17258
17259            println!("world");
17260        }
17261        "#
17262    .unindent();
17263
17264    cx.set_state(
17265        &r#"
17266        use some::mod2;
17267
17268        const A: u32 = 42;
17269        const C: u32 = 42;
17270
17271        fn main(ˇ) {
17272            //println!("hello");
17273
17274            println!("world");
17275            //
17276            //
17277        }
17278        "#
17279        .unindent(),
17280    );
17281
17282    cx.set_head_text(&diff_base);
17283    executor.run_until_parked();
17284
17285    cx.update_editor(|editor, window, cx| {
17286        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17287    });
17288    executor.run_until_parked();
17289    cx.assert_state_with_diff(
17290        r#"
17291        - use some::mod1;
17292          use some::mod2;
17293
17294          const A: u32 = 42;
17295        - const B: u32 = 42;
17296          const C: u32 = 42;
17297
17298          fn main(ˇ) {
17299        -     println!("hello");
17300        +     //println!("hello");
17301
17302              println!("world");
17303        +     //
17304        +     //
17305          }
17306        "#
17307        .unindent(),
17308    );
17309
17310    cx.set_head_text("new diff base!");
17311    executor.run_until_parked();
17312    cx.assert_state_with_diff(
17313        r#"
17314        - new diff base!
17315        + use some::mod2;
17316        +
17317        + const A: u32 = 42;
17318        + const C: u32 = 42;
17319        +
17320        + fn main(ˇ) {
17321        +     //println!("hello");
17322        +
17323        +     println!("world");
17324        +     //
17325        +     //
17326        + }
17327        "#
17328        .unindent(),
17329    );
17330}
17331
17332#[gpui::test]
17333async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
17334    init_test(cx, |_| {});
17335
17336    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
17337    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
17338    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
17339    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
17340    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
17341    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
17342
17343    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
17344    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
17345    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
17346
17347    let multi_buffer = cx.new(|cx| {
17348        let mut multibuffer = MultiBuffer::new(ReadWrite);
17349        multibuffer.push_excerpts(
17350            buffer_1.clone(),
17351            [
17352                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17353                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17354                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17355            ],
17356            cx,
17357        );
17358        multibuffer.push_excerpts(
17359            buffer_2.clone(),
17360            [
17361                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17362                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17363                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17364            ],
17365            cx,
17366        );
17367        multibuffer.push_excerpts(
17368            buffer_3.clone(),
17369            [
17370                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17371                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17372                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17373            ],
17374            cx,
17375        );
17376        multibuffer
17377    });
17378
17379    let editor =
17380        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
17381    editor
17382        .update(cx, |editor, _window, cx| {
17383            for (buffer, diff_base) in [
17384                (buffer_1.clone(), file_1_old),
17385                (buffer_2.clone(), file_2_old),
17386                (buffer_3.clone(), file_3_old),
17387            ] {
17388                let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
17389                editor
17390                    .buffer
17391                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
17392            }
17393        })
17394        .unwrap();
17395
17396    let mut cx = EditorTestContext::for_editor(editor, cx).await;
17397    cx.run_until_parked();
17398
17399    cx.assert_editor_state(
17400        &"
17401            ˇaaa
17402            ccc
17403            ddd
17404
17405            ggg
17406            hhh
17407
17408
17409            lll
17410            mmm
17411            NNN
17412
17413            qqq
17414            rrr
17415
17416            uuu
17417            111
17418            222
17419            333
17420
17421            666
17422            777
17423
17424            000
17425            !!!"
17426        .unindent(),
17427    );
17428
17429    cx.update_editor(|editor, window, cx| {
17430        editor.select_all(&SelectAll, window, cx);
17431        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17432    });
17433    cx.executor().run_until_parked();
17434
17435    cx.assert_state_with_diff(
17436        "
17437            «aaa
17438          - bbb
17439            ccc
17440            ddd
17441
17442            ggg
17443            hhh
17444
17445
17446            lll
17447            mmm
17448          - nnn
17449          + NNN
17450
17451            qqq
17452            rrr
17453
17454            uuu
17455            111
17456            222
17457            333
17458
17459          + 666
17460            777
17461
17462            000
17463            !!!ˇ»"
17464            .unindent(),
17465    );
17466}
17467
17468#[gpui::test]
17469async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
17470    init_test(cx, |_| {});
17471
17472    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
17473    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
17474
17475    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
17476    let multi_buffer = cx.new(|cx| {
17477        let mut multibuffer = MultiBuffer::new(ReadWrite);
17478        multibuffer.push_excerpts(
17479            buffer.clone(),
17480            [
17481                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
17482                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
17483                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
17484            ],
17485            cx,
17486        );
17487        multibuffer
17488    });
17489
17490    let editor =
17491        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
17492    editor
17493        .update(cx, |editor, _window, cx| {
17494            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
17495            editor
17496                .buffer
17497                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
17498        })
17499        .unwrap();
17500
17501    let mut cx = EditorTestContext::for_editor(editor, cx).await;
17502    cx.run_until_parked();
17503
17504    cx.update_editor(|editor, window, cx| {
17505        editor.expand_all_diff_hunks(&Default::default(), window, cx)
17506    });
17507    cx.executor().run_until_parked();
17508
17509    // When the start of a hunk coincides with the start of its excerpt,
17510    // the hunk is expanded. When the start of a a hunk is earlier than
17511    // the start of its excerpt, the hunk is not expanded.
17512    cx.assert_state_with_diff(
17513        "
17514            ˇaaa
17515          - bbb
17516          + BBB
17517
17518          - ddd
17519          - eee
17520          + DDD
17521          + EEE
17522            fff
17523
17524            iii
17525        "
17526        .unindent(),
17527    );
17528}
17529
17530#[gpui::test]
17531async fn test_edits_around_expanded_insertion_hunks(
17532    executor: BackgroundExecutor,
17533    cx: &mut TestAppContext,
17534) {
17535    init_test(cx, |_| {});
17536
17537    let mut cx = EditorTestContext::new(cx).await;
17538
17539    let diff_base = r#"
17540        use some::mod1;
17541        use some::mod2;
17542
17543        const A: u32 = 42;
17544
17545        fn main() {
17546            println!("hello");
17547
17548            println!("world");
17549        }
17550        "#
17551    .unindent();
17552    executor.run_until_parked();
17553    cx.set_state(
17554        &r#"
17555        use some::mod1;
17556        use some::mod2;
17557
17558        const A: u32 = 42;
17559        const B: u32 = 42;
17560        const C: u32 = 42;
17561        ˇ
17562
17563        fn main() {
17564            println!("hello");
17565
17566            println!("world");
17567        }
17568        "#
17569        .unindent(),
17570    );
17571
17572    cx.set_head_text(&diff_base);
17573    executor.run_until_parked();
17574
17575    cx.update_editor(|editor, window, cx| {
17576        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17577    });
17578    executor.run_until_parked();
17579
17580    cx.assert_state_with_diff(
17581        r#"
17582        use some::mod1;
17583        use some::mod2;
17584
17585        const A: u32 = 42;
17586      + const B: u32 = 42;
17587      + const C: u32 = 42;
17588      + ˇ
17589
17590        fn main() {
17591            println!("hello");
17592
17593            println!("world");
17594        }
17595      "#
17596        .unindent(),
17597    );
17598
17599    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
17600    executor.run_until_parked();
17601
17602    cx.assert_state_with_diff(
17603        r#"
17604        use some::mod1;
17605        use some::mod2;
17606
17607        const A: u32 = 42;
17608      + const B: u32 = 42;
17609      + const C: u32 = 42;
17610      + const D: u32 = 42;
17611      + ˇ
17612
17613        fn main() {
17614            println!("hello");
17615
17616            println!("world");
17617        }
17618      "#
17619        .unindent(),
17620    );
17621
17622    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
17623    executor.run_until_parked();
17624
17625    cx.assert_state_with_diff(
17626        r#"
17627        use some::mod1;
17628        use some::mod2;
17629
17630        const A: u32 = 42;
17631      + const B: u32 = 42;
17632      + const C: u32 = 42;
17633      + const D: u32 = 42;
17634      + const E: u32 = 42;
17635      + ˇ
17636
17637        fn main() {
17638            println!("hello");
17639
17640            println!("world");
17641        }
17642      "#
17643        .unindent(),
17644    );
17645
17646    cx.update_editor(|editor, window, cx| {
17647        editor.delete_line(&DeleteLine, window, cx);
17648    });
17649    executor.run_until_parked();
17650
17651    cx.assert_state_with_diff(
17652        r#"
17653        use some::mod1;
17654        use some::mod2;
17655
17656        const A: u32 = 42;
17657      + const B: u32 = 42;
17658      + const C: u32 = 42;
17659      + const D: u32 = 42;
17660      + const E: u32 = 42;
17661        ˇ
17662        fn main() {
17663            println!("hello");
17664
17665            println!("world");
17666        }
17667      "#
17668        .unindent(),
17669    );
17670
17671    cx.update_editor(|editor, window, cx| {
17672        editor.move_up(&MoveUp, window, cx);
17673        editor.delete_line(&DeleteLine, window, cx);
17674        editor.move_up(&MoveUp, window, cx);
17675        editor.delete_line(&DeleteLine, window, cx);
17676        editor.move_up(&MoveUp, window, cx);
17677        editor.delete_line(&DeleteLine, window, cx);
17678    });
17679    executor.run_until_parked();
17680    cx.assert_state_with_diff(
17681        r#"
17682        use some::mod1;
17683        use some::mod2;
17684
17685        const A: u32 = 42;
17686      + const B: u32 = 42;
17687        ˇ
17688        fn main() {
17689            println!("hello");
17690
17691            println!("world");
17692        }
17693      "#
17694        .unindent(),
17695    );
17696
17697    cx.update_editor(|editor, window, cx| {
17698        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
17699        editor.delete_line(&DeleteLine, window, cx);
17700    });
17701    executor.run_until_parked();
17702    cx.assert_state_with_diff(
17703        r#"
17704        ˇ
17705        fn main() {
17706            println!("hello");
17707
17708            println!("world");
17709        }
17710      "#
17711        .unindent(),
17712    );
17713}
17714
17715#[gpui::test]
17716async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
17717    init_test(cx, |_| {});
17718
17719    let mut cx = EditorTestContext::new(cx).await;
17720    cx.set_head_text(indoc! { "
17721        one
17722        two
17723        three
17724        four
17725        five
17726        "
17727    });
17728    cx.set_state(indoc! { "
17729        one
17730        ˇthree
17731        five
17732    "});
17733    cx.run_until_parked();
17734    cx.update_editor(|editor, window, cx| {
17735        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17736    });
17737    cx.assert_state_with_diff(
17738        indoc! { "
17739        one
17740      - two
17741        ˇthree
17742      - four
17743        five
17744    "}
17745        .to_string(),
17746    );
17747    cx.update_editor(|editor, window, cx| {
17748        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17749    });
17750
17751    cx.assert_state_with_diff(
17752        indoc! { "
17753        one
17754        ˇthree
17755        five
17756    "}
17757        .to_string(),
17758    );
17759
17760    cx.set_state(indoc! { "
17761        one
17762        ˇTWO
17763        three
17764        four
17765        five
17766    "});
17767    cx.run_until_parked();
17768    cx.update_editor(|editor, window, cx| {
17769        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17770    });
17771
17772    cx.assert_state_with_diff(
17773        indoc! { "
17774            one
17775          - two
17776          + ˇTWO
17777            three
17778            four
17779            five
17780        "}
17781        .to_string(),
17782    );
17783    cx.update_editor(|editor, window, cx| {
17784        editor.move_up(&Default::default(), window, cx);
17785        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17786    });
17787    cx.assert_state_with_diff(
17788        indoc! { "
17789            one
17790            ˇTWO
17791            three
17792            four
17793            five
17794        "}
17795        .to_string(),
17796    );
17797}
17798
17799#[gpui::test]
17800async fn test_edits_around_expanded_deletion_hunks(
17801    executor: BackgroundExecutor,
17802    cx: &mut TestAppContext,
17803) {
17804    init_test(cx, |_| {});
17805
17806    let mut cx = EditorTestContext::new(cx).await;
17807
17808    let diff_base = r#"
17809        use some::mod1;
17810        use some::mod2;
17811
17812        const A: u32 = 42;
17813        const B: u32 = 42;
17814        const C: u32 = 42;
17815
17816
17817        fn main() {
17818            println!("hello");
17819
17820            println!("world");
17821        }
17822    "#
17823    .unindent();
17824    executor.run_until_parked();
17825    cx.set_state(
17826        &r#"
17827        use some::mod1;
17828        use some::mod2;
17829
17830        ˇconst B: u32 = 42;
17831        const C: u32 = 42;
17832
17833
17834        fn main() {
17835            println!("hello");
17836
17837            println!("world");
17838        }
17839        "#
17840        .unindent(),
17841    );
17842
17843    cx.set_head_text(&diff_base);
17844    executor.run_until_parked();
17845
17846    cx.update_editor(|editor, window, cx| {
17847        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17848    });
17849    executor.run_until_parked();
17850
17851    cx.assert_state_with_diff(
17852        r#"
17853        use some::mod1;
17854        use some::mod2;
17855
17856      - const A: u32 = 42;
17857        ˇconst B: u32 = 42;
17858        const C: u32 = 42;
17859
17860
17861        fn main() {
17862            println!("hello");
17863
17864            println!("world");
17865        }
17866      "#
17867        .unindent(),
17868    );
17869
17870    cx.update_editor(|editor, window, cx| {
17871        editor.delete_line(&DeleteLine, window, cx);
17872    });
17873    executor.run_until_parked();
17874    cx.assert_state_with_diff(
17875        r#"
17876        use some::mod1;
17877        use some::mod2;
17878
17879      - const A: u32 = 42;
17880      - const B: u32 = 42;
17881        ˇconst C: u32 = 42;
17882
17883
17884        fn main() {
17885            println!("hello");
17886
17887            println!("world");
17888        }
17889      "#
17890        .unindent(),
17891    );
17892
17893    cx.update_editor(|editor, window, cx| {
17894        editor.delete_line(&DeleteLine, window, cx);
17895    });
17896    executor.run_until_parked();
17897    cx.assert_state_with_diff(
17898        r#"
17899        use some::mod1;
17900        use some::mod2;
17901
17902      - const A: u32 = 42;
17903      - const B: u32 = 42;
17904      - const C: u32 = 42;
17905        ˇ
17906
17907        fn main() {
17908            println!("hello");
17909
17910            println!("world");
17911        }
17912      "#
17913        .unindent(),
17914    );
17915
17916    cx.update_editor(|editor, window, cx| {
17917        editor.handle_input("replacement", window, cx);
17918    });
17919    executor.run_until_parked();
17920    cx.assert_state_with_diff(
17921        r#"
17922        use some::mod1;
17923        use some::mod2;
17924
17925      - const A: u32 = 42;
17926      - const B: u32 = 42;
17927      - const C: u32 = 42;
17928      -
17929      + replacementˇ
17930
17931        fn main() {
17932            println!("hello");
17933
17934            println!("world");
17935        }
17936      "#
17937        .unindent(),
17938    );
17939}
17940
17941#[gpui::test]
17942async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17943    init_test(cx, |_| {});
17944
17945    let mut cx = EditorTestContext::new(cx).await;
17946
17947    let base_text = r#"
17948        one
17949        two
17950        three
17951        four
17952        five
17953    "#
17954    .unindent();
17955    executor.run_until_parked();
17956    cx.set_state(
17957        &r#"
17958        one
17959        two
17960        fˇour
17961        five
17962        "#
17963        .unindent(),
17964    );
17965
17966    cx.set_head_text(&base_text);
17967    executor.run_until_parked();
17968
17969    cx.update_editor(|editor, window, cx| {
17970        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17971    });
17972    executor.run_until_parked();
17973
17974    cx.assert_state_with_diff(
17975        r#"
17976          one
17977          two
17978        - three
17979          fˇour
17980          five
17981        "#
17982        .unindent(),
17983    );
17984
17985    cx.update_editor(|editor, window, cx| {
17986        editor.backspace(&Backspace, window, cx);
17987        editor.backspace(&Backspace, window, cx);
17988    });
17989    executor.run_until_parked();
17990    cx.assert_state_with_diff(
17991        r#"
17992          one
17993          two
17994        - threeˇ
17995        - four
17996        + our
17997          five
17998        "#
17999        .unindent(),
18000    );
18001}
18002
18003#[gpui::test]
18004async fn test_edit_after_expanded_modification_hunk(
18005    executor: BackgroundExecutor,
18006    cx: &mut TestAppContext,
18007) {
18008    init_test(cx, |_| {});
18009
18010    let mut cx = EditorTestContext::new(cx).await;
18011
18012    let diff_base = r#"
18013        use some::mod1;
18014        use some::mod2;
18015
18016        const A: u32 = 42;
18017        const B: u32 = 42;
18018        const C: u32 = 42;
18019        const D: u32 = 42;
18020
18021
18022        fn main() {
18023            println!("hello");
18024
18025            println!("world");
18026        }"#
18027    .unindent();
18028
18029    cx.set_state(
18030        &r#"
18031        use some::mod1;
18032        use some::mod2;
18033
18034        const A: u32 = 42;
18035        const B: u32 = 42;
18036        const C: u32 = 43ˇ
18037        const D: u32 = 42;
18038
18039
18040        fn main() {
18041            println!("hello");
18042
18043            println!("world");
18044        }"#
18045        .unindent(),
18046    );
18047
18048    cx.set_head_text(&diff_base);
18049    executor.run_until_parked();
18050    cx.update_editor(|editor, window, cx| {
18051        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18052    });
18053    executor.run_until_parked();
18054
18055    cx.assert_state_with_diff(
18056        r#"
18057        use some::mod1;
18058        use some::mod2;
18059
18060        const A: u32 = 42;
18061        const B: u32 = 42;
18062      - const C: u32 = 42;
18063      + const C: u32 = 43ˇ
18064        const D: u32 = 42;
18065
18066
18067        fn main() {
18068            println!("hello");
18069
18070            println!("world");
18071        }"#
18072        .unindent(),
18073    );
18074
18075    cx.update_editor(|editor, window, cx| {
18076        editor.handle_input("\nnew_line\n", window, cx);
18077    });
18078    executor.run_until_parked();
18079
18080    cx.assert_state_with_diff(
18081        r#"
18082        use some::mod1;
18083        use some::mod2;
18084
18085        const A: u32 = 42;
18086        const B: u32 = 42;
18087      - const C: u32 = 42;
18088      + const C: u32 = 43
18089      + new_line
18090      + ˇ
18091        const D: u32 = 42;
18092
18093
18094        fn main() {
18095            println!("hello");
18096
18097            println!("world");
18098        }"#
18099        .unindent(),
18100    );
18101}
18102
18103#[gpui::test]
18104async fn test_stage_and_unstage_added_file_hunk(
18105    executor: BackgroundExecutor,
18106    cx: &mut TestAppContext,
18107) {
18108    init_test(cx, |_| {});
18109
18110    let mut cx = EditorTestContext::new(cx).await;
18111    cx.update_editor(|editor, _, cx| {
18112        editor.set_expand_all_diff_hunks(cx);
18113    });
18114
18115    let working_copy = r#"
18116            ˇfn main() {
18117                println!("hello, world!");
18118            }
18119        "#
18120    .unindent();
18121
18122    cx.set_state(&working_copy);
18123    executor.run_until_parked();
18124
18125    cx.assert_state_with_diff(
18126        r#"
18127            + ˇfn main() {
18128            +     println!("hello, world!");
18129            + }
18130        "#
18131        .unindent(),
18132    );
18133    cx.assert_index_text(None);
18134
18135    cx.update_editor(|editor, window, cx| {
18136        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18137    });
18138    executor.run_until_parked();
18139    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
18140    cx.assert_state_with_diff(
18141        r#"
18142            + ˇfn main() {
18143            +     println!("hello, world!");
18144            + }
18145        "#
18146        .unindent(),
18147    );
18148
18149    cx.update_editor(|editor, window, cx| {
18150        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18151    });
18152    executor.run_until_parked();
18153    cx.assert_index_text(None);
18154}
18155
18156async fn setup_indent_guides_editor(
18157    text: &str,
18158    cx: &mut TestAppContext,
18159) -> (BufferId, EditorTestContext) {
18160    init_test(cx, |_| {});
18161
18162    let mut cx = EditorTestContext::new(cx).await;
18163
18164    let buffer_id = cx.update_editor(|editor, window, cx| {
18165        editor.set_text(text, window, cx);
18166        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
18167
18168        buffer_ids[0]
18169    });
18170
18171    (buffer_id, cx)
18172}
18173
18174fn assert_indent_guides(
18175    range: Range<u32>,
18176    expected: Vec<IndentGuide>,
18177    active_indices: Option<Vec<usize>>,
18178    cx: &mut EditorTestContext,
18179) {
18180    let indent_guides = cx.update_editor(|editor, window, cx| {
18181        let snapshot = editor.snapshot(window, cx).display_snapshot;
18182        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
18183            editor,
18184            MultiBufferRow(range.start)..MultiBufferRow(range.end),
18185            true,
18186            &snapshot,
18187            cx,
18188        );
18189
18190        indent_guides.sort_by(|a, b| {
18191            a.depth.cmp(&b.depth).then(
18192                a.start_row
18193                    .cmp(&b.start_row)
18194                    .then(a.end_row.cmp(&b.end_row)),
18195            )
18196        });
18197        indent_guides
18198    });
18199
18200    if let Some(expected) = active_indices {
18201        let active_indices = cx.update_editor(|editor, window, cx| {
18202            let snapshot = editor.snapshot(window, cx).display_snapshot;
18203            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
18204        });
18205
18206        assert_eq!(
18207            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
18208            expected,
18209            "Active indent guide indices do not match"
18210        );
18211    }
18212
18213    assert_eq!(indent_guides, expected, "Indent guides do not match");
18214}
18215
18216fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
18217    IndentGuide {
18218        buffer_id,
18219        start_row: MultiBufferRow(start_row),
18220        end_row: MultiBufferRow(end_row),
18221        depth,
18222        tab_size: 4,
18223        settings: IndentGuideSettings {
18224            enabled: true,
18225            line_width: 1,
18226            active_line_width: 1,
18227            ..Default::default()
18228        },
18229    }
18230}
18231
18232#[gpui::test]
18233async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
18234    let (buffer_id, mut cx) = setup_indent_guides_editor(
18235        &"
18236        fn main() {
18237            let a = 1;
18238        }"
18239        .unindent(),
18240        cx,
18241    )
18242    .await;
18243
18244    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
18245}
18246
18247#[gpui::test]
18248async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
18249    let (buffer_id, mut cx) = setup_indent_guides_editor(
18250        &"
18251        fn main() {
18252            let a = 1;
18253            let b = 2;
18254        }"
18255        .unindent(),
18256        cx,
18257    )
18258    .await;
18259
18260    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
18261}
18262
18263#[gpui::test]
18264async fn test_indent_guide_nested(cx: &mut TestAppContext) {
18265    let (buffer_id, mut cx) = setup_indent_guides_editor(
18266        &"
18267        fn main() {
18268            let a = 1;
18269            if a == 3 {
18270                let b = 2;
18271            } else {
18272                let c = 3;
18273            }
18274        }"
18275        .unindent(),
18276        cx,
18277    )
18278    .await;
18279
18280    assert_indent_guides(
18281        0..8,
18282        vec![
18283            indent_guide(buffer_id, 1, 6, 0),
18284            indent_guide(buffer_id, 3, 3, 1),
18285            indent_guide(buffer_id, 5, 5, 1),
18286        ],
18287        None,
18288        &mut cx,
18289    );
18290}
18291
18292#[gpui::test]
18293async fn test_indent_guide_tab(cx: &mut TestAppContext) {
18294    let (buffer_id, mut cx) = setup_indent_guides_editor(
18295        &"
18296        fn main() {
18297            let a = 1;
18298                let b = 2;
18299            let c = 3;
18300        }"
18301        .unindent(),
18302        cx,
18303    )
18304    .await;
18305
18306    assert_indent_guides(
18307        0..5,
18308        vec![
18309            indent_guide(buffer_id, 1, 3, 0),
18310            indent_guide(buffer_id, 2, 2, 1),
18311        ],
18312        None,
18313        &mut cx,
18314    );
18315}
18316
18317#[gpui::test]
18318async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
18319    let (buffer_id, mut cx) = setup_indent_guides_editor(
18320        &"
18321        fn main() {
18322            let a = 1;
18323
18324            let c = 3;
18325        }"
18326        .unindent(),
18327        cx,
18328    )
18329    .await;
18330
18331    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
18332}
18333
18334#[gpui::test]
18335async fn test_indent_guide_complex(cx: &mut TestAppContext) {
18336    let (buffer_id, mut cx) = setup_indent_guides_editor(
18337        &"
18338        fn main() {
18339            let a = 1;
18340
18341            let c = 3;
18342
18343            if a == 3 {
18344                let b = 2;
18345            } else {
18346                let c = 3;
18347            }
18348        }"
18349        .unindent(),
18350        cx,
18351    )
18352    .await;
18353
18354    assert_indent_guides(
18355        0..11,
18356        vec![
18357            indent_guide(buffer_id, 1, 9, 0),
18358            indent_guide(buffer_id, 6, 6, 1),
18359            indent_guide(buffer_id, 8, 8, 1),
18360        ],
18361        None,
18362        &mut cx,
18363    );
18364}
18365
18366#[gpui::test]
18367async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
18368    let (buffer_id, mut cx) = setup_indent_guides_editor(
18369        &"
18370        fn main() {
18371            let a = 1;
18372
18373            let c = 3;
18374
18375            if a == 3 {
18376                let b = 2;
18377            } else {
18378                let c = 3;
18379            }
18380        }"
18381        .unindent(),
18382        cx,
18383    )
18384    .await;
18385
18386    assert_indent_guides(
18387        1..11,
18388        vec![
18389            indent_guide(buffer_id, 1, 9, 0),
18390            indent_guide(buffer_id, 6, 6, 1),
18391            indent_guide(buffer_id, 8, 8, 1),
18392        ],
18393        None,
18394        &mut cx,
18395    );
18396}
18397
18398#[gpui::test]
18399async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
18400    let (buffer_id, mut cx) = setup_indent_guides_editor(
18401        &"
18402        fn main() {
18403            let a = 1;
18404
18405            let c = 3;
18406
18407            if a == 3 {
18408                let b = 2;
18409            } else {
18410                let c = 3;
18411            }
18412        }"
18413        .unindent(),
18414        cx,
18415    )
18416    .await;
18417
18418    assert_indent_guides(
18419        1..10,
18420        vec![
18421            indent_guide(buffer_id, 1, 9, 0),
18422            indent_guide(buffer_id, 6, 6, 1),
18423            indent_guide(buffer_id, 8, 8, 1),
18424        ],
18425        None,
18426        &mut cx,
18427    );
18428}
18429
18430#[gpui::test]
18431async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
18432    let (buffer_id, mut cx) = setup_indent_guides_editor(
18433        &"
18434        fn main() {
18435            if a {
18436                b(
18437                    c,
18438                    d,
18439                )
18440            } else {
18441                e(
18442                    f
18443                )
18444            }
18445        }"
18446        .unindent(),
18447        cx,
18448    )
18449    .await;
18450
18451    assert_indent_guides(
18452        0..11,
18453        vec![
18454            indent_guide(buffer_id, 1, 10, 0),
18455            indent_guide(buffer_id, 2, 5, 1),
18456            indent_guide(buffer_id, 7, 9, 1),
18457            indent_guide(buffer_id, 3, 4, 2),
18458            indent_guide(buffer_id, 8, 8, 2),
18459        ],
18460        None,
18461        &mut cx,
18462    );
18463
18464    cx.update_editor(|editor, window, cx| {
18465        editor.fold_at(MultiBufferRow(2), window, cx);
18466        assert_eq!(
18467            editor.display_text(cx),
18468            "
18469            fn main() {
18470                if a {
18471                    b(⋯
18472                    )
18473                } else {
18474                    e(
18475                        f
18476                    )
18477                }
18478            }"
18479            .unindent()
18480        );
18481    });
18482
18483    assert_indent_guides(
18484        0..11,
18485        vec![
18486            indent_guide(buffer_id, 1, 10, 0),
18487            indent_guide(buffer_id, 2, 5, 1),
18488            indent_guide(buffer_id, 7, 9, 1),
18489            indent_guide(buffer_id, 8, 8, 2),
18490        ],
18491        None,
18492        &mut cx,
18493    );
18494}
18495
18496#[gpui::test]
18497async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
18498    let (buffer_id, mut cx) = setup_indent_guides_editor(
18499        &"
18500        block1
18501            block2
18502                block3
18503                    block4
18504            block2
18505        block1
18506        block1"
18507            .unindent(),
18508        cx,
18509    )
18510    .await;
18511
18512    assert_indent_guides(
18513        1..10,
18514        vec![
18515            indent_guide(buffer_id, 1, 4, 0),
18516            indent_guide(buffer_id, 2, 3, 1),
18517            indent_guide(buffer_id, 3, 3, 2),
18518        ],
18519        None,
18520        &mut cx,
18521    );
18522}
18523
18524#[gpui::test]
18525async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
18526    let (buffer_id, mut cx) = setup_indent_guides_editor(
18527        &"
18528        block1
18529            block2
18530                block3
18531
18532        block1
18533        block1"
18534            .unindent(),
18535        cx,
18536    )
18537    .await;
18538
18539    assert_indent_guides(
18540        0..6,
18541        vec![
18542            indent_guide(buffer_id, 1, 2, 0),
18543            indent_guide(buffer_id, 2, 2, 1),
18544        ],
18545        None,
18546        &mut cx,
18547    );
18548}
18549
18550#[gpui::test]
18551async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
18552    let (buffer_id, mut cx) = setup_indent_guides_editor(
18553        &"
18554        function component() {
18555        \treturn (
18556        \t\t\t
18557        \t\t<div>
18558        \t\t\t<abc></abc>
18559        \t\t</div>
18560        \t)
18561        }"
18562        .unindent(),
18563        cx,
18564    )
18565    .await;
18566
18567    assert_indent_guides(
18568        0..8,
18569        vec![
18570            indent_guide(buffer_id, 1, 6, 0),
18571            indent_guide(buffer_id, 2, 5, 1),
18572            indent_guide(buffer_id, 4, 4, 2),
18573        ],
18574        None,
18575        &mut cx,
18576    );
18577}
18578
18579#[gpui::test]
18580async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
18581    let (buffer_id, mut cx) = setup_indent_guides_editor(
18582        &"
18583        function component() {
18584        \treturn (
18585        \t
18586        \t\t<div>
18587        \t\t\t<abc></abc>
18588        \t\t</div>
18589        \t)
18590        }"
18591        .unindent(),
18592        cx,
18593    )
18594    .await;
18595
18596    assert_indent_guides(
18597        0..8,
18598        vec![
18599            indent_guide(buffer_id, 1, 6, 0),
18600            indent_guide(buffer_id, 2, 5, 1),
18601            indent_guide(buffer_id, 4, 4, 2),
18602        ],
18603        None,
18604        &mut cx,
18605    );
18606}
18607
18608#[gpui::test]
18609async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
18610    let (buffer_id, mut cx) = setup_indent_guides_editor(
18611        &"
18612        block1
18613
18614
18615
18616            block2
18617        "
18618        .unindent(),
18619        cx,
18620    )
18621    .await;
18622
18623    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
18624}
18625
18626#[gpui::test]
18627async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
18628    let (buffer_id, mut cx) = setup_indent_guides_editor(
18629        &"
18630        def a:
18631        \tb = 3
18632        \tif True:
18633        \t\tc = 4
18634        \t\td = 5
18635        \tprint(b)
18636        "
18637        .unindent(),
18638        cx,
18639    )
18640    .await;
18641
18642    assert_indent_guides(
18643        0..6,
18644        vec![
18645            indent_guide(buffer_id, 1, 5, 0),
18646            indent_guide(buffer_id, 3, 4, 1),
18647        ],
18648        None,
18649        &mut cx,
18650    );
18651}
18652
18653#[gpui::test]
18654async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
18655    let (buffer_id, mut cx) = setup_indent_guides_editor(
18656        &"
18657    fn main() {
18658        let a = 1;
18659    }"
18660        .unindent(),
18661        cx,
18662    )
18663    .await;
18664
18665    cx.update_editor(|editor, window, cx| {
18666        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18667            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18668        });
18669    });
18670
18671    assert_indent_guides(
18672        0..3,
18673        vec![indent_guide(buffer_id, 1, 1, 0)],
18674        Some(vec![0]),
18675        &mut cx,
18676    );
18677}
18678
18679#[gpui::test]
18680async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
18681    let (buffer_id, mut cx) = setup_indent_guides_editor(
18682        &"
18683    fn main() {
18684        if 1 == 2 {
18685            let a = 1;
18686        }
18687    }"
18688        .unindent(),
18689        cx,
18690    )
18691    .await;
18692
18693    cx.update_editor(|editor, window, cx| {
18694        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18695            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18696        });
18697    });
18698
18699    assert_indent_guides(
18700        0..4,
18701        vec![
18702            indent_guide(buffer_id, 1, 3, 0),
18703            indent_guide(buffer_id, 2, 2, 1),
18704        ],
18705        Some(vec![1]),
18706        &mut cx,
18707    );
18708
18709    cx.update_editor(|editor, window, cx| {
18710        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18711            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
18712        });
18713    });
18714
18715    assert_indent_guides(
18716        0..4,
18717        vec![
18718            indent_guide(buffer_id, 1, 3, 0),
18719            indent_guide(buffer_id, 2, 2, 1),
18720        ],
18721        Some(vec![1]),
18722        &mut cx,
18723    );
18724
18725    cx.update_editor(|editor, window, cx| {
18726        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18727            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
18728        });
18729    });
18730
18731    assert_indent_guides(
18732        0..4,
18733        vec![
18734            indent_guide(buffer_id, 1, 3, 0),
18735            indent_guide(buffer_id, 2, 2, 1),
18736        ],
18737        Some(vec![0]),
18738        &mut cx,
18739    );
18740}
18741
18742#[gpui::test]
18743async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
18744    let (buffer_id, mut cx) = setup_indent_guides_editor(
18745        &"
18746    fn main() {
18747        let a = 1;
18748
18749        let b = 2;
18750    }"
18751        .unindent(),
18752        cx,
18753    )
18754    .await;
18755
18756    cx.update_editor(|editor, window, cx| {
18757        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18758            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
18759        });
18760    });
18761
18762    assert_indent_guides(
18763        0..5,
18764        vec![indent_guide(buffer_id, 1, 3, 0)],
18765        Some(vec![0]),
18766        &mut cx,
18767    );
18768}
18769
18770#[gpui::test]
18771async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
18772    let (buffer_id, mut cx) = setup_indent_guides_editor(
18773        &"
18774    def m:
18775        a = 1
18776        pass"
18777            .unindent(),
18778        cx,
18779    )
18780    .await;
18781
18782    cx.update_editor(|editor, window, cx| {
18783        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18784            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18785        });
18786    });
18787
18788    assert_indent_guides(
18789        0..3,
18790        vec![indent_guide(buffer_id, 1, 2, 0)],
18791        Some(vec![0]),
18792        &mut cx,
18793    );
18794}
18795
18796#[gpui::test]
18797async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
18798    init_test(cx, |_| {});
18799    let mut cx = EditorTestContext::new(cx).await;
18800    let text = indoc! {
18801        "
18802        impl A {
18803            fn b() {
18804                0;
18805                3;
18806                5;
18807                6;
18808                7;
18809            }
18810        }
18811        "
18812    };
18813    let base_text = indoc! {
18814        "
18815        impl A {
18816            fn b() {
18817                0;
18818                1;
18819                2;
18820                3;
18821                4;
18822            }
18823            fn c() {
18824                5;
18825                6;
18826                7;
18827            }
18828        }
18829        "
18830    };
18831
18832    cx.update_editor(|editor, window, cx| {
18833        editor.set_text(text, window, cx);
18834
18835        editor.buffer().update(cx, |multibuffer, cx| {
18836            let buffer = multibuffer.as_singleton().unwrap();
18837            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
18838
18839            multibuffer.set_all_diff_hunks_expanded(cx);
18840            multibuffer.add_diff(diff, cx);
18841
18842            buffer.read(cx).remote_id()
18843        })
18844    });
18845    cx.run_until_parked();
18846
18847    cx.assert_state_with_diff(
18848        indoc! { "
18849          impl A {
18850              fn b() {
18851                  0;
18852        -         1;
18853        -         2;
18854                  3;
18855        -         4;
18856        -     }
18857        -     fn c() {
18858                  5;
18859                  6;
18860                  7;
18861              }
18862          }
18863          ˇ"
18864        }
18865        .to_string(),
18866    );
18867
18868    let mut actual_guides = cx.update_editor(|editor, window, cx| {
18869        editor
18870            .snapshot(window, cx)
18871            .buffer_snapshot
18872            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
18873            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
18874            .collect::<Vec<_>>()
18875    });
18876    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
18877    assert_eq!(
18878        actual_guides,
18879        vec![
18880            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
18881            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
18882            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
18883        ]
18884    );
18885}
18886
18887#[gpui::test]
18888async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18889    init_test(cx, |_| {});
18890    let mut cx = EditorTestContext::new(cx).await;
18891
18892    let diff_base = r#"
18893        a
18894        b
18895        c
18896        "#
18897    .unindent();
18898
18899    cx.set_state(
18900        &r#"
18901        ˇA
18902        b
18903        C
18904        "#
18905        .unindent(),
18906    );
18907    cx.set_head_text(&diff_base);
18908    cx.update_editor(|editor, window, cx| {
18909        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18910    });
18911    executor.run_until_parked();
18912
18913    let both_hunks_expanded = r#"
18914        - a
18915        + ˇA
18916          b
18917        - c
18918        + C
18919        "#
18920    .unindent();
18921
18922    cx.assert_state_with_diff(both_hunks_expanded.clone());
18923
18924    let hunk_ranges = cx.update_editor(|editor, window, cx| {
18925        let snapshot = editor.snapshot(window, cx);
18926        let hunks = editor
18927            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18928            .collect::<Vec<_>>();
18929        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
18930        let buffer_id = hunks[0].buffer_id;
18931        hunks
18932            .into_iter()
18933            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
18934            .collect::<Vec<_>>()
18935    });
18936    assert_eq!(hunk_ranges.len(), 2);
18937
18938    cx.update_editor(|editor, _, cx| {
18939        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18940    });
18941    executor.run_until_parked();
18942
18943    let second_hunk_expanded = r#"
18944          ˇA
18945          b
18946        - c
18947        + C
18948        "#
18949    .unindent();
18950
18951    cx.assert_state_with_diff(second_hunk_expanded);
18952
18953    cx.update_editor(|editor, _, cx| {
18954        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18955    });
18956    executor.run_until_parked();
18957
18958    cx.assert_state_with_diff(both_hunks_expanded.clone());
18959
18960    cx.update_editor(|editor, _, cx| {
18961        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
18962    });
18963    executor.run_until_parked();
18964
18965    let first_hunk_expanded = r#"
18966        - a
18967        + ˇA
18968          b
18969          C
18970        "#
18971    .unindent();
18972
18973    cx.assert_state_with_diff(first_hunk_expanded);
18974
18975    cx.update_editor(|editor, _, cx| {
18976        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
18977    });
18978    executor.run_until_parked();
18979
18980    cx.assert_state_with_diff(both_hunks_expanded);
18981
18982    cx.set_state(
18983        &r#"
18984        ˇA
18985        b
18986        "#
18987        .unindent(),
18988    );
18989    cx.run_until_parked();
18990
18991    // TODO this cursor position seems bad
18992    cx.assert_state_with_diff(
18993        r#"
18994        - ˇa
18995        + A
18996          b
18997        "#
18998        .unindent(),
18999    );
19000
19001    cx.update_editor(|editor, window, cx| {
19002        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19003    });
19004
19005    cx.assert_state_with_diff(
19006        r#"
19007            - ˇa
19008            + A
19009              b
19010            - c
19011            "#
19012        .unindent(),
19013    );
19014
19015    let hunk_ranges = cx.update_editor(|editor, window, cx| {
19016        let snapshot = editor.snapshot(window, cx);
19017        let hunks = editor
19018            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19019            .collect::<Vec<_>>();
19020        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
19021        let buffer_id = hunks[0].buffer_id;
19022        hunks
19023            .into_iter()
19024            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
19025            .collect::<Vec<_>>()
19026    });
19027    assert_eq!(hunk_ranges.len(), 2);
19028
19029    cx.update_editor(|editor, _, cx| {
19030        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
19031    });
19032    executor.run_until_parked();
19033
19034    cx.assert_state_with_diff(
19035        r#"
19036        - ˇa
19037        + A
19038          b
19039        "#
19040        .unindent(),
19041    );
19042}
19043
19044#[gpui::test]
19045async fn test_toggle_deletion_hunk_at_start_of_file(
19046    executor: BackgroundExecutor,
19047    cx: &mut TestAppContext,
19048) {
19049    init_test(cx, |_| {});
19050    let mut cx = EditorTestContext::new(cx).await;
19051
19052    let diff_base = r#"
19053        a
19054        b
19055        c
19056        "#
19057    .unindent();
19058
19059    cx.set_state(
19060        &r#"
19061        ˇb
19062        c
19063        "#
19064        .unindent(),
19065    );
19066    cx.set_head_text(&diff_base);
19067    cx.update_editor(|editor, window, cx| {
19068        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19069    });
19070    executor.run_until_parked();
19071
19072    let hunk_expanded = r#"
19073        - a
19074          ˇb
19075          c
19076        "#
19077    .unindent();
19078
19079    cx.assert_state_with_diff(hunk_expanded.clone());
19080
19081    let hunk_ranges = cx.update_editor(|editor, window, cx| {
19082        let snapshot = editor.snapshot(window, cx);
19083        let hunks = editor
19084            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19085            .collect::<Vec<_>>();
19086        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
19087        let buffer_id = hunks[0].buffer_id;
19088        hunks
19089            .into_iter()
19090            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
19091            .collect::<Vec<_>>()
19092    });
19093    assert_eq!(hunk_ranges.len(), 1);
19094
19095    cx.update_editor(|editor, _, cx| {
19096        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19097    });
19098    executor.run_until_parked();
19099
19100    let hunk_collapsed = r#"
19101          ˇb
19102          c
19103        "#
19104    .unindent();
19105
19106    cx.assert_state_with_diff(hunk_collapsed);
19107
19108    cx.update_editor(|editor, _, cx| {
19109        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19110    });
19111    executor.run_until_parked();
19112
19113    cx.assert_state_with_diff(hunk_expanded.clone());
19114}
19115
19116#[gpui::test]
19117async fn test_display_diff_hunks(cx: &mut TestAppContext) {
19118    init_test(cx, |_| {});
19119
19120    let fs = FakeFs::new(cx.executor());
19121    fs.insert_tree(
19122        path!("/test"),
19123        json!({
19124            ".git": {},
19125            "file-1": "ONE\n",
19126            "file-2": "TWO\n",
19127            "file-3": "THREE\n",
19128        }),
19129    )
19130    .await;
19131
19132    fs.set_head_for_repo(
19133        path!("/test/.git").as_ref(),
19134        &[
19135            ("file-1".into(), "one\n".into()),
19136            ("file-2".into(), "two\n".into()),
19137            ("file-3".into(), "three\n".into()),
19138        ],
19139        "deadbeef",
19140    );
19141
19142    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
19143    let mut buffers = vec![];
19144    for i in 1..=3 {
19145        let buffer = project
19146            .update(cx, |project, cx| {
19147                let path = format!(path!("/test/file-{}"), i);
19148                project.open_local_buffer(path, cx)
19149            })
19150            .await
19151            .unwrap();
19152        buffers.push(buffer);
19153    }
19154
19155    let multibuffer = cx.new(|cx| {
19156        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
19157        multibuffer.set_all_diff_hunks_expanded(cx);
19158        for buffer in &buffers {
19159            let snapshot = buffer.read(cx).snapshot();
19160            multibuffer.set_excerpts_for_path(
19161                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
19162                buffer.clone(),
19163                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
19164                DEFAULT_MULTIBUFFER_CONTEXT,
19165                cx,
19166            );
19167        }
19168        multibuffer
19169    });
19170
19171    let editor = cx.add_window(|window, cx| {
19172        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
19173    });
19174    cx.run_until_parked();
19175
19176    let snapshot = editor
19177        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19178        .unwrap();
19179    let hunks = snapshot
19180        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
19181        .map(|hunk| match hunk {
19182            DisplayDiffHunk::Unfolded {
19183                display_row_range, ..
19184            } => display_row_range,
19185            DisplayDiffHunk::Folded { .. } => unreachable!(),
19186        })
19187        .collect::<Vec<_>>();
19188    assert_eq!(
19189        hunks,
19190        [
19191            DisplayRow(2)..DisplayRow(4),
19192            DisplayRow(7)..DisplayRow(9),
19193            DisplayRow(12)..DisplayRow(14),
19194        ]
19195    );
19196}
19197
19198#[gpui::test]
19199async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
19200    init_test(cx, |_| {});
19201
19202    let mut cx = EditorTestContext::new(cx).await;
19203    cx.set_head_text(indoc! { "
19204        one
19205        two
19206        three
19207        four
19208        five
19209        "
19210    });
19211    cx.set_index_text(indoc! { "
19212        one
19213        two
19214        three
19215        four
19216        five
19217        "
19218    });
19219    cx.set_state(indoc! {"
19220        one
19221        TWO
19222        ˇTHREE
19223        FOUR
19224        five
19225    "});
19226    cx.run_until_parked();
19227    cx.update_editor(|editor, window, cx| {
19228        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19229    });
19230    cx.run_until_parked();
19231    cx.assert_index_text(Some(indoc! {"
19232        one
19233        TWO
19234        THREE
19235        FOUR
19236        five
19237    "}));
19238    cx.set_state(indoc! { "
19239        one
19240        TWO
19241        ˇTHREE-HUNDRED
19242        FOUR
19243        five
19244    "});
19245    cx.run_until_parked();
19246    cx.update_editor(|editor, window, cx| {
19247        let snapshot = editor.snapshot(window, cx);
19248        let hunks = editor
19249            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19250            .collect::<Vec<_>>();
19251        assert_eq!(hunks.len(), 1);
19252        assert_eq!(
19253            hunks[0].status(),
19254            DiffHunkStatus {
19255                kind: DiffHunkStatusKind::Modified,
19256                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
19257            }
19258        );
19259
19260        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19261    });
19262    cx.run_until_parked();
19263    cx.assert_index_text(Some(indoc! {"
19264        one
19265        TWO
19266        THREE-HUNDRED
19267        FOUR
19268        five
19269    "}));
19270}
19271
19272#[gpui::test]
19273fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
19274    init_test(cx, |_| {});
19275
19276    let editor = cx.add_window(|window, cx| {
19277        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
19278        build_editor(buffer, window, cx)
19279    });
19280
19281    let render_args = Arc::new(Mutex::new(None));
19282    let snapshot = editor
19283        .update(cx, |editor, window, cx| {
19284            let snapshot = editor.buffer().read(cx).snapshot(cx);
19285            let range =
19286                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
19287
19288            struct RenderArgs {
19289                row: MultiBufferRow,
19290                folded: bool,
19291                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
19292            }
19293
19294            let crease = Crease::inline(
19295                range,
19296                FoldPlaceholder::test(),
19297                {
19298                    let toggle_callback = render_args.clone();
19299                    move |row, folded, callback, _window, _cx| {
19300                        *toggle_callback.lock() = Some(RenderArgs {
19301                            row,
19302                            folded,
19303                            callback,
19304                        });
19305                        div()
19306                    }
19307                },
19308                |_row, _folded, _window, _cx| div(),
19309            );
19310
19311            editor.insert_creases(Some(crease), cx);
19312            let snapshot = editor.snapshot(window, cx);
19313            let _div = snapshot.render_crease_toggle(
19314                MultiBufferRow(1),
19315                false,
19316                cx.entity().clone(),
19317                window,
19318                cx,
19319            );
19320            snapshot
19321        })
19322        .unwrap();
19323
19324    let render_args = render_args.lock().take().unwrap();
19325    assert_eq!(render_args.row, MultiBufferRow(1));
19326    assert!(!render_args.folded);
19327    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
19328
19329    cx.update_window(*editor, |_, window, cx| {
19330        (render_args.callback)(true, window, cx)
19331    })
19332    .unwrap();
19333    let snapshot = editor
19334        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19335        .unwrap();
19336    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
19337
19338    cx.update_window(*editor, |_, window, cx| {
19339        (render_args.callback)(false, window, cx)
19340    })
19341    .unwrap();
19342    let snapshot = editor
19343        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19344        .unwrap();
19345    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
19346}
19347
19348#[gpui::test]
19349async fn test_input_text(cx: &mut TestAppContext) {
19350    init_test(cx, |_| {});
19351    let mut cx = EditorTestContext::new(cx).await;
19352
19353    cx.set_state(
19354        &r#"ˇone
19355        two
19356
19357        three
19358        fourˇ
19359        five
19360
19361        siˇx"#
19362            .unindent(),
19363    );
19364
19365    cx.dispatch_action(HandleInput(String::new()));
19366    cx.assert_editor_state(
19367        &r#"ˇone
19368        two
19369
19370        three
19371        fourˇ
19372        five
19373
19374        siˇx"#
19375            .unindent(),
19376    );
19377
19378    cx.dispatch_action(HandleInput("AAAA".to_string()));
19379    cx.assert_editor_state(
19380        &r#"AAAAˇone
19381        two
19382
19383        three
19384        fourAAAAˇ
19385        five
19386
19387        siAAAAˇx"#
19388            .unindent(),
19389    );
19390}
19391
19392#[gpui::test]
19393async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
19394    init_test(cx, |_| {});
19395
19396    let mut cx = EditorTestContext::new(cx).await;
19397    cx.set_state(
19398        r#"let foo = 1;
19399let foo = 2;
19400let foo = 3;
19401let fooˇ = 4;
19402let foo = 5;
19403let foo = 6;
19404let foo = 7;
19405let foo = 8;
19406let foo = 9;
19407let foo = 10;
19408let foo = 11;
19409let foo = 12;
19410let foo = 13;
19411let foo = 14;
19412let foo = 15;"#,
19413    );
19414
19415    cx.update_editor(|e, window, cx| {
19416        assert_eq!(
19417            e.next_scroll_position,
19418            NextScrollCursorCenterTopBottom::Center,
19419            "Default next scroll direction is center",
19420        );
19421
19422        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19423        assert_eq!(
19424            e.next_scroll_position,
19425            NextScrollCursorCenterTopBottom::Top,
19426            "After center, next scroll direction should be top",
19427        );
19428
19429        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19430        assert_eq!(
19431            e.next_scroll_position,
19432            NextScrollCursorCenterTopBottom::Bottom,
19433            "After top, next scroll direction should be bottom",
19434        );
19435
19436        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19437        assert_eq!(
19438            e.next_scroll_position,
19439            NextScrollCursorCenterTopBottom::Center,
19440            "After bottom, scrolling should start over",
19441        );
19442
19443        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19444        assert_eq!(
19445            e.next_scroll_position,
19446            NextScrollCursorCenterTopBottom::Top,
19447            "Scrolling continues if retriggered fast enough"
19448        );
19449    });
19450
19451    cx.executor()
19452        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
19453    cx.executor().run_until_parked();
19454    cx.update_editor(|e, _, _| {
19455        assert_eq!(
19456            e.next_scroll_position,
19457            NextScrollCursorCenterTopBottom::Center,
19458            "If scrolling is not triggered fast enough, it should reset"
19459        );
19460    });
19461}
19462
19463#[gpui::test]
19464async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
19465    init_test(cx, |_| {});
19466    let mut cx = EditorLspTestContext::new_rust(
19467        lsp::ServerCapabilities {
19468            definition_provider: Some(lsp::OneOf::Left(true)),
19469            references_provider: Some(lsp::OneOf::Left(true)),
19470            ..lsp::ServerCapabilities::default()
19471        },
19472        cx,
19473    )
19474    .await;
19475
19476    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
19477        let go_to_definition = cx
19478            .lsp
19479            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
19480                move |params, _| async move {
19481                    if empty_go_to_definition {
19482                        Ok(None)
19483                    } else {
19484                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
19485                            uri: params.text_document_position_params.text_document.uri,
19486                            range: lsp::Range::new(
19487                                lsp::Position::new(4, 3),
19488                                lsp::Position::new(4, 6),
19489                            ),
19490                        })))
19491                    }
19492                },
19493            );
19494        let references = cx
19495            .lsp
19496            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
19497                Ok(Some(vec![lsp::Location {
19498                    uri: params.text_document_position.text_document.uri,
19499                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
19500                }]))
19501            });
19502        (go_to_definition, references)
19503    };
19504
19505    cx.set_state(
19506        &r#"fn one() {
19507            let mut a = ˇtwo();
19508        }
19509
19510        fn two() {}"#
19511            .unindent(),
19512    );
19513    set_up_lsp_handlers(false, &mut cx);
19514    let navigated = cx
19515        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19516        .await
19517        .expect("Failed to navigate to definition");
19518    assert_eq!(
19519        navigated,
19520        Navigated::Yes,
19521        "Should have navigated to definition from the GetDefinition response"
19522    );
19523    cx.assert_editor_state(
19524        &r#"fn one() {
19525            let mut a = two();
19526        }
19527
19528        fn «twoˇ»() {}"#
19529            .unindent(),
19530    );
19531
19532    let editors = cx.update_workspace(|workspace, _, cx| {
19533        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19534    });
19535    cx.update_editor(|_, _, test_editor_cx| {
19536        assert_eq!(
19537            editors.len(),
19538            1,
19539            "Initially, only one, test, editor should be open in the workspace"
19540        );
19541        assert_eq!(
19542            test_editor_cx.entity(),
19543            editors.last().expect("Asserted len is 1").clone()
19544        );
19545    });
19546
19547    set_up_lsp_handlers(true, &mut cx);
19548    let navigated = cx
19549        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19550        .await
19551        .expect("Failed to navigate to lookup references");
19552    assert_eq!(
19553        navigated,
19554        Navigated::Yes,
19555        "Should have navigated to references as a fallback after empty GoToDefinition response"
19556    );
19557    // We should not change the selections in the existing file,
19558    // if opening another milti buffer with the references
19559    cx.assert_editor_state(
19560        &r#"fn one() {
19561            let mut a = two();
19562        }
19563
19564        fn «twoˇ»() {}"#
19565            .unindent(),
19566    );
19567    let editors = cx.update_workspace(|workspace, _, cx| {
19568        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19569    });
19570    cx.update_editor(|_, _, test_editor_cx| {
19571        assert_eq!(
19572            editors.len(),
19573            2,
19574            "After falling back to references search, we open a new editor with the results"
19575        );
19576        let references_fallback_text = editors
19577            .into_iter()
19578            .find(|new_editor| *new_editor != test_editor_cx.entity())
19579            .expect("Should have one non-test editor now")
19580            .read(test_editor_cx)
19581            .text(test_editor_cx);
19582        assert_eq!(
19583            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
19584            "Should use the range from the references response and not the GoToDefinition one"
19585        );
19586    });
19587}
19588
19589#[gpui::test]
19590async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
19591    init_test(cx, |_| {});
19592    cx.update(|cx| {
19593        let mut editor_settings = EditorSettings::get_global(cx).clone();
19594        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
19595        EditorSettings::override_global(editor_settings, cx);
19596    });
19597    let mut cx = EditorLspTestContext::new_rust(
19598        lsp::ServerCapabilities {
19599            definition_provider: Some(lsp::OneOf::Left(true)),
19600            references_provider: Some(lsp::OneOf::Left(true)),
19601            ..lsp::ServerCapabilities::default()
19602        },
19603        cx,
19604    )
19605    .await;
19606    let original_state = r#"fn one() {
19607        let mut a = ˇtwo();
19608    }
19609
19610    fn two() {}"#
19611        .unindent();
19612    cx.set_state(&original_state);
19613
19614    let mut go_to_definition = cx
19615        .lsp
19616        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
19617            move |_, _| async move { Ok(None) },
19618        );
19619    let _references = cx
19620        .lsp
19621        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
19622            panic!("Should not call for references with no go to definition fallback")
19623        });
19624
19625    let navigated = cx
19626        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19627        .await
19628        .expect("Failed to navigate to lookup references");
19629    go_to_definition
19630        .next()
19631        .await
19632        .expect("Should have called the go_to_definition handler");
19633
19634    assert_eq!(
19635        navigated,
19636        Navigated::No,
19637        "Should have navigated to references as a fallback after empty GoToDefinition response"
19638    );
19639    cx.assert_editor_state(&original_state);
19640    let editors = cx.update_workspace(|workspace, _, cx| {
19641        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19642    });
19643    cx.update_editor(|_, _, _| {
19644        assert_eq!(
19645            editors.len(),
19646            1,
19647            "After unsuccessful fallback, no other editor should have been opened"
19648        );
19649    });
19650}
19651
19652#[gpui::test]
19653async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
19654    init_test(cx, |_| {});
19655
19656    let language = Arc::new(Language::new(
19657        LanguageConfig::default(),
19658        Some(tree_sitter_rust::LANGUAGE.into()),
19659    ));
19660
19661    let text = r#"
19662        #[cfg(test)]
19663        mod tests() {
19664            #[test]
19665            fn runnable_1() {
19666                let a = 1;
19667            }
19668
19669            #[test]
19670            fn runnable_2() {
19671                let a = 1;
19672                let b = 2;
19673            }
19674        }
19675    "#
19676    .unindent();
19677
19678    let fs = FakeFs::new(cx.executor());
19679    fs.insert_file("/file.rs", Default::default()).await;
19680
19681    let project = Project::test(fs, ["/a".as_ref()], cx).await;
19682    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19683    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19684    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
19685    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
19686
19687    let editor = cx.new_window_entity(|window, cx| {
19688        Editor::new(
19689            EditorMode::full(),
19690            multi_buffer,
19691            Some(project.clone()),
19692            window,
19693            cx,
19694        )
19695    });
19696
19697    editor.update_in(cx, |editor, window, cx| {
19698        let snapshot = editor.buffer().read(cx).snapshot(cx);
19699        editor.tasks.insert(
19700            (buffer.read(cx).remote_id(), 3),
19701            RunnableTasks {
19702                templates: vec![],
19703                offset: snapshot.anchor_before(43),
19704                column: 0,
19705                extra_variables: HashMap::default(),
19706                context_range: BufferOffset(43)..BufferOffset(85),
19707            },
19708        );
19709        editor.tasks.insert(
19710            (buffer.read(cx).remote_id(), 8),
19711            RunnableTasks {
19712                templates: vec![],
19713                offset: snapshot.anchor_before(86),
19714                column: 0,
19715                extra_variables: HashMap::default(),
19716                context_range: BufferOffset(86)..BufferOffset(191),
19717            },
19718        );
19719
19720        // Test finding task when cursor is inside function body
19721        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19722            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
19723        });
19724        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
19725        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
19726
19727        // Test finding task when cursor is on function name
19728        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19729            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
19730        });
19731        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
19732        assert_eq!(row, 8, "Should find task when cursor is on function name");
19733    });
19734}
19735
19736#[gpui::test]
19737async fn test_folding_buffers(cx: &mut TestAppContext) {
19738    init_test(cx, |_| {});
19739
19740    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
19741    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
19742    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
19743
19744    let fs = FakeFs::new(cx.executor());
19745    fs.insert_tree(
19746        path!("/a"),
19747        json!({
19748            "first.rs": sample_text_1,
19749            "second.rs": sample_text_2,
19750            "third.rs": sample_text_3,
19751        }),
19752    )
19753    .await;
19754    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19755    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19756    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19757    let worktree = project.update(cx, |project, cx| {
19758        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19759        assert_eq!(worktrees.len(), 1);
19760        worktrees.pop().unwrap()
19761    });
19762    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19763
19764    let buffer_1 = project
19765        .update(cx, |project, cx| {
19766            project.open_buffer((worktree_id, "first.rs"), cx)
19767        })
19768        .await
19769        .unwrap();
19770    let buffer_2 = project
19771        .update(cx, |project, cx| {
19772            project.open_buffer((worktree_id, "second.rs"), cx)
19773        })
19774        .await
19775        .unwrap();
19776    let buffer_3 = project
19777        .update(cx, |project, cx| {
19778            project.open_buffer((worktree_id, "third.rs"), cx)
19779        })
19780        .await
19781        .unwrap();
19782
19783    let multi_buffer = cx.new(|cx| {
19784        let mut multi_buffer = MultiBuffer::new(ReadWrite);
19785        multi_buffer.push_excerpts(
19786            buffer_1.clone(),
19787            [
19788                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19789                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19790                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19791            ],
19792            cx,
19793        );
19794        multi_buffer.push_excerpts(
19795            buffer_2.clone(),
19796            [
19797                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19798                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19799                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19800            ],
19801            cx,
19802        );
19803        multi_buffer.push_excerpts(
19804            buffer_3.clone(),
19805            [
19806                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19807                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19808                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19809            ],
19810            cx,
19811        );
19812        multi_buffer
19813    });
19814    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19815        Editor::new(
19816            EditorMode::full(),
19817            multi_buffer.clone(),
19818            Some(project.clone()),
19819            window,
19820            cx,
19821        )
19822    });
19823
19824    assert_eq!(
19825        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19826        "\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",
19827    );
19828
19829    multi_buffer_editor.update(cx, |editor, cx| {
19830        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
19831    });
19832    assert_eq!(
19833        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19834        "\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",
19835        "After folding the first buffer, its text should not be displayed"
19836    );
19837
19838    multi_buffer_editor.update(cx, |editor, cx| {
19839        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
19840    });
19841    assert_eq!(
19842        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19843        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
19844        "After folding the second buffer, its text should not be displayed"
19845    );
19846
19847    multi_buffer_editor.update(cx, |editor, cx| {
19848        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
19849    });
19850    assert_eq!(
19851        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19852        "\n\n\n\n\n",
19853        "After folding the third buffer, its text should not be displayed"
19854    );
19855
19856    // Emulate selection inside the fold logic, that should work
19857    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19858        editor
19859            .snapshot(window, cx)
19860            .next_line_boundary(Point::new(0, 4));
19861    });
19862
19863    multi_buffer_editor.update(cx, |editor, cx| {
19864        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
19865    });
19866    assert_eq!(
19867        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19868        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
19869        "After unfolding the second buffer, its text should be displayed"
19870    );
19871
19872    // Typing inside of buffer 1 causes that buffer to be unfolded.
19873    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19874        assert_eq!(
19875            multi_buffer
19876                .read(cx)
19877                .snapshot(cx)
19878                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
19879                .collect::<String>(),
19880            "bbbb"
19881        );
19882        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
19883            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
19884        });
19885        editor.handle_input("B", window, cx);
19886    });
19887
19888    assert_eq!(
19889        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19890        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
19891        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
19892    );
19893
19894    multi_buffer_editor.update(cx, |editor, cx| {
19895        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
19896    });
19897    assert_eq!(
19898        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19899        "\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",
19900        "After unfolding the all buffers, all original text should be displayed"
19901    );
19902}
19903
19904#[gpui::test]
19905async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
19906    init_test(cx, |_| {});
19907
19908    let sample_text_1 = "1111\n2222\n3333".to_string();
19909    let sample_text_2 = "4444\n5555\n6666".to_string();
19910    let sample_text_3 = "7777\n8888\n9999".to_string();
19911
19912    let fs = FakeFs::new(cx.executor());
19913    fs.insert_tree(
19914        path!("/a"),
19915        json!({
19916            "first.rs": sample_text_1,
19917            "second.rs": sample_text_2,
19918            "third.rs": sample_text_3,
19919        }),
19920    )
19921    .await;
19922    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19923    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19924    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19925    let worktree = project.update(cx, |project, cx| {
19926        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19927        assert_eq!(worktrees.len(), 1);
19928        worktrees.pop().unwrap()
19929    });
19930    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19931
19932    let buffer_1 = project
19933        .update(cx, |project, cx| {
19934            project.open_buffer((worktree_id, "first.rs"), cx)
19935        })
19936        .await
19937        .unwrap();
19938    let buffer_2 = project
19939        .update(cx, |project, cx| {
19940            project.open_buffer((worktree_id, "second.rs"), cx)
19941        })
19942        .await
19943        .unwrap();
19944    let buffer_3 = project
19945        .update(cx, |project, cx| {
19946            project.open_buffer((worktree_id, "third.rs"), cx)
19947        })
19948        .await
19949        .unwrap();
19950
19951    let multi_buffer = cx.new(|cx| {
19952        let mut multi_buffer = MultiBuffer::new(ReadWrite);
19953        multi_buffer.push_excerpts(
19954            buffer_1.clone(),
19955            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19956            cx,
19957        );
19958        multi_buffer.push_excerpts(
19959            buffer_2.clone(),
19960            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19961            cx,
19962        );
19963        multi_buffer.push_excerpts(
19964            buffer_3.clone(),
19965            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19966            cx,
19967        );
19968        multi_buffer
19969    });
19970
19971    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19972        Editor::new(
19973            EditorMode::full(),
19974            multi_buffer,
19975            Some(project.clone()),
19976            window,
19977            cx,
19978        )
19979    });
19980
19981    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
19982    assert_eq!(
19983        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19984        full_text,
19985    );
19986
19987    multi_buffer_editor.update(cx, |editor, cx| {
19988        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
19989    });
19990    assert_eq!(
19991        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19992        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
19993        "After folding the first buffer, its text should not be displayed"
19994    );
19995
19996    multi_buffer_editor.update(cx, |editor, cx| {
19997        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
19998    });
19999
20000    assert_eq!(
20001        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20002        "\n\n\n\n\n\n7777\n8888\n9999",
20003        "After folding the second buffer, its text should not be displayed"
20004    );
20005
20006    multi_buffer_editor.update(cx, |editor, cx| {
20007        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
20008    });
20009    assert_eq!(
20010        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20011        "\n\n\n\n\n",
20012        "After folding the third buffer, its text should not be displayed"
20013    );
20014
20015    multi_buffer_editor.update(cx, |editor, cx| {
20016        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
20017    });
20018    assert_eq!(
20019        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20020        "\n\n\n\n4444\n5555\n6666\n\n",
20021        "After unfolding the second buffer, its text should be displayed"
20022    );
20023
20024    multi_buffer_editor.update(cx, |editor, cx| {
20025        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
20026    });
20027    assert_eq!(
20028        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20029        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
20030        "After unfolding the first buffer, its text should be displayed"
20031    );
20032
20033    multi_buffer_editor.update(cx, |editor, cx| {
20034        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
20035    });
20036    assert_eq!(
20037        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20038        full_text,
20039        "After unfolding all buffers, all original text should be displayed"
20040    );
20041}
20042
20043#[gpui::test]
20044async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
20045    init_test(cx, |_| {});
20046
20047    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
20048
20049    let fs = FakeFs::new(cx.executor());
20050    fs.insert_tree(
20051        path!("/a"),
20052        json!({
20053            "main.rs": sample_text,
20054        }),
20055    )
20056    .await;
20057    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20058    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20059    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20060    let worktree = project.update(cx, |project, cx| {
20061        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
20062        assert_eq!(worktrees.len(), 1);
20063        worktrees.pop().unwrap()
20064    });
20065    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
20066
20067    let buffer_1 = project
20068        .update(cx, |project, cx| {
20069            project.open_buffer((worktree_id, "main.rs"), cx)
20070        })
20071        .await
20072        .unwrap();
20073
20074    let multi_buffer = cx.new(|cx| {
20075        let mut multi_buffer = MultiBuffer::new(ReadWrite);
20076        multi_buffer.push_excerpts(
20077            buffer_1.clone(),
20078            [ExcerptRange::new(
20079                Point::new(0, 0)
20080                    ..Point::new(
20081                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
20082                        0,
20083                    ),
20084            )],
20085            cx,
20086        );
20087        multi_buffer
20088    });
20089    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20090        Editor::new(
20091            EditorMode::full(),
20092            multi_buffer,
20093            Some(project.clone()),
20094            window,
20095            cx,
20096        )
20097    });
20098
20099    let selection_range = Point::new(1, 0)..Point::new(2, 0);
20100    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20101        enum TestHighlight {}
20102        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
20103        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
20104        editor.highlight_text::<TestHighlight>(
20105            vec![highlight_range.clone()],
20106            HighlightStyle::color(Hsla::green()),
20107            cx,
20108        );
20109        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20110            s.select_ranges(Some(highlight_range))
20111        });
20112    });
20113
20114    let full_text = format!("\n\n{sample_text}");
20115    assert_eq!(
20116        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20117        full_text,
20118    );
20119}
20120
20121#[gpui::test]
20122async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
20123    init_test(cx, |_| {});
20124    cx.update(|cx| {
20125        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
20126            "keymaps/default-linux.json",
20127            cx,
20128        )
20129        .unwrap();
20130        cx.bind_keys(default_key_bindings);
20131    });
20132
20133    let (editor, cx) = cx.add_window_view(|window, cx| {
20134        let multi_buffer = MultiBuffer::build_multi(
20135            [
20136                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
20137                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
20138                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
20139                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
20140            ],
20141            cx,
20142        );
20143        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
20144
20145        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
20146        // fold all but the second buffer, so that we test navigating between two
20147        // adjacent folded buffers, as well as folded buffers at the start and
20148        // end the multibuffer
20149        editor.fold_buffer(buffer_ids[0], cx);
20150        editor.fold_buffer(buffer_ids[2], cx);
20151        editor.fold_buffer(buffer_ids[3], cx);
20152
20153        editor
20154    });
20155    cx.simulate_resize(size(px(1000.), px(1000.)));
20156
20157    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
20158    cx.assert_excerpts_with_selections(indoc! {"
20159        [EXCERPT]
20160        ˇ[FOLDED]
20161        [EXCERPT]
20162        a1
20163        b1
20164        [EXCERPT]
20165        [FOLDED]
20166        [EXCERPT]
20167        [FOLDED]
20168        "
20169    });
20170    cx.simulate_keystroke("down");
20171    cx.assert_excerpts_with_selections(indoc! {"
20172        [EXCERPT]
20173        [FOLDED]
20174        [EXCERPT]
20175        ˇa1
20176        b1
20177        [EXCERPT]
20178        [FOLDED]
20179        [EXCERPT]
20180        [FOLDED]
20181        "
20182    });
20183    cx.simulate_keystroke("down");
20184    cx.assert_excerpts_with_selections(indoc! {"
20185        [EXCERPT]
20186        [FOLDED]
20187        [EXCERPT]
20188        a1
20189        ˇb1
20190        [EXCERPT]
20191        [FOLDED]
20192        [EXCERPT]
20193        [FOLDED]
20194        "
20195    });
20196    cx.simulate_keystroke("down");
20197    cx.assert_excerpts_with_selections(indoc! {"
20198        [EXCERPT]
20199        [FOLDED]
20200        [EXCERPT]
20201        a1
20202        b1
20203        ˇ[EXCERPT]
20204        [FOLDED]
20205        [EXCERPT]
20206        [FOLDED]
20207        "
20208    });
20209    cx.simulate_keystroke("down");
20210    cx.assert_excerpts_with_selections(indoc! {"
20211        [EXCERPT]
20212        [FOLDED]
20213        [EXCERPT]
20214        a1
20215        b1
20216        [EXCERPT]
20217        ˇ[FOLDED]
20218        [EXCERPT]
20219        [FOLDED]
20220        "
20221    });
20222    for _ in 0..5 {
20223        cx.simulate_keystroke("down");
20224        cx.assert_excerpts_with_selections(indoc! {"
20225            [EXCERPT]
20226            [FOLDED]
20227            [EXCERPT]
20228            a1
20229            b1
20230            [EXCERPT]
20231            [FOLDED]
20232            [EXCERPT]
20233            ˇ[FOLDED]
20234            "
20235        });
20236    }
20237
20238    cx.simulate_keystroke("up");
20239    cx.assert_excerpts_with_selections(indoc! {"
20240        [EXCERPT]
20241        [FOLDED]
20242        [EXCERPT]
20243        a1
20244        b1
20245        [EXCERPT]
20246        ˇ[FOLDED]
20247        [EXCERPT]
20248        [FOLDED]
20249        "
20250    });
20251    cx.simulate_keystroke("up");
20252    cx.assert_excerpts_with_selections(indoc! {"
20253        [EXCERPT]
20254        [FOLDED]
20255        [EXCERPT]
20256        a1
20257        b1
20258        ˇ[EXCERPT]
20259        [FOLDED]
20260        [EXCERPT]
20261        [FOLDED]
20262        "
20263    });
20264    cx.simulate_keystroke("up");
20265    cx.assert_excerpts_with_selections(indoc! {"
20266        [EXCERPT]
20267        [FOLDED]
20268        [EXCERPT]
20269        a1
20270        ˇb1
20271        [EXCERPT]
20272        [FOLDED]
20273        [EXCERPT]
20274        [FOLDED]
20275        "
20276    });
20277    cx.simulate_keystroke("up");
20278    cx.assert_excerpts_with_selections(indoc! {"
20279        [EXCERPT]
20280        [FOLDED]
20281        [EXCERPT]
20282        ˇa1
20283        b1
20284        [EXCERPT]
20285        [FOLDED]
20286        [EXCERPT]
20287        [FOLDED]
20288        "
20289    });
20290    for _ in 0..5 {
20291        cx.simulate_keystroke("up");
20292        cx.assert_excerpts_with_selections(indoc! {"
20293            [EXCERPT]
20294            ˇ[FOLDED]
20295            [EXCERPT]
20296            a1
20297            b1
20298            [EXCERPT]
20299            [FOLDED]
20300            [EXCERPT]
20301            [FOLDED]
20302            "
20303        });
20304    }
20305}
20306
20307#[gpui::test]
20308async fn test_inline_completion_text(cx: &mut TestAppContext) {
20309    init_test(cx, |_| {});
20310
20311    // Simple insertion
20312    assert_highlighted_edits(
20313        "Hello, world!",
20314        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
20315        true,
20316        cx,
20317        |highlighted_edits, cx| {
20318            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
20319            assert_eq!(highlighted_edits.highlights.len(), 1);
20320            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
20321            assert_eq!(
20322                highlighted_edits.highlights[0].1.background_color,
20323                Some(cx.theme().status().created_background)
20324            );
20325        },
20326    )
20327    .await;
20328
20329    // Replacement
20330    assert_highlighted_edits(
20331        "This is a test.",
20332        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
20333        false,
20334        cx,
20335        |highlighted_edits, cx| {
20336            assert_eq!(highlighted_edits.text, "That is a test.");
20337            assert_eq!(highlighted_edits.highlights.len(), 1);
20338            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
20339            assert_eq!(
20340                highlighted_edits.highlights[0].1.background_color,
20341                Some(cx.theme().status().created_background)
20342            );
20343        },
20344    )
20345    .await;
20346
20347    // Multiple edits
20348    assert_highlighted_edits(
20349        "Hello, world!",
20350        vec![
20351            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
20352            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
20353        ],
20354        false,
20355        cx,
20356        |highlighted_edits, cx| {
20357            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
20358            assert_eq!(highlighted_edits.highlights.len(), 2);
20359            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
20360            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
20361            assert_eq!(
20362                highlighted_edits.highlights[0].1.background_color,
20363                Some(cx.theme().status().created_background)
20364            );
20365            assert_eq!(
20366                highlighted_edits.highlights[1].1.background_color,
20367                Some(cx.theme().status().created_background)
20368            );
20369        },
20370    )
20371    .await;
20372
20373    // Multiple lines with edits
20374    assert_highlighted_edits(
20375        "First line\nSecond line\nThird line\nFourth line",
20376        vec![
20377            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
20378            (
20379                Point::new(2, 0)..Point::new(2, 10),
20380                "New third line".to_string(),
20381            ),
20382            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
20383        ],
20384        false,
20385        cx,
20386        |highlighted_edits, cx| {
20387            assert_eq!(
20388                highlighted_edits.text,
20389                "Second modified\nNew third line\nFourth updated line"
20390            );
20391            assert_eq!(highlighted_edits.highlights.len(), 3);
20392            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
20393            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
20394            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
20395            for highlight in &highlighted_edits.highlights {
20396                assert_eq!(
20397                    highlight.1.background_color,
20398                    Some(cx.theme().status().created_background)
20399                );
20400            }
20401        },
20402    )
20403    .await;
20404}
20405
20406#[gpui::test]
20407async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
20408    init_test(cx, |_| {});
20409
20410    // Deletion
20411    assert_highlighted_edits(
20412        "Hello, world!",
20413        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
20414        true,
20415        cx,
20416        |highlighted_edits, cx| {
20417            assert_eq!(highlighted_edits.text, "Hello, world!");
20418            assert_eq!(highlighted_edits.highlights.len(), 1);
20419            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
20420            assert_eq!(
20421                highlighted_edits.highlights[0].1.background_color,
20422                Some(cx.theme().status().deleted_background)
20423            );
20424        },
20425    )
20426    .await;
20427
20428    // Insertion
20429    assert_highlighted_edits(
20430        "Hello, world!",
20431        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
20432        true,
20433        cx,
20434        |highlighted_edits, cx| {
20435            assert_eq!(highlighted_edits.highlights.len(), 1);
20436            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
20437            assert_eq!(
20438                highlighted_edits.highlights[0].1.background_color,
20439                Some(cx.theme().status().created_background)
20440            );
20441        },
20442    )
20443    .await;
20444}
20445
20446async fn assert_highlighted_edits(
20447    text: &str,
20448    edits: Vec<(Range<Point>, String)>,
20449    include_deletions: bool,
20450    cx: &mut TestAppContext,
20451    assertion_fn: impl Fn(HighlightedText, &App),
20452) {
20453    let window = cx.add_window(|window, cx| {
20454        let buffer = MultiBuffer::build_simple(text, cx);
20455        Editor::new(EditorMode::full(), buffer, None, window, cx)
20456    });
20457    let cx = &mut VisualTestContext::from_window(*window, cx);
20458
20459    let (buffer, snapshot) = window
20460        .update(cx, |editor, _window, cx| {
20461            (
20462                editor.buffer().clone(),
20463                editor.buffer().read(cx).snapshot(cx),
20464            )
20465        })
20466        .unwrap();
20467
20468    let edits = edits
20469        .into_iter()
20470        .map(|(range, edit)| {
20471            (
20472                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
20473                edit,
20474            )
20475        })
20476        .collect::<Vec<_>>();
20477
20478    let text_anchor_edits = edits
20479        .clone()
20480        .into_iter()
20481        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
20482        .collect::<Vec<_>>();
20483
20484    let edit_preview = window
20485        .update(cx, |_, _window, cx| {
20486            buffer
20487                .read(cx)
20488                .as_singleton()
20489                .unwrap()
20490                .read(cx)
20491                .preview_edits(text_anchor_edits.into(), cx)
20492        })
20493        .unwrap()
20494        .await;
20495
20496    cx.update(|_window, cx| {
20497        let highlighted_edits = inline_completion_edit_text(
20498            &snapshot.as_singleton().unwrap().2,
20499            &edits,
20500            &edit_preview,
20501            include_deletions,
20502            cx,
20503        );
20504        assertion_fn(highlighted_edits, cx)
20505    });
20506}
20507
20508#[track_caller]
20509fn assert_breakpoint(
20510    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
20511    path: &Arc<Path>,
20512    expected: Vec<(u32, Breakpoint)>,
20513) {
20514    if expected.len() == 0usize {
20515        assert!(!breakpoints.contains_key(path), "{}", path.display());
20516    } else {
20517        let mut breakpoint = breakpoints
20518            .get(path)
20519            .unwrap()
20520            .into_iter()
20521            .map(|breakpoint| {
20522                (
20523                    breakpoint.row,
20524                    Breakpoint {
20525                        message: breakpoint.message.clone(),
20526                        state: breakpoint.state,
20527                        condition: breakpoint.condition.clone(),
20528                        hit_condition: breakpoint.hit_condition.clone(),
20529                    },
20530                )
20531            })
20532            .collect::<Vec<_>>();
20533
20534        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
20535
20536        assert_eq!(expected, breakpoint);
20537    }
20538}
20539
20540fn add_log_breakpoint_at_cursor(
20541    editor: &mut Editor,
20542    log_message: &str,
20543    window: &mut Window,
20544    cx: &mut Context<Editor>,
20545) {
20546    let (anchor, bp) = editor
20547        .breakpoints_at_cursors(window, cx)
20548        .first()
20549        .and_then(|(anchor, bp)| {
20550            if let Some(bp) = bp {
20551                Some((*anchor, bp.clone()))
20552            } else {
20553                None
20554            }
20555        })
20556        .unwrap_or_else(|| {
20557            let cursor_position: Point = editor.selections.newest(cx).head();
20558
20559            let breakpoint_position = editor
20560                .snapshot(window, cx)
20561                .display_snapshot
20562                .buffer_snapshot
20563                .anchor_before(Point::new(cursor_position.row, 0));
20564
20565            (breakpoint_position, Breakpoint::new_log(&log_message))
20566        });
20567
20568    editor.edit_breakpoint_at_anchor(
20569        anchor,
20570        bp,
20571        BreakpointEditAction::EditLogMessage(log_message.into()),
20572        cx,
20573    );
20574}
20575
20576#[gpui::test]
20577async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
20578    init_test(cx, |_| {});
20579
20580    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20581    let fs = FakeFs::new(cx.executor());
20582    fs.insert_tree(
20583        path!("/a"),
20584        json!({
20585            "main.rs": sample_text,
20586        }),
20587    )
20588    .await;
20589    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20590    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20591    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20592
20593    let fs = FakeFs::new(cx.executor());
20594    fs.insert_tree(
20595        path!("/a"),
20596        json!({
20597            "main.rs": sample_text,
20598        }),
20599    )
20600    .await;
20601    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20602    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20603    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20604    let worktree_id = workspace
20605        .update(cx, |workspace, _window, cx| {
20606            workspace.project().update(cx, |project, cx| {
20607                project.worktrees(cx).next().unwrap().read(cx).id()
20608            })
20609        })
20610        .unwrap();
20611
20612    let buffer = project
20613        .update(cx, |project, cx| {
20614            project.open_buffer((worktree_id, "main.rs"), cx)
20615        })
20616        .await
20617        .unwrap();
20618
20619    let (editor, cx) = cx.add_window_view(|window, cx| {
20620        Editor::new(
20621            EditorMode::full(),
20622            MultiBuffer::build_from_buffer(buffer, cx),
20623            Some(project.clone()),
20624            window,
20625            cx,
20626        )
20627    });
20628
20629    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20630    let abs_path = project.read_with(cx, |project, cx| {
20631        project
20632            .absolute_path(&project_path, cx)
20633            .map(|path_buf| Arc::from(path_buf.to_owned()))
20634            .unwrap()
20635    });
20636
20637    // assert we can add breakpoint on the first line
20638    editor.update_in(cx, |editor, window, cx| {
20639        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20640        editor.move_to_end(&MoveToEnd, window, cx);
20641        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20642    });
20643
20644    let breakpoints = editor.update(cx, |editor, cx| {
20645        editor
20646            .breakpoint_store()
20647            .as_ref()
20648            .unwrap()
20649            .read(cx)
20650            .all_source_breakpoints(cx)
20651            .clone()
20652    });
20653
20654    assert_eq!(1, breakpoints.len());
20655    assert_breakpoint(
20656        &breakpoints,
20657        &abs_path,
20658        vec![
20659            (0, Breakpoint::new_standard()),
20660            (3, Breakpoint::new_standard()),
20661        ],
20662    );
20663
20664    editor.update_in(cx, |editor, window, cx| {
20665        editor.move_to_beginning(&MoveToBeginning, window, cx);
20666        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20667    });
20668
20669    let breakpoints = editor.update(cx, |editor, cx| {
20670        editor
20671            .breakpoint_store()
20672            .as_ref()
20673            .unwrap()
20674            .read(cx)
20675            .all_source_breakpoints(cx)
20676            .clone()
20677    });
20678
20679    assert_eq!(1, breakpoints.len());
20680    assert_breakpoint(
20681        &breakpoints,
20682        &abs_path,
20683        vec![(3, Breakpoint::new_standard())],
20684    );
20685
20686    editor.update_in(cx, |editor, window, cx| {
20687        editor.move_to_end(&MoveToEnd, window, cx);
20688        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20689    });
20690
20691    let breakpoints = editor.update(cx, |editor, cx| {
20692        editor
20693            .breakpoint_store()
20694            .as_ref()
20695            .unwrap()
20696            .read(cx)
20697            .all_source_breakpoints(cx)
20698            .clone()
20699    });
20700
20701    assert_eq!(0, breakpoints.len());
20702    assert_breakpoint(&breakpoints, &abs_path, vec![]);
20703}
20704
20705#[gpui::test]
20706async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
20707    init_test(cx, |_| {});
20708
20709    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20710
20711    let fs = FakeFs::new(cx.executor());
20712    fs.insert_tree(
20713        path!("/a"),
20714        json!({
20715            "main.rs": sample_text,
20716        }),
20717    )
20718    .await;
20719    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20720    let (workspace, cx) =
20721        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
20722
20723    let worktree_id = workspace.update(cx, |workspace, cx| {
20724        workspace.project().update(cx, |project, cx| {
20725            project.worktrees(cx).next().unwrap().read(cx).id()
20726        })
20727    });
20728
20729    let buffer = project
20730        .update(cx, |project, cx| {
20731            project.open_buffer((worktree_id, "main.rs"), cx)
20732        })
20733        .await
20734        .unwrap();
20735
20736    let (editor, cx) = cx.add_window_view(|window, cx| {
20737        Editor::new(
20738            EditorMode::full(),
20739            MultiBuffer::build_from_buffer(buffer, cx),
20740            Some(project.clone()),
20741            window,
20742            cx,
20743        )
20744    });
20745
20746    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20747    let abs_path = project.read_with(cx, |project, cx| {
20748        project
20749            .absolute_path(&project_path, cx)
20750            .map(|path_buf| Arc::from(path_buf.to_owned()))
20751            .unwrap()
20752    });
20753
20754    editor.update_in(cx, |editor, window, cx| {
20755        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
20756    });
20757
20758    let breakpoints = editor.update(cx, |editor, cx| {
20759        editor
20760            .breakpoint_store()
20761            .as_ref()
20762            .unwrap()
20763            .read(cx)
20764            .all_source_breakpoints(cx)
20765            .clone()
20766    });
20767
20768    assert_breakpoint(
20769        &breakpoints,
20770        &abs_path,
20771        vec![(0, Breakpoint::new_log("hello world"))],
20772    );
20773
20774    // Removing a log message from a log breakpoint should remove it
20775    editor.update_in(cx, |editor, window, cx| {
20776        add_log_breakpoint_at_cursor(editor, "", window, cx);
20777    });
20778
20779    let breakpoints = editor.update(cx, |editor, cx| {
20780        editor
20781            .breakpoint_store()
20782            .as_ref()
20783            .unwrap()
20784            .read(cx)
20785            .all_source_breakpoints(cx)
20786            .clone()
20787    });
20788
20789    assert_breakpoint(&breakpoints, &abs_path, vec![]);
20790
20791    editor.update_in(cx, |editor, window, cx| {
20792        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20793        editor.move_to_end(&MoveToEnd, window, cx);
20794        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20795        // Not adding a log message to a standard breakpoint shouldn't remove it
20796        add_log_breakpoint_at_cursor(editor, "", window, cx);
20797    });
20798
20799    let breakpoints = editor.update(cx, |editor, cx| {
20800        editor
20801            .breakpoint_store()
20802            .as_ref()
20803            .unwrap()
20804            .read(cx)
20805            .all_source_breakpoints(cx)
20806            .clone()
20807    });
20808
20809    assert_breakpoint(
20810        &breakpoints,
20811        &abs_path,
20812        vec![
20813            (0, Breakpoint::new_standard()),
20814            (3, Breakpoint::new_standard()),
20815        ],
20816    );
20817
20818    editor.update_in(cx, |editor, window, cx| {
20819        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
20820    });
20821
20822    let breakpoints = editor.update(cx, |editor, cx| {
20823        editor
20824            .breakpoint_store()
20825            .as_ref()
20826            .unwrap()
20827            .read(cx)
20828            .all_source_breakpoints(cx)
20829            .clone()
20830    });
20831
20832    assert_breakpoint(
20833        &breakpoints,
20834        &abs_path,
20835        vec![
20836            (0, Breakpoint::new_standard()),
20837            (3, Breakpoint::new_log("hello world")),
20838        ],
20839    );
20840
20841    editor.update_in(cx, |editor, window, cx| {
20842        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
20843    });
20844
20845    let breakpoints = editor.update(cx, |editor, cx| {
20846        editor
20847            .breakpoint_store()
20848            .as_ref()
20849            .unwrap()
20850            .read(cx)
20851            .all_source_breakpoints(cx)
20852            .clone()
20853    });
20854
20855    assert_breakpoint(
20856        &breakpoints,
20857        &abs_path,
20858        vec![
20859            (0, Breakpoint::new_standard()),
20860            (3, Breakpoint::new_log("hello Earth!!")),
20861        ],
20862    );
20863}
20864
20865/// This also tests that Editor::breakpoint_at_cursor_head is working properly
20866/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
20867/// or when breakpoints were placed out of order. This tests for a regression too
20868#[gpui::test]
20869async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
20870    init_test(cx, |_| {});
20871
20872    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20873    let fs = FakeFs::new(cx.executor());
20874    fs.insert_tree(
20875        path!("/a"),
20876        json!({
20877            "main.rs": sample_text,
20878        }),
20879    )
20880    .await;
20881    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20882    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20883    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20884
20885    let fs = FakeFs::new(cx.executor());
20886    fs.insert_tree(
20887        path!("/a"),
20888        json!({
20889            "main.rs": sample_text,
20890        }),
20891    )
20892    .await;
20893    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20894    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20895    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20896    let worktree_id = workspace
20897        .update(cx, |workspace, _window, cx| {
20898            workspace.project().update(cx, |project, cx| {
20899                project.worktrees(cx).next().unwrap().read(cx).id()
20900            })
20901        })
20902        .unwrap();
20903
20904    let buffer = project
20905        .update(cx, |project, cx| {
20906            project.open_buffer((worktree_id, "main.rs"), cx)
20907        })
20908        .await
20909        .unwrap();
20910
20911    let (editor, cx) = cx.add_window_view(|window, cx| {
20912        Editor::new(
20913            EditorMode::full(),
20914            MultiBuffer::build_from_buffer(buffer, cx),
20915            Some(project.clone()),
20916            window,
20917            cx,
20918        )
20919    });
20920
20921    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20922    let abs_path = project.read_with(cx, |project, cx| {
20923        project
20924            .absolute_path(&project_path, cx)
20925            .map(|path_buf| Arc::from(path_buf.to_owned()))
20926            .unwrap()
20927    });
20928
20929    // assert we can add breakpoint on the first line
20930    editor.update_in(cx, |editor, window, cx| {
20931        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20932        editor.move_to_end(&MoveToEnd, window, cx);
20933        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20934        editor.move_up(&MoveUp, window, cx);
20935        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20936    });
20937
20938    let breakpoints = editor.update(cx, |editor, cx| {
20939        editor
20940            .breakpoint_store()
20941            .as_ref()
20942            .unwrap()
20943            .read(cx)
20944            .all_source_breakpoints(cx)
20945            .clone()
20946    });
20947
20948    assert_eq!(1, breakpoints.len());
20949    assert_breakpoint(
20950        &breakpoints,
20951        &abs_path,
20952        vec![
20953            (0, Breakpoint::new_standard()),
20954            (2, Breakpoint::new_standard()),
20955            (3, Breakpoint::new_standard()),
20956        ],
20957    );
20958
20959    editor.update_in(cx, |editor, window, cx| {
20960        editor.move_to_beginning(&MoveToBeginning, window, cx);
20961        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20962        editor.move_to_end(&MoveToEnd, window, cx);
20963        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20964        // Disabling a breakpoint that doesn't exist should do nothing
20965        editor.move_up(&MoveUp, window, cx);
20966        editor.move_up(&MoveUp, window, cx);
20967        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20968    });
20969
20970    let breakpoints = editor.update(cx, |editor, cx| {
20971        editor
20972            .breakpoint_store()
20973            .as_ref()
20974            .unwrap()
20975            .read(cx)
20976            .all_source_breakpoints(cx)
20977            .clone()
20978    });
20979
20980    let disable_breakpoint = {
20981        let mut bp = Breakpoint::new_standard();
20982        bp.state = BreakpointState::Disabled;
20983        bp
20984    };
20985
20986    assert_eq!(1, breakpoints.len());
20987    assert_breakpoint(
20988        &breakpoints,
20989        &abs_path,
20990        vec![
20991            (0, disable_breakpoint.clone()),
20992            (2, Breakpoint::new_standard()),
20993            (3, disable_breakpoint.clone()),
20994        ],
20995    );
20996
20997    editor.update_in(cx, |editor, window, cx| {
20998        editor.move_to_beginning(&MoveToBeginning, window, cx);
20999        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
21000        editor.move_to_end(&MoveToEnd, window, cx);
21001        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
21002        editor.move_up(&MoveUp, window, cx);
21003        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21004    });
21005
21006    let breakpoints = editor.update(cx, |editor, cx| {
21007        editor
21008            .breakpoint_store()
21009            .as_ref()
21010            .unwrap()
21011            .read(cx)
21012            .all_source_breakpoints(cx)
21013            .clone()
21014    });
21015
21016    assert_eq!(1, breakpoints.len());
21017    assert_breakpoint(
21018        &breakpoints,
21019        &abs_path,
21020        vec![
21021            (0, Breakpoint::new_standard()),
21022            (2, disable_breakpoint),
21023            (3, Breakpoint::new_standard()),
21024        ],
21025    );
21026}
21027
21028#[gpui::test]
21029async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
21030    init_test(cx, |_| {});
21031    let capabilities = lsp::ServerCapabilities {
21032        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
21033            prepare_provider: Some(true),
21034            work_done_progress_options: Default::default(),
21035        })),
21036        ..Default::default()
21037    };
21038    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
21039
21040    cx.set_state(indoc! {"
21041        struct Fˇoo {}
21042    "});
21043
21044    cx.update_editor(|editor, _, cx| {
21045        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
21046        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
21047        editor.highlight_background::<DocumentHighlightRead>(
21048            &[highlight_range],
21049            |theme| theme.colors().editor_document_highlight_read_background,
21050            cx,
21051        );
21052    });
21053
21054    let mut prepare_rename_handler = cx
21055        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
21056            move |_, _, _| async move {
21057                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
21058                    start: lsp::Position {
21059                        line: 0,
21060                        character: 7,
21061                    },
21062                    end: lsp::Position {
21063                        line: 0,
21064                        character: 10,
21065                    },
21066                })))
21067            },
21068        );
21069    let prepare_rename_task = cx
21070        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
21071        .expect("Prepare rename was not started");
21072    prepare_rename_handler.next().await.unwrap();
21073    prepare_rename_task.await.expect("Prepare rename failed");
21074
21075    let mut rename_handler =
21076        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
21077            let edit = lsp::TextEdit {
21078                range: lsp::Range {
21079                    start: lsp::Position {
21080                        line: 0,
21081                        character: 7,
21082                    },
21083                    end: lsp::Position {
21084                        line: 0,
21085                        character: 10,
21086                    },
21087                },
21088                new_text: "FooRenamed".to_string(),
21089            };
21090            Ok(Some(lsp::WorkspaceEdit::new(
21091                // Specify the same edit twice
21092                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
21093            )))
21094        });
21095    let rename_task = cx
21096        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
21097        .expect("Confirm rename was not started");
21098    rename_handler.next().await.unwrap();
21099    rename_task.await.expect("Confirm rename failed");
21100    cx.run_until_parked();
21101
21102    // Despite two edits, only one is actually applied as those are identical
21103    cx.assert_editor_state(indoc! {"
21104        struct FooRenamedˇ {}
21105    "});
21106}
21107
21108#[gpui::test]
21109async fn test_rename_without_prepare(cx: &mut TestAppContext) {
21110    init_test(cx, |_| {});
21111    // These capabilities indicate that the server does not support prepare rename.
21112    let capabilities = lsp::ServerCapabilities {
21113        rename_provider: Some(lsp::OneOf::Left(true)),
21114        ..Default::default()
21115    };
21116    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
21117
21118    cx.set_state(indoc! {"
21119        struct Fˇoo {}
21120    "});
21121
21122    cx.update_editor(|editor, _window, cx| {
21123        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
21124        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
21125        editor.highlight_background::<DocumentHighlightRead>(
21126            &[highlight_range],
21127            |theme| theme.colors().editor_document_highlight_read_background,
21128            cx,
21129        );
21130    });
21131
21132    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
21133        .expect("Prepare rename was not started")
21134        .await
21135        .expect("Prepare rename failed");
21136
21137    let mut rename_handler =
21138        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
21139            let edit = lsp::TextEdit {
21140                range: lsp::Range {
21141                    start: lsp::Position {
21142                        line: 0,
21143                        character: 7,
21144                    },
21145                    end: lsp::Position {
21146                        line: 0,
21147                        character: 10,
21148                    },
21149                },
21150                new_text: "FooRenamed".to_string(),
21151            };
21152            Ok(Some(lsp::WorkspaceEdit::new(
21153                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
21154            )))
21155        });
21156    let rename_task = cx
21157        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
21158        .expect("Confirm rename was not started");
21159    rename_handler.next().await.unwrap();
21160    rename_task.await.expect("Confirm rename failed");
21161    cx.run_until_parked();
21162
21163    // Correct range is renamed, as `surrounding_word` is used to find it.
21164    cx.assert_editor_state(indoc! {"
21165        struct FooRenamedˇ {}
21166    "});
21167}
21168
21169#[gpui::test]
21170async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
21171    init_test(cx, |_| {});
21172    let mut cx = EditorTestContext::new(cx).await;
21173
21174    let language = Arc::new(
21175        Language::new(
21176            LanguageConfig::default(),
21177            Some(tree_sitter_html::LANGUAGE.into()),
21178        )
21179        .with_brackets_query(
21180            r#"
21181            ("<" @open "/>" @close)
21182            ("</" @open ">" @close)
21183            ("<" @open ">" @close)
21184            ("\"" @open "\"" @close)
21185            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
21186        "#,
21187        )
21188        .unwrap(),
21189    );
21190    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
21191
21192    cx.set_state(indoc! {"
21193        <span>ˇ</span>
21194    "});
21195    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21196    cx.assert_editor_state(indoc! {"
21197        <span>
21198        ˇ
21199        </span>
21200    "});
21201
21202    cx.set_state(indoc! {"
21203        <span><span></span>ˇ</span>
21204    "});
21205    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21206    cx.assert_editor_state(indoc! {"
21207        <span><span></span>
21208        ˇ</span>
21209    "});
21210
21211    cx.set_state(indoc! {"
21212        <span>ˇ
21213        </span>
21214    "});
21215    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21216    cx.assert_editor_state(indoc! {"
21217        <span>
21218        ˇ
21219        </span>
21220    "});
21221}
21222
21223#[gpui::test(iterations = 10)]
21224async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
21225    init_test(cx, |_| {});
21226
21227    let fs = FakeFs::new(cx.executor());
21228    fs.insert_tree(
21229        path!("/dir"),
21230        json!({
21231            "a.ts": "a",
21232        }),
21233    )
21234    .await;
21235
21236    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
21237    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21238    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21239
21240    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21241    language_registry.add(Arc::new(Language::new(
21242        LanguageConfig {
21243            name: "TypeScript".into(),
21244            matcher: LanguageMatcher {
21245                path_suffixes: vec!["ts".to_string()],
21246                ..Default::default()
21247            },
21248            ..Default::default()
21249        },
21250        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
21251    )));
21252    let mut fake_language_servers = language_registry.register_fake_lsp(
21253        "TypeScript",
21254        FakeLspAdapter {
21255            capabilities: lsp::ServerCapabilities {
21256                code_lens_provider: Some(lsp::CodeLensOptions {
21257                    resolve_provider: Some(true),
21258                }),
21259                execute_command_provider: Some(lsp::ExecuteCommandOptions {
21260                    commands: vec!["_the/command".to_string()],
21261                    ..lsp::ExecuteCommandOptions::default()
21262                }),
21263                ..lsp::ServerCapabilities::default()
21264            },
21265            ..FakeLspAdapter::default()
21266        },
21267    );
21268
21269    let (buffer, _handle) = project
21270        .update(cx, |p, cx| {
21271            p.open_local_buffer_with_lsp(path!("/dir/a.ts"), cx)
21272        })
21273        .await
21274        .unwrap();
21275    cx.executor().run_until_parked();
21276
21277    let fake_server = fake_language_servers.next().await.unwrap();
21278
21279    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
21280    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
21281    drop(buffer_snapshot);
21282    let actions = cx
21283        .update_window(*workspace, |_, window, cx| {
21284            project.code_actions(&buffer, anchor..anchor, window, cx)
21285        })
21286        .unwrap();
21287
21288    fake_server
21289        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
21290            Ok(Some(vec![
21291                lsp::CodeLens {
21292                    range: lsp::Range::default(),
21293                    command: Some(lsp::Command {
21294                        title: "Code lens command".to_owned(),
21295                        command: "_the/command".to_owned(),
21296                        arguments: None,
21297                    }),
21298                    data: None,
21299                },
21300                lsp::CodeLens {
21301                    range: lsp::Range::default(),
21302                    command: Some(lsp::Command {
21303                        title: "Command not in capabilities".to_owned(),
21304                        command: "not in capabilities".to_owned(),
21305                        arguments: None,
21306                    }),
21307                    data: None,
21308                },
21309                lsp::CodeLens {
21310                    range: lsp::Range {
21311                        start: lsp::Position {
21312                            line: 1,
21313                            character: 1,
21314                        },
21315                        end: lsp::Position {
21316                            line: 1,
21317                            character: 1,
21318                        },
21319                    },
21320                    command: Some(lsp::Command {
21321                        title: "Command not in range".to_owned(),
21322                        command: "_the/command".to_owned(),
21323                        arguments: None,
21324                    }),
21325                    data: None,
21326                },
21327            ]))
21328        })
21329        .next()
21330        .await;
21331
21332    let actions = actions.await.unwrap();
21333    assert_eq!(
21334        actions.len(),
21335        1,
21336        "Should have only one valid action for the 0..0 range"
21337    );
21338    let action = actions[0].clone();
21339    let apply = project.update(cx, |project, cx| {
21340        project.apply_code_action(buffer.clone(), action, true, cx)
21341    });
21342
21343    // Resolving the code action does not populate its edits. In absence of
21344    // edits, we must execute the given command.
21345    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
21346        |mut lens, _| async move {
21347            let lens_command = lens.command.as_mut().expect("should have a command");
21348            assert_eq!(lens_command.title, "Code lens command");
21349            lens_command.arguments = Some(vec![json!("the-argument")]);
21350            Ok(lens)
21351        },
21352    );
21353
21354    // While executing the command, the language server sends the editor
21355    // a `workspaceEdit` request.
21356    fake_server
21357        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
21358            let fake = fake_server.clone();
21359            move |params, _| {
21360                assert_eq!(params.command, "_the/command");
21361                let fake = fake.clone();
21362                async move {
21363                    fake.server
21364                        .request::<lsp::request::ApplyWorkspaceEdit>(
21365                            lsp::ApplyWorkspaceEditParams {
21366                                label: None,
21367                                edit: lsp::WorkspaceEdit {
21368                                    changes: Some(
21369                                        [(
21370                                            lsp::Url::from_file_path(path!("/dir/a.ts")).unwrap(),
21371                                            vec![lsp::TextEdit {
21372                                                range: lsp::Range::new(
21373                                                    lsp::Position::new(0, 0),
21374                                                    lsp::Position::new(0, 0),
21375                                                ),
21376                                                new_text: "X".into(),
21377                                            }],
21378                                        )]
21379                                        .into_iter()
21380                                        .collect(),
21381                                    ),
21382                                    ..Default::default()
21383                                },
21384                            },
21385                        )
21386                        .await
21387                        .into_response()
21388                        .unwrap();
21389                    Ok(Some(json!(null)))
21390                }
21391            }
21392        })
21393        .next()
21394        .await;
21395
21396    // Applying the code lens command returns a project transaction containing the edits
21397    // sent by the language server in its `workspaceEdit` request.
21398    let transaction = apply.await.unwrap();
21399    assert!(transaction.0.contains_key(&buffer));
21400    buffer.update(cx, |buffer, cx| {
21401        assert_eq!(buffer.text(), "Xa");
21402        buffer.undo(cx);
21403        assert_eq!(buffer.text(), "a");
21404    });
21405}
21406
21407#[gpui::test]
21408async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
21409    init_test(cx, |_| {});
21410
21411    let fs = FakeFs::new(cx.executor());
21412    let main_text = r#"fn main() {
21413println!("1");
21414println!("2");
21415println!("3");
21416println!("4");
21417println!("5");
21418}"#;
21419    let lib_text = "mod foo {}";
21420    fs.insert_tree(
21421        path!("/a"),
21422        json!({
21423            "lib.rs": lib_text,
21424            "main.rs": main_text,
21425        }),
21426    )
21427    .await;
21428
21429    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21430    let (workspace, cx) =
21431        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21432    let worktree_id = workspace.update(cx, |workspace, cx| {
21433        workspace.project().update(cx, |project, cx| {
21434            project.worktrees(cx).next().unwrap().read(cx).id()
21435        })
21436    });
21437
21438    let expected_ranges = vec![
21439        Point::new(0, 0)..Point::new(0, 0),
21440        Point::new(1, 0)..Point::new(1, 1),
21441        Point::new(2, 0)..Point::new(2, 2),
21442        Point::new(3, 0)..Point::new(3, 3),
21443    ];
21444
21445    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21446    let editor_1 = workspace
21447        .update_in(cx, |workspace, window, cx| {
21448            workspace.open_path(
21449                (worktree_id, "main.rs"),
21450                Some(pane_1.downgrade()),
21451                true,
21452                window,
21453                cx,
21454            )
21455        })
21456        .unwrap()
21457        .await
21458        .downcast::<Editor>()
21459        .unwrap();
21460    pane_1.update(cx, |pane, cx| {
21461        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21462        open_editor.update(cx, |editor, cx| {
21463            assert_eq!(
21464                editor.display_text(cx),
21465                main_text,
21466                "Original main.rs text on initial open",
21467            );
21468            assert_eq!(
21469                editor
21470                    .selections
21471                    .all::<Point>(cx)
21472                    .into_iter()
21473                    .map(|s| s.range())
21474                    .collect::<Vec<_>>(),
21475                vec![Point::zero()..Point::zero()],
21476                "Default selections on initial open",
21477            );
21478        })
21479    });
21480    editor_1.update_in(cx, |editor, window, cx| {
21481        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21482            s.select_ranges(expected_ranges.clone());
21483        });
21484    });
21485
21486    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
21487        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
21488    });
21489    let editor_2 = workspace
21490        .update_in(cx, |workspace, window, cx| {
21491            workspace.open_path(
21492                (worktree_id, "main.rs"),
21493                Some(pane_2.downgrade()),
21494                true,
21495                window,
21496                cx,
21497            )
21498        })
21499        .unwrap()
21500        .await
21501        .downcast::<Editor>()
21502        .unwrap();
21503    pane_2.update(cx, |pane, cx| {
21504        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21505        open_editor.update(cx, |editor, cx| {
21506            assert_eq!(
21507                editor.display_text(cx),
21508                main_text,
21509                "Original main.rs text on initial open in another panel",
21510            );
21511            assert_eq!(
21512                editor
21513                    .selections
21514                    .all::<Point>(cx)
21515                    .into_iter()
21516                    .map(|s| s.range())
21517                    .collect::<Vec<_>>(),
21518                vec![Point::zero()..Point::zero()],
21519                "Default selections on initial open in another panel",
21520            );
21521        })
21522    });
21523
21524    editor_2.update_in(cx, |editor, window, cx| {
21525        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
21526    });
21527
21528    let _other_editor_1 = workspace
21529        .update_in(cx, |workspace, window, cx| {
21530            workspace.open_path(
21531                (worktree_id, "lib.rs"),
21532                Some(pane_1.downgrade()),
21533                true,
21534                window,
21535                cx,
21536            )
21537        })
21538        .unwrap()
21539        .await
21540        .downcast::<Editor>()
21541        .unwrap();
21542    pane_1
21543        .update_in(cx, |pane, window, cx| {
21544            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
21545        })
21546        .await
21547        .unwrap();
21548    drop(editor_1);
21549    pane_1.update(cx, |pane, cx| {
21550        pane.active_item()
21551            .unwrap()
21552            .downcast::<Editor>()
21553            .unwrap()
21554            .update(cx, |editor, cx| {
21555                assert_eq!(
21556                    editor.display_text(cx),
21557                    lib_text,
21558                    "Other file should be open and active",
21559                );
21560            });
21561        assert_eq!(pane.items().count(), 1, "No other editors should be open");
21562    });
21563
21564    let _other_editor_2 = workspace
21565        .update_in(cx, |workspace, window, cx| {
21566            workspace.open_path(
21567                (worktree_id, "lib.rs"),
21568                Some(pane_2.downgrade()),
21569                true,
21570                window,
21571                cx,
21572            )
21573        })
21574        .unwrap()
21575        .await
21576        .downcast::<Editor>()
21577        .unwrap();
21578    pane_2
21579        .update_in(cx, |pane, window, cx| {
21580            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
21581        })
21582        .await
21583        .unwrap();
21584    drop(editor_2);
21585    pane_2.update(cx, |pane, cx| {
21586        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21587        open_editor.update(cx, |editor, cx| {
21588            assert_eq!(
21589                editor.display_text(cx),
21590                lib_text,
21591                "Other file should be open and active in another panel too",
21592            );
21593        });
21594        assert_eq!(
21595            pane.items().count(),
21596            1,
21597            "No other editors should be open in another pane",
21598        );
21599    });
21600
21601    let _editor_1_reopened = workspace
21602        .update_in(cx, |workspace, window, cx| {
21603            workspace.open_path(
21604                (worktree_id, "main.rs"),
21605                Some(pane_1.downgrade()),
21606                true,
21607                window,
21608                cx,
21609            )
21610        })
21611        .unwrap()
21612        .await
21613        .downcast::<Editor>()
21614        .unwrap();
21615    let _editor_2_reopened = workspace
21616        .update_in(cx, |workspace, window, cx| {
21617            workspace.open_path(
21618                (worktree_id, "main.rs"),
21619                Some(pane_2.downgrade()),
21620                true,
21621                window,
21622                cx,
21623            )
21624        })
21625        .unwrap()
21626        .await
21627        .downcast::<Editor>()
21628        .unwrap();
21629    pane_1.update(cx, |pane, cx| {
21630        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21631        open_editor.update(cx, |editor, cx| {
21632            assert_eq!(
21633                editor.display_text(cx),
21634                main_text,
21635                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
21636            );
21637            assert_eq!(
21638                editor
21639                    .selections
21640                    .all::<Point>(cx)
21641                    .into_iter()
21642                    .map(|s| s.range())
21643                    .collect::<Vec<_>>(),
21644                expected_ranges,
21645                "Previous editor in the 1st panel had selections and should get them restored on reopen",
21646            );
21647        })
21648    });
21649    pane_2.update(cx, |pane, cx| {
21650        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21651        open_editor.update(cx, |editor, cx| {
21652            assert_eq!(
21653                editor.display_text(cx),
21654                r#"fn main() {
21655⋯rintln!("1");
21656⋯intln!("2");
21657⋯ntln!("3");
21658println!("4");
21659println!("5");
21660}"#,
21661                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
21662            );
21663            assert_eq!(
21664                editor
21665                    .selections
21666                    .all::<Point>(cx)
21667                    .into_iter()
21668                    .map(|s| s.range())
21669                    .collect::<Vec<_>>(),
21670                vec![Point::zero()..Point::zero()],
21671                "Previous editor in the 2nd pane had no selections changed hence should restore none",
21672            );
21673        })
21674    });
21675}
21676
21677#[gpui::test]
21678async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
21679    init_test(cx, |_| {});
21680
21681    let fs = FakeFs::new(cx.executor());
21682    let main_text = r#"fn main() {
21683println!("1");
21684println!("2");
21685println!("3");
21686println!("4");
21687println!("5");
21688}"#;
21689    let lib_text = "mod foo {}";
21690    fs.insert_tree(
21691        path!("/a"),
21692        json!({
21693            "lib.rs": lib_text,
21694            "main.rs": main_text,
21695        }),
21696    )
21697    .await;
21698
21699    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21700    let (workspace, cx) =
21701        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21702    let worktree_id = workspace.update(cx, |workspace, cx| {
21703        workspace.project().update(cx, |project, cx| {
21704            project.worktrees(cx).next().unwrap().read(cx).id()
21705        })
21706    });
21707
21708    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21709    let editor = workspace
21710        .update_in(cx, |workspace, window, cx| {
21711            workspace.open_path(
21712                (worktree_id, "main.rs"),
21713                Some(pane.downgrade()),
21714                true,
21715                window,
21716                cx,
21717            )
21718        })
21719        .unwrap()
21720        .await
21721        .downcast::<Editor>()
21722        .unwrap();
21723    pane.update(cx, |pane, cx| {
21724        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21725        open_editor.update(cx, |editor, cx| {
21726            assert_eq!(
21727                editor.display_text(cx),
21728                main_text,
21729                "Original main.rs text on initial open",
21730            );
21731        })
21732    });
21733    editor.update_in(cx, |editor, window, cx| {
21734        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
21735    });
21736
21737    cx.update_global(|store: &mut SettingsStore, cx| {
21738        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
21739            s.restore_on_file_reopen = Some(false);
21740        });
21741    });
21742    editor.update_in(cx, |editor, window, cx| {
21743        editor.fold_ranges(
21744            vec![
21745                Point::new(1, 0)..Point::new(1, 1),
21746                Point::new(2, 0)..Point::new(2, 2),
21747                Point::new(3, 0)..Point::new(3, 3),
21748            ],
21749            false,
21750            window,
21751            cx,
21752        );
21753    });
21754    pane.update_in(cx, |pane, window, cx| {
21755        pane.close_all_items(&CloseAllItems::default(), window, cx)
21756    })
21757    .await
21758    .unwrap();
21759    pane.update(cx, |pane, _| {
21760        assert!(pane.active_item().is_none());
21761    });
21762    cx.update_global(|store: &mut SettingsStore, cx| {
21763        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
21764            s.restore_on_file_reopen = Some(true);
21765        });
21766    });
21767
21768    let _editor_reopened = workspace
21769        .update_in(cx, |workspace, window, cx| {
21770            workspace.open_path(
21771                (worktree_id, "main.rs"),
21772                Some(pane.downgrade()),
21773                true,
21774                window,
21775                cx,
21776            )
21777        })
21778        .unwrap()
21779        .await
21780        .downcast::<Editor>()
21781        .unwrap();
21782    pane.update(cx, |pane, cx| {
21783        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21784        open_editor.update(cx, |editor, cx| {
21785            assert_eq!(
21786                editor.display_text(cx),
21787                main_text,
21788                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
21789            );
21790        })
21791    });
21792}
21793
21794#[gpui::test]
21795async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
21796    struct EmptyModalView {
21797        focus_handle: gpui::FocusHandle,
21798    }
21799    impl EventEmitter<DismissEvent> for EmptyModalView {}
21800    impl Render for EmptyModalView {
21801        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
21802            div()
21803        }
21804    }
21805    impl Focusable for EmptyModalView {
21806        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
21807            self.focus_handle.clone()
21808        }
21809    }
21810    impl workspace::ModalView for EmptyModalView {}
21811    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
21812        EmptyModalView {
21813            focus_handle: cx.focus_handle(),
21814        }
21815    }
21816
21817    init_test(cx, |_| {});
21818
21819    let fs = FakeFs::new(cx.executor());
21820    let project = Project::test(fs, [], cx).await;
21821    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21822    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
21823    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21824    let editor = cx.new_window_entity(|window, cx| {
21825        Editor::new(
21826            EditorMode::full(),
21827            buffer,
21828            Some(project.clone()),
21829            window,
21830            cx,
21831        )
21832    });
21833    workspace
21834        .update(cx, |workspace, window, cx| {
21835            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
21836        })
21837        .unwrap();
21838    editor.update_in(cx, |editor, window, cx| {
21839        editor.open_context_menu(&OpenContextMenu, window, cx);
21840        assert!(editor.mouse_context_menu.is_some());
21841    });
21842    workspace
21843        .update(cx, |workspace, window, cx| {
21844            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
21845        })
21846        .unwrap();
21847    cx.read(|cx| {
21848        assert!(editor.read(cx).mouse_context_menu.is_none());
21849    });
21850}
21851
21852#[gpui::test]
21853async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
21854    init_test(cx, |_| {});
21855
21856    let fs = FakeFs::new(cx.executor());
21857    fs.insert_file(path!("/file.html"), Default::default())
21858        .await;
21859
21860    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
21861
21862    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21863    let html_language = Arc::new(Language::new(
21864        LanguageConfig {
21865            name: "HTML".into(),
21866            matcher: LanguageMatcher {
21867                path_suffixes: vec!["html".to_string()],
21868                ..LanguageMatcher::default()
21869            },
21870            brackets: BracketPairConfig {
21871                pairs: vec![BracketPair {
21872                    start: "<".into(),
21873                    end: ">".into(),
21874                    close: true,
21875                    ..Default::default()
21876                }],
21877                ..Default::default()
21878            },
21879            ..Default::default()
21880        },
21881        Some(tree_sitter_html::LANGUAGE.into()),
21882    ));
21883    language_registry.add(html_language);
21884    let mut fake_servers = language_registry.register_fake_lsp(
21885        "HTML",
21886        FakeLspAdapter {
21887            capabilities: lsp::ServerCapabilities {
21888                completion_provider: Some(lsp::CompletionOptions {
21889                    resolve_provider: Some(true),
21890                    ..Default::default()
21891                }),
21892                ..Default::default()
21893            },
21894            ..Default::default()
21895        },
21896    );
21897
21898    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21899    let cx = &mut VisualTestContext::from_window(*workspace, cx);
21900
21901    let worktree_id = workspace
21902        .update(cx, |workspace, _window, cx| {
21903            workspace.project().update(cx, |project, cx| {
21904                project.worktrees(cx).next().unwrap().read(cx).id()
21905            })
21906        })
21907        .unwrap();
21908    project
21909        .update(cx, |project, cx| {
21910            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
21911        })
21912        .await
21913        .unwrap();
21914    let editor = workspace
21915        .update(cx, |workspace, window, cx| {
21916            workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
21917        })
21918        .unwrap()
21919        .await
21920        .unwrap()
21921        .downcast::<Editor>()
21922        .unwrap();
21923
21924    let fake_server = fake_servers.next().await.unwrap();
21925    editor.update_in(cx, |editor, window, cx| {
21926        editor.set_text("<ad></ad>", window, cx);
21927        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21928            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
21929        });
21930        let Some((buffer, _)) = editor
21931            .buffer
21932            .read(cx)
21933            .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
21934        else {
21935            panic!("Failed to get buffer for selection position");
21936        };
21937        let buffer = buffer.read(cx);
21938        let buffer_id = buffer.remote_id();
21939        let opening_range =
21940            buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3));
21941        let closing_range =
21942            buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8));
21943        let mut linked_ranges = HashMap::default();
21944        linked_ranges.insert(
21945            buffer_id,
21946            vec![(opening_range.clone(), vec![closing_range.clone()])],
21947        );
21948        editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
21949    });
21950    let mut completion_handle =
21951        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
21952            Ok(Some(lsp::CompletionResponse::Array(vec![
21953                lsp::CompletionItem {
21954                    label: "head".to_string(),
21955                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21956                        lsp::InsertReplaceEdit {
21957                            new_text: "head".to_string(),
21958                            insert: lsp::Range::new(
21959                                lsp::Position::new(0, 1),
21960                                lsp::Position::new(0, 3),
21961                            ),
21962                            replace: lsp::Range::new(
21963                                lsp::Position::new(0, 1),
21964                                lsp::Position::new(0, 3),
21965                            ),
21966                        },
21967                    )),
21968                    ..Default::default()
21969                },
21970            ])))
21971        });
21972    editor.update_in(cx, |editor, window, cx| {
21973        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
21974    });
21975    cx.run_until_parked();
21976    completion_handle.next().await.unwrap();
21977    editor.update(cx, |editor, _| {
21978        assert!(
21979            editor.context_menu_visible(),
21980            "Completion menu should be visible"
21981        );
21982    });
21983    editor.update_in(cx, |editor, window, cx| {
21984        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
21985    });
21986    cx.executor().run_until_parked();
21987    editor.update(cx, |editor, cx| {
21988        assert_eq!(editor.text(cx), "<head></head>");
21989    });
21990}
21991
21992#[gpui::test]
21993async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
21994    init_test(cx, |_| {});
21995
21996    let fs = FakeFs::new(cx.executor());
21997    fs.insert_tree(
21998        path!("/root"),
21999        json!({
22000            "a": {
22001                "main.rs": "fn main() {}",
22002            },
22003            "foo": {
22004                "bar": {
22005                    "external_file.rs": "pub mod external {}",
22006                }
22007            }
22008        }),
22009    )
22010    .await;
22011
22012    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
22013    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22014    language_registry.add(rust_lang());
22015    let _fake_servers = language_registry.register_fake_lsp(
22016        "Rust",
22017        FakeLspAdapter {
22018            ..FakeLspAdapter::default()
22019        },
22020    );
22021    let (workspace, cx) =
22022        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22023    let worktree_id = workspace.update(cx, |workspace, cx| {
22024        workspace.project().update(cx, |project, cx| {
22025            project.worktrees(cx).next().unwrap().read(cx).id()
22026        })
22027    });
22028
22029    let assert_language_servers_count =
22030        |expected: usize, context: &str, cx: &mut VisualTestContext| {
22031            project.update(cx, |project, cx| {
22032                let current = project
22033                    .lsp_store()
22034                    .read(cx)
22035                    .as_local()
22036                    .unwrap()
22037                    .language_servers
22038                    .len();
22039                assert_eq!(expected, current, "{context}");
22040            });
22041        };
22042
22043    assert_language_servers_count(
22044        0,
22045        "No servers should be running before any file is open",
22046        cx,
22047    );
22048    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
22049    let main_editor = workspace
22050        .update_in(cx, |workspace, window, cx| {
22051            workspace.open_path(
22052                (worktree_id, "main.rs"),
22053                Some(pane.downgrade()),
22054                true,
22055                window,
22056                cx,
22057            )
22058        })
22059        .unwrap()
22060        .await
22061        .downcast::<Editor>()
22062        .unwrap();
22063    pane.update(cx, |pane, cx| {
22064        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22065        open_editor.update(cx, |editor, cx| {
22066            assert_eq!(
22067                editor.display_text(cx),
22068                "fn main() {}",
22069                "Original main.rs text on initial open",
22070            );
22071        });
22072        assert_eq!(open_editor, main_editor);
22073    });
22074    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
22075
22076    let external_editor = workspace
22077        .update_in(cx, |workspace, window, cx| {
22078            workspace.open_abs_path(
22079                PathBuf::from("/root/foo/bar/external_file.rs"),
22080                OpenOptions::default(),
22081                window,
22082                cx,
22083            )
22084        })
22085        .await
22086        .expect("opening external file")
22087        .downcast::<Editor>()
22088        .expect("downcasted external file's open element to editor");
22089    pane.update(cx, |pane, cx| {
22090        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22091        open_editor.update(cx, |editor, cx| {
22092            assert_eq!(
22093                editor.display_text(cx),
22094                "pub mod external {}",
22095                "External file is open now",
22096            );
22097        });
22098        assert_eq!(open_editor, external_editor);
22099    });
22100    assert_language_servers_count(
22101        1,
22102        "Second, external, *.rs file should join the existing server",
22103        cx,
22104    );
22105
22106    pane.update_in(cx, |pane, window, cx| {
22107        pane.close_active_item(&CloseActiveItem::default(), window, cx)
22108    })
22109    .await
22110    .unwrap();
22111    pane.update_in(cx, |pane, window, cx| {
22112        pane.navigate_backward(window, cx);
22113    });
22114    cx.run_until_parked();
22115    pane.update(cx, |pane, cx| {
22116        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22117        open_editor.update(cx, |editor, cx| {
22118            assert_eq!(
22119                editor.display_text(cx),
22120                "pub mod external {}",
22121                "External file is open now",
22122            );
22123        });
22124    });
22125    assert_language_servers_count(
22126        1,
22127        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
22128        cx,
22129    );
22130
22131    cx.update(|_, cx| {
22132        workspace::reload(&workspace::Reload::default(), cx);
22133    });
22134    assert_language_servers_count(
22135        1,
22136        "After reloading the worktree with local and external files opened, only one project should be started",
22137        cx,
22138    );
22139}
22140
22141#[gpui::test]
22142async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
22143    init_test(cx, |_| {});
22144
22145    let mut cx = EditorTestContext::new(cx).await;
22146    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22147    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22148
22149    // test cursor move to start of each line on tab
22150    // for `if`, `elif`, `else`, `while`, `with` and `for`
22151    cx.set_state(indoc! {"
22152        def main():
22153        ˇ    for item in items:
22154        ˇ        while item.active:
22155        ˇ            if item.value > 10:
22156        ˇ                continue
22157        ˇ            elif item.value < 0:
22158        ˇ                break
22159        ˇ            else:
22160        ˇ                with item.context() as ctx:
22161        ˇ                    yield count
22162        ˇ        else:
22163        ˇ            log('while else')
22164        ˇ    else:
22165        ˇ        log('for else')
22166    "});
22167    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22168    cx.assert_editor_state(indoc! {"
22169        def main():
22170            ˇfor item in items:
22171                ˇwhile item.active:
22172                    ˇif item.value > 10:
22173                        ˇcontinue
22174                    ˇelif item.value < 0:
22175                        ˇbreak
22176                    ˇelse:
22177                        ˇwith item.context() as ctx:
22178                            ˇyield count
22179                ˇelse:
22180                    ˇlog('while else')
22181            ˇelse:
22182                ˇlog('for else')
22183    "});
22184    // test relative indent is preserved when tab
22185    // for `if`, `elif`, `else`, `while`, `with` and `for`
22186    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22187    cx.assert_editor_state(indoc! {"
22188        def main():
22189                ˇfor item in items:
22190                    ˇwhile item.active:
22191                        ˇif item.value > 10:
22192                            ˇcontinue
22193                        ˇelif item.value < 0:
22194                            ˇbreak
22195                        ˇelse:
22196                            ˇwith item.context() as ctx:
22197                                ˇyield count
22198                    ˇelse:
22199                        ˇlog('while else')
22200                ˇelse:
22201                    ˇlog('for else')
22202    "});
22203
22204    // test cursor move to start of each line on tab
22205    // for `try`, `except`, `else`, `finally`, `match` and `def`
22206    cx.set_state(indoc! {"
22207        def main():
22208        ˇ    try:
22209        ˇ        fetch()
22210        ˇ    except ValueError:
22211        ˇ        handle_error()
22212        ˇ    else:
22213        ˇ        match value:
22214        ˇ            case _:
22215        ˇ    finally:
22216        ˇ        def status():
22217        ˇ            return 0
22218    "});
22219    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22220    cx.assert_editor_state(indoc! {"
22221        def main():
22222            ˇtry:
22223                ˇfetch()
22224            ˇexcept ValueError:
22225                ˇhandle_error()
22226            ˇelse:
22227                ˇmatch value:
22228                    ˇcase _:
22229            ˇfinally:
22230                ˇdef status():
22231                    ˇreturn 0
22232    "});
22233    // test relative indent is preserved when tab
22234    // for `try`, `except`, `else`, `finally`, `match` and `def`
22235    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22236    cx.assert_editor_state(indoc! {"
22237        def main():
22238                ˇtry:
22239                    ˇfetch()
22240                ˇexcept ValueError:
22241                    ˇhandle_error()
22242                ˇelse:
22243                    ˇmatch value:
22244                        ˇcase _:
22245                ˇfinally:
22246                    ˇdef status():
22247                        ˇreturn 0
22248    "});
22249}
22250
22251#[gpui::test]
22252async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
22253    init_test(cx, |_| {});
22254
22255    let mut cx = EditorTestContext::new(cx).await;
22256    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22257    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22258
22259    // test `else` auto outdents when typed inside `if` block
22260    cx.set_state(indoc! {"
22261        def main():
22262            if i == 2:
22263                return
22264                ˇ
22265    "});
22266    cx.update_editor(|editor, window, cx| {
22267        editor.handle_input("else:", window, cx);
22268    });
22269    cx.assert_editor_state(indoc! {"
22270        def main():
22271            if i == 2:
22272                return
22273            else:ˇ
22274    "});
22275
22276    // test `except` auto outdents when typed inside `try` block
22277    cx.set_state(indoc! {"
22278        def main():
22279            try:
22280                i = 2
22281                ˇ
22282    "});
22283    cx.update_editor(|editor, window, cx| {
22284        editor.handle_input("except:", window, cx);
22285    });
22286    cx.assert_editor_state(indoc! {"
22287        def main():
22288            try:
22289                i = 2
22290            except:ˇ
22291    "});
22292
22293    // test `else` auto outdents when typed inside `except` block
22294    cx.set_state(indoc! {"
22295        def main():
22296            try:
22297                i = 2
22298            except:
22299                j = 2
22300                ˇ
22301    "});
22302    cx.update_editor(|editor, window, cx| {
22303        editor.handle_input("else:", window, cx);
22304    });
22305    cx.assert_editor_state(indoc! {"
22306        def main():
22307            try:
22308                i = 2
22309            except:
22310                j = 2
22311            else:ˇ
22312    "});
22313
22314    // test `finally` auto outdents when typed inside `else` block
22315    cx.set_state(indoc! {"
22316        def main():
22317            try:
22318                i = 2
22319            except:
22320                j = 2
22321            else:
22322                k = 2
22323                ˇ
22324    "});
22325    cx.update_editor(|editor, window, cx| {
22326        editor.handle_input("finally:", window, cx);
22327    });
22328    cx.assert_editor_state(indoc! {"
22329        def main():
22330            try:
22331                i = 2
22332            except:
22333                j = 2
22334            else:
22335                k = 2
22336            finally:ˇ
22337    "});
22338
22339    // test `else` does not outdents when typed inside `except` block right after for block
22340    cx.set_state(indoc! {"
22341        def main():
22342            try:
22343                i = 2
22344            except:
22345                for i in range(n):
22346                    pass
22347                ˇ
22348    "});
22349    cx.update_editor(|editor, window, cx| {
22350        editor.handle_input("else:", window, cx);
22351    });
22352    cx.assert_editor_state(indoc! {"
22353        def main():
22354            try:
22355                i = 2
22356            except:
22357                for i in range(n):
22358                    pass
22359                else:ˇ
22360    "});
22361
22362    // test `finally` auto outdents when typed inside `else` block right after for block
22363    cx.set_state(indoc! {"
22364        def main():
22365            try:
22366                i = 2
22367            except:
22368                j = 2
22369            else:
22370                for i in range(n):
22371                    pass
22372                ˇ
22373    "});
22374    cx.update_editor(|editor, window, cx| {
22375        editor.handle_input("finally:", window, cx);
22376    });
22377    cx.assert_editor_state(indoc! {"
22378        def main():
22379            try:
22380                i = 2
22381            except:
22382                j = 2
22383            else:
22384                for i in range(n):
22385                    pass
22386            finally:ˇ
22387    "});
22388
22389    // test `except` outdents to inner "try" block
22390    cx.set_state(indoc! {"
22391        def main():
22392            try:
22393                i = 2
22394                if i == 2:
22395                    try:
22396                        i = 3
22397                        ˇ
22398    "});
22399    cx.update_editor(|editor, window, cx| {
22400        editor.handle_input("except:", window, cx);
22401    });
22402    cx.assert_editor_state(indoc! {"
22403        def main():
22404            try:
22405                i = 2
22406                if i == 2:
22407                    try:
22408                        i = 3
22409                    except:ˇ
22410    "});
22411
22412    // test `except` outdents to outer "try" block
22413    cx.set_state(indoc! {"
22414        def main():
22415            try:
22416                i = 2
22417                if i == 2:
22418                    try:
22419                        i = 3
22420                ˇ
22421    "});
22422    cx.update_editor(|editor, window, cx| {
22423        editor.handle_input("except:", window, cx);
22424    });
22425    cx.assert_editor_state(indoc! {"
22426        def main():
22427            try:
22428                i = 2
22429                if i == 2:
22430                    try:
22431                        i = 3
22432            except:ˇ
22433    "});
22434
22435    // test `else` stays at correct indent when typed after `for` block
22436    cx.set_state(indoc! {"
22437        def main():
22438            for i in range(10):
22439                if i == 3:
22440                    break
22441            ˇ
22442    "});
22443    cx.update_editor(|editor, window, cx| {
22444        editor.handle_input("else:", window, cx);
22445    });
22446    cx.assert_editor_state(indoc! {"
22447        def main():
22448            for i in range(10):
22449                if i == 3:
22450                    break
22451            else:ˇ
22452    "});
22453
22454    // test does not outdent on typing after line with square brackets
22455    cx.set_state(indoc! {"
22456        def f() -> list[str]:
22457            ˇ
22458    "});
22459    cx.update_editor(|editor, window, cx| {
22460        editor.handle_input("a", window, cx);
22461    });
22462    cx.assert_editor_state(indoc! {"
22463        def f() -> list[str]:
2246422465    "});
22466
22467    // test does not outdent on typing : after case keyword
22468    cx.set_state(indoc! {"
22469        match 1:
22470            caseˇ
22471    "});
22472    cx.update_editor(|editor, window, cx| {
22473        editor.handle_input(":", window, cx);
22474    });
22475    cx.assert_editor_state(indoc! {"
22476        match 1:
22477            case:ˇ
22478    "});
22479}
22480
22481#[gpui::test]
22482async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
22483    init_test(cx, |_| {});
22484    update_test_language_settings(cx, |settings| {
22485        settings.defaults.extend_comment_on_newline = Some(false);
22486    });
22487    let mut cx = EditorTestContext::new(cx).await;
22488    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22489    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22490
22491    // test correct indent after newline on comment
22492    cx.set_state(indoc! {"
22493        # COMMENT:ˇ
22494    "});
22495    cx.update_editor(|editor, window, cx| {
22496        editor.newline(&Newline, window, cx);
22497    });
22498    cx.assert_editor_state(indoc! {"
22499        # COMMENT:
22500        ˇ
22501    "});
22502
22503    // test correct indent after newline in brackets
22504    cx.set_state(indoc! {"
22505        {ˇ}
22506    "});
22507    cx.update_editor(|editor, window, cx| {
22508        editor.newline(&Newline, window, cx);
22509    });
22510    cx.run_until_parked();
22511    cx.assert_editor_state(indoc! {"
22512        {
22513            ˇ
22514        }
22515    "});
22516
22517    cx.set_state(indoc! {"
22518        (ˇ)
22519    "});
22520    cx.update_editor(|editor, window, cx| {
22521        editor.newline(&Newline, window, cx);
22522    });
22523    cx.run_until_parked();
22524    cx.assert_editor_state(indoc! {"
22525        (
22526            ˇ
22527        )
22528    "});
22529
22530    // do not indent after empty lists or dictionaries
22531    cx.set_state(indoc! {"
22532        a = []ˇ
22533    "});
22534    cx.update_editor(|editor, window, cx| {
22535        editor.newline(&Newline, window, cx);
22536    });
22537    cx.run_until_parked();
22538    cx.assert_editor_state(indoc! {"
22539        a = []
22540        ˇ
22541    "});
22542}
22543
22544fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
22545    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
22546    point..point
22547}
22548
22549#[track_caller]
22550fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
22551    let (text, ranges) = marked_text_ranges(marked_text, true);
22552    assert_eq!(editor.text(cx), text);
22553    assert_eq!(
22554        editor.selections.ranges(cx),
22555        ranges,
22556        "Assert selections are {}",
22557        marked_text
22558    );
22559}
22560
22561pub fn handle_signature_help_request(
22562    cx: &mut EditorLspTestContext,
22563    mocked_response: lsp::SignatureHelp,
22564) -> impl Future<Output = ()> + use<> {
22565    let mut request =
22566        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
22567            let mocked_response = mocked_response.clone();
22568            async move { Ok(Some(mocked_response)) }
22569        });
22570
22571    async move {
22572        request.next().await;
22573    }
22574}
22575
22576#[track_caller]
22577pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
22578    cx.update_editor(|editor, _, _| {
22579        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
22580            let entries = menu.entries.borrow();
22581            let entries = entries
22582                .iter()
22583                .map(|entry| entry.string.as_str())
22584                .collect::<Vec<_>>();
22585            assert_eq!(entries, expected);
22586        } else {
22587            panic!("Expected completions menu");
22588        }
22589    });
22590}
22591
22592/// Handle completion request passing a marked string specifying where the completion
22593/// should be triggered from using '|' character, what range should be replaced, and what completions
22594/// should be returned using '<' and '>' to delimit the range.
22595///
22596/// Also see `handle_completion_request_with_insert_and_replace`.
22597#[track_caller]
22598pub fn handle_completion_request(
22599    marked_string: &str,
22600    completions: Vec<&'static str>,
22601    is_incomplete: bool,
22602    counter: Arc<AtomicUsize>,
22603    cx: &mut EditorLspTestContext,
22604) -> impl Future<Output = ()> {
22605    let complete_from_marker: TextRangeMarker = '|'.into();
22606    let replace_range_marker: TextRangeMarker = ('<', '>').into();
22607    let (_, mut marked_ranges) = marked_text_ranges_by(
22608        marked_string,
22609        vec![complete_from_marker.clone(), replace_range_marker.clone()],
22610    );
22611
22612    let complete_from_position =
22613        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
22614    let replace_range =
22615        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
22616
22617    let mut request =
22618        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
22619            let completions = completions.clone();
22620            counter.fetch_add(1, atomic::Ordering::Release);
22621            async move {
22622                assert_eq!(params.text_document_position.text_document.uri, url.clone());
22623                assert_eq!(
22624                    params.text_document_position.position,
22625                    complete_from_position
22626                );
22627                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
22628                    is_incomplete: is_incomplete,
22629                    item_defaults: None,
22630                    items: completions
22631                        .iter()
22632                        .map(|completion_text| lsp::CompletionItem {
22633                            label: completion_text.to_string(),
22634                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
22635                                range: replace_range,
22636                                new_text: completion_text.to_string(),
22637                            })),
22638                            ..Default::default()
22639                        })
22640                        .collect(),
22641                })))
22642            }
22643        });
22644
22645    async move {
22646        request.next().await;
22647    }
22648}
22649
22650/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
22651/// given instead, which also contains an `insert` range.
22652///
22653/// This function uses markers to define ranges:
22654/// - `|` marks the cursor position
22655/// - `<>` marks the replace range
22656/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
22657pub fn handle_completion_request_with_insert_and_replace(
22658    cx: &mut EditorLspTestContext,
22659    marked_string: &str,
22660    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
22661    counter: Arc<AtomicUsize>,
22662) -> impl Future<Output = ()> {
22663    let complete_from_marker: TextRangeMarker = '|'.into();
22664    let replace_range_marker: TextRangeMarker = ('<', '>').into();
22665    let insert_range_marker: TextRangeMarker = ('{', '}').into();
22666
22667    let (_, mut marked_ranges) = marked_text_ranges_by(
22668        marked_string,
22669        vec![
22670            complete_from_marker.clone(),
22671            replace_range_marker.clone(),
22672            insert_range_marker.clone(),
22673        ],
22674    );
22675
22676    let complete_from_position =
22677        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
22678    let replace_range =
22679        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
22680
22681    let insert_range = match marked_ranges.remove(&insert_range_marker) {
22682        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
22683        _ => lsp::Range {
22684            start: replace_range.start,
22685            end: complete_from_position,
22686        },
22687    };
22688
22689    let mut request =
22690        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
22691            let completions = completions.clone();
22692            counter.fetch_add(1, atomic::Ordering::Release);
22693            async move {
22694                assert_eq!(params.text_document_position.text_document.uri, url.clone());
22695                assert_eq!(
22696                    params.text_document_position.position, complete_from_position,
22697                    "marker `|` position doesn't match",
22698                );
22699                Ok(Some(lsp::CompletionResponse::Array(
22700                    completions
22701                        .iter()
22702                        .map(|(label, new_text)| lsp::CompletionItem {
22703                            label: label.to_string(),
22704                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22705                                lsp::InsertReplaceEdit {
22706                                    insert: insert_range,
22707                                    replace: replace_range,
22708                                    new_text: new_text.to_string(),
22709                                },
22710                            )),
22711                            ..Default::default()
22712                        })
22713                        .collect(),
22714                )))
22715            }
22716        });
22717
22718    async move {
22719        request.next().await;
22720    }
22721}
22722
22723fn handle_resolve_completion_request(
22724    cx: &mut EditorLspTestContext,
22725    edits: Option<Vec<(&'static str, &'static str)>>,
22726) -> impl Future<Output = ()> {
22727    let edits = edits.map(|edits| {
22728        edits
22729            .iter()
22730            .map(|(marked_string, new_text)| {
22731                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
22732                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
22733                lsp::TextEdit::new(replace_range, new_text.to_string())
22734            })
22735            .collect::<Vec<_>>()
22736    });
22737
22738    let mut request =
22739        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
22740            let edits = edits.clone();
22741            async move {
22742                Ok(lsp::CompletionItem {
22743                    additional_text_edits: edits,
22744                    ..Default::default()
22745                })
22746            }
22747        });
22748
22749    async move {
22750        request.next().await;
22751    }
22752}
22753
22754pub(crate) fn update_test_language_settings(
22755    cx: &mut TestAppContext,
22756    f: impl Fn(&mut AllLanguageSettingsContent),
22757) {
22758    cx.update(|cx| {
22759        SettingsStore::update_global(cx, |store, cx| {
22760            store.update_user_settings::<AllLanguageSettings>(cx, f);
22761        });
22762    });
22763}
22764
22765pub(crate) fn update_test_project_settings(
22766    cx: &mut TestAppContext,
22767    f: impl Fn(&mut ProjectSettings),
22768) {
22769    cx.update(|cx| {
22770        SettingsStore::update_global(cx, |store, cx| {
22771            store.update_user_settings::<ProjectSettings>(cx, f);
22772        });
22773    });
22774}
22775
22776pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
22777    cx.update(|cx| {
22778        assets::Assets.load_test_fonts(cx);
22779        let store = SettingsStore::test(cx);
22780        cx.set_global(store);
22781        theme::init(theme::LoadThemes::JustBase, cx);
22782        release_channel::init(SemanticVersion::default(), cx);
22783        client::init_settings(cx);
22784        language::init(cx);
22785        Project::init_settings(cx);
22786        workspace::init_settings(cx);
22787        crate::init(cx);
22788    });
22789    zlog::init_test();
22790    update_test_language_settings(cx, f);
22791}
22792
22793#[track_caller]
22794fn assert_hunk_revert(
22795    not_reverted_text_with_selections: &str,
22796    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
22797    expected_reverted_text_with_selections: &str,
22798    base_text: &str,
22799    cx: &mut EditorLspTestContext,
22800) {
22801    cx.set_state(not_reverted_text_with_selections);
22802    cx.set_head_text(base_text);
22803    cx.executor().run_until_parked();
22804
22805    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
22806        let snapshot = editor.snapshot(window, cx);
22807        let reverted_hunk_statuses = snapshot
22808            .buffer_snapshot
22809            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
22810            .map(|hunk| hunk.status().kind)
22811            .collect::<Vec<_>>();
22812
22813        editor.git_restore(&Default::default(), window, cx);
22814        reverted_hunk_statuses
22815    });
22816    cx.executor().run_until_parked();
22817    cx.assert_editor_state(expected_reverted_text_with_selections);
22818    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
22819}
22820
22821#[gpui::test(iterations = 10)]
22822async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
22823    init_test(cx, |_| {});
22824
22825    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
22826    let counter = diagnostic_requests.clone();
22827
22828    let fs = FakeFs::new(cx.executor());
22829    fs.insert_tree(
22830        path!("/a"),
22831        json!({
22832            "first.rs": "fn main() { let a = 5; }",
22833            "second.rs": "// Test file",
22834        }),
22835    )
22836    .await;
22837
22838    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22839    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22840    let cx = &mut VisualTestContext::from_window(*workspace, cx);
22841
22842    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22843    language_registry.add(rust_lang());
22844    let mut fake_servers = language_registry.register_fake_lsp(
22845        "Rust",
22846        FakeLspAdapter {
22847            capabilities: lsp::ServerCapabilities {
22848                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
22849                    lsp::DiagnosticOptions {
22850                        identifier: None,
22851                        inter_file_dependencies: true,
22852                        workspace_diagnostics: true,
22853                        work_done_progress_options: Default::default(),
22854                    },
22855                )),
22856                ..Default::default()
22857            },
22858            ..Default::default()
22859        },
22860    );
22861
22862    let editor = workspace
22863        .update(cx, |workspace, window, cx| {
22864            workspace.open_abs_path(
22865                PathBuf::from(path!("/a/first.rs")),
22866                OpenOptions::default(),
22867                window,
22868                cx,
22869            )
22870        })
22871        .unwrap()
22872        .await
22873        .unwrap()
22874        .downcast::<Editor>()
22875        .unwrap();
22876    let fake_server = fake_servers.next().await.unwrap();
22877    let server_id = fake_server.server.server_id();
22878    let mut first_request = fake_server
22879        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
22880            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
22881            let result_id = Some(new_result_id.to_string());
22882            assert_eq!(
22883                params.text_document.uri,
22884                lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
22885            );
22886            async move {
22887                Ok(lsp::DocumentDiagnosticReportResult::Report(
22888                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
22889                        related_documents: None,
22890                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
22891                            items: Vec::new(),
22892                            result_id,
22893                        },
22894                    }),
22895                ))
22896            }
22897        });
22898
22899    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
22900        project.update(cx, |project, cx| {
22901            let buffer_id = editor
22902                .read(cx)
22903                .buffer()
22904                .read(cx)
22905                .as_singleton()
22906                .expect("created a singleton buffer")
22907                .read(cx)
22908                .remote_id();
22909            let buffer_result_id = project
22910                .lsp_store()
22911                .read(cx)
22912                .result_id(server_id, buffer_id, cx);
22913            assert_eq!(expected, buffer_result_id);
22914        });
22915    };
22916
22917    ensure_result_id(None, cx);
22918    cx.executor().advance_clock(Duration::from_millis(60));
22919    cx.executor().run_until_parked();
22920    assert_eq!(
22921        diagnostic_requests.load(atomic::Ordering::Acquire),
22922        1,
22923        "Opening file should trigger diagnostic request"
22924    );
22925    first_request
22926        .next()
22927        .await
22928        .expect("should have sent the first diagnostics pull request");
22929    ensure_result_id(Some("1".to_string()), cx);
22930
22931    // Editing should trigger diagnostics
22932    editor.update_in(cx, |editor, window, cx| {
22933        editor.handle_input("2", window, cx)
22934    });
22935    cx.executor().advance_clock(Duration::from_millis(60));
22936    cx.executor().run_until_parked();
22937    assert_eq!(
22938        diagnostic_requests.load(atomic::Ordering::Acquire),
22939        2,
22940        "Editing should trigger diagnostic request"
22941    );
22942    ensure_result_id(Some("2".to_string()), cx);
22943
22944    // Moving cursor should not trigger diagnostic request
22945    editor.update_in(cx, |editor, window, cx| {
22946        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22947            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
22948        });
22949    });
22950    cx.executor().advance_clock(Duration::from_millis(60));
22951    cx.executor().run_until_parked();
22952    assert_eq!(
22953        diagnostic_requests.load(atomic::Ordering::Acquire),
22954        2,
22955        "Cursor movement should not trigger diagnostic request"
22956    );
22957    ensure_result_id(Some("2".to_string()), cx);
22958    // Multiple rapid edits should be debounced
22959    for _ in 0..5 {
22960        editor.update_in(cx, |editor, window, cx| {
22961            editor.handle_input("x", window, cx)
22962        });
22963    }
22964    cx.executor().advance_clock(Duration::from_millis(60));
22965    cx.executor().run_until_parked();
22966
22967    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
22968    assert!(
22969        final_requests <= 4,
22970        "Multiple rapid edits should be debounced (got {final_requests} requests)",
22971    );
22972    ensure_result_id(Some(final_requests.to_string()), cx);
22973}
22974
22975#[gpui::test]
22976async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
22977    // Regression test for issue #11671
22978    // Previously, adding a cursor after moving multiple cursors would reset
22979    // the cursor count instead of adding to the existing cursors.
22980    init_test(cx, |_| {});
22981    let mut cx = EditorTestContext::new(cx).await;
22982
22983    // Create a simple buffer with cursor at start
22984    cx.set_state(indoc! {"
22985        ˇaaaa
22986        bbbb
22987        cccc
22988        dddd
22989        eeee
22990        ffff
22991        gggg
22992        hhhh"});
22993
22994    // Add 2 cursors below (so we have 3 total)
22995    cx.update_editor(|editor, window, cx| {
22996        editor.add_selection_below(&Default::default(), window, cx);
22997        editor.add_selection_below(&Default::default(), window, cx);
22998    });
22999
23000    // Verify we have 3 cursors
23001    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
23002    assert_eq!(
23003        initial_count, 3,
23004        "Should have 3 cursors after adding 2 below"
23005    );
23006
23007    // Move down one line
23008    cx.update_editor(|editor, window, cx| {
23009        editor.move_down(&MoveDown, window, cx);
23010    });
23011
23012    // Add another cursor below
23013    cx.update_editor(|editor, window, cx| {
23014        editor.add_selection_below(&Default::default(), window, cx);
23015    });
23016
23017    // Should now have 4 cursors (3 original + 1 new)
23018    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
23019    assert_eq!(
23020        final_count, 4,
23021        "Should have 4 cursors after moving and adding another"
23022    );
23023}
23024
23025#[gpui::test(iterations = 10)]
23026async fn test_document_colors(cx: &mut TestAppContext) {
23027    let expected_color = Rgba {
23028        r: 0.33,
23029        g: 0.33,
23030        b: 0.33,
23031        a: 0.33,
23032    };
23033
23034    init_test(cx, |_| {});
23035
23036    let fs = FakeFs::new(cx.executor());
23037    fs.insert_tree(
23038        path!("/a"),
23039        json!({
23040            "first.rs": "fn main() { let a = 5; }",
23041        }),
23042    )
23043    .await;
23044
23045    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23046    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23047    let cx = &mut VisualTestContext::from_window(*workspace, cx);
23048
23049    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23050    language_registry.add(rust_lang());
23051    let mut fake_servers = language_registry.register_fake_lsp(
23052        "Rust",
23053        FakeLspAdapter {
23054            capabilities: lsp::ServerCapabilities {
23055                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
23056                ..lsp::ServerCapabilities::default()
23057            },
23058            name: "rust-analyzer",
23059            ..FakeLspAdapter::default()
23060        },
23061    );
23062    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
23063        "Rust",
23064        FakeLspAdapter {
23065            capabilities: lsp::ServerCapabilities {
23066                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
23067                ..lsp::ServerCapabilities::default()
23068            },
23069            name: "not-rust-analyzer",
23070            ..FakeLspAdapter::default()
23071        },
23072    );
23073
23074    let editor = workspace
23075        .update(cx, |workspace, window, cx| {
23076            workspace.open_abs_path(
23077                PathBuf::from(path!("/a/first.rs")),
23078                OpenOptions::default(),
23079                window,
23080                cx,
23081            )
23082        })
23083        .unwrap()
23084        .await
23085        .unwrap()
23086        .downcast::<Editor>()
23087        .unwrap();
23088    let fake_language_server = fake_servers.next().await.unwrap();
23089    let fake_language_server_without_capabilities =
23090        fake_servers_without_capabilities.next().await.unwrap();
23091    let requests_made = Arc::new(AtomicUsize::new(0));
23092    let closure_requests_made = Arc::clone(&requests_made);
23093    let mut color_request_handle = fake_language_server
23094        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
23095            let requests_made = Arc::clone(&closure_requests_made);
23096            async move {
23097                assert_eq!(
23098                    params.text_document.uri,
23099                    lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
23100                );
23101                requests_made.fetch_add(1, atomic::Ordering::Release);
23102                Ok(vec![
23103                    lsp::ColorInformation {
23104                        range: lsp::Range {
23105                            start: lsp::Position {
23106                                line: 0,
23107                                character: 0,
23108                            },
23109                            end: lsp::Position {
23110                                line: 0,
23111                                character: 1,
23112                            },
23113                        },
23114                        color: lsp::Color {
23115                            red: 0.33,
23116                            green: 0.33,
23117                            blue: 0.33,
23118                            alpha: 0.33,
23119                        },
23120                    },
23121                    lsp::ColorInformation {
23122                        range: lsp::Range {
23123                            start: lsp::Position {
23124                                line: 0,
23125                                character: 0,
23126                            },
23127                            end: lsp::Position {
23128                                line: 0,
23129                                character: 1,
23130                            },
23131                        },
23132                        color: lsp::Color {
23133                            red: 0.33,
23134                            green: 0.33,
23135                            blue: 0.33,
23136                            alpha: 0.33,
23137                        },
23138                    },
23139                ])
23140            }
23141        });
23142
23143    let _handle = fake_language_server_without_capabilities
23144        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
23145            panic!("Should not be called");
23146        });
23147    cx.executor().advance_clock(Duration::from_millis(100));
23148    color_request_handle.next().await.unwrap();
23149    cx.run_until_parked();
23150    assert_eq!(
23151        1,
23152        requests_made.load(atomic::Ordering::Acquire),
23153        "Should query for colors once per editor open"
23154    );
23155    editor.update_in(cx, |editor, _, cx| {
23156        assert_eq!(
23157            vec![expected_color],
23158            extract_color_inlays(editor, cx),
23159            "Should have an initial inlay"
23160        );
23161    });
23162
23163    // opening another file in a split should not influence the LSP query counter
23164    workspace
23165        .update(cx, |workspace, window, cx| {
23166            assert_eq!(
23167                workspace.panes().len(),
23168                1,
23169                "Should have one pane with one editor"
23170            );
23171            workspace.move_item_to_pane_in_direction(
23172                &MoveItemToPaneInDirection {
23173                    direction: SplitDirection::Right,
23174                    focus: false,
23175                    clone: true,
23176                },
23177                window,
23178                cx,
23179            );
23180        })
23181        .unwrap();
23182    cx.run_until_parked();
23183    workspace
23184        .update(cx, |workspace, _, cx| {
23185            let panes = workspace.panes();
23186            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
23187            for pane in panes {
23188                let editor = pane
23189                    .read(cx)
23190                    .active_item()
23191                    .and_then(|item| item.downcast::<Editor>())
23192                    .expect("Should have opened an editor in each split");
23193                let editor_file = editor
23194                    .read(cx)
23195                    .buffer()
23196                    .read(cx)
23197                    .as_singleton()
23198                    .expect("test deals with singleton buffers")
23199                    .read(cx)
23200                    .file()
23201                    .expect("test buffese should have a file")
23202                    .path();
23203                assert_eq!(
23204                    editor_file.as_ref(),
23205                    Path::new("first.rs"),
23206                    "Both editors should be opened for the same file"
23207                )
23208            }
23209        })
23210        .unwrap();
23211
23212    cx.executor().advance_clock(Duration::from_millis(500));
23213    let save = editor.update_in(cx, |editor, window, cx| {
23214        editor.move_to_end(&MoveToEnd, window, cx);
23215        editor.handle_input("dirty", window, cx);
23216        editor.save(
23217            SaveOptions {
23218                format: true,
23219                autosave: true,
23220            },
23221            project.clone(),
23222            window,
23223            cx,
23224        )
23225    });
23226    save.await.unwrap();
23227
23228    color_request_handle.next().await.unwrap();
23229    cx.run_until_parked();
23230    assert_eq!(
23231        3,
23232        requests_made.load(atomic::Ordering::Acquire),
23233        "Should query for colors once per save and once per formatting after save"
23234    );
23235
23236    drop(editor);
23237    let close = workspace
23238        .update(cx, |workspace, window, cx| {
23239            workspace.active_pane().update(cx, |pane, cx| {
23240                pane.close_active_item(&CloseActiveItem::default(), window, cx)
23241            })
23242        })
23243        .unwrap();
23244    close.await.unwrap();
23245    let close = workspace
23246        .update(cx, |workspace, window, cx| {
23247            workspace.active_pane().update(cx, |pane, cx| {
23248                pane.close_active_item(&CloseActiveItem::default(), window, cx)
23249            })
23250        })
23251        .unwrap();
23252    close.await.unwrap();
23253    assert_eq!(
23254        3,
23255        requests_made.load(atomic::Ordering::Acquire),
23256        "After saving and closing all editors, no extra requests should be made"
23257    );
23258    workspace
23259        .update(cx, |workspace, _, cx| {
23260            assert!(
23261                workspace.active_item(cx).is_none(),
23262                "Should close all editors"
23263            )
23264        })
23265        .unwrap();
23266
23267    workspace
23268        .update(cx, |workspace, window, cx| {
23269            workspace.active_pane().update(cx, |pane, cx| {
23270                pane.navigate_backward(window, cx);
23271            })
23272        })
23273        .unwrap();
23274    cx.executor().advance_clock(Duration::from_millis(100));
23275    cx.run_until_parked();
23276    let editor = workspace
23277        .update(cx, |workspace, _, cx| {
23278            workspace
23279                .active_item(cx)
23280                .expect("Should have reopened the editor again after navigating back")
23281                .downcast::<Editor>()
23282                .expect("Should be an editor")
23283        })
23284        .unwrap();
23285    color_request_handle.next().await.unwrap();
23286    assert_eq!(
23287        3,
23288        requests_made.load(atomic::Ordering::Acquire),
23289        "Cache should be reused on buffer close and reopen"
23290    );
23291    editor.update(cx, |editor, cx| {
23292        assert_eq!(
23293            vec![expected_color],
23294            extract_color_inlays(editor, cx),
23295            "Should have an initial inlay"
23296        );
23297    });
23298}
23299
23300#[gpui::test]
23301async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
23302    init_test(cx, |_| {});
23303    let (editor, cx) = cx.add_window_view(Editor::single_line);
23304    editor.update_in(cx, |editor, window, cx| {
23305        editor.set_text("oops\n\nwow\n", window, cx)
23306    });
23307    cx.run_until_parked();
23308    editor.update(cx, |editor, cx| {
23309        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
23310    });
23311    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
23312    cx.run_until_parked();
23313    editor.update(cx, |editor, cx| {
23314        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
23315    });
23316}
23317
23318#[track_caller]
23319fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
23320    editor
23321        .all_inlays(cx)
23322        .into_iter()
23323        .filter_map(|inlay| inlay.get_color())
23324        .map(Rgba::from)
23325        .collect()
23326}