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: Some(language::DocumentationConfig {
 2879                    start: "/**".into(),
 2880                    end: "*/".into(),
 2881                    prefix: "* ".into(),
 2882                    tab_size: NonZeroU32::new(1).unwrap(),
 2883                }),
 2884
 2885                ..LanguageConfig::default()
 2886            },
 2887            Some(tree_sitter_rust::LANGUAGE.into()),
 2888        )
 2889        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 2890        .unwrap(),
 2891    );
 2892
 2893    {
 2894        let mut cx = EditorTestContext::new(cx).await;
 2895        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2896        cx.set_state(indoc! {"
 2897        /**ˇ
 2898    "});
 2899
 2900        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2901        cx.assert_editor_state(indoc! {"
 2902        /**
 2903         * ˇ
 2904    "});
 2905        // Ensure that if cursor is before the comment start,
 2906        // we do not actually insert a comment prefix.
 2907        cx.set_state(indoc! {"
 2908        ˇ/**
 2909    "});
 2910        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2911        cx.assert_editor_state(indoc! {"
 2912
 2913        ˇ/**
 2914    "});
 2915        // Ensure that if cursor is between it doesn't add comment prefix.
 2916        cx.set_state(indoc! {"
 2917        /*ˇ*
 2918    "});
 2919        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2920        cx.assert_editor_state(indoc! {"
 2921        /*
 2922        ˇ*
 2923    "});
 2924        // Ensure that if suffix exists on same line after cursor it adds new line.
 2925        cx.set_state(indoc! {"
 2926        /**ˇ*/
 2927    "});
 2928        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2929        cx.assert_editor_state(indoc! {"
 2930        /**
 2931         * ˇ
 2932         */
 2933    "});
 2934        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2935        cx.set_state(indoc! {"
 2936        /**ˇ */
 2937    "});
 2938        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2939        cx.assert_editor_state(indoc! {"
 2940        /**
 2941         * ˇ
 2942         */
 2943    "});
 2944        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2945        cx.set_state(indoc! {"
 2946        /** ˇ*/
 2947    "});
 2948        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2949        cx.assert_editor_state(
 2950            indoc! {"
 2951        /**s
 2952         * ˇ
 2953         */
 2954    "}
 2955            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2956            .as_str(),
 2957        );
 2958        // Ensure that delimiter space is preserved when newline on already
 2959        // spaced delimiter.
 2960        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2961        cx.assert_editor_state(
 2962            indoc! {"
 2963        /**s
 2964         *s
 2965         * ˇ
 2966         */
 2967    "}
 2968            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2969            .as_str(),
 2970        );
 2971        // Ensure that delimiter space is preserved when space is not
 2972        // on existing delimiter.
 2973        cx.set_state(indoc! {"
 2974        /**
 2975 2976         */
 2977    "});
 2978        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2979        cx.assert_editor_state(indoc! {"
 2980        /**
 2981         *
 2982         * ˇ
 2983         */
 2984    "});
 2985        // Ensure that if suffix exists on same line after cursor it
 2986        // doesn't add extra new line if prefix is not on same line.
 2987        cx.set_state(indoc! {"
 2988        /**
 2989        ˇ*/
 2990    "});
 2991        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2992        cx.assert_editor_state(indoc! {"
 2993        /**
 2994
 2995        ˇ*/
 2996    "});
 2997        // Ensure that it detects suffix after existing prefix.
 2998        cx.set_state(indoc! {"
 2999        /**ˇ/
 3000    "});
 3001        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3002        cx.assert_editor_state(indoc! {"
 3003        /**
 3004        ˇ/
 3005    "});
 3006        // Ensure that if suffix exists on same line before
 3007        // cursor it does not add comment prefix.
 3008        cx.set_state(indoc! {"
 3009        /** */ˇ
 3010    "});
 3011        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3012        cx.assert_editor_state(indoc! {"
 3013        /** */
 3014        ˇ
 3015    "});
 3016        // Ensure that if suffix exists on same line before
 3017        // cursor it does not add comment prefix.
 3018        cx.set_state(indoc! {"
 3019        /**
 3020         *
 3021         */ˇ
 3022    "});
 3023        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3024        cx.assert_editor_state(indoc! {"
 3025        /**
 3026         *
 3027         */
 3028         ˇ
 3029    "});
 3030
 3031        // Ensure that inline comment followed by code
 3032        // doesn't add comment prefix on newline
 3033        cx.set_state(indoc! {"
 3034        /** */ textˇ
 3035    "});
 3036        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3037        cx.assert_editor_state(indoc! {"
 3038        /** */ text
 3039        ˇ
 3040    "});
 3041
 3042        // Ensure that text after comment end tag
 3043        // doesn't add comment prefix on newline
 3044        cx.set_state(indoc! {"
 3045        /**
 3046         *
 3047         */ˇtext
 3048    "});
 3049        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3050        cx.assert_editor_state(indoc! {"
 3051        /**
 3052         *
 3053         */
 3054         ˇtext
 3055    "});
 3056
 3057        // Ensure if not comment block it doesn't
 3058        // add comment prefix on newline
 3059        cx.set_state(indoc! {"
 3060        * textˇ
 3061    "});
 3062        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3063        cx.assert_editor_state(indoc! {"
 3064        * text
 3065        ˇ
 3066    "});
 3067    }
 3068    // Ensure that comment continuations can be disabled.
 3069    update_test_language_settings(cx, |settings| {
 3070        settings.defaults.extend_comment_on_newline = Some(false);
 3071    });
 3072    let mut cx = EditorTestContext::new(cx).await;
 3073    cx.set_state(indoc! {"
 3074        /**ˇ
 3075    "});
 3076    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3077    cx.assert_editor_state(indoc! {"
 3078        /**
 3079        ˇ
 3080    "});
 3081}
 3082
 3083#[gpui::test]
 3084async fn test_newline_comments_with_block_comment(cx: &mut TestAppContext) {
 3085    init_test(cx, |settings| {
 3086        settings.defaults.tab_size = NonZeroU32::new(4)
 3087    });
 3088
 3089    let lua_language = Arc::new(Language::new(
 3090        LanguageConfig {
 3091            line_comments: vec!["--".into()],
 3092            block_comment: Some(("--[[".into(), "]]".into())),
 3093            ..LanguageConfig::default()
 3094        },
 3095        None,
 3096    ));
 3097
 3098    let mut cx = EditorTestContext::new(cx).await;
 3099    cx.update_buffer(|buffer, cx| buffer.set_language(Some(lua_language), cx));
 3100
 3101    // Line with line comment should extend
 3102    cx.set_state(indoc! {"
 3103        --ˇ
 3104    "});
 3105    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3106    cx.assert_editor_state(indoc! {"
 3107        --
 3108        --ˇ
 3109    "});
 3110
 3111    // Line with block comment that matches line comment should not extend
 3112    cx.set_state(indoc! {"
 3113        --[[ˇ
 3114    "});
 3115    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3116    cx.assert_editor_state(indoc! {"
 3117        --[[
 3118        ˇ
 3119    "});
 3120}
 3121
 3122#[gpui::test]
 3123fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3124    init_test(cx, |_| {});
 3125
 3126    let editor = cx.add_window(|window, cx| {
 3127        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3128        let mut editor = build_editor(buffer.clone(), window, cx);
 3129        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3130            s.select_ranges([3..4, 11..12, 19..20])
 3131        });
 3132        editor
 3133    });
 3134
 3135    _ = editor.update(cx, |editor, window, cx| {
 3136        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3137        editor.buffer.update(cx, |buffer, cx| {
 3138            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 3139            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3140        });
 3141        assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
 3142
 3143        editor.insert("Z", window, cx);
 3144        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3145
 3146        // The selections are moved after the inserted characters
 3147        assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
 3148    });
 3149}
 3150
 3151#[gpui::test]
 3152async fn test_tab(cx: &mut TestAppContext) {
 3153    init_test(cx, |settings| {
 3154        settings.defaults.tab_size = NonZeroU32::new(3)
 3155    });
 3156
 3157    let mut cx = EditorTestContext::new(cx).await;
 3158    cx.set_state(indoc! {"
 3159        ˇabˇc
 3160        ˇ🏀ˇ🏀ˇefg
 3161 3162    "});
 3163    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3164    cx.assert_editor_state(indoc! {"
 3165           ˇab ˇc
 3166           ˇ🏀  ˇ🏀  ˇefg
 3167        d  ˇ
 3168    "});
 3169
 3170    cx.set_state(indoc! {"
 3171        a
 3172        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3173    "});
 3174    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3175    cx.assert_editor_state(indoc! {"
 3176        a
 3177           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3178    "});
 3179}
 3180
 3181#[gpui::test]
 3182async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3183    init_test(cx, |_| {});
 3184
 3185    let mut cx = EditorTestContext::new(cx).await;
 3186    let language = Arc::new(
 3187        Language::new(
 3188            LanguageConfig::default(),
 3189            Some(tree_sitter_rust::LANGUAGE.into()),
 3190        )
 3191        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3192        .unwrap(),
 3193    );
 3194    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3195
 3196    // test when all cursors are not at suggested indent
 3197    // then simply move to their suggested indent location
 3198    cx.set_state(indoc! {"
 3199        const a: B = (
 3200            c(
 3201        ˇ
 3202        ˇ    )
 3203        );
 3204    "});
 3205    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3206    cx.assert_editor_state(indoc! {"
 3207        const a: B = (
 3208            c(
 3209                ˇ
 3210            ˇ)
 3211        );
 3212    "});
 3213
 3214    // test cursor already at suggested indent not moving when
 3215    // other cursors are yet to reach their suggested indents
 3216    cx.set_state(indoc! {"
 3217        ˇ
 3218        const a: B = (
 3219            c(
 3220                d(
 3221        ˇ
 3222                )
 3223        ˇ
 3224        ˇ    )
 3225        );
 3226    "});
 3227    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3228    cx.assert_editor_state(indoc! {"
 3229        ˇ
 3230        const a: B = (
 3231            c(
 3232                d(
 3233                    ˇ
 3234                )
 3235                ˇ
 3236            ˇ)
 3237        );
 3238    "});
 3239    // test when all cursors are at suggested indent then tab is inserted
 3240    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3241    cx.assert_editor_state(indoc! {"
 3242            ˇ
 3243        const a: B = (
 3244            c(
 3245                d(
 3246                        ˇ
 3247                )
 3248                    ˇ
 3249                ˇ)
 3250        );
 3251    "});
 3252
 3253    // test when current indent is less than suggested indent,
 3254    // we adjust line to match suggested indent and move cursor to it
 3255    //
 3256    // when no other cursor is at word boundary, all of them should move
 3257    cx.set_state(indoc! {"
 3258        const a: B = (
 3259            c(
 3260                d(
 3261        ˇ
 3262        ˇ   )
 3263        ˇ   )
 3264        );
 3265    "});
 3266    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3267    cx.assert_editor_state(indoc! {"
 3268        const a: B = (
 3269            c(
 3270                d(
 3271                    ˇ
 3272                ˇ)
 3273            ˇ)
 3274        );
 3275    "});
 3276
 3277    // test when current indent is less than suggested indent,
 3278    // we adjust line to match suggested indent and move cursor to it
 3279    //
 3280    // when some other cursor is at word boundary, it should not move
 3281    cx.set_state(indoc! {"
 3282        const a: B = (
 3283            c(
 3284                d(
 3285        ˇ
 3286        ˇ   )
 3287           ˇ)
 3288        );
 3289    "});
 3290    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3291    cx.assert_editor_state(indoc! {"
 3292        const a: B = (
 3293            c(
 3294                d(
 3295                    ˇ
 3296                ˇ)
 3297            ˇ)
 3298        );
 3299    "});
 3300
 3301    // test when current indent is more than suggested indent,
 3302    // we just move cursor to current indent instead of suggested indent
 3303    //
 3304    // when no other cursor is at word boundary, all of them should move
 3305    cx.set_state(indoc! {"
 3306        const a: B = (
 3307            c(
 3308                d(
 3309        ˇ
 3310        ˇ                )
 3311        ˇ   )
 3312        );
 3313    "});
 3314    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3315    cx.assert_editor_state(indoc! {"
 3316        const a: B = (
 3317            c(
 3318                d(
 3319                    ˇ
 3320                        ˇ)
 3321            ˇ)
 3322        );
 3323    "});
 3324    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3325    cx.assert_editor_state(indoc! {"
 3326        const a: B = (
 3327            c(
 3328                d(
 3329                        ˇ
 3330                            ˇ)
 3331                ˇ)
 3332        );
 3333    "});
 3334
 3335    // test when current indent is more than suggested indent,
 3336    // we just move cursor to current indent instead of suggested indent
 3337    //
 3338    // when some other cursor is at word boundary, it doesn't move
 3339    cx.set_state(indoc! {"
 3340        const a: B = (
 3341            c(
 3342                d(
 3343        ˇ
 3344        ˇ                )
 3345            ˇ)
 3346        );
 3347    "});
 3348    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3349    cx.assert_editor_state(indoc! {"
 3350        const a: B = (
 3351            c(
 3352                d(
 3353                    ˇ
 3354                        ˇ)
 3355            ˇ)
 3356        );
 3357    "});
 3358
 3359    // handle auto-indent when there are multiple cursors on the same line
 3360    cx.set_state(indoc! {"
 3361        const a: B = (
 3362            c(
 3363        ˇ    ˇ
 3364        ˇ    )
 3365        );
 3366    "});
 3367    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3368    cx.assert_editor_state(indoc! {"
 3369        const a: B = (
 3370            c(
 3371                ˇ
 3372            ˇ)
 3373        );
 3374    "});
 3375}
 3376
 3377#[gpui::test]
 3378async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 3379    init_test(cx, |settings| {
 3380        settings.defaults.tab_size = NonZeroU32::new(3)
 3381    });
 3382
 3383    let mut cx = EditorTestContext::new(cx).await;
 3384    cx.set_state(indoc! {"
 3385         ˇ
 3386        \t ˇ
 3387        \t  ˇ
 3388        \t   ˇ
 3389         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 3390    "});
 3391
 3392    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3393    cx.assert_editor_state(indoc! {"
 3394           ˇ
 3395        \t   ˇ
 3396        \t   ˇ
 3397        \t      ˇ
 3398         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 3399    "});
 3400}
 3401
 3402#[gpui::test]
 3403async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 3404    init_test(cx, |settings| {
 3405        settings.defaults.tab_size = NonZeroU32::new(4)
 3406    });
 3407
 3408    let language = Arc::new(
 3409        Language::new(
 3410            LanguageConfig::default(),
 3411            Some(tree_sitter_rust::LANGUAGE.into()),
 3412        )
 3413        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 3414        .unwrap(),
 3415    );
 3416
 3417    let mut cx = EditorTestContext::new(cx).await;
 3418    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3419    cx.set_state(indoc! {"
 3420        fn a() {
 3421            if b {
 3422        \t ˇc
 3423            }
 3424        }
 3425    "});
 3426
 3427    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3428    cx.assert_editor_state(indoc! {"
 3429        fn a() {
 3430            if b {
 3431                ˇc
 3432            }
 3433        }
 3434    "});
 3435}
 3436
 3437#[gpui::test]
 3438async fn test_indent_outdent(cx: &mut TestAppContext) {
 3439    init_test(cx, |settings| {
 3440        settings.defaults.tab_size = NonZeroU32::new(4);
 3441    });
 3442
 3443    let mut cx = EditorTestContext::new(cx).await;
 3444
 3445    cx.set_state(indoc! {"
 3446          «oneˇ» «twoˇ»
 3447        three
 3448         four
 3449    "});
 3450    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3451    cx.assert_editor_state(indoc! {"
 3452            «oneˇ» «twoˇ»
 3453        three
 3454         four
 3455    "});
 3456
 3457    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3458    cx.assert_editor_state(indoc! {"
 3459        «oneˇ» «twoˇ»
 3460        three
 3461         four
 3462    "});
 3463
 3464    // select across line ending
 3465    cx.set_state(indoc! {"
 3466        one two
 3467        t«hree
 3468        ˇ» four
 3469    "});
 3470    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3471    cx.assert_editor_state(indoc! {"
 3472        one two
 3473            t«hree
 3474        ˇ» four
 3475    "});
 3476
 3477    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3478    cx.assert_editor_state(indoc! {"
 3479        one two
 3480        t«hree
 3481        ˇ» four
 3482    "});
 3483
 3484    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3485    cx.set_state(indoc! {"
 3486        one two
 3487        ˇthree
 3488            four
 3489    "});
 3490    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3491    cx.assert_editor_state(indoc! {"
 3492        one two
 3493            ˇthree
 3494            four
 3495    "});
 3496
 3497    cx.set_state(indoc! {"
 3498        one two
 3499        ˇ    three
 3500            four
 3501    "});
 3502    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3503    cx.assert_editor_state(indoc! {"
 3504        one two
 3505        ˇthree
 3506            four
 3507    "});
 3508}
 3509
 3510#[gpui::test]
 3511async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3512    // This is a regression test for issue #33761
 3513    init_test(cx, |_| {});
 3514
 3515    let mut cx = EditorTestContext::new(cx).await;
 3516    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3517    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3518
 3519    cx.set_state(
 3520        r#"ˇ#     ingress:
 3521ˇ#         api:
 3522ˇ#             enabled: false
 3523ˇ#             pathType: Prefix
 3524ˇ#           console:
 3525ˇ#               enabled: false
 3526ˇ#               pathType: Prefix
 3527"#,
 3528    );
 3529
 3530    // Press tab to indent all lines
 3531    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3532
 3533    cx.assert_editor_state(
 3534        r#"    ˇ#     ingress:
 3535    ˇ#         api:
 3536    ˇ#             enabled: false
 3537    ˇ#             pathType: Prefix
 3538    ˇ#           console:
 3539    ˇ#               enabled: false
 3540    ˇ#               pathType: Prefix
 3541"#,
 3542    );
 3543}
 3544
 3545#[gpui::test]
 3546async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3547    // This is a test to make sure our fix for issue #33761 didn't break anything
 3548    init_test(cx, |_| {});
 3549
 3550    let mut cx = EditorTestContext::new(cx).await;
 3551    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3552    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3553
 3554    cx.set_state(
 3555        r#"ˇingress:
 3556ˇ  api:
 3557ˇ    enabled: false
 3558ˇ    pathType: Prefix
 3559"#,
 3560    );
 3561
 3562    // Press tab to indent all lines
 3563    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3564
 3565    cx.assert_editor_state(
 3566        r#"ˇingress:
 3567    ˇapi:
 3568        ˇenabled: false
 3569        ˇpathType: Prefix
 3570"#,
 3571    );
 3572}
 3573
 3574#[gpui::test]
 3575async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 3576    init_test(cx, |settings| {
 3577        settings.defaults.hard_tabs = Some(true);
 3578    });
 3579
 3580    let mut cx = EditorTestContext::new(cx).await;
 3581
 3582    // select two ranges on one line
 3583    cx.set_state(indoc! {"
 3584        «oneˇ» «twoˇ»
 3585        three
 3586        four
 3587    "});
 3588    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3589    cx.assert_editor_state(indoc! {"
 3590        \t«oneˇ» «twoˇ»
 3591        three
 3592        four
 3593    "});
 3594    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3595    cx.assert_editor_state(indoc! {"
 3596        \t\t«oneˇ» «twoˇ»
 3597        three
 3598        four
 3599    "});
 3600    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3601    cx.assert_editor_state(indoc! {"
 3602        \t«oneˇ» «twoˇ»
 3603        three
 3604        four
 3605    "});
 3606    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3607    cx.assert_editor_state(indoc! {"
 3608        «oneˇ» «twoˇ»
 3609        three
 3610        four
 3611    "});
 3612
 3613    // select across a line ending
 3614    cx.set_state(indoc! {"
 3615        one two
 3616        t«hree
 3617        ˇ»four
 3618    "});
 3619    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3620    cx.assert_editor_state(indoc! {"
 3621        one two
 3622        \tt«hree
 3623        ˇ»four
 3624    "});
 3625    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3626    cx.assert_editor_state(indoc! {"
 3627        one two
 3628        \t\tt«hree
 3629        ˇ»four
 3630    "});
 3631    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3632    cx.assert_editor_state(indoc! {"
 3633        one two
 3634        \tt«hree
 3635        ˇ»four
 3636    "});
 3637    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3638    cx.assert_editor_state(indoc! {"
 3639        one two
 3640        t«hree
 3641        ˇ»four
 3642    "});
 3643
 3644    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3645    cx.set_state(indoc! {"
 3646        one two
 3647        ˇthree
 3648        four
 3649    "});
 3650    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3651    cx.assert_editor_state(indoc! {"
 3652        one two
 3653        ˇthree
 3654        four
 3655    "});
 3656    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3657    cx.assert_editor_state(indoc! {"
 3658        one two
 3659        \tˇthree
 3660        four
 3661    "});
 3662    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3663    cx.assert_editor_state(indoc! {"
 3664        one two
 3665        ˇthree
 3666        four
 3667    "});
 3668}
 3669
 3670#[gpui::test]
 3671fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 3672    init_test(cx, |settings| {
 3673        settings.languages.0.extend([
 3674            (
 3675                "TOML".into(),
 3676                LanguageSettingsContent {
 3677                    tab_size: NonZeroU32::new(2),
 3678                    ..Default::default()
 3679                },
 3680            ),
 3681            (
 3682                "Rust".into(),
 3683                LanguageSettingsContent {
 3684                    tab_size: NonZeroU32::new(4),
 3685                    ..Default::default()
 3686                },
 3687            ),
 3688        ]);
 3689    });
 3690
 3691    let toml_language = Arc::new(Language::new(
 3692        LanguageConfig {
 3693            name: "TOML".into(),
 3694            ..Default::default()
 3695        },
 3696        None,
 3697    ));
 3698    let rust_language = Arc::new(Language::new(
 3699        LanguageConfig {
 3700            name: "Rust".into(),
 3701            ..Default::default()
 3702        },
 3703        None,
 3704    ));
 3705
 3706    let toml_buffer =
 3707        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 3708    let rust_buffer =
 3709        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 3710    let multibuffer = cx.new(|cx| {
 3711        let mut multibuffer = MultiBuffer::new(ReadWrite);
 3712        multibuffer.push_excerpts(
 3713            toml_buffer.clone(),
 3714            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 3715            cx,
 3716        );
 3717        multibuffer.push_excerpts(
 3718            rust_buffer.clone(),
 3719            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 3720            cx,
 3721        );
 3722        multibuffer
 3723    });
 3724
 3725    cx.add_window(|window, cx| {
 3726        let mut editor = build_editor(multibuffer, window, cx);
 3727
 3728        assert_eq!(
 3729            editor.text(cx),
 3730            indoc! {"
 3731                a = 1
 3732                b = 2
 3733
 3734                const c: usize = 3;
 3735            "}
 3736        );
 3737
 3738        select_ranges(
 3739            &mut editor,
 3740            indoc! {"
 3741                «aˇ» = 1
 3742                b = 2
 3743
 3744                «const c:ˇ» usize = 3;
 3745            "},
 3746            window,
 3747            cx,
 3748        );
 3749
 3750        editor.tab(&Tab, window, cx);
 3751        assert_text_with_selections(
 3752            &mut editor,
 3753            indoc! {"
 3754                  «aˇ» = 1
 3755                b = 2
 3756
 3757                    «const c:ˇ» usize = 3;
 3758            "},
 3759            cx,
 3760        );
 3761        editor.backtab(&Backtab, window, cx);
 3762        assert_text_with_selections(
 3763            &mut editor,
 3764            indoc! {"
 3765                «aˇ» = 1
 3766                b = 2
 3767
 3768                «const c:ˇ» usize = 3;
 3769            "},
 3770            cx,
 3771        );
 3772
 3773        editor
 3774    });
 3775}
 3776
 3777#[gpui::test]
 3778async fn test_backspace(cx: &mut TestAppContext) {
 3779    init_test(cx, |_| {});
 3780
 3781    let mut cx = EditorTestContext::new(cx).await;
 3782
 3783    // Basic backspace
 3784    cx.set_state(indoc! {"
 3785        onˇe two three
 3786        fou«rˇ» five six
 3787        seven «ˇeight nine
 3788        »ten
 3789    "});
 3790    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3791    cx.assert_editor_state(indoc! {"
 3792        oˇe two three
 3793        fouˇ five six
 3794        seven ˇten
 3795    "});
 3796
 3797    // Test backspace inside and around indents
 3798    cx.set_state(indoc! {"
 3799        zero
 3800            ˇone
 3801                ˇtwo
 3802            ˇ ˇ ˇ  three
 3803        ˇ  ˇ  four
 3804    "});
 3805    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3806    cx.assert_editor_state(indoc! {"
 3807        zero
 3808        ˇone
 3809            ˇtwo
 3810        ˇ  threeˇ  four
 3811    "});
 3812}
 3813
 3814#[gpui::test]
 3815async fn test_delete(cx: &mut TestAppContext) {
 3816    init_test(cx, |_| {});
 3817
 3818    let mut cx = EditorTestContext::new(cx).await;
 3819    cx.set_state(indoc! {"
 3820        onˇe two three
 3821        fou«rˇ» five six
 3822        seven «ˇeight nine
 3823        »ten
 3824    "});
 3825    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 3826    cx.assert_editor_state(indoc! {"
 3827        onˇ two three
 3828        fouˇ five six
 3829        seven ˇten
 3830    "});
 3831}
 3832
 3833#[gpui::test]
 3834fn test_delete_line(cx: &mut TestAppContext) {
 3835    init_test(cx, |_| {});
 3836
 3837    let editor = cx.add_window(|window, cx| {
 3838        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3839        build_editor(buffer, window, cx)
 3840    });
 3841    _ = editor.update(cx, |editor, window, cx| {
 3842        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3843            s.select_display_ranges([
 3844                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 3845                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 3846                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 3847            ])
 3848        });
 3849        editor.delete_line(&DeleteLine, window, cx);
 3850        assert_eq!(editor.display_text(cx), "ghi");
 3851        assert_eq!(
 3852            editor.selections.display_ranges(cx),
 3853            vec![
 3854                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 3855                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
 3856            ]
 3857        );
 3858    });
 3859
 3860    let editor = cx.add_window(|window, cx| {
 3861        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3862        build_editor(buffer, window, cx)
 3863    });
 3864    _ = editor.update(cx, |editor, window, cx| {
 3865        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3866            s.select_display_ranges([
 3867                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 3868            ])
 3869        });
 3870        editor.delete_line(&DeleteLine, window, cx);
 3871        assert_eq!(editor.display_text(cx), "ghi\n");
 3872        assert_eq!(
 3873            editor.selections.display_ranges(cx),
 3874            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 3875        );
 3876    });
 3877}
 3878
 3879#[gpui::test]
 3880fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 3881    init_test(cx, |_| {});
 3882
 3883    cx.add_window(|window, cx| {
 3884        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3885        let mut editor = build_editor(buffer.clone(), window, cx);
 3886        let buffer = buffer.read(cx).as_singleton().unwrap();
 3887
 3888        assert_eq!(
 3889            editor.selections.ranges::<Point>(cx),
 3890            &[Point::new(0, 0)..Point::new(0, 0)]
 3891        );
 3892
 3893        // When on single line, replace newline at end by space
 3894        editor.join_lines(&JoinLines, window, cx);
 3895        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3896        assert_eq!(
 3897            editor.selections.ranges::<Point>(cx),
 3898            &[Point::new(0, 3)..Point::new(0, 3)]
 3899        );
 3900
 3901        // When multiple lines are selected, remove newlines that are spanned by the selection
 3902        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3903            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 3904        });
 3905        editor.join_lines(&JoinLines, window, cx);
 3906        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 3907        assert_eq!(
 3908            editor.selections.ranges::<Point>(cx),
 3909            &[Point::new(0, 11)..Point::new(0, 11)]
 3910        );
 3911
 3912        // Undo should be transactional
 3913        editor.undo(&Undo, window, cx);
 3914        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3915        assert_eq!(
 3916            editor.selections.ranges::<Point>(cx),
 3917            &[Point::new(0, 5)..Point::new(2, 2)]
 3918        );
 3919
 3920        // When joining an empty line don't insert a space
 3921        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3922            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 3923        });
 3924        editor.join_lines(&JoinLines, window, cx);
 3925        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 3926        assert_eq!(
 3927            editor.selections.ranges::<Point>(cx),
 3928            [Point::new(2, 3)..Point::new(2, 3)]
 3929        );
 3930
 3931        // We can remove trailing newlines
 3932        editor.join_lines(&JoinLines, window, cx);
 3933        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3934        assert_eq!(
 3935            editor.selections.ranges::<Point>(cx),
 3936            [Point::new(2, 3)..Point::new(2, 3)]
 3937        );
 3938
 3939        // We don't blow up on the last line
 3940        editor.join_lines(&JoinLines, window, cx);
 3941        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3942        assert_eq!(
 3943            editor.selections.ranges::<Point>(cx),
 3944            [Point::new(2, 3)..Point::new(2, 3)]
 3945        );
 3946
 3947        // reset to test indentation
 3948        editor.buffer.update(cx, |buffer, cx| {
 3949            buffer.edit(
 3950                [
 3951                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 3952                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 3953                ],
 3954                None,
 3955                cx,
 3956            )
 3957        });
 3958
 3959        // We remove any leading spaces
 3960        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 3961        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3962            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 3963        });
 3964        editor.join_lines(&JoinLines, window, cx);
 3965        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 3966
 3967        // We don't insert a space for a line containing only spaces
 3968        editor.join_lines(&JoinLines, window, cx);
 3969        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 3970
 3971        // We ignore any leading tabs
 3972        editor.join_lines(&JoinLines, window, cx);
 3973        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 3974
 3975        editor
 3976    });
 3977}
 3978
 3979#[gpui::test]
 3980fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 3981    init_test(cx, |_| {});
 3982
 3983    cx.add_window(|window, cx| {
 3984        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3985        let mut editor = build_editor(buffer.clone(), window, cx);
 3986        let buffer = buffer.read(cx).as_singleton().unwrap();
 3987
 3988        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3989            s.select_ranges([
 3990                Point::new(0, 2)..Point::new(1, 1),
 3991                Point::new(1, 2)..Point::new(1, 2),
 3992                Point::new(3, 1)..Point::new(3, 2),
 3993            ])
 3994        });
 3995
 3996        editor.join_lines(&JoinLines, window, cx);
 3997        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 3998
 3999        assert_eq!(
 4000            editor.selections.ranges::<Point>(cx),
 4001            [
 4002                Point::new(0, 7)..Point::new(0, 7),
 4003                Point::new(1, 3)..Point::new(1, 3)
 4004            ]
 4005        );
 4006        editor
 4007    });
 4008}
 4009
 4010#[gpui::test]
 4011async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 4012    init_test(cx, |_| {});
 4013
 4014    let mut cx = EditorTestContext::new(cx).await;
 4015
 4016    let diff_base = r#"
 4017        Line 0
 4018        Line 1
 4019        Line 2
 4020        Line 3
 4021        "#
 4022    .unindent();
 4023
 4024    cx.set_state(
 4025        &r#"
 4026        ˇLine 0
 4027        Line 1
 4028        Line 2
 4029        Line 3
 4030        "#
 4031        .unindent(),
 4032    );
 4033
 4034    cx.set_head_text(&diff_base);
 4035    executor.run_until_parked();
 4036
 4037    // Join lines
 4038    cx.update_editor(|editor, window, cx| {
 4039        editor.join_lines(&JoinLines, window, cx);
 4040    });
 4041    executor.run_until_parked();
 4042
 4043    cx.assert_editor_state(
 4044        &r#"
 4045        Line 0ˇ Line 1
 4046        Line 2
 4047        Line 3
 4048        "#
 4049        .unindent(),
 4050    );
 4051    // Join again
 4052    cx.update_editor(|editor, window, cx| {
 4053        editor.join_lines(&JoinLines, window, cx);
 4054    });
 4055    executor.run_until_parked();
 4056
 4057    cx.assert_editor_state(
 4058        &r#"
 4059        Line 0 Line 1ˇ Line 2
 4060        Line 3
 4061        "#
 4062        .unindent(),
 4063    );
 4064}
 4065
 4066#[gpui::test]
 4067async fn test_custom_newlines_cause_no_false_positive_diffs(
 4068    executor: BackgroundExecutor,
 4069    cx: &mut TestAppContext,
 4070) {
 4071    init_test(cx, |_| {});
 4072    let mut cx = EditorTestContext::new(cx).await;
 4073    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 4074    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 4075    executor.run_until_parked();
 4076
 4077    cx.update_editor(|editor, window, cx| {
 4078        let snapshot = editor.snapshot(window, cx);
 4079        assert_eq!(
 4080            snapshot
 4081                .buffer_snapshot
 4082                .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
 4083                .collect::<Vec<_>>(),
 4084            Vec::new(),
 4085            "Should not have any diffs for files with custom newlines"
 4086        );
 4087    });
 4088}
 4089
 4090#[gpui::test]
 4091async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 4092    init_test(cx, |_| {});
 4093
 4094    let mut cx = EditorTestContext::new(cx).await;
 4095
 4096    // Test sort_lines_case_insensitive()
 4097    cx.set_state(indoc! {"
 4098        «z
 4099        y
 4100        x
 4101        Z
 4102        Y
 4103        Xˇ»
 4104    "});
 4105    cx.update_editor(|e, window, cx| {
 4106        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4107    });
 4108    cx.assert_editor_state(indoc! {"
 4109        «x
 4110        X
 4111        y
 4112        Y
 4113        z
 4114        Zˇ»
 4115    "});
 4116
 4117    // Test sort_lines_by_length()
 4118    //
 4119    // Demonstrates:
 4120    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 4121    // - sort is stable
 4122    cx.set_state(indoc! {"
 4123        «123
 4124        æ
 4125        12
 4126 4127        1
 4128        æˇ»
 4129    "});
 4130    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 4131    cx.assert_editor_state(indoc! {"
 4132        «æ
 4133 4134        1
 4135        æ
 4136        12
 4137        123ˇ»
 4138    "});
 4139
 4140    // Test reverse_lines()
 4141    cx.set_state(indoc! {"
 4142        «5
 4143        4
 4144        3
 4145        2
 4146        1ˇ»
 4147    "});
 4148    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4149    cx.assert_editor_state(indoc! {"
 4150        «1
 4151        2
 4152        3
 4153        4
 4154        5ˇ»
 4155    "});
 4156
 4157    // Skip testing shuffle_line()
 4158
 4159    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4160    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4161
 4162    // Don't manipulate when cursor is on single line, but expand the selection
 4163    cx.set_state(indoc! {"
 4164        ddˇdd
 4165        ccc
 4166        bb
 4167        a
 4168    "});
 4169    cx.update_editor(|e, window, cx| {
 4170        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4171    });
 4172    cx.assert_editor_state(indoc! {"
 4173        «ddddˇ»
 4174        ccc
 4175        bb
 4176        a
 4177    "});
 4178
 4179    // Basic manipulate case
 4180    // Start selection moves to column 0
 4181    // End of selection shrinks to fit shorter line
 4182    cx.set_state(indoc! {"
 4183        dd«d
 4184        ccc
 4185        bb
 4186        aaaaaˇ»
 4187    "});
 4188    cx.update_editor(|e, window, cx| {
 4189        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4190    });
 4191    cx.assert_editor_state(indoc! {"
 4192        «aaaaa
 4193        bb
 4194        ccc
 4195        dddˇ»
 4196    "});
 4197
 4198    // Manipulate case with newlines
 4199    cx.set_state(indoc! {"
 4200        dd«d
 4201        ccc
 4202
 4203        bb
 4204        aaaaa
 4205
 4206        ˇ»
 4207    "});
 4208    cx.update_editor(|e, window, cx| {
 4209        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4210    });
 4211    cx.assert_editor_state(indoc! {"
 4212        «
 4213
 4214        aaaaa
 4215        bb
 4216        ccc
 4217        dddˇ»
 4218
 4219    "});
 4220
 4221    // Adding new line
 4222    cx.set_state(indoc! {"
 4223        aa«a
 4224        bbˇ»b
 4225    "});
 4226    cx.update_editor(|e, window, cx| {
 4227        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4228    });
 4229    cx.assert_editor_state(indoc! {"
 4230        «aaa
 4231        bbb
 4232        added_lineˇ»
 4233    "});
 4234
 4235    // Removing line
 4236    cx.set_state(indoc! {"
 4237        aa«a
 4238        bbbˇ»
 4239    "});
 4240    cx.update_editor(|e, window, cx| {
 4241        e.manipulate_immutable_lines(window, cx, |lines| {
 4242            lines.pop();
 4243        })
 4244    });
 4245    cx.assert_editor_state(indoc! {"
 4246        «aaaˇ»
 4247    "});
 4248
 4249    // Removing all lines
 4250    cx.set_state(indoc! {"
 4251        aa«a
 4252        bbbˇ»
 4253    "});
 4254    cx.update_editor(|e, window, cx| {
 4255        e.manipulate_immutable_lines(window, cx, |lines| {
 4256            lines.drain(..);
 4257        })
 4258    });
 4259    cx.assert_editor_state(indoc! {"
 4260        ˇ
 4261    "});
 4262}
 4263
 4264#[gpui::test]
 4265async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4266    init_test(cx, |_| {});
 4267
 4268    let mut cx = EditorTestContext::new(cx).await;
 4269
 4270    // Consider continuous selection as single selection
 4271    cx.set_state(indoc! {"
 4272        Aaa«aa
 4273        cˇ»c«c
 4274        bb
 4275        aaaˇ»aa
 4276    "});
 4277    cx.update_editor(|e, window, cx| {
 4278        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4279    });
 4280    cx.assert_editor_state(indoc! {"
 4281        «Aaaaa
 4282        ccc
 4283        bb
 4284        aaaaaˇ»
 4285    "});
 4286
 4287    cx.set_state(indoc! {"
 4288        Aaa«aa
 4289        cˇ»c«c
 4290        bb
 4291        aaaˇ»aa
 4292    "});
 4293    cx.update_editor(|e, window, cx| {
 4294        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4295    });
 4296    cx.assert_editor_state(indoc! {"
 4297        «Aaaaa
 4298        ccc
 4299        bbˇ»
 4300    "});
 4301
 4302    // Consider non continuous selection as distinct dedup operations
 4303    cx.set_state(indoc! {"
 4304        «aaaaa
 4305        bb
 4306        aaaaa
 4307        aaaaaˇ»
 4308
 4309        aaa«aaˇ»
 4310    "});
 4311    cx.update_editor(|e, window, cx| {
 4312        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4313    });
 4314    cx.assert_editor_state(indoc! {"
 4315        «aaaaa
 4316        bbˇ»
 4317
 4318        «aaaaaˇ»
 4319    "});
 4320}
 4321
 4322#[gpui::test]
 4323async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4324    init_test(cx, |_| {});
 4325
 4326    let mut cx = EditorTestContext::new(cx).await;
 4327
 4328    cx.set_state(indoc! {"
 4329        «Aaa
 4330        aAa
 4331        Aaaˇ»
 4332    "});
 4333    cx.update_editor(|e, window, cx| {
 4334        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4335    });
 4336    cx.assert_editor_state(indoc! {"
 4337        «Aaa
 4338        aAaˇ»
 4339    "});
 4340
 4341    cx.set_state(indoc! {"
 4342        «Aaa
 4343        aAa
 4344        aaAˇ»
 4345    "});
 4346    cx.update_editor(|e, window, cx| {
 4347        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4348    });
 4349    cx.assert_editor_state(indoc! {"
 4350        «Aaaˇ»
 4351    "});
 4352}
 4353
 4354#[gpui::test]
 4355async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 4356    init_test(cx, |_| {});
 4357
 4358    let mut cx = EditorTestContext::new(cx).await;
 4359
 4360    // Manipulate with multiple selections on a single line
 4361    cx.set_state(indoc! {"
 4362        dd«dd
 4363        cˇ»c«c
 4364        bb
 4365        aaaˇ»aa
 4366    "});
 4367    cx.update_editor(|e, window, cx| {
 4368        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4369    });
 4370    cx.assert_editor_state(indoc! {"
 4371        «aaaaa
 4372        bb
 4373        ccc
 4374        ddddˇ»
 4375    "});
 4376
 4377    // Manipulate with multiple disjoin selections
 4378    cx.set_state(indoc! {"
 4379 4380        4
 4381        3
 4382        2
 4383        1ˇ»
 4384
 4385        dd«dd
 4386        ccc
 4387        bb
 4388        aaaˇ»aa
 4389    "});
 4390    cx.update_editor(|e, window, cx| {
 4391        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4392    });
 4393    cx.assert_editor_state(indoc! {"
 4394        «1
 4395        2
 4396        3
 4397        4
 4398        5ˇ»
 4399
 4400        «aaaaa
 4401        bb
 4402        ccc
 4403        ddddˇ»
 4404    "});
 4405
 4406    // Adding lines on each selection
 4407    cx.set_state(indoc! {"
 4408 4409        1ˇ»
 4410
 4411        bb«bb
 4412        aaaˇ»aa
 4413    "});
 4414    cx.update_editor(|e, window, cx| {
 4415        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 4416    });
 4417    cx.assert_editor_state(indoc! {"
 4418        «2
 4419        1
 4420        added lineˇ»
 4421
 4422        «bbbb
 4423        aaaaa
 4424        added lineˇ»
 4425    "});
 4426
 4427    // Removing lines on each selection
 4428    cx.set_state(indoc! {"
 4429 4430        1ˇ»
 4431
 4432        bb«bb
 4433        aaaˇ»aa
 4434    "});
 4435    cx.update_editor(|e, window, cx| {
 4436        e.manipulate_immutable_lines(window, cx, |lines| {
 4437            lines.pop();
 4438        })
 4439    });
 4440    cx.assert_editor_state(indoc! {"
 4441        «2ˇ»
 4442
 4443        «bbbbˇ»
 4444    "});
 4445}
 4446
 4447#[gpui::test]
 4448async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 4449    init_test(cx, |settings| {
 4450        settings.defaults.tab_size = NonZeroU32::new(3)
 4451    });
 4452
 4453    let mut cx = EditorTestContext::new(cx).await;
 4454
 4455    // MULTI SELECTION
 4456    // Ln.1 "«" tests empty lines
 4457    // Ln.9 tests just leading whitespace
 4458    cx.set_state(indoc! {"
 4459        «
 4460        abc                 // No indentationˇ»
 4461        «\tabc              // 1 tabˇ»
 4462        \t\tabc «      ˇ»   // 2 tabs
 4463        \t ab«c             // Tab followed by space
 4464         \tabc              // Space followed by tab (3 spaces should be the result)
 4465        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4466           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 4467        \t
 4468        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4469    "});
 4470    cx.update_editor(|e, window, cx| {
 4471        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4472    });
 4473    cx.assert_editor_state(
 4474        indoc! {"
 4475            «
 4476            abc                 // No indentation
 4477               abc              // 1 tab
 4478                  abc          // 2 tabs
 4479                abc             // Tab followed by space
 4480               abc              // Space followed by tab (3 spaces should be the result)
 4481                           abc   // Mixed indentation (tab conversion depends on the column)
 4482               abc         // Already space indented
 4483               ·
 4484               abc\tdef          // Only the leading tab is manipulatedˇ»
 4485        "}
 4486        .replace("·", "")
 4487        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4488    );
 4489
 4490    // Test on just a few lines, the others should remain unchanged
 4491    // Only lines (3, 5, 10, 11) should change
 4492    cx.set_state(
 4493        indoc! {"
 4494            ·
 4495            abc                 // No indentation
 4496            \tabcˇ               // 1 tab
 4497            \t\tabc             // 2 tabs
 4498            \t abcˇ              // Tab followed by space
 4499             \tabc              // Space followed by tab (3 spaces should be the result)
 4500            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4501               abc              // Already space indented
 4502            «\t
 4503            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4504        "}
 4505        .replace("·", "")
 4506        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4507    );
 4508    cx.update_editor(|e, window, cx| {
 4509        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4510    });
 4511    cx.assert_editor_state(
 4512        indoc! {"
 4513            ·
 4514            abc                 // No indentation
 4515            «   abc               // 1 tabˇ»
 4516            \t\tabc             // 2 tabs
 4517            «    abc              // Tab followed by spaceˇ»
 4518             \tabc              // Space followed by tab (3 spaces should be the result)
 4519            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4520               abc              // Already space indented
 4521            «   ·
 4522               abc\tdef          // Only the leading tab is manipulatedˇ»
 4523        "}
 4524        .replace("·", "")
 4525        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4526    );
 4527
 4528    // SINGLE SELECTION
 4529    // Ln.1 "«" tests empty lines
 4530    // Ln.9 tests just leading whitespace
 4531    cx.set_state(indoc! {"
 4532        «
 4533        abc                 // No indentation
 4534        \tabc               // 1 tab
 4535        \t\tabc             // 2 tabs
 4536        \t abc              // Tab followed by space
 4537         \tabc              // Space followed by tab (3 spaces should be the result)
 4538        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4539           abc              // Already space indented
 4540        \t
 4541        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4542    "});
 4543    cx.update_editor(|e, window, cx| {
 4544        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4545    });
 4546    cx.assert_editor_state(
 4547        indoc! {"
 4548            «
 4549            abc                 // No indentation
 4550               abc               // 1 tab
 4551                  abc             // 2 tabs
 4552                abc              // Tab followed by space
 4553               abc              // Space followed by tab (3 spaces should be the result)
 4554                           abc   // Mixed indentation (tab conversion depends on the column)
 4555               abc              // Already space indented
 4556               ·
 4557               abc\tdef          // Only the leading tab is manipulatedˇ»
 4558        "}
 4559        .replace("·", "")
 4560        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4561    );
 4562}
 4563
 4564#[gpui::test]
 4565async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 4566    init_test(cx, |settings| {
 4567        settings.defaults.tab_size = NonZeroU32::new(3)
 4568    });
 4569
 4570    let mut cx = EditorTestContext::new(cx).await;
 4571
 4572    // MULTI SELECTION
 4573    // Ln.1 "«" tests empty lines
 4574    // Ln.11 tests just leading whitespace
 4575    cx.set_state(indoc! {"
 4576        «
 4577        abˇ»ˇc                 // No indentation
 4578         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 4579          abc  «             // 2 spaces (< 3 so dont convert)
 4580           abc              // 3 spaces (convert)
 4581             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 4582        «\tˇ»\t«\tˇ»abc           // Already tab indented
 4583        «\t abc              // Tab followed by space
 4584         \tabc              // Space followed by tab (should be consumed due to tab)
 4585        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4586           \tˇ»  «\t
 4587           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 4588    "});
 4589    cx.update_editor(|e, window, cx| {
 4590        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4591    });
 4592    cx.assert_editor_state(indoc! {"
 4593        «
 4594        abc                 // No indentation
 4595         abc                // 1 space (< 3 so dont convert)
 4596          abc               // 2 spaces (< 3 so dont convert)
 4597        \tabc              // 3 spaces (convert)
 4598        \t  abc            // 5 spaces (1 tab + 2 spaces)
 4599        \t\t\tabc           // Already tab indented
 4600        \t abc              // Tab followed by space
 4601        \tabc              // Space followed by tab (should be consumed due to tab)
 4602        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4603        \t\t\t
 4604        \tabc   \t         // Only the leading spaces should be convertedˇ»
 4605    "});
 4606
 4607    // Test on just a few lines, the other should remain unchanged
 4608    // Only lines (4, 8, 11, 12) should change
 4609    cx.set_state(
 4610        indoc! {"
 4611            ·
 4612            abc                 // No indentation
 4613             abc                // 1 space (< 3 so dont convert)
 4614              abc               // 2 spaces (< 3 so dont convert)
 4615            «   abc              // 3 spaces (convert)ˇ»
 4616                 abc            // 5 spaces (1 tab + 2 spaces)
 4617            \t\t\tabc           // Already tab indented
 4618            \t abc              // Tab followed by space
 4619             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 4620               \t\t  \tabc      // Mixed indentation
 4621            \t \t  \t   \tabc   // Mixed indentation
 4622               \t  \tˇ
 4623            «   abc   \t         // Only the leading spaces should be convertedˇ»
 4624        "}
 4625        .replace("·", "")
 4626        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4627    );
 4628    cx.update_editor(|e, window, cx| {
 4629        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4630    });
 4631    cx.assert_editor_state(
 4632        indoc! {"
 4633            ·
 4634            abc                 // No indentation
 4635             abc                // 1 space (< 3 so dont convert)
 4636              abc               // 2 spaces (< 3 so dont convert)
 4637            «\tabc              // 3 spaces (convert)ˇ»
 4638                 abc            // 5 spaces (1 tab + 2 spaces)
 4639            \t\t\tabc           // Already tab indented
 4640            \t abc              // Tab followed by space
 4641            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 4642               \t\t  \tabc      // Mixed indentation
 4643            \t \t  \t   \tabc   // Mixed indentation
 4644            «\t\t\t
 4645            \tabc   \t         // Only the leading spaces should be convertedˇ»
 4646        "}
 4647        .replace("·", "")
 4648        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4649    );
 4650
 4651    // SINGLE SELECTION
 4652    // Ln.1 "«" tests empty lines
 4653    // Ln.11 tests just leading whitespace
 4654    cx.set_state(indoc! {"
 4655        «
 4656        abc                 // No indentation
 4657         abc                // 1 space (< 3 so dont convert)
 4658          abc               // 2 spaces (< 3 so dont convert)
 4659           abc              // 3 spaces (convert)
 4660             abc            // 5 spaces (1 tab + 2 spaces)
 4661        \t\t\tabc           // Already tab indented
 4662        \t abc              // Tab followed by space
 4663         \tabc              // Space followed by tab (should be consumed due to tab)
 4664        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4665           \t  \t
 4666           abc   \t         // Only the leading spaces should be convertedˇ»
 4667    "});
 4668    cx.update_editor(|e, window, cx| {
 4669        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4670    });
 4671    cx.assert_editor_state(indoc! {"
 4672        «
 4673        abc                 // No indentation
 4674         abc                // 1 space (< 3 so dont convert)
 4675          abc               // 2 spaces (< 3 so dont convert)
 4676        \tabc              // 3 spaces (convert)
 4677        \t  abc            // 5 spaces (1 tab + 2 spaces)
 4678        \t\t\tabc           // Already tab indented
 4679        \t abc              // Tab followed by space
 4680        \tabc              // Space followed by tab (should be consumed due to tab)
 4681        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4682        \t\t\t
 4683        \tabc   \t         // Only the leading spaces should be convertedˇ»
 4684    "});
 4685}
 4686
 4687#[gpui::test]
 4688async fn test_toggle_case(cx: &mut TestAppContext) {
 4689    init_test(cx, |_| {});
 4690
 4691    let mut cx = EditorTestContext::new(cx).await;
 4692
 4693    // If all lower case -> upper case
 4694    cx.set_state(indoc! {"
 4695        «hello worldˇ»
 4696    "});
 4697    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4698    cx.assert_editor_state(indoc! {"
 4699        «HELLO WORLDˇ»
 4700    "});
 4701
 4702    // If all upper case -> lower case
 4703    cx.set_state(indoc! {"
 4704        «HELLO WORLDˇ»
 4705    "});
 4706    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4707    cx.assert_editor_state(indoc! {"
 4708        «hello worldˇ»
 4709    "});
 4710
 4711    // If any upper case characters are identified -> lower case
 4712    // This matches JetBrains IDEs
 4713    cx.set_state(indoc! {"
 4714        «hEllo worldˇ»
 4715    "});
 4716    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4717    cx.assert_editor_state(indoc! {"
 4718        «hello worldˇ»
 4719    "});
 4720}
 4721
 4722#[gpui::test]
 4723async fn test_manipulate_text(cx: &mut TestAppContext) {
 4724    init_test(cx, |_| {});
 4725
 4726    let mut cx = EditorTestContext::new(cx).await;
 4727
 4728    // Test convert_to_upper_case()
 4729    cx.set_state(indoc! {"
 4730        «hello worldˇ»
 4731    "});
 4732    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4733    cx.assert_editor_state(indoc! {"
 4734        «HELLO WORLDˇ»
 4735    "});
 4736
 4737    // Test convert_to_lower_case()
 4738    cx.set_state(indoc! {"
 4739        «HELLO WORLDˇ»
 4740    "});
 4741    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 4742    cx.assert_editor_state(indoc! {"
 4743        «hello worldˇ»
 4744    "});
 4745
 4746    // Test multiple line, single selection case
 4747    cx.set_state(indoc! {"
 4748        «The quick brown
 4749        fox jumps over
 4750        the lazy dogˇ»
 4751    "});
 4752    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 4753    cx.assert_editor_state(indoc! {"
 4754        «The Quick Brown
 4755        Fox Jumps Over
 4756        The Lazy Dogˇ»
 4757    "});
 4758
 4759    // Test multiple line, single selection case
 4760    cx.set_state(indoc! {"
 4761        «The quick brown
 4762        fox jumps over
 4763        the lazy dogˇ»
 4764    "});
 4765    cx.update_editor(|e, window, cx| {
 4766        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 4767    });
 4768    cx.assert_editor_state(indoc! {"
 4769        «TheQuickBrown
 4770        FoxJumpsOver
 4771        TheLazyDogˇ»
 4772    "});
 4773
 4774    // From here on out, test more complex cases of manipulate_text()
 4775
 4776    // Test no selection case - should affect words cursors are in
 4777    // Cursor at beginning, middle, and end of word
 4778    cx.set_state(indoc! {"
 4779        ˇhello big beauˇtiful worldˇ
 4780    "});
 4781    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4782    cx.assert_editor_state(indoc! {"
 4783        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 4784    "});
 4785
 4786    // Test multiple selections on a single line and across multiple lines
 4787    cx.set_state(indoc! {"
 4788        «Theˇ» quick «brown
 4789        foxˇ» jumps «overˇ»
 4790        the «lazyˇ» dog
 4791    "});
 4792    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4793    cx.assert_editor_state(indoc! {"
 4794        «THEˇ» quick «BROWN
 4795        FOXˇ» jumps «OVERˇ»
 4796        the «LAZYˇ» dog
 4797    "});
 4798
 4799    // Test case where text length grows
 4800    cx.set_state(indoc! {"
 4801        «tschüߡ»
 4802    "});
 4803    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4804    cx.assert_editor_state(indoc! {"
 4805        «TSCHÜSSˇ»
 4806    "});
 4807
 4808    // Test to make sure we don't crash when text shrinks
 4809    cx.set_state(indoc! {"
 4810        aaa_bbbˇ
 4811    "});
 4812    cx.update_editor(|e, window, cx| {
 4813        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4814    });
 4815    cx.assert_editor_state(indoc! {"
 4816        «aaaBbbˇ»
 4817    "});
 4818
 4819    // Test to make sure we all aware of the fact that each word can grow and shrink
 4820    // Final selections should be aware of this fact
 4821    cx.set_state(indoc! {"
 4822        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 4823    "});
 4824    cx.update_editor(|e, window, cx| {
 4825        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4826    });
 4827    cx.assert_editor_state(indoc! {"
 4828        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 4829    "});
 4830
 4831    cx.set_state(indoc! {"
 4832        «hElLo, WoRld!ˇ»
 4833    "});
 4834    cx.update_editor(|e, window, cx| {
 4835        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 4836    });
 4837    cx.assert_editor_state(indoc! {"
 4838        «HeLlO, wOrLD!ˇ»
 4839    "});
 4840}
 4841
 4842#[gpui::test]
 4843fn test_duplicate_line(cx: &mut TestAppContext) {
 4844    init_test(cx, |_| {});
 4845
 4846    let editor = cx.add_window(|window, cx| {
 4847        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4848        build_editor(buffer, window, cx)
 4849    });
 4850    _ = editor.update(cx, |editor, window, cx| {
 4851        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4852            s.select_display_ranges([
 4853                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4854                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4855                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4856                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4857            ])
 4858        });
 4859        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4860        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4861        assert_eq!(
 4862            editor.selections.display_ranges(cx),
 4863            vec![
 4864                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4865                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 4866                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4867                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4868            ]
 4869        );
 4870    });
 4871
 4872    let editor = cx.add_window(|window, cx| {
 4873        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4874        build_editor(buffer, window, cx)
 4875    });
 4876    _ = editor.update(cx, |editor, window, cx| {
 4877        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4878            s.select_display_ranges([
 4879                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4880                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4881            ])
 4882        });
 4883        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4884        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4885        assert_eq!(
 4886            editor.selections.display_ranges(cx),
 4887            vec![
 4888                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 4889                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 4890            ]
 4891        );
 4892    });
 4893
 4894    // With `move_upwards` the selections stay in place, except for
 4895    // the lines inserted above them
 4896    let editor = cx.add_window(|window, cx| {
 4897        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4898        build_editor(buffer, window, cx)
 4899    });
 4900    _ = editor.update(cx, |editor, window, cx| {
 4901        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4902            s.select_display_ranges([
 4903                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4904                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4905                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4906                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4907            ])
 4908        });
 4909        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4910        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4911        assert_eq!(
 4912            editor.selections.display_ranges(cx),
 4913            vec![
 4914                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4915                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4916                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 4917                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4918            ]
 4919        );
 4920    });
 4921
 4922    let editor = cx.add_window(|window, cx| {
 4923        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4924        build_editor(buffer, window, cx)
 4925    });
 4926    _ = editor.update(cx, |editor, window, cx| {
 4927        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4928            s.select_display_ranges([
 4929                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4930                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4931            ])
 4932        });
 4933        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4934        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4935        assert_eq!(
 4936            editor.selections.display_ranges(cx),
 4937            vec![
 4938                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4939                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4940            ]
 4941        );
 4942    });
 4943
 4944    let editor = cx.add_window(|window, cx| {
 4945        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4946        build_editor(buffer, window, cx)
 4947    });
 4948    _ = editor.update(cx, |editor, window, cx| {
 4949        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4950            s.select_display_ranges([
 4951                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4952                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4953            ])
 4954        });
 4955        editor.duplicate_selection(&DuplicateSelection, window, cx);
 4956        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 4957        assert_eq!(
 4958            editor.selections.display_ranges(cx),
 4959            vec![
 4960                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4961                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 4962            ]
 4963        );
 4964    });
 4965}
 4966
 4967#[gpui::test]
 4968fn test_move_line_up_down(cx: &mut TestAppContext) {
 4969    init_test(cx, |_| {});
 4970
 4971    let editor = cx.add_window(|window, cx| {
 4972        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 4973        build_editor(buffer, window, cx)
 4974    });
 4975    _ = editor.update(cx, |editor, window, cx| {
 4976        editor.fold_creases(
 4977            vec![
 4978                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 4979                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 4980                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 4981            ],
 4982            true,
 4983            window,
 4984            cx,
 4985        );
 4986        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4987            s.select_display_ranges([
 4988                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4989                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4990                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4991                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 4992            ])
 4993        });
 4994        assert_eq!(
 4995            editor.display_text(cx),
 4996            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 4997        );
 4998
 4999        editor.move_line_up(&MoveLineUp, window, cx);
 5000        assert_eq!(
 5001            editor.display_text(cx),
 5002            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 5003        );
 5004        assert_eq!(
 5005            editor.selections.display_ranges(cx),
 5006            vec![
 5007                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5008                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5009                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5010                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5011            ]
 5012        );
 5013    });
 5014
 5015    _ = editor.update(cx, |editor, window, cx| {
 5016        editor.move_line_down(&MoveLineDown, window, cx);
 5017        assert_eq!(
 5018            editor.display_text(cx),
 5019            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 5020        );
 5021        assert_eq!(
 5022            editor.selections.display_ranges(cx),
 5023            vec![
 5024                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5025                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5026                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5027                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5028            ]
 5029        );
 5030    });
 5031
 5032    _ = editor.update(cx, |editor, window, cx| {
 5033        editor.move_line_down(&MoveLineDown, window, cx);
 5034        assert_eq!(
 5035            editor.display_text(cx),
 5036            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 5037        );
 5038        assert_eq!(
 5039            editor.selections.display_ranges(cx),
 5040            vec![
 5041                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5042                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5043                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5044                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5045            ]
 5046        );
 5047    });
 5048
 5049    _ = editor.update(cx, |editor, window, cx| {
 5050        editor.move_line_up(&MoveLineUp, window, cx);
 5051        assert_eq!(
 5052            editor.display_text(cx),
 5053            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 5054        );
 5055        assert_eq!(
 5056            editor.selections.display_ranges(cx),
 5057            vec![
 5058                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5059                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5060                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5061                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5062            ]
 5063        );
 5064    });
 5065}
 5066
 5067#[gpui::test]
 5068fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 5069    init_test(cx, |_| {});
 5070
 5071    let editor = cx.add_window(|window, cx| {
 5072        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5073        build_editor(buffer, window, cx)
 5074    });
 5075    _ = editor.update(cx, |editor, window, cx| {
 5076        let snapshot = editor.buffer.read(cx).snapshot(cx);
 5077        editor.insert_blocks(
 5078            [BlockProperties {
 5079                style: BlockStyle::Fixed,
 5080                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 5081                height: Some(1),
 5082                render: Arc::new(|_| div().into_any()),
 5083                priority: 0,
 5084            }],
 5085            Some(Autoscroll::fit()),
 5086            cx,
 5087        );
 5088        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5089            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 5090        });
 5091        editor.move_line_down(&MoveLineDown, window, cx);
 5092    });
 5093}
 5094
 5095#[gpui::test]
 5096async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 5097    init_test(cx, |_| {});
 5098
 5099    let mut cx = EditorTestContext::new(cx).await;
 5100    cx.set_state(
 5101        &"
 5102            ˇzero
 5103            one
 5104            two
 5105            three
 5106            four
 5107            five
 5108        "
 5109        .unindent(),
 5110    );
 5111
 5112    // Create a four-line block that replaces three lines of text.
 5113    cx.update_editor(|editor, window, cx| {
 5114        let snapshot = editor.snapshot(window, cx);
 5115        let snapshot = &snapshot.buffer_snapshot;
 5116        let placement = BlockPlacement::Replace(
 5117            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 5118        );
 5119        editor.insert_blocks(
 5120            [BlockProperties {
 5121                placement,
 5122                height: Some(4),
 5123                style: BlockStyle::Sticky,
 5124                render: Arc::new(|_| gpui::div().into_any_element()),
 5125                priority: 0,
 5126            }],
 5127            None,
 5128            cx,
 5129        );
 5130    });
 5131
 5132    // Move down so that the cursor touches the block.
 5133    cx.update_editor(|editor, window, cx| {
 5134        editor.move_down(&Default::default(), window, cx);
 5135    });
 5136    cx.assert_editor_state(
 5137        &"
 5138            zero
 5139            «one
 5140            two
 5141            threeˇ»
 5142            four
 5143            five
 5144        "
 5145        .unindent(),
 5146    );
 5147
 5148    // Move down past the block.
 5149    cx.update_editor(|editor, window, cx| {
 5150        editor.move_down(&Default::default(), window, cx);
 5151    });
 5152    cx.assert_editor_state(
 5153        &"
 5154            zero
 5155            one
 5156            two
 5157            three
 5158            ˇfour
 5159            five
 5160        "
 5161        .unindent(),
 5162    );
 5163}
 5164
 5165#[gpui::test]
 5166fn test_transpose(cx: &mut TestAppContext) {
 5167    init_test(cx, |_| {});
 5168
 5169    _ = cx.add_window(|window, cx| {
 5170        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 5171        editor.set_style(EditorStyle::default(), window, cx);
 5172        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5173            s.select_ranges([1..1])
 5174        });
 5175        editor.transpose(&Default::default(), window, cx);
 5176        assert_eq!(editor.text(cx), "bac");
 5177        assert_eq!(editor.selections.ranges(cx), [2..2]);
 5178
 5179        editor.transpose(&Default::default(), window, cx);
 5180        assert_eq!(editor.text(cx), "bca");
 5181        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5182
 5183        editor.transpose(&Default::default(), window, cx);
 5184        assert_eq!(editor.text(cx), "bac");
 5185        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5186
 5187        editor
 5188    });
 5189
 5190    _ = cx.add_window(|window, cx| {
 5191        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5192        editor.set_style(EditorStyle::default(), window, cx);
 5193        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5194            s.select_ranges([3..3])
 5195        });
 5196        editor.transpose(&Default::default(), window, cx);
 5197        assert_eq!(editor.text(cx), "acb\nde");
 5198        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5199
 5200        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5201            s.select_ranges([4..4])
 5202        });
 5203        editor.transpose(&Default::default(), window, cx);
 5204        assert_eq!(editor.text(cx), "acbd\ne");
 5205        assert_eq!(editor.selections.ranges(cx), [5..5]);
 5206
 5207        editor.transpose(&Default::default(), window, cx);
 5208        assert_eq!(editor.text(cx), "acbde\n");
 5209        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5210
 5211        editor.transpose(&Default::default(), window, cx);
 5212        assert_eq!(editor.text(cx), "acbd\ne");
 5213        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5214
 5215        editor
 5216    });
 5217
 5218    _ = cx.add_window(|window, cx| {
 5219        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5220        editor.set_style(EditorStyle::default(), window, cx);
 5221        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5222            s.select_ranges([1..1, 2..2, 4..4])
 5223        });
 5224        editor.transpose(&Default::default(), window, cx);
 5225        assert_eq!(editor.text(cx), "bacd\ne");
 5226        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 5227
 5228        editor.transpose(&Default::default(), window, cx);
 5229        assert_eq!(editor.text(cx), "bcade\n");
 5230        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 5231
 5232        editor.transpose(&Default::default(), window, cx);
 5233        assert_eq!(editor.text(cx), "bcda\ne");
 5234        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5235
 5236        editor.transpose(&Default::default(), window, cx);
 5237        assert_eq!(editor.text(cx), "bcade\n");
 5238        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5239
 5240        editor.transpose(&Default::default(), window, cx);
 5241        assert_eq!(editor.text(cx), "bcaed\n");
 5242        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 5243
 5244        editor
 5245    });
 5246
 5247    _ = cx.add_window(|window, cx| {
 5248        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 5249        editor.set_style(EditorStyle::default(), window, cx);
 5250        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5251            s.select_ranges([4..4])
 5252        });
 5253        editor.transpose(&Default::default(), window, cx);
 5254        assert_eq!(editor.text(cx), "🏀🍐✋");
 5255        assert_eq!(editor.selections.ranges(cx), [8..8]);
 5256
 5257        editor.transpose(&Default::default(), window, cx);
 5258        assert_eq!(editor.text(cx), "🏀✋🍐");
 5259        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5260
 5261        editor.transpose(&Default::default(), window, cx);
 5262        assert_eq!(editor.text(cx), "🏀🍐✋");
 5263        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5264
 5265        editor
 5266    });
 5267}
 5268
 5269#[gpui::test]
 5270async fn test_rewrap(cx: &mut TestAppContext) {
 5271    init_test(cx, |settings| {
 5272        settings.languages.0.extend([
 5273            (
 5274                "Markdown".into(),
 5275                LanguageSettingsContent {
 5276                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5277                    preferred_line_length: Some(40),
 5278                    ..Default::default()
 5279                },
 5280            ),
 5281            (
 5282                "Plain Text".into(),
 5283                LanguageSettingsContent {
 5284                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5285                    preferred_line_length: Some(40),
 5286                    ..Default::default()
 5287                },
 5288            ),
 5289            (
 5290                "C++".into(),
 5291                LanguageSettingsContent {
 5292                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5293                    preferred_line_length: Some(40),
 5294                    ..Default::default()
 5295                },
 5296            ),
 5297            (
 5298                "Python".into(),
 5299                LanguageSettingsContent {
 5300                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5301                    preferred_line_length: Some(40),
 5302                    ..Default::default()
 5303                },
 5304            ),
 5305            (
 5306                "Rust".into(),
 5307                LanguageSettingsContent {
 5308                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5309                    preferred_line_length: Some(40),
 5310                    ..Default::default()
 5311                },
 5312            ),
 5313        ])
 5314    });
 5315
 5316    let mut cx = EditorTestContext::new(cx).await;
 5317
 5318    let cpp_language = Arc::new(Language::new(
 5319        LanguageConfig {
 5320            name: "C++".into(),
 5321            line_comments: vec!["// ".into()],
 5322            ..LanguageConfig::default()
 5323        },
 5324        None,
 5325    ));
 5326    let python_language = Arc::new(Language::new(
 5327        LanguageConfig {
 5328            name: "Python".into(),
 5329            line_comments: vec!["# ".into()],
 5330            ..LanguageConfig::default()
 5331        },
 5332        None,
 5333    ));
 5334    let markdown_language = Arc::new(Language::new(
 5335        LanguageConfig {
 5336            name: "Markdown".into(),
 5337            rewrap_prefixes: vec![
 5338                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 5339                regex::Regex::new("[-*+]\\s+").unwrap(),
 5340            ],
 5341            ..LanguageConfig::default()
 5342        },
 5343        None,
 5344    ));
 5345    let rust_language = Arc::new(Language::new(
 5346        LanguageConfig {
 5347            name: "Rust".into(),
 5348            line_comments: vec!["// ".into(), "/// ".into()],
 5349            ..LanguageConfig::default()
 5350        },
 5351        Some(tree_sitter_rust::LANGUAGE.into()),
 5352    ));
 5353
 5354    let plaintext_language = Arc::new(Language::new(
 5355        LanguageConfig {
 5356            name: "Plain Text".into(),
 5357            ..LanguageConfig::default()
 5358        },
 5359        None,
 5360    ));
 5361
 5362    // Test basic rewrapping of a long line with a cursor
 5363    assert_rewrap(
 5364        indoc! {"
 5365            // ˇThis is a long comment that needs to be wrapped.
 5366        "},
 5367        indoc! {"
 5368            // ˇThis is a long comment that needs to
 5369            // be wrapped.
 5370        "},
 5371        cpp_language.clone(),
 5372        &mut cx,
 5373    );
 5374
 5375    // Test rewrapping a full selection
 5376    assert_rewrap(
 5377        indoc! {"
 5378            «// This selected long comment needs to be wrapped.ˇ»"
 5379        },
 5380        indoc! {"
 5381            «// This selected long comment needs to
 5382            // be wrapped.ˇ»"
 5383        },
 5384        cpp_language.clone(),
 5385        &mut cx,
 5386    );
 5387
 5388    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 5389    assert_rewrap(
 5390        indoc! {"
 5391            // ˇThis is the first line.
 5392            // Thisˇ is the second line.
 5393            // This is the thirdˇ line, all part of one paragraph.
 5394         "},
 5395        indoc! {"
 5396            // ˇThis is the first line. Thisˇ is the
 5397            // second line. This is the thirdˇ line,
 5398            // all part of one paragraph.
 5399         "},
 5400        cpp_language.clone(),
 5401        &mut cx,
 5402    );
 5403
 5404    // Test multiple cursors in different paragraphs trigger separate rewraps
 5405    assert_rewrap(
 5406        indoc! {"
 5407            // ˇThis is the first paragraph, first line.
 5408            // ˇThis is the first paragraph, second line.
 5409
 5410            // ˇThis is the second paragraph, first line.
 5411            // ˇThis is the second paragraph, second line.
 5412        "},
 5413        indoc! {"
 5414            // ˇThis is the first paragraph, first
 5415            // line. ˇThis is the first paragraph,
 5416            // second line.
 5417
 5418            // ˇThis is the second paragraph, first
 5419            // line. ˇThis is the second paragraph,
 5420            // second line.
 5421        "},
 5422        cpp_language.clone(),
 5423        &mut cx,
 5424    );
 5425
 5426    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 5427    assert_rewrap(
 5428        indoc! {"
 5429            «// A regular long long comment to be wrapped.
 5430            /// A documentation long comment to be wrapped.ˇ»
 5431          "},
 5432        indoc! {"
 5433            «// A regular long long comment to be
 5434            // wrapped.
 5435            /// A documentation long comment to be
 5436            /// wrapped.ˇ»
 5437          "},
 5438        rust_language.clone(),
 5439        &mut cx,
 5440    );
 5441
 5442    // Test that change in indentation level trigger seperate rewraps
 5443    assert_rewrap(
 5444        indoc! {"
 5445            fn foo() {
 5446                «// This is a long comment at the base indent.
 5447                    // This is a long comment at the next indent.ˇ»
 5448            }
 5449        "},
 5450        indoc! {"
 5451            fn foo() {
 5452                «// This is a long comment at the
 5453                // base indent.
 5454                    // This is a long comment at the
 5455                    // next indent.ˇ»
 5456            }
 5457        "},
 5458        rust_language.clone(),
 5459        &mut cx,
 5460    );
 5461
 5462    // Test that different comment prefix characters (e.g., '#') are handled correctly
 5463    assert_rewrap(
 5464        indoc! {"
 5465            # ˇThis is a long comment using a pound sign.
 5466        "},
 5467        indoc! {"
 5468            # ˇThis is a long comment using a pound
 5469            # sign.
 5470        "},
 5471        python_language.clone(),
 5472        &mut cx,
 5473    );
 5474
 5475    // Test rewrapping only affects comments, not code even when selected
 5476    assert_rewrap(
 5477        indoc! {"
 5478            «/// This doc comment is long and should be wrapped.
 5479            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 5480        "},
 5481        indoc! {"
 5482            «/// This doc comment is long and should
 5483            /// be wrapped.
 5484            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 5485        "},
 5486        rust_language.clone(),
 5487        &mut cx,
 5488    );
 5489
 5490    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 5491    assert_rewrap(
 5492        indoc! {"
 5493            # Header
 5494
 5495            A long long long line of markdown text to wrap.ˇ
 5496         "},
 5497        indoc! {"
 5498            # Header
 5499
 5500            A long long long line of markdown text
 5501            to wrap.ˇ
 5502         "},
 5503        markdown_language.clone(),
 5504        &mut cx,
 5505    );
 5506
 5507    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 5508    assert_rewrap(
 5509        indoc! {"
 5510            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 5511            2. This is a numbered list item that is very long and needs to be wrapped properly.
 5512            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 5513        "},
 5514        indoc! {"
 5515            «1. This is a numbered list item that is
 5516               very long and needs to be wrapped
 5517               properly.
 5518            2. This is a numbered list item that is
 5519               very long and needs to be wrapped
 5520               properly.
 5521            - This is an unordered list item that is
 5522              also very long and should not merge
 5523              with the numbered item.ˇ»
 5524        "},
 5525        markdown_language.clone(),
 5526        &mut cx,
 5527    );
 5528
 5529    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 5530    assert_rewrap(
 5531        indoc! {"
 5532            «1. This is a numbered list item that is
 5533            very long and needs to be wrapped
 5534            properly.
 5535            2. This is a numbered list item that is
 5536            very long and needs to be wrapped
 5537            properly.
 5538            - This is an unordered list item that is
 5539            also very long and should not merge with
 5540            the numbered item.ˇ»
 5541        "},
 5542        indoc! {"
 5543            «1. This is a numbered list item that is
 5544               very long and needs to be wrapped
 5545               properly.
 5546            2. This is a numbered list item that is
 5547               very long and needs to be wrapped
 5548               properly.
 5549            - This is an unordered list item that is
 5550              also very long and should not merge
 5551              with the numbered item.ˇ»
 5552        "},
 5553        markdown_language.clone(),
 5554        &mut cx,
 5555    );
 5556
 5557    // Test that rewrapping maintain indents even when they already exists.
 5558    assert_rewrap(
 5559        indoc! {"
 5560            «1. This is a numbered list
 5561               item that is very long and needs to be wrapped properly.
 5562            2. This is a numbered list
 5563               item that is very long and needs to be wrapped properly.
 5564            - This is an unordered list item that is also very long and
 5565              should not merge with the numbered item.ˇ»
 5566        "},
 5567        indoc! {"
 5568            «1. This is a numbered list item that is
 5569               very long and needs to be wrapped
 5570               properly.
 5571            2. This is a numbered list item that is
 5572               very long and needs to be wrapped
 5573               properly.
 5574            - This is an unordered list item that is
 5575              also very long and should not merge
 5576              with the numbered item.ˇ»
 5577        "},
 5578        markdown_language.clone(),
 5579        &mut cx,
 5580    );
 5581
 5582    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 5583    assert_rewrap(
 5584        indoc! {"
 5585            ˇThis is a very long line of plain text that will be wrapped.
 5586        "},
 5587        indoc! {"
 5588            ˇThis is a very long line of plain text
 5589            that will be wrapped.
 5590        "},
 5591        plaintext_language.clone(),
 5592        &mut cx,
 5593    );
 5594
 5595    // Test that non-commented code acts as a paragraph boundary within a selection
 5596    assert_rewrap(
 5597        indoc! {"
 5598               «// This is the first long comment block to be wrapped.
 5599               fn my_func(a: u32);
 5600               // This is the second long comment block to be wrapped.ˇ»
 5601           "},
 5602        indoc! {"
 5603               «// This is the first long comment block
 5604               // to be wrapped.
 5605               fn my_func(a: u32);
 5606               // This is the second long comment block
 5607               // to be wrapped.ˇ»
 5608           "},
 5609        rust_language.clone(),
 5610        &mut cx,
 5611    );
 5612
 5613    // Test rewrapping multiple selections, including ones with blank lines or tabs
 5614    assert_rewrap(
 5615        indoc! {"
 5616            «ˇThis is a very long line that will be wrapped.
 5617
 5618            This is another paragraph in the same selection.»
 5619
 5620            «\tThis is a very long indented line that will be wrapped.ˇ»
 5621         "},
 5622        indoc! {"
 5623            «ˇThis is a very long line that will be
 5624            wrapped.
 5625
 5626            This is another paragraph in the same
 5627            selection.»
 5628
 5629            «\tThis is a very long indented line
 5630            \tthat will be wrapped.ˇ»
 5631         "},
 5632        plaintext_language.clone(),
 5633        &mut cx,
 5634    );
 5635
 5636    // Test that an empty comment line acts as a paragraph boundary
 5637    assert_rewrap(
 5638        indoc! {"
 5639            // ˇThis is a long comment that will be wrapped.
 5640            //
 5641            // And this is another long comment that will also be wrapped.ˇ
 5642         "},
 5643        indoc! {"
 5644            // ˇThis is a long comment that will be
 5645            // wrapped.
 5646            //
 5647            // And this is another long comment that
 5648            // will also be wrapped.ˇ
 5649         "},
 5650        cpp_language,
 5651        &mut cx,
 5652    );
 5653
 5654    #[track_caller]
 5655    fn assert_rewrap(
 5656        unwrapped_text: &str,
 5657        wrapped_text: &str,
 5658        language: Arc<Language>,
 5659        cx: &mut EditorTestContext,
 5660    ) {
 5661        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 5662        cx.set_state(unwrapped_text);
 5663        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 5664        cx.assert_editor_state(wrapped_text);
 5665    }
 5666}
 5667
 5668#[gpui::test]
 5669async fn test_hard_wrap(cx: &mut TestAppContext) {
 5670    init_test(cx, |_| {});
 5671    let mut cx = EditorTestContext::new(cx).await;
 5672
 5673    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 5674    cx.update_editor(|editor, _, cx| {
 5675        editor.set_hard_wrap(Some(14), cx);
 5676    });
 5677
 5678    cx.set_state(indoc!(
 5679        "
 5680        one two three ˇ
 5681        "
 5682    ));
 5683    cx.simulate_input("four");
 5684    cx.run_until_parked();
 5685
 5686    cx.assert_editor_state(indoc!(
 5687        "
 5688        one two three
 5689        fourˇ
 5690        "
 5691    ));
 5692
 5693    cx.update_editor(|editor, window, cx| {
 5694        editor.newline(&Default::default(), window, cx);
 5695    });
 5696    cx.run_until_parked();
 5697    cx.assert_editor_state(indoc!(
 5698        "
 5699        one two three
 5700        four
 5701        ˇ
 5702        "
 5703    ));
 5704
 5705    cx.simulate_input("five");
 5706    cx.run_until_parked();
 5707    cx.assert_editor_state(indoc!(
 5708        "
 5709        one two three
 5710        four
 5711        fiveˇ
 5712        "
 5713    ));
 5714
 5715    cx.update_editor(|editor, window, cx| {
 5716        editor.newline(&Default::default(), window, cx);
 5717    });
 5718    cx.run_until_parked();
 5719    cx.simulate_input("# ");
 5720    cx.run_until_parked();
 5721    cx.assert_editor_state(indoc!(
 5722        "
 5723        one two three
 5724        four
 5725        five
 5726        # ˇ
 5727        "
 5728    ));
 5729
 5730    cx.update_editor(|editor, window, cx| {
 5731        editor.newline(&Default::default(), window, cx);
 5732    });
 5733    cx.run_until_parked();
 5734    cx.assert_editor_state(indoc!(
 5735        "
 5736        one two three
 5737        four
 5738        five
 5739        #\x20
 5740 5741        "
 5742    ));
 5743
 5744    cx.simulate_input(" 6");
 5745    cx.run_until_parked();
 5746    cx.assert_editor_state(indoc!(
 5747        "
 5748        one two three
 5749        four
 5750        five
 5751        #
 5752        # 6ˇ
 5753        "
 5754    ));
 5755}
 5756
 5757#[gpui::test]
 5758async fn test_clipboard(cx: &mut TestAppContext) {
 5759    init_test(cx, |_| {});
 5760
 5761    let mut cx = EditorTestContext::new(cx).await;
 5762
 5763    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 5764    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5765    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 5766
 5767    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 5768    cx.set_state("two ˇfour ˇsix ˇ");
 5769    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5770    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 5771
 5772    // Paste again but with only two cursors. Since the number of cursors doesn't
 5773    // match the number of slices in the clipboard, the entire clipboard text
 5774    // is pasted at each cursor.
 5775    cx.set_state("ˇtwo one✅ four three six five ˇ");
 5776    cx.update_editor(|e, window, cx| {
 5777        e.handle_input("( ", window, cx);
 5778        e.paste(&Paste, window, cx);
 5779        e.handle_input(") ", window, cx);
 5780    });
 5781    cx.assert_editor_state(
 5782        &([
 5783            "( one✅ ",
 5784            "three ",
 5785            "five ) ˇtwo one✅ four three six five ( one✅ ",
 5786            "three ",
 5787            "five ) ˇ",
 5788        ]
 5789        .join("\n")),
 5790    );
 5791
 5792    // Cut with three selections, one of which is full-line.
 5793    cx.set_state(indoc! {"
 5794        1«2ˇ»3
 5795        4ˇ567
 5796        «8ˇ»9"});
 5797    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5798    cx.assert_editor_state(indoc! {"
 5799        1ˇ3
 5800        ˇ9"});
 5801
 5802    // Paste with three selections, noticing how the copied selection that was full-line
 5803    // gets inserted before the second cursor.
 5804    cx.set_state(indoc! {"
 5805        1ˇ3
 5806 5807        «oˇ»ne"});
 5808    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5809    cx.assert_editor_state(indoc! {"
 5810        12ˇ3
 5811        4567
 5812 5813        8ˇne"});
 5814
 5815    // Copy with a single cursor only, which writes the whole line into the clipboard.
 5816    cx.set_state(indoc! {"
 5817        The quick brown
 5818        fox juˇmps over
 5819        the lazy dog"});
 5820    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5821    assert_eq!(
 5822        cx.read_from_clipboard()
 5823            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5824        Some("fox jumps over\n".to_string())
 5825    );
 5826
 5827    // Paste with three selections, noticing how the copied full-line selection is inserted
 5828    // before the empty selections but replaces the selection that is non-empty.
 5829    cx.set_state(indoc! {"
 5830        Tˇhe quick brown
 5831        «foˇ»x jumps over
 5832        tˇhe lazy dog"});
 5833    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5834    cx.assert_editor_state(indoc! {"
 5835        fox jumps over
 5836        Tˇhe quick brown
 5837        fox jumps over
 5838        ˇx jumps over
 5839        fox jumps over
 5840        tˇhe lazy dog"});
 5841}
 5842
 5843#[gpui::test]
 5844async fn test_copy_trim(cx: &mut TestAppContext) {
 5845    init_test(cx, |_| {});
 5846
 5847    let mut cx = EditorTestContext::new(cx).await;
 5848    cx.set_state(
 5849        r#"            «for selection in selections.iter() {
 5850            let mut start = selection.start;
 5851            let mut end = selection.end;
 5852            let is_entire_line = selection.is_empty();
 5853            if is_entire_line {
 5854                start = Point::new(start.row, 0);ˇ»
 5855                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5856            }
 5857        "#,
 5858    );
 5859    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5860    assert_eq!(
 5861        cx.read_from_clipboard()
 5862            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5863        Some(
 5864            "for selection in selections.iter() {
 5865            let mut start = selection.start;
 5866            let mut end = selection.end;
 5867            let is_entire_line = selection.is_empty();
 5868            if is_entire_line {
 5869                start = Point::new(start.row, 0);"
 5870                .to_string()
 5871        ),
 5872        "Regular copying preserves all indentation selected",
 5873    );
 5874    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5875    assert_eq!(
 5876        cx.read_from_clipboard()
 5877            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5878        Some(
 5879            "for selection in selections.iter() {
 5880let mut start = selection.start;
 5881let mut end = selection.end;
 5882let is_entire_line = selection.is_empty();
 5883if is_entire_line {
 5884    start = Point::new(start.row, 0);"
 5885                .to_string()
 5886        ),
 5887        "Copying with stripping should strip all leading whitespaces"
 5888    );
 5889
 5890    cx.set_state(
 5891        r#"       «     for selection in selections.iter() {
 5892            let mut start = selection.start;
 5893            let mut end = selection.end;
 5894            let is_entire_line = selection.is_empty();
 5895            if is_entire_line {
 5896                start = Point::new(start.row, 0);ˇ»
 5897                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5898            }
 5899        "#,
 5900    );
 5901    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5902    assert_eq!(
 5903        cx.read_from_clipboard()
 5904            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5905        Some(
 5906            "     for selection in selections.iter() {
 5907            let mut start = selection.start;
 5908            let mut end = selection.end;
 5909            let is_entire_line = selection.is_empty();
 5910            if is_entire_line {
 5911                start = Point::new(start.row, 0);"
 5912                .to_string()
 5913        ),
 5914        "Regular copying preserves all indentation selected",
 5915    );
 5916    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5917    assert_eq!(
 5918        cx.read_from_clipboard()
 5919            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5920        Some(
 5921            "for selection in selections.iter() {
 5922let mut start = selection.start;
 5923let mut end = selection.end;
 5924let is_entire_line = selection.is_empty();
 5925if is_entire_line {
 5926    start = Point::new(start.row, 0);"
 5927                .to_string()
 5928        ),
 5929        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 5930    );
 5931
 5932    cx.set_state(
 5933        r#"       «ˇ     for selection in selections.iter() {
 5934            let mut start = selection.start;
 5935            let mut end = selection.end;
 5936            let is_entire_line = selection.is_empty();
 5937            if is_entire_line {
 5938                start = Point::new(start.row, 0);»
 5939                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5940            }
 5941        "#,
 5942    );
 5943    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5944    assert_eq!(
 5945        cx.read_from_clipboard()
 5946            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5947        Some(
 5948            "     for selection in selections.iter() {
 5949            let mut start = selection.start;
 5950            let mut end = selection.end;
 5951            let is_entire_line = selection.is_empty();
 5952            if is_entire_line {
 5953                start = Point::new(start.row, 0);"
 5954                .to_string()
 5955        ),
 5956        "Regular copying for reverse selection works the same",
 5957    );
 5958    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5959    assert_eq!(
 5960        cx.read_from_clipboard()
 5961            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5962        Some(
 5963            "for selection in selections.iter() {
 5964let mut start = selection.start;
 5965let mut end = selection.end;
 5966let is_entire_line = selection.is_empty();
 5967if is_entire_line {
 5968    start = Point::new(start.row, 0);"
 5969                .to_string()
 5970        ),
 5971        "Copying with stripping for reverse selection works the same"
 5972    );
 5973
 5974    cx.set_state(
 5975        r#"            for selection «in selections.iter() {
 5976            let mut start = selection.start;
 5977            let mut end = selection.end;
 5978            let is_entire_line = selection.is_empty();
 5979            if is_entire_line {
 5980                start = Point::new(start.row, 0);ˇ»
 5981                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5982            }
 5983        "#,
 5984    );
 5985    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5986    assert_eq!(
 5987        cx.read_from_clipboard()
 5988            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5989        Some(
 5990            "in selections.iter() {
 5991            let mut start = selection.start;
 5992            let mut end = selection.end;
 5993            let is_entire_line = selection.is_empty();
 5994            if is_entire_line {
 5995                start = Point::new(start.row, 0);"
 5996                .to_string()
 5997        ),
 5998        "When selecting past the indent, the copying works as usual",
 5999    );
 6000    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6001    assert_eq!(
 6002        cx.read_from_clipboard()
 6003            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6004        Some(
 6005            "in selections.iter() {
 6006            let mut start = selection.start;
 6007            let mut end = selection.end;
 6008            let is_entire_line = selection.is_empty();
 6009            if is_entire_line {
 6010                start = Point::new(start.row, 0);"
 6011                .to_string()
 6012        ),
 6013        "When selecting past the indent, nothing is trimmed"
 6014    );
 6015
 6016    cx.set_state(
 6017        r#"            «for selection in selections.iter() {
 6018            let mut start = selection.start;
 6019
 6020            let mut end = selection.end;
 6021            let is_entire_line = selection.is_empty();
 6022            if is_entire_line {
 6023                start = Point::new(start.row, 0);
 6024ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6025            }
 6026        "#,
 6027    );
 6028    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6029    assert_eq!(
 6030        cx.read_from_clipboard()
 6031            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6032        Some(
 6033            "for selection in selections.iter() {
 6034let mut start = selection.start;
 6035
 6036let mut end = selection.end;
 6037let is_entire_line = selection.is_empty();
 6038if is_entire_line {
 6039    start = Point::new(start.row, 0);
 6040"
 6041            .to_string()
 6042        ),
 6043        "Copying with stripping should ignore empty lines"
 6044    );
 6045}
 6046
 6047#[gpui::test]
 6048async fn test_paste_multiline(cx: &mut TestAppContext) {
 6049    init_test(cx, |_| {});
 6050
 6051    let mut cx = EditorTestContext::new(cx).await;
 6052    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 6053
 6054    // Cut an indented block, without the leading whitespace.
 6055    cx.set_state(indoc! {"
 6056        const a: B = (
 6057            c(),
 6058            «d(
 6059                e,
 6060                f
 6061            )ˇ»
 6062        );
 6063    "});
 6064    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6065    cx.assert_editor_state(indoc! {"
 6066        const a: B = (
 6067            c(),
 6068            ˇ
 6069        );
 6070    "});
 6071
 6072    // Paste it at the same position.
 6073    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6074    cx.assert_editor_state(indoc! {"
 6075        const a: B = (
 6076            c(),
 6077            d(
 6078                e,
 6079                f
 6080 6081        );
 6082    "});
 6083
 6084    // Paste it at a line with a lower indent level.
 6085    cx.set_state(indoc! {"
 6086        ˇ
 6087        const a: B = (
 6088            c(),
 6089        );
 6090    "});
 6091    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6092    cx.assert_editor_state(indoc! {"
 6093        d(
 6094            e,
 6095            f
 6096 6097        const a: B = (
 6098            c(),
 6099        );
 6100    "});
 6101
 6102    // Cut an indented block, with the leading whitespace.
 6103    cx.set_state(indoc! {"
 6104        const a: B = (
 6105            c(),
 6106        «    d(
 6107                e,
 6108                f
 6109            )
 6110        ˇ»);
 6111    "});
 6112    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6113    cx.assert_editor_state(indoc! {"
 6114        const a: B = (
 6115            c(),
 6116        ˇ);
 6117    "});
 6118
 6119    // Paste it at the same position.
 6120    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6121    cx.assert_editor_state(indoc! {"
 6122        const a: B = (
 6123            c(),
 6124            d(
 6125                e,
 6126                f
 6127            )
 6128        ˇ);
 6129    "});
 6130
 6131    // Paste it at a line with a higher indent level.
 6132    cx.set_state(indoc! {"
 6133        const a: B = (
 6134            c(),
 6135            d(
 6136                e,
 6137 6138            )
 6139        );
 6140    "});
 6141    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6142    cx.assert_editor_state(indoc! {"
 6143        const a: B = (
 6144            c(),
 6145            d(
 6146                e,
 6147                f    d(
 6148                    e,
 6149                    f
 6150                )
 6151        ˇ
 6152            )
 6153        );
 6154    "});
 6155
 6156    // Copy an indented block, starting mid-line
 6157    cx.set_state(indoc! {"
 6158        const a: B = (
 6159            c(),
 6160            somethin«g(
 6161                e,
 6162                f
 6163            )ˇ»
 6164        );
 6165    "});
 6166    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6167
 6168    // Paste it on a line with a lower indent level
 6169    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 6170    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6171    cx.assert_editor_state(indoc! {"
 6172        const a: B = (
 6173            c(),
 6174            something(
 6175                e,
 6176                f
 6177            )
 6178        );
 6179        g(
 6180            e,
 6181            f
 6182"});
 6183}
 6184
 6185#[gpui::test]
 6186async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 6187    init_test(cx, |_| {});
 6188
 6189    cx.write_to_clipboard(ClipboardItem::new_string(
 6190        "    d(\n        e\n    );\n".into(),
 6191    ));
 6192
 6193    let mut cx = EditorTestContext::new(cx).await;
 6194    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 6195
 6196    cx.set_state(indoc! {"
 6197        fn a() {
 6198            b();
 6199            if c() {
 6200                ˇ
 6201            }
 6202        }
 6203    "});
 6204
 6205    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6206    cx.assert_editor_state(indoc! {"
 6207        fn a() {
 6208            b();
 6209            if c() {
 6210                d(
 6211                    e
 6212                );
 6213        ˇ
 6214            }
 6215        }
 6216    "});
 6217
 6218    cx.set_state(indoc! {"
 6219        fn a() {
 6220            b();
 6221            ˇ
 6222        }
 6223    "});
 6224
 6225    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6226    cx.assert_editor_state(indoc! {"
 6227        fn a() {
 6228            b();
 6229            d(
 6230                e
 6231            );
 6232        ˇ
 6233        }
 6234    "});
 6235}
 6236
 6237#[gpui::test]
 6238fn test_select_all(cx: &mut TestAppContext) {
 6239    init_test(cx, |_| {});
 6240
 6241    let editor = cx.add_window(|window, cx| {
 6242        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 6243        build_editor(buffer, window, cx)
 6244    });
 6245    _ = editor.update(cx, |editor, window, cx| {
 6246        editor.select_all(&SelectAll, window, cx);
 6247        assert_eq!(
 6248            editor.selections.display_ranges(cx),
 6249            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 6250        );
 6251    });
 6252}
 6253
 6254#[gpui::test]
 6255fn test_select_line(cx: &mut TestAppContext) {
 6256    init_test(cx, |_| {});
 6257
 6258    let editor = cx.add_window(|window, cx| {
 6259        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 6260        build_editor(buffer, window, cx)
 6261    });
 6262    _ = editor.update(cx, |editor, window, cx| {
 6263        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6264            s.select_display_ranges([
 6265                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6266                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6267                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6268                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 6269            ])
 6270        });
 6271        editor.select_line(&SelectLine, window, cx);
 6272        assert_eq!(
 6273            editor.selections.display_ranges(cx),
 6274            vec![
 6275                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 6276                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 6277            ]
 6278        );
 6279    });
 6280
 6281    _ = editor.update(cx, |editor, window, cx| {
 6282        editor.select_line(&SelectLine, window, cx);
 6283        assert_eq!(
 6284            editor.selections.display_ranges(cx),
 6285            vec![
 6286                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 6287                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 6288            ]
 6289        );
 6290    });
 6291
 6292    _ = editor.update(cx, |editor, window, cx| {
 6293        editor.select_line(&SelectLine, window, cx);
 6294        assert_eq!(
 6295            editor.selections.display_ranges(cx),
 6296            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 6297        );
 6298    });
 6299}
 6300
 6301#[gpui::test]
 6302async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 6303    init_test(cx, |_| {});
 6304    let mut cx = EditorTestContext::new(cx).await;
 6305
 6306    #[track_caller]
 6307    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 6308        cx.set_state(initial_state);
 6309        cx.update_editor(|e, window, cx| {
 6310            e.split_selection_into_lines(&SplitSelectionIntoLines, window, cx)
 6311        });
 6312        cx.assert_editor_state(expected_state);
 6313    }
 6314
 6315    // Selection starts and ends at the middle of lines, left-to-right
 6316    test(
 6317        &mut cx,
 6318        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 6319        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 6320    );
 6321    // Same thing, right-to-left
 6322    test(
 6323        &mut cx,
 6324        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 6325        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 6326    );
 6327
 6328    // Whole buffer, left-to-right, last line *doesn't* end with newline
 6329    test(
 6330        &mut cx,
 6331        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 6332        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 6333    );
 6334    // Same thing, right-to-left
 6335    test(
 6336        &mut cx,
 6337        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 6338        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 6339    );
 6340
 6341    // Whole buffer, left-to-right, last line ends with newline
 6342    test(
 6343        &mut cx,
 6344        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 6345        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 6346    );
 6347    // Same thing, right-to-left
 6348    test(
 6349        &mut cx,
 6350        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 6351        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 6352    );
 6353
 6354    // Starts at the end of a line, ends at the start of another
 6355    test(
 6356        &mut cx,
 6357        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 6358        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 6359    );
 6360}
 6361
 6362#[gpui::test]
 6363async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 6364    init_test(cx, |_| {});
 6365
 6366    let editor = cx.add_window(|window, cx| {
 6367        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 6368        build_editor(buffer, window, cx)
 6369    });
 6370
 6371    // setup
 6372    _ = editor.update(cx, |editor, window, cx| {
 6373        editor.fold_creases(
 6374            vec![
 6375                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 6376                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 6377                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 6378            ],
 6379            true,
 6380            window,
 6381            cx,
 6382        );
 6383        assert_eq!(
 6384            editor.display_text(cx),
 6385            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 6386        );
 6387    });
 6388
 6389    _ = editor.update(cx, |editor, window, cx| {
 6390        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6391            s.select_display_ranges([
 6392                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6393                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6394                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6395                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 6396            ])
 6397        });
 6398        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 6399        assert_eq!(
 6400            editor.display_text(cx),
 6401            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 6402        );
 6403    });
 6404    EditorTestContext::for_editor(editor, cx)
 6405        .await
 6406        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 6407
 6408    _ = editor.update(cx, |editor, window, cx| {
 6409        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6410            s.select_display_ranges([
 6411                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 6412            ])
 6413        });
 6414        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 6415        assert_eq!(
 6416            editor.display_text(cx),
 6417            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 6418        );
 6419        assert_eq!(
 6420            editor.selections.display_ranges(cx),
 6421            [
 6422                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 6423                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 6424                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 6425                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 6426                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 6427                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 6428                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 6429            ]
 6430        );
 6431    });
 6432    EditorTestContext::for_editor(editor, cx)
 6433        .await
 6434        .assert_editor_state(
 6435            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 6436        );
 6437}
 6438
 6439#[gpui::test]
 6440async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 6441    init_test(cx, |_| {});
 6442
 6443    let mut cx = EditorTestContext::new(cx).await;
 6444
 6445    cx.set_state(indoc!(
 6446        r#"abc
 6447           defˇghi
 6448
 6449           jk
 6450           nlmo
 6451           "#
 6452    ));
 6453
 6454    cx.update_editor(|editor, window, cx| {
 6455        editor.add_selection_above(&Default::default(), window, cx);
 6456    });
 6457
 6458    cx.assert_editor_state(indoc!(
 6459        r#"abcˇ
 6460           defˇghi
 6461
 6462           jk
 6463           nlmo
 6464           "#
 6465    ));
 6466
 6467    cx.update_editor(|editor, window, cx| {
 6468        editor.add_selection_above(&Default::default(), window, cx);
 6469    });
 6470
 6471    cx.assert_editor_state(indoc!(
 6472        r#"abcˇ
 6473            defˇghi
 6474
 6475            jk
 6476            nlmo
 6477            "#
 6478    ));
 6479
 6480    cx.update_editor(|editor, window, cx| {
 6481        editor.add_selection_below(&Default::default(), window, cx);
 6482    });
 6483
 6484    cx.assert_editor_state(indoc!(
 6485        r#"abc
 6486           defˇghi
 6487
 6488           jk
 6489           nlmo
 6490           "#
 6491    ));
 6492
 6493    cx.update_editor(|editor, window, cx| {
 6494        editor.undo_selection(&Default::default(), window, cx);
 6495    });
 6496
 6497    cx.assert_editor_state(indoc!(
 6498        r#"abcˇ
 6499           defˇghi
 6500
 6501           jk
 6502           nlmo
 6503           "#
 6504    ));
 6505
 6506    cx.update_editor(|editor, window, cx| {
 6507        editor.redo_selection(&Default::default(), window, cx);
 6508    });
 6509
 6510    cx.assert_editor_state(indoc!(
 6511        r#"abc
 6512           defˇghi
 6513
 6514           jk
 6515           nlmo
 6516           "#
 6517    ));
 6518
 6519    cx.update_editor(|editor, window, cx| {
 6520        editor.add_selection_below(&Default::default(), window, cx);
 6521    });
 6522
 6523    cx.assert_editor_state(indoc!(
 6524        r#"abc
 6525           defˇghi
 6526           ˇ
 6527           jk
 6528           nlmo
 6529           "#
 6530    ));
 6531
 6532    cx.update_editor(|editor, window, cx| {
 6533        editor.add_selection_below(&Default::default(), window, cx);
 6534    });
 6535
 6536    cx.assert_editor_state(indoc!(
 6537        r#"abc
 6538           defˇghi
 6539           ˇ
 6540           jkˇ
 6541           nlmo
 6542           "#
 6543    ));
 6544
 6545    cx.update_editor(|editor, window, cx| {
 6546        editor.add_selection_below(&Default::default(), window, cx);
 6547    });
 6548
 6549    cx.assert_editor_state(indoc!(
 6550        r#"abc
 6551           defˇghi
 6552           ˇ
 6553           jkˇ
 6554           nlmˇo
 6555           "#
 6556    ));
 6557
 6558    cx.update_editor(|editor, window, cx| {
 6559        editor.add_selection_below(&Default::default(), window, cx);
 6560    });
 6561
 6562    cx.assert_editor_state(indoc!(
 6563        r#"abc
 6564           defˇghi
 6565           ˇ
 6566           jkˇ
 6567           nlmˇo
 6568           ˇ"#
 6569    ));
 6570
 6571    // change selections
 6572    cx.set_state(indoc!(
 6573        r#"abc
 6574           def«ˇg»hi
 6575
 6576           jk
 6577           nlmo
 6578           "#
 6579    ));
 6580
 6581    cx.update_editor(|editor, window, cx| {
 6582        editor.add_selection_below(&Default::default(), window, cx);
 6583    });
 6584
 6585    cx.assert_editor_state(indoc!(
 6586        r#"abc
 6587           def«ˇg»hi
 6588
 6589           jk
 6590           nlm«ˇo»
 6591           "#
 6592    ));
 6593
 6594    cx.update_editor(|editor, window, cx| {
 6595        editor.add_selection_below(&Default::default(), window, cx);
 6596    });
 6597
 6598    cx.assert_editor_state(indoc!(
 6599        r#"abc
 6600           def«ˇg»hi
 6601
 6602           jk
 6603           nlm«ˇo»
 6604           "#
 6605    ));
 6606
 6607    cx.update_editor(|editor, window, cx| {
 6608        editor.add_selection_above(&Default::default(), window, cx);
 6609    });
 6610
 6611    cx.assert_editor_state(indoc!(
 6612        r#"abc
 6613           def«ˇg»hi
 6614
 6615           jk
 6616           nlmo
 6617           "#
 6618    ));
 6619
 6620    cx.update_editor(|editor, window, cx| {
 6621        editor.add_selection_above(&Default::default(), window, cx);
 6622    });
 6623
 6624    cx.assert_editor_state(indoc!(
 6625        r#"abc
 6626           def«ˇg»hi
 6627
 6628           jk
 6629           nlmo
 6630           "#
 6631    ));
 6632
 6633    // Change selections again
 6634    cx.set_state(indoc!(
 6635        r#"a«bc
 6636           defgˇ»hi
 6637
 6638           jk
 6639           nlmo
 6640           "#
 6641    ));
 6642
 6643    cx.update_editor(|editor, window, cx| {
 6644        editor.add_selection_below(&Default::default(), window, cx);
 6645    });
 6646
 6647    cx.assert_editor_state(indoc!(
 6648        r#"a«bcˇ»
 6649           d«efgˇ»hi
 6650
 6651           j«kˇ»
 6652           nlmo
 6653           "#
 6654    ));
 6655
 6656    cx.update_editor(|editor, window, cx| {
 6657        editor.add_selection_below(&Default::default(), window, cx);
 6658    });
 6659    cx.assert_editor_state(indoc!(
 6660        r#"a«bcˇ»
 6661           d«efgˇ»hi
 6662
 6663           j«kˇ»
 6664           n«lmoˇ»
 6665           "#
 6666    ));
 6667    cx.update_editor(|editor, window, cx| {
 6668        editor.add_selection_above(&Default::default(), window, cx);
 6669    });
 6670
 6671    cx.assert_editor_state(indoc!(
 6672        r#"a«bcˇ»
 6673           d«efgˇ»hi
 6674
 6675           j«kˇ»
 6676           nlmo
 6677           "#
 6678    ));
 6679
 6680    // Change selections again
 6681    cx.set_state(indoc!(
 6682        r#"abc
 6683           d«ˇefghi
 6684
 6685           jk
 6686           nlm»o
 6687           "#
 6688    ));
 6689
 6690    cx.update_editor(|editor, window, cx| {
 6691        editor.add_selection_above(&Default::default(), window, cx);
 6692    });
 6693
 6694    cx.assert_editor_state(indoc!(
 6695        r#"a«ˇbc»
 6696           d«ˇef»ghi
 6697
 6698           j«ˇk»
 6699           n«ˇlm»o
 6700           "#
 6701    ));
 6702
 6703    cx.update_editor(|editor, window, cx| {
 6704        editor.add_selection_below(&Default::default(), window, cx);
 6705    });
 6706
 6707    cx.assert_editor_state(indoc!(
 6708        r#"abc
 6709           d«ˇef»ghi
 6710
 6711           j«ˇk»
 6712           n«ˇlm»o
 6713           "#
 6714    ));
 6715}
 6716
 6717#[gpui::test]
 6718async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 6719    init_test(cx, |_| {});
 6720    let mut cx = EditorTestContext::new(cx).await;
 6721
 6722    cx.set_state(indoc!(
 6723        r#"line onˇe
 6724           liˇne two
 6725           line three
 6726           line four"#
 6727    ));
 6728
 6729    cx.update_editor(|editor, window, cx| {
 6730        editor.add_selection_below(&Default::default(), window, cx);
 6731    });
 6732
 6733    // test multiple cursors expand in the same direction
 6734    cx.assert_editor_state(indoc!(
 6735        r#"line onˇe
 6736           liˇne twˇo
 6737           liˇne three
 6738           line four"#
 6739    ));
 6740
 6741    cx.update_editor(|editor, window, cx| {
 6742        editor.add_selection_below(&Default::default(), window, cx);
 6743    });
 6744
 6745    cx.update_editor(|editor, window, cx| {
 6746        editor.add_selection_below(&Default::default(), window, cx);
 6747    });
 6748
 6749    // test multiple cursors expand below overflow
 6750    cx.assert_editor_state(indoc!(
 6751        r#"line onˇe
 6752           liˇne twˇo
 6753           liˇne thˇree
 6754           liˇne foˇur"#
 6755    ));
 6756
 6757    cx.update_editor(|editor, window, cx| {
 6758        editor.add_selection_above(&Default::default(), window, cx);
 6759    });
 6760
 6761    // test multiple cursors retrieves back correctly
 6762    cx.assert_editor_state(indoc!(
 6763        r#"line onˇe
 6764           liˇne twˇo
 6765           liˇne thˇree
 6766           line four"#
 6767    ));
 6768
 6769    cx.update_editor(|editor, window, cx| {
 6770        editor.add_selection_above(&Default::default(), window, cx);
 6771    });
 6772
 6773    cx.update_editor(|editor, window, cx| {
 6774        editor.add_selection_above(&Default::default(), window, cx);
 6775    });
 6776
 6777    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 6778    cx.assert_editor_state(indoc!(
 6779        r#"liˇne onˇe
 6780           liˇne two
 6781           line three
 6782           line four"#
 6783    ));
 6784
 6785    cx.update_editor(|editor, window, cx| {
 6786        editor.undo_selection(&Default::default(), window, cx);
 6787    });
 6788
 6789    // test undo
 6790    cx.assert_editor_state(indoc!(
 6791        r#"line onˇe
 6792           liˇne twˇo
 6793           line three
 6794           line four"#
 6795    ));
 6796
 6797    cx.update_editor(|editor, window, cx| {
 6798        editor.redo_selection(&Default::default(), window, cx);
 6799    });
 6800
 6801    // test redo
 6802    cx.assert_editor_state(indoc!(
 6803        r#"liˇne onˇe
 6804           liˇne two
 6805           line three
 6806           line four"#
 6807    ));
 6808
 6809    cx.set_state(indoc!(
 6810        r#"abcd
 6811           ef«ghˇ»
 6812           ijkl
 6813           «mˇ»nop"#
 6814    ));
 6815
 6816    cx.update_editor(|editor, window, cx| {
 6817        editor.add_selection_above(&Default::default(), window, cx);
 6818    });
 6819
 6820    // test multiple selections expand in the same direction
 6821    cx.assert_editor_state(indoc!(
 6822        r#"ab«cdˇ»
 6823           ef«ghˇ»
 6824           «iˇ»jkl
 6825           «mˇ»nop"#
 6826    ));
 6827
 6828    cx.update_editor(|editor, window, cx| {
 6829        editor.add_selection_above(&Default::default(), window, cx);
 6830    });
 6831
 6832    // test multiple selection upward overflow
 6833    cx.assert_editor_state(indoc!(
 6834        r#"ab«cdˇ»
 6835           «eˇ»f«ghˇ»
 6836           «iˇ»jkl
 6837           «mˇ»nop"#
 6838    ));
 6839
 6840    cx.update_editor(|editor, window, cx| {
 6841        editor.add_selection_below(&Default::default(), window, cx);
 6842    });
 6843
 6844    // test multiple selection retrieves back correctly
 6845    cx.assert_editor_state(indoc!(
 6846        r#"abcd
 6847           ef«ghˇ»
 6848           «iˇ»jkl
 6849           «mˇ»nop"#
 6850    ));
 6851
 6852    cx.update_editor(|editor, window, cx| {
 6853        editor.add_selection_below(&Default::default(), window, cx);
 6854    });
 6855
 6856    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 6857    cx.assert_editor_state(indoc!(
 6858        r#"abcd
 6859           ef«ghˇ»
 6860           ij«klˇ»
 6861           «mˇ»nop"#
 6862    ));
 6863
 6864    cx.update_editor(|editor, window, cx| {
 6865        editor.undo_selection(&Default::default(), window, cx);
 6866    });
 6867
 6868    // test undo
 6869    cx.assert_editor_state(indoc!(
 6870        r#"abcd
 6871           ef«ghˇ»
 6872           «iˇ»jkl
 6873           «mˇ»nop"#
 6874    ));
 6875
 6876    cx.update_editor(|editor, window, cx| {
 6877        editor.redo_selection(&Default::default(), window, cx);
 6878    });
 6879
 6880    // test redo
 6881    cx.assert_editor_state(indoc!(
 6882        r#"abcd
 6883           ef«ghˇ»
 6884           ij«klˇ»
 6885           «mˇ»nop"#
 6886    ));
 6887}
 6888
 6889#[gpui::test]
 6890async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 6891    init_test(cx, |_| {});
 6892    let mut cx = EditorTestContext::new(cx).await;
 6893
 6894    cx.set_state(indoc!(
 6895        r#"line onˇe
 6896           liˇne two
 6897           line three
 6898           line four"#
 6899    ));
 6900
 6901    cx.update_editor(|editor, window, cx| {
 6902        editor.add_selection_below(&Default::default(), window, cx);
 6903        editor.add_selection_below(&Default::default(), window, cx);
 6904        editor.add_selection_below(&Default::default(), window, cx);
 6905    });
 6906
 6907    // initial state with two multi cursor groups
 6908    cx.assert_editor_state(indoc!(
 6909        r#"line onˇe
 6910           liˇne twˇo
 6911           liˇne thˇree
 6912           liˇne foˇur"#
 6913    ));
 6914
 6915    // add single cursor in middle - simulate opt click
 6916    cx.update_editor(|editor, window, cx| {
 6917        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 6918        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 6919        editor.end_selection(window, cx);
 6920    });
 6921
 6922    cx.assert_editor_state(indoc!(
 6923        r#"line onˇe
 6924           liˇne twˇo
 6925           liˇneˇ thˇree
 6926           liˇne foˇur"#
 6927    ));
 6928
 6929    cx.update_editor(|editor, window, cx| {
 6930        editor.add_selection_above(&Default::default(), window, cx);
 6931    });
 6932
 6933    // test new added selection expands above and existing selection shrinks
 6934    cx.assert_editor_state(indoc!(
 6935        r#"line onˇe
 6936           liˇneˇ twˇo
 6937           liˇneˇ thˇree
 6938           line four"#
 6939    ));
 6940
 6941    cx.update_editor(|editor, window, cx| {
 6942        editor.add_selection_above(&Default::default(), window, cx);
 6943    });
 6944
 6945    // test new added selection expands above and existing selection shrinks
 6946    cx.assert_editor_state(indoc!(
 6947        r#"lineˇ onˇe
 6948           liˇneˇ twˇo
 6949           lineˇ three
 6950           line four"#
 6951    ));
 6952
 6953    // intial state with two selection groups
 6954    cx.set_state(indoc!(
 6955        r#"abcd
 6956           ef«ghˇ»
 6957           ijkl
 6958           «mˇ»nop"#
 6959    ));
 6960
 6961    cx.update_editor(|editor, window, cx| {
 6962        editor.add_selection_above(&Default::default(), window, cx);
 6963        editor.add_selection_above(&Default::default(), window, cx);
 6964    });
 6965
 6966    cx.assert_editor_state(indoc!(
 6967        r#"ab«cdˇ»
 6968           «eˇ»f«ghˇ»
 6969           «iˇ»jkl
 6970           «mˇ»nop"#
 6971    ));
 6972
 6973    // add single selection in middle - simulate opt drag
 6974    cx.update_editor(|editor, window, cx| {
 6975        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 6976        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 6977        editor.update_selection(
 6978            DisplayPoint::new(DisplayRow(2), 4),
 6979            0,
 6980            gpui::Point::<f32>::default(),
 6981            window,
 6982            cx,
 6983        );
 6984        editor.end_selection(window, cx);
 6985    });
 6986
 6987    cx.assert_editor_state(indoc!(
 6988        r#"ab«cdˇ»
 6989           «eˇ»f«ghˇ»
 6990           «iˇ»jk«lˇ»
 6991           «mˇ»nop"#
 6992    ));
 6993
 6994    cx.update_editor(|editor, window, cx| {
 6995        editor.add_selection_below(&Default::default(), window, cx);
 6996    });
 6997
 6998    // test new added selection expands below, others shrinks from above
 6999    cx.assert_editor_state(indoc!(
 7000        r#"abcd
 7001           ef«ghˇ»
 7002           «iˇ»jk«lˇ»
 7003           «mˇ»no«pˇ»"#
 7004    ));
 7005}
 7006
 7007#[gpui::test]
 7008async fn test_select_next(cx: &mut TestAppContext) {
 7009    init_test(cx, |_| {});
 7010
 7011    let mut cx = EditorTestContext::new(cx).await;
 7012    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7013
 7014    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7015        .unwrap();
 7016    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7017
 7018    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7019        .unwrap();
 7020    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 7021
 7022    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7023    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7024
 7025    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7026    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 7027
 7028    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7029        .unwrap();
 7030    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7031
 7032    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7033        .unwrap();
 7034    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7035
 7036    // Test selection direction should be preserved
 7037    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 7038
 7039    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7040        .unwrap();
 7041    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 7042}
 7043
 7044#[gpui::test]
 7045async fn test_select_all_matches(cx: &mut TestAppContext) {
 7046    init_test(cx, |_| {});
 7047
 7048    let mut cx = EditorTestContext::new(cx).await;
 7049
 7050    // Test caret-only selections
 7051    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7052    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7053        .unwrap();
 7054    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7055
 7056    // Test left-to-right selections
 7057    cx.set_state("abc\n«abcˇ»\nabc");
 7058    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7059        .unwrap();
 7060    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 7061
 7062    // Test right-to-left selections
 7063    cx.set_state("abc\n«ˇabc»\nabc");
 7064    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7065        .unwrap();
 7066    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 7067
 7068    // Test selecting whitespace with caret selection
 7069    cx.set_state("abc\nˇ   abc\nabc");
 7070    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7071        .unwrap();
 7072    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 7073
 7074    // Test selecting whitespace with left-to-right selection
 7075    cx.set_state("abc\n«ˇ  »abc\nabc");
 7076    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7077        .unwrap();
 7078    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 7079
 7080    // Test no matches with right-to-left selection
 7081    cx.set_state("abc\n«  ˇ»abc\nabc");
 7082    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7083        .unwrap();
 7084    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 7085
 7086    // Test with a single word and clip_at_line_ends=true (#29823)
 7087    cx.set_state("aˇbc");
 7088    cx.update_editor(|e, window, cx| {
 7089        e.set_clip_at_line_ends(true, cx);
 7090        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 7091        e.set_clip_at_line_ends(false, cx);
 7092    });
 7093    cx.assert_editor_state("«abcˇ»");
 7094}
 7095
 7096#[gpui::test]
 7097async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 7098    init_test(cx, |_| {});
 7099
 7100    let mut cx = EditorTestContext::new(cx).await;
 7101
 7102    let large_body_1 = "\nd".repeat(200);
 7103    let large_body_2 = "\ne".repeat(200);
 7104
 7105    cx.set_state(&format!(
 7106        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 7107    ));
 7108    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 7109        let scroll_position = editor.scroll_position(cx);
 7110        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 7111        scroll_position
 7112    });
 7113
 7114    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7115        .unwrap();
 7116    cx.assert_editor_state(&format!(
 7117        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 7118    ));
 7119    let scroll_position_after_selection =
 7120        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 7121    assert_eq!(
 7122        initial_scroll_position, scroll_position_after_selection,
 7123        "Scroll position should not change after selecting all matches"
 7124    );
 7125}
 7126
 7127#[gpui::test]
 7128async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 7129    init_test(cx, |_| {});
 7130
 7131    let mut cx = EditorLspTestContext::new_rust(
 7132        lsp::ServerCapabilities {
 7133            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 7134            ..Default::default()
 7135        },
 7136        cx,
 7137    )
 7138    .await;
 7139
 7140    cx.set_state(indoc! {"
 7141        line 1
 7142        line 2
 7143        linˇe 3
 7144        line 4
 7145        line 5
 7146    "});
 7147
 7148    // Make an edit
 7149    cx.update_editor(|editor, window, cx| {
 7150        editor.handle_input("X", window, cx);
 7151    });
 7152
 7153    // Move cursor to a different position
 7154    cx.update_editor(|editor, window, cx| {
 7155        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7156            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 7157        });
 7158    });
 7159
 7160    cx.assert_editor_state(indoc! {"
 7161        line 1
 7162        line 2
 7163        linXe 3
 7164        line 4
 7165        liˇne 5
 7166    "});
 7167
 7168    cx.lsp
 7169        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 7170            Ok(Some(vec![lsp::TextEdit::new(
 7171                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 7172                "PREFIX ".to_string(),
 7173            )]))
 7174        });
 7175
 7176    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 7177        .unwrap()
 7178        .await
 7179        .unwrap();
 7180
 7181    cx.assert_editor_state(indoc! {"
 7182        PREFIX line 1
 7183        line 2
 7184        linXe 3
 7185        line 4
 7186        liˇne 5
 7187    "});
 7188
 7189    // Undo formatting
 7190    cx.update_editor(|editor, window, cx| {
 7191        editor.undo(&Default::default(), window, cx);
 7192    });
 7193
 7194    // Verify cursor moved back to position after edit
 7195    cx.assert_editor_state(indoc! {"
 7196        line 1
 7197        line 2
 7198        linXˇe 3
 7199        line 4
 7200        line 5
 7201    "});
 7202}
 7203
 7204#[gpui::test]
 7205async fn test_undo_inline_completion_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 7206    init_test(cx, |_| {});
 7207
 7208    let mut cx = EditorTestContext::new(cx).await;
 7209
 7210    let provider = cx.new(|_| FakeInlineCompletionProvider::default());
 7211    cx.update_editor(|editor, window, cx| {
 7212        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 7213    });
 7214
 7215    cx.set_state(indoc! {"
 7216        line 1
 7217        line 2
 7218        linˇe 3
 7219        line 4
 7220        line 5
 7221        line 6
 7222        line 7
 7223        line 8
 7224        line 9
 7225        line 10
 7226    "});
 7227
 7228    let snapshot = cx.buffer_snapshot();
 7229    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 7230
 7231    cx.update(|_, cx| {
 7232        provider.update(cx, |provider, _| {
 7233            provider.set_inline_completion(Some(inline_completion::InlineCompletion {
 7234                id: None,
 7235                edits: vec![(edit_position..edit_position, "X".into())],
 7236                edit_preview: None,
 7237            }))
 7238        })
 7239    });
 7240
 7241    cx.update_editor(|editor, window, cx| editor.update_visible_inline_completion(window, cx));
 7242    cx.update_editor(|editor, window, cx| {
 7243        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 7244    });
 7245
 7246    cx.assert_editor_state(indoc! {"
 7247        line 1
 7248        line 2
 7249        lineXˇ 3
 7250        line 4
 7251        line 5
 7252        line 6
 7253        line 7
 7254        line 8
 7255        line 9
 7256        line 10
 7257    "});
 7258
 7259    cx.update_editor(|editor, window, cx| {
 7260        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7261            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 7262        });
 7263    });
 7264
 7265    cx.assert_editor_state(indoc! {"
 7266        line 1
 7267        line 2
 7268        lineX 3
 7269        line 4
 7270        line 5
 7271        line 6
 7272        line 7
 7273        line 8
 7274        line 9
 7275        liˇne 10
 7276    "});
 7277
 7278    cx.update_editor(|editor, window, cx| {
 7279        editor.undo(&Default::default(), window, cx);
 7280    });
 7281
 7282    cx.assert_editor_state(indoc! {"
 7283        line 1
 7284        line 2
 7285        lineˇ 3
 7286        line 4
 7287        line 5
 7288        line 6
 7289        line 7
 7290        line 8
 7291        line 9
 7292        line 10
 7293    "});
 7294}
 7295
 7296#[gpui::test]
 7297async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 7298    init_test(cx, |_| {});
 7299
 7300    let mut cx = EditorTestContext::new(cx).await;
 7301    cx.set_state(
 7302        r#"let foo = 2;
 7303lˇet foo = 2;
 7304let fooˇ = 2;
 7305let foo = 2;
 7306let foo = ˇ2;"#,
 7307    );
 7308
 7309    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7310        .unwrap();
 7311    cx.assert_editor_state(
 7312        r#"let foo = 2;
 7313«letˇ» foo = 2;
 7314let «fooˇ» = 2;
 7315let foo = 2;
 7316let foo = «2ˇ»;"#,
 7317    );
 7318
 7319    // noop for multiple selections with different contents
 7320    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7321        .unwrap();
 7322    cx.assert_editor_state(
 7323        r#"let foo = 2;
 7324«letˇ» foo = 2;
 7325let «fooˇ» = 2;
 7326let foo = 2;
 7327let foo = «2ˇ»;"#,
 7328    );
 7329
 7330    // Test last selection direction should be preserved
 7331    cx.set_state(
 7332        r#"let foo = 2;
 7333let foo = 2;
 7334let «fooˇ» = 2;
 7335let «ˇfoo» = 2;
 7336let foo = 2;"#,
 7337    );
 7338
 7339    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7340        .unwrap();
 7341    cx.assert_editor_state(
 7342        r#"let foo = 2;
 7343let foo = 2;
 7344let «fooˇ» = 2;
 7345let «ˇfoo» = 2;
 7346let «ˇfoo» = 2;"#,
 7347    );
 7348}
 7349
 7350#[gpui::test]
 7351async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 7352    init_test(cx, |_| {});
 7353
 7354    let mut cx =
 7355        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 7356
 7357    cx.assert_editor_state(indoc! {"
 7358        ˇbbb
 7359        ccc
 7360
 7361        bbb
 7362        ccc
 7363        "});
 7364    cx.dispatch_action(SelectPrevious::default());
 7365    cx.assert_editor_state(indoc! {"
 7366                «bbbˇ»
 7367                ccc
 7368
 7369                bbb
 7370                ccc
 7371                "});
 7372    cx.dispatch_action(SelectPrevious::default());
 7373    cx.assert_editor_state(indoc! {"
 7374                «bbbˇ»
 7375                ccc
 7376
 7377                «bbbˇ»
 7378                ccc
 7379                "});
 7380}
 7381
 7382#[gpui::test]
 7383async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 7384    init_test(cx, |_| {});
 7385
 7386    let mut cx = EditorTestContext::new(cx).await;
 7387    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7388
 7389    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7390        .unwrap();
 7391    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7392
 7393    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7394        .unwrap();
 7395    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 7396
 7397    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7398    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7399
 7400    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7401    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 7402
 7403    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7404        .unwrap();
 7405    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 7406
 7407    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7408        .unwrap();
 7409    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7410}
 7411
 7412#[gpui::test]
 7413async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 7414    init_test(cx, |_| {});
 7415
 7416    let mut cx = EditorTestContext::new(cx).await;
 7417    cx.set_state("");
 7418
 7419    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7420        .unwrap();
 7421    cx.assert_editor_state("«aˇ»");
 7422    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7423        .unwrap();
 7424    cx.assert_editor_state("«aˇ»");
 7425}
 7426
 7427#[gpui::test]
 7428async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 7429    init_test(cx, |_| {});
 7430
 7431    let mut cx = EditorTestContext::new(cx).await;
 7432    cx.set_state(
 7433        r#"let foo = 2;
 7434lˇet foo = 2;
 7435let fooˇ = 2;
 7436let foo = 2;
 7437let foo = ˇ2;"#,
 7438    );
 7439
 7440    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7441        .unwrap();
 7442    cx.assert_editor_state(
 7443        r#"let foo = 2;
 7444«letˇ» foo = 2;
 7445let «fooˇ» = 2;
 7446let foo = 2;
 7447let foo = «2ˇ»;"#,
 7448    );
 7449
 7450    // noop for multiple selections with different contents
 7451    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7452        .unwrap();
 7453    cx.assert_editor_state(
 7454        r#"let foo = 2;
 7455«letˇ» foo = 2;
 7456let «fooˇ» = 2;
 7457let foo = 2;
 7458let foo = «2ˇ»;"#,
 7459    );
 7460}
 7461
 7462#[gpui::test]
 7463async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 7464    init_test(cx, |_| {});
 7465
 7466    let mut cx = EditorTestContext::new(cx).await;
 7467    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 7468
 7469    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7470        .unwrap();
 7471    // selection direction is preserved
 7472    cx.assert_editor_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    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7477
 7478    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7479    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 7480
 7481    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7482    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7483
 7484    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7485        .unwrap();
 7486    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 7487
 7488    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7489        .unwrap();
 7490    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 7491}
 7492
 7493#[gpui::test]
 7494async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 7495    init_test(cx, |_| {});
 7496
 7497    let language = Arc::new(Language::new(
 7498        LanguageConfig::default(),
 7499        Some(tree_sitter_rust::LANGUAGE.into()),
 7500    ));
 7501
 7502    let text = r#"
 7503        use mod1::mod2::{mod3, mod4};
 7504
 7505        fn fn_1(param1: bool, param2: &str) {
 7506            let var1 = "text";
 7507        }
 7508    "#
 7509    .unindent();
 7510
 7511    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7512    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7513    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7514
 7515    editor
 7516        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7517        .await;
 7518
 7519    editor.update_in(cx, |editor, window, cx| {
 7520        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7521            s.select_display_ranges([
 7522                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 7523                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 7524                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 7525            ]);
 7526        });
 7527        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7528    });
 7529    editor.update(cx, |editor, cx| {
 7530        assert_text_with_selections(
 7531            editor,
 7532            indoc! {r#"
 7533                use mod1::mod2::{mod3, «mod4ˇ»};
 7534
 7535                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7536                    let var1 = "«ˇtext»";
 7537                }
 7538            "#},
 7539            cx,
 7540        );
 7541    });
 7542
 7543    editor.update_in(cx, |editor, window, cx| {
 7544        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7545    });
 7546    editor.update(cx, |editor, cx| {
 7547        assert_text_with_selections(
 7548            editor,
 7549            indoc! {r#"
 7550                use mod1::mod2::«{mod3, mod4}ˇ»;
 7551
 7552                «ˇfn fn_1(param1: bool, param2: &str) {
 7553                    let var1 = "text";
 7554 7555            "#},
 7556            cx,
 7557        );
 7558    });
 7559
 7560    editor.update_in(cx, |editor, window, cx| {
 7561        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7562    });
 7563    assert_eq!(
 7564        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7565        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7566    );
 7567
 7568    // Trying to expand the selected syntax node one more time has no effect.
 7569    editor.update_in(cx, |editor, window, cx| {
 7570        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7571    });
 7572    assert_eq!(
 7573        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7574        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7575    );
 7576
 7577    editor.update_in(cx, |editor, window, cx| {
 7578        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7579    });
 7580    editor.update(cx, |editor, cx| {
 7581        assert_text_with_selections(
 7582            editor,
 7583            indoc! {r#"
 7584                use mod1::mod2::«{mod3, mod4}ˇ»;
 7585
 7586                «ˇfn fn_1(param1: bool, param2: &str) {
 7587                    let var1 = "text";
 7588 7589            "#},
 7590            cx,
 7591        );
 7592    });
 7593
 7594    editor.update_in(cx, |editor, window, cx| {
 7595        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7596    });
 7597    editor.update(cx, |editor, cx| {
 7598        assert_text_with_selections(
 7599            editor,
 7600            indoc! {r#"
 7601                use mod1::mod2::{mod3, «mod4ˇ»};
 7602
 7603                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7604                    let var1 = "«ˇtext»";
 7605                }
 7606            "#},
 7607            cx,
 7608        );
 7609    });
 7610
 7611    editor.update_in(cx, |editor, window, cx| {
 7612        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7613    });
 7614    editor.update(cx, |editor, cx| {
 7615        assert_text_with_selections(
 7616            editor,
 7617            indoc! {r#"
 7618                use mod1::mod2::{mod3, mo«ˇ»d4};
 7619
 7620                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7621                    let var1 = "te«ˇ»xt";
 7622                }
 7623            "#},
 7624            cx,
 7625        );
 7626    });
 7627
 7628    // Trying to shrink the selected syntax node one more time has no effect.
 7629    editor.update_in(cx, |editor, window, cx| {
 7630        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7631    });
 7632    editor.update_in(cx, |editor, _, cx| {
 7633        assert_text_with_selections(
 7634            editor,
 7635            indoc! {r#"
 7636                use mod1::mod2::{mod3, mo«ˇ»d4};
 7637
 7638                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7639                    let var1 = "te«ˇ»xt";
 7640                }
 7641            "#},
 7642            cx,
 7643        );
 7644    });
 7645
 7646    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 7647    // a fold.
 7648    editor.update_in(cx, |editor, window, cx| {
 7649        editor.fold_creases(
 7650            vec![
 7651                Crease::simple(
 7652                    Point::new(0, 21)..Point::new(0, 24),
 7653                    FoldPlaceholder::test(),
 7654                ),
 7655                Crease::simple(
 7656                    Point::new(3, 20)..Point::new(3, 22),
 7657                    FoldPlaceholder::test(),
 7658                ),
 7659            ],
 7660            true,
 7661            window,
 7662            cx,
 7663        );
 7664        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7665    });
 7666    editor.update(cx, |editor, cx| {
 7667        assert_text_with_selections(
 7668            editor,
 7669            indoc! {r#"
 7670                use mod1::mod2::«{mod3, mod4}ˇ»;
 7671
 7672                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7673                    let var1 = "«ˇtext»";
 7674                }
 7675            "#},
 7676            cx,
 7677        );
 7678    });
 7679}
 7680
 7681#[gpui::test]
 7682async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 7683    init_test(cx, |_| {});
 7684
 7685    let language = Arc::new(Language::new(
 7686        LanguageConfig::default(),
 7687        Some(tree_sitter_rust::LANGUAGE.into()),
 7688    ));
 7689
 7690    let text = "let a = 2;";
 7691
 7692    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7693    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7694    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7695
 7696    editor
 7697        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7698        .await;
 7699
 7700    // Test case 1: Cursor at end of word
 7701    editor.update_in(cx, |editor, window, cx| {
 7702        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7703            s.select_display_ranges([
 7704                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 7705            ]);
 7706        });
 7707    });
 7708    editor.update(cx, |editor, cx| {
 7709        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 7710    });
 7711    editor.update_in(cx, |editor, window, cx| {
 7712        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7713    });
 7714    editor.update(cx, |editor, cx| {
 7715        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 7716    });
 7717    editor.update_in(cx, |editor, window, cx| {
 7718        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7719    });
 7720    editor.update(cx, |editor, cx| {
 7721        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7722    });
 7723
 7724    // Test case 2: Cursor at end of statement
 7725    editor.update_in(cx, |editor, window, cx| {
 7726        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7727            s.select_display_ranges([
 7728                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 7729            ]);
 7730        });
 7731    });
 7732    editor.update(cx, |editor, cx| {
 7733        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 7734    });
 7735    editor.update_in(cx, |editor, window, cx| {
 7736        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7737    });
 7738    editor.update(cx, |editor, cx| {
 7739        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7740    });
 7741}
 7742
 7743#[gpui::test]
 7744async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 7745    init_test(cx, |_| {});
 7746
 7747    let language = Arc::new(Language::new(
 7748        LanguageConfig::default(),
 7749        Some(tree_sitter_rust::LANGUAGE.into()),
 7750    ));
 7751
 7752    let text = r#"
 7753        use mod1::mod2::{mod3, mod4};
 7754
 7755        fn fn_1(param1: bool, param2: &str) {
 7756            let var1 = "hello world";
 7757        }
 7758    "#
 7759    .unindent();
 7760
 7761    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7762    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7763    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7764
 7765    editor
 7766        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7767        .await;
 7768
 7769    // Test 1: Cursor on a letter of a string word
 7770    editor.update_in(cx, |editor, window, cx| {
 7771        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7772            s.select_display_ranges([
 7773                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 7774            ]);
 7775        });
 7776    });
 7777    editor.update_in(cx, |editor, window, cx| {
 7778        assert_text_with_selections(
 7779            editor,
 7780            indoc! {r#"
 7781                use mod1::mod2::{mod3, mod4};
 7782
 7783                fn fn_1(param1: bool, param2: &str) {
 7784                    let var1 = "hˇello world";
 7785                }
 7786            "#},
 7787            cx,
 7788        );
 7789        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7790        assert_text_with_selections(
 7791            editor,
 7792            indoc! {r#"
 7793                use mod1::mod2::{mod3, mod4};
 7794
 7795                fn fn_1(param1: bool, param2: &str) {
 7796                    let var1 = "«ˇhello» world";
 7797                }
 7798            "#},
 7799            cx,
 7800        );
 7801    });
 7802
 7803    // Test 2: Partial selection within a word
 7804    editor.update_in(cx, |editor, window, cx| {
 7805        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7806            s.select_display_ranges([
 7807                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 7808            ]);
 7809        });
 7810    });
 7811    editor.update_in(cx, |editor, window, cx| {
 7812        assert_text_with_selections(
 7813            editor,
 7814            indoc! {r#"
 7815                use mod1::mod2::{mod3, mod4};
 7816
 7817                fn fn_1(param1: bool, param2: &str) {
 7818                    let var1 = "h«elˇ»lo world";
 7819                }
 7820            "#},
 7821            cx,
 7822        );
 7823        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7824        assert_text_with_selections(
 7825            editor,
 7826            indoc! {r#"
 7827                use mod1::mod2::{mod3, mod4};
 7828
 7829                fn fn_1(param1: bool, param2: &str) {
 7830                    let var1 = "«ˇhello» world";
 7831                }
 7832            "#},
 7833            cx,
 7834        );
 7835    });
 7836
 7837    // Test 3: Complete word already selected
 7838    editor.update_in(cx, |editor, window, cx| {
 7839        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7840            s.select_display_ranges([
 7841                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 7842            ]);
 7843        });
 7844    });
 7845    editor.update_in(cx, |editor, window, cx| {
 7846        assert_text_with_selections(
 7847            editor,
 7848            indoc! {r#"
 7849                use mod1::mod2::{mod3, mod4};
 7850
 7851                fn fn_1(param1: bool, param2: &str) {
 7852                    let var1 = "«helloˇ» world";
 7853                }
 7854            "#},
 7855            cx,
 7856        );
 7857        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7858        assert_text_with_selections(
 7859            editor,
 7860            indoc! {r#"
 7861                use mod1::mod2::{mod3, mod4};
 7862
 7863                fn fn_1(param1: bool, param2: &str) {
 7864                    let var1 = "«hello worldˇ»";
 7865                }
 7866            "#},
 7867            cx,
 7868        );
 7869    });
 7870
 7871    // Test 4: Selection spanning across words
 7872    editor.update_in(cx, |editor, window, cx| {
 7873        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7874            s.select_display_ranges([
 7875                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 7876            ]);
 7877        });
 7878    });
 7879    editor.update_in(cx, |editor, window, cx| {
 7880        assert_text_with_selections(
 7881            editor,
 7882            indoc! {r#"
 7883                use mod1::mod2::{mod3, mod4};
 7884
 7885                fn fn_1(param1: bool, param2: &str) {
 7886                    let var1 = "hel«lo woˇ»rld";
 7887                }
 7888            "#},
 7889            cx,
 7890        );
 7891        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7892        assert_text_with_selections(
 7893            editor,
 7894            indoc! {r#"
 7895                use mod1::mod2::{mod3, mod4};
 7896
 7897                fn fn_1(param1: bool, param2: &str) {
 7898                    let var1 = "«ˇhello world»";
 7899                }
 7900            "#},
 7901            cx,
 7902        );
 7903    });
 7904
 7905    // Test 5: Expansion beyond string
 7906    editor.update_in(cx, |editor, window, cx| {
 7907        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7908        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7909        assert_text_with_selections(
 7910            editor,
 7911            indoc! {r#"
 7912                use mod1::mod2::{mod3, mod4};
 7913
 7914                fn fn_1(param1: bool, param2: &str) {
 7915                    «ˇlet var1 = "hello world";»
 7916                }
 7917            "#},
 7918            cx,
 7919        );
 7920    });
 7921}
 7922
 7923#[gpui::test]
 7924async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 7925    init_test(cx, |_| {});
 7926
 7927    let base_text = r#"
 7928        impl A {
 7929            // this is an uncommitted comment
 7930
 7931            fn b() {
 7932                c();
 7933            }
 7934
 7935            // this is another uncommitted comment
 7936
 7937            fn d() {
 7938                // e
 7939                // f
 7940            }
 7941        }
 7942
 7943        fn g() {
 7944            // h
 7945        }
 7946    "#
 7947    .unindent();
 7948
 7949    let text = r#"
 7950        ˇimpl A {
 7951
 7952            fn b() {
 7953                c();
 7954            }
 7955
 7956            fn d() {
 7957                // e
 7958                // f
 7959            }
 7960        }
 7961
 7962        fn g() {
 7963            // h
 7964        }
 7965    "#
 7966    .unindent();
 7967
 7968    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 7969    cx.set_state(&text);
 7970    cx.set_head_text(&base_text);
 7971    cx.update_editor(|editor, window, cx| {
 7972        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 7973    });
 7974
 7975    cx.assert_state_with_diff(
 7976        "
 7977        ˇimpl A {
 7978      -     // this is an uncommitted comment
 7979
 7980            fn b() {
 7981                c();
 7982            }
 7983
 7984      -     // this is another uncommitted comment
 7985      -
 7986            fn d() {
 7987                // e
 7988                // f
 7989            }
 7990        }
 7991
 7992        fn g() {
 7993            // h
 7994        }
 7995    "
 7996        .unindent(),
 7997    );
 7998
 7999    let expected_display_text = "
 8000        impl A {
 8001            // this is an uncommitted comment
 8002
 8003            fn b() {
 8004 8005            }
 8006
 8007            // this is another uncommitted comment
 8008
 8009            fn d() {
 8010 8011            }
 8012        }
 8013
 8014        fn g() {
 8015 8016        }
 8017        "
 8018    .unindent();
 8019
 8020    cx.update_editor(|editor, window, cx| {
 8021        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 8022        assert_eq!(editor.display_text(cx), expected_display_text);
 8023    });
 8024}
 8025
 8026#[gpui::test]
 8027async fn test_autoindent(cx: &mut TestAppContext) {
 8028    init_test(cx, |_| {});
 8029
 8030    let language = Arc::new(
 8031        Language::new(
 8032            LanguageConfig {
 8033                brackets: BracketPairConfig {
 8034                    pairs: vec![
 8035                        BracketPair {
 8036                            start: "{".to_string(),
 8037                            end: "}".to_string(),
 8038                            close: false,
 8039                            surround: false,
 8040                            newline: true,
 8041                        },
 8042                        BracketPair {
 8043                            start: "(".to_string(),
 8044                            end: ")".to_string(),
 8045                            close: false,
 8046                            surround: false,
 8047                            newline: true,
 8048                        },
 8049                    ],
 8050                    ..Default::default()
 8051                },
 8052                ..Default::default()
 8053            },
 8054            Some(tree_sitter_rust::LANGUAGE.into()),
 8055        )
 8056        .with_indents_query(
 8057            r#"
 8058                (_ "(" ")" @end) @indent
 8059                (_ "{" "}" @end) @indent
 8060            "#,
 8061        )
 8062        .unwrap(),
 8063    );
 8064
 8065    let text = "fn a() {}";
 8066
 8067    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8068    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8069    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8070    editor
 8071        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8072        .await;
 8073
 8074    editor.update_in(cx, |editor, window, cx| {
 8075        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8076            s.select_ranges([5..5, 8..8, 9..9])
 8077        });
 8078        editor.newline(&Newline, window, cx);
 8079        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 8080        assert_eq!(
 8081            editor.selections.ranges(cx),
 8082            &[
 8083                Point::new(1, 4)..Point::new(1, 4),
 8084                Point::new(3, 4)..Point::new(3, 4),
 8085                Point::new(5, 0)..Point::new(5, 0)
 8086            ]
 8087        );
 8088    });
 8089}
 8090
 8091#[gpui::test]
 8092async fn test_autoindent_selections(cx: &mut TestAppContext) {
 8093    init_test(cx, |_| {});
 8094
 8095    {
 8096        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 8097        cx.set_state(indoc! {"
 8098            impl A {
 8099
 8100                fn b() {}
 8101
 8102            «fn c() {
 8103
 8104            }ˇ»
 8105            }
 8106        "});
 8107
 8108        cx.update_editor(|editor, window, cx| {
 8109            editor.autoindent(&Default::default(), window, cx);
 8110        });
 8111
 8112        cx.assert_editor_state(indoc! {"
 8113            impl A {
 8114
 8115                fn b() {}
 8116
 8117                «fn c() {
 8118
 8119                }ˇ»
 8120            }
 8121        "});
 8122    }
 8123
 8124    {
 8125        let mut cx = EditorTestContext::new_multibuffer(
 8126            cx,
 8127            [indoc! { "
 8128                impl A {
 8129                «
 8130                // a
 8131                fn b(){}
 8132                »
 8133                «
 8134                    }
 8135                    fn c(){}
 8136                »
 8137            "}],
 8138        );
 8139
 8140        let buffer = cx.update_editor(|editor, _, cx| {
 8141            let buffer = editor.buffer().update(cx, |buffer, _| {
 8142                buffer.all_buffers().iter().next().unwrap().clone()
 8143            });
 8144            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 8145            buffer
 8146        });
 8147
 8148        cx.run_until_parked();
 8149        cx.update_editor(|editor, window, cx| {
 8150            editor.select_all(&Default::default(), window, cx);
 8151            editor.autoindent(&Default::default(), window, cx)
 8152        });
 8153        cx.run_until_parked();
 8154
 8155        cx.update(|_, cx| {
 8156            assert_eq!(
 8157                buffer.read(cx).text(),
 8158                indoc! { "
 8159                    impl A {
 8160
 8161                        // a
 8162                        fn b(){}
 8163
 8164
 8165                    }
 8166                    fn c(){}
 8167
 8168                " }
 8169            )
 8170        });
 8171    }
 8172}
 8173
 8174#[gpui::test]
 8175async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 8176    init_test(cx, |_| {});
 8177
 8178    let mut cx = EditorTestContext::new(cx).await;
 8179
 8180    let language = Arc::new(Language::new(
 8181        LanguageConfig {
 8182            brackets: BracketPairConfig {
 8183                pairs: vec![
 8184                    BracketPair {
 8185                        start: "{".to_string(),
 8186                        end: "}".to_string(),
 8187                        close: true,
 8188                        surround: true,
 8189                        newline: true,
 8190                    },
 8191                    BracketPair {
 8192                        start: "(".to_string(),
 8193                        end: ")".to_string(),
 8194                        close: true,
 8195                        surround: true,
 8196                        newline: true,
 8197                    },
 8198                    BracketPair {
 8199                        start: "/*".to_string(),
 8200                        end: " */".to_string(),
 8201                        close: true,
 8202                        surround: true,
 8203                        newline: true,
 8204                    },
 8205                    BracketPair {
 8206                        start: "[".to_string(),
 8207                        end: "]".to_string(),
 8208                        close: false,
 8209                        surround: false,
 8210                        newline: true,
 8211                    },
 8212                    BracketPair {
 8213                        start: "\"".to_string(),
 8214                        end: "\"".to_string(),
 8215                        close: true,
 8216                        surround: true,
 8217                        newline: false,
 8218                    },
 8219                    BracketPair {
 8220                        start: "<".to_string(),
 8221                        end: ">".to_string(),
 8222                        close: false,
 8223                        surround: true,
 8224                        newline: true,
 8225                    },
 8226                ],
 8227                ..Default::default()
 8228            },
 8229            autoclose_before: "})]".to_string(),
 8230            ..Default::default()
 8231        },
 8232        Some(tree_sitter_rust::LANGUAGE.into()),
 8233    ));
 8234
 8235    cx.language_registry().add(language.clone());
 8236    cx.update_buffer(|buffer, cx| {
 8237        buffer.set_language(Some(language), cx);
 8238    });
 8239
 8240    cx.set_state(
 8241        &r#"
 8242            🏀ˇ
 8243            εˇ
 8244            ❤️ˇ
 8245        "#
 8246        .unindent(),
 8247    );
 8248
 8249    // autoclose multiple nested brackets at multiple cursors
 8250    cx.update_editor(|editor, window, cx| {
 8251        editor.handle_input("{", window, cx);
 8252        editor.handle_input("{", window, cx);
 8253        editor.handle_input("{", window, cx);
 8254    });
 8255    cx.assert_editor_state(
 8256        &"
 8257            🏀{{{ˇ}}}
 8258            ε{{{ˇ}}}
 8259            ❤️{{{ˇ}}}
 8260        "
 8261        .unindent(),
 8262    );
 8263
 8264    // insert a different closing bracket
 8265    cx.update_editor(|editor, window, cx| {
 8266        editor.handle_input(")", window, cx);
 8267    });
 8268    cx.assert_editor_state(
 8269        &"
 8270            🏀{{{)ˇ}}}
 8271            ε{{{)ˇ}}}
 8272            ❤️{{{)ˇ}}}
 8273        "
 8274        .unindent(),
 8275    );
 8276
 8277    // skip over the auto-closed brackets when typing a closing bracket
 8278    cx.update_editor(|editor, window, cx| {
 8279        editor.move_right(&MoveRight, window, cx);
 8280        editor.handle_input("}", window, cx);
 8281        editor.handle_input("}", window, cx);
 8282        editor.handle_input("}", window, cx);
 8283    });
 8284    cx.assert_editor_state(
 8285        &"
 8286            🏀{{{)}}}}ˇ
 8287            ε{{{)}}}}ˇ
 8288            ❤️{{{)}}}}ˇ
 8289        "
 8290        .unindent(),
 8291    );
 8292
 8293    // autoclose multi-character pairs
 8294    cx.set_state(
 8295        &"
 8296            ˇ
 8297            ˇ
 8298        "
 8299        .unindent(),
 8300    );
 8301    cx.update_editor(|editor, window, cx| {
 8302        editor.handle_input("/", window, cx);
 8303        editor.handle_input("*", window, cx);
 8304    });
 8305    cx.assert_editor_state(
 8306        &"
 8307            /*ˇ */
 8308            /*ˇ */
 8309        "
 8310        .unindent(),
 8311    );
 8312
 8313    // one cursor autocloses a multi-character pair, one cursor
 8314    // does not autoclose.
 8315    cx.set_state(
 8316        &"
 8317 8318            ˇ
 8319        "
 8320        .unindent(),
 8321    );
 8322    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 8323    cx.assert_editor_state(
 8324        &"
 8325            /*ˇ */
 8326 8327        "
 8328        .unindent(),
 8329    );
 8330
 8331    // Don't autoclose if the next character isn't whitespace and isn't
 8332    // listed in the language's "autoclose_before" section.
 8333    cx.set_state("ˇa b");
 8334    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8335    cx.assert_editor_state("{ˇa b");
 8336
 8337    // Don't autoclose if `close` is false for the bracket pair
 8338    cx.set_state("ˇ");
 8339    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 8340    cx.assert_editor_state("");
 8341
 8342    // Surround with brackets if text is selected
 8343    cx.set_state("«aˇ» b");
 8344    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8345    cx.assert_editor_state("{«aˇ»} b");
 8346
 8347    // Autoclose when not immediately after a word character
 8348    cx.set_state("a ˇ");
 8349    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8350    cx.assert_editor_state("a \"ˇ\"");
 8351
 8352    // Autoclose pair where the start and end characters are the same
 8353    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8354    cx.assert_editor_state("a \"\"ˇ");
 8355
 8356    // Don't autoclose when immediately after a word character
 8357    cx.set_state("");
 8358    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8359    cx.assert_editor_state("a\"ˇ");
 8360
 8361    // Do autoclose when after a non-word character
 8362    cx.set_state("");
 8363    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8364    cx.assert_editor_state("{\"ˇ\"");
 8365
 8366    // Non identical pairs autoclose regardless of preceding character
 8367    cx.set_state("");
 8368    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8369    cx.assert_editor_state("a{ˇ}");
 8370
 8371    // Don't autoclose pair if autoclose is disabled
 8372    cx.set_state("ˇ");
 8373    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 8374    cx.assert_editor_state("");
 8375
 8376    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 8377    cx.set_state("«aˇ» b");
 8378    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 8379    cx.assert_editor_state("<«aˇ»> b");
 8380}
 8381
 8382#[gpui::test]
 8383async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 8384    init_test(cx, |settings| {
 8385        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 8386    });
 8387
 8388    let mut cx = EditorTestContext::new(cx).await;
 8389
 8390    let language = Arc::new(Language::new(
 8391        LanguageConfig {
 8392            brackets: BracketPairConfig {
 8393                pairs: vec![
 8394                    BracketPair {
 8395                        start: "{".to_string(),
 8396                        end: "}".to_string(),
 8397                        close: true,
 8398                        surround: true,
 8399                        newline: true,
 8400                    },
 8401                    BracketPair {
 8402                        start: "(".to_string(),
 8403                        end: ")".to_string(),
 8404                        close: true,
 8405                        surround: true,
 8406                        newline: true,
 8407                    },
 8408                    BracketPair {
 8409                        start: "[".to_string(),
 8410                        end: "]".to_string(),
 8411                        close: false,
 8412                        surround: false,
 8413                        newline: true,
 8414                    },
 8415                ],
 8416                ..Default::default()
 8417            },
 8418            autoclose_before: "})]".to_string(),
 8419            ..Default::default()
 8420        },
 8421        Some(tree_sitter_rust::LANGUAGE.into()),
 8422    ));
 8423
 8424    cx.language_registry().add(language.clone());
 8425    cx.update_buffer(|buffer, cx| {
 8426        buffer.set_language(Some(language), cx);
 8427    });
 8428
 8429    cx.set_state(
 8430        &"
 8431            ˇ
 8432            ˇ
 8433            ˇ
 8434        "
 8435        .unindent(),
 8436    );
 8437
 8438    // ensure only matching closing brackets are skipped over
 8439    cx.update_editor(|editor, window, cx| {
 8440        editor.handle_input("}", window, cx);
 8441        editor.move_left(&MoveLeft, window, cx);
 8442        editor.handle_input(")", window, cx);
 8443        editor.move_left(&MoveLeft, window, cx);
 8444    });
 8445    cx.assert_editor_state(
 8446        &"
 8447            ˇ)}
 8448            ˇ)}
 8449            ˇ)}
 8450        "
 8451        .unindent(),
 8452    );
 8453
 8454    // skip-over closing brackets at multiple cursors
 8455    cx.update_editor(|editor, window, cx| {
 8456        editor.handle_input(")", window, cx);
 8457        editor.handle_input("}", window, cx);
 8458    });
 8459    cx.assert_editor_state(
 8460        &"
 8461            )}ˇ
 8462            )}ˇ
 8463            )}ˇ
 8464        "
 8465        .unindent(),
 8466    );
 8467
 8468    // ignore non-close brackets
 8469    cx.update_editor(|editor, window, cx| {
 8470        editor.handle_input("]", window, cx);
 8471        editor.move_left(&MoveLeft, window, cx);
 8472        editor.handle_input("]", window, cx);
 8473    });
 8474    cx.assert_editor_state(
 8475        &"
 8476            )}]ˇ]
 8477            )}]ˇ]
 8478            )}]ˇ]
 8479        "
 8480        .unindent(),
 8481    );
 8482}
 8483
 8484#[gpui::test]
 8485async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
 8486    init_test(cx, |_| {});
 8487
 8488    let mut cx = EditorTestContext::new(cx).await;
 8489
 8490    let html_language = Arc::new(
 8491        Language::new(
 8492            LanguageConfig {
 8493                name: "HTML".into(),
 8494                brackets: BracketPairConfig {
 8495                    pairs: vec![
 8496                        BracketPair {
 8497                            start: "<".into(),
 8498                            end: ">".into(),
 8499                            close: true,
 8500                            ..Default::default()
 8501                        },
 8502                        BracketPair {
 8503                            start: "{".into(),
 8504                            end: "}".into(),
 8505                            close: true,
 8506                            ..Default::default()
 8507                        },
 8508                        BracketPair {
 8509                            start: "(".into(),
 8510                            end: ")".into(),
 8511                            close: true,
 8512                            ..Default::default()
 8513                        },
 8514                    ],
 8515                    ..Default::default()
 8516                },
 8517                autoclose_before: "})]>".into(),
 8518                ..Default::default()
 8519            },
 8520            Some(tree_sitter_html::LANGUAGE.into()),
 8521        )
 8522        .with_injection_query(
 8523            r#"
 8524            (script_element
 8525                (raw_text) @injection.content
 8526                (#set! injection.language "javascript"))
 8527            "#,
 8528        )
 8529        .unwrap(),
 8530    );
 8531
 8532    let javascript_language = Arc::new(Language::new(
 8533        LanguageConfig {
 8534            name: "JavaScript".into(),
 8535            brackets: BracketPairConfig {
 8536                pairs: vec![
 8537                    BracketPair {
 8538                        start: "/*".into(),
 8539                        end: " */".into(),
 8540                        close: true,
 8541                        ..Default::default()
 8542                    },
 8543                    BracketPair {
 8544                        start: "{".into(),
 8545                        end: "}".into(),
 8546                        close: true,
 8547                        ..Default::default()
 8548                    },
 8549                    BracketPair {
 8550                        start: "(".into(),
 8551                        end: ")".into(),
 8552                        close: true,
 8553                        ..Default::default()
 8554                    },
 8555                ],
 8556                ..Default::default()
 8557            },
 8558            autoclose_before: "})]>".into(),
 8559            ..Default::default()
 8560        },
 8561        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 8562    ));
 8563
 8564    cx.language_registry().add(html_language.clone());
 8565    cx.language_registry().add(javascript_language.clone());
 8566
 8567    cx.update_buffer(|buffer, cx| {
 8568        buffer.set_language(Some(html_language), cx);
 8569    });
 8570
 8571    cx.set_state(
 8572        &r#"
 8573            <body>ˇ
 8574                <script>
 8575                    var x = 1;ˇ
 8576                </script>
 8577            </body>ˇ
 8578        "#
 8579        .unindent(),
 8580    );
 8581
 8582    // Precondition: different languages are active at different locations.
 8583    cx.update_editor(|editor, window, cx| {
 8584        let snapshot = editor.snapshot(window, cx);
 8585        let cursors = editor.selections.ranges::<usize>(cx);
 8586        let languages = cursors
 8587            .iter()
 8588            .map(|c| snapshot.language_at(c.start).unwrap().name())
 8589            .collect::<Vec<_>>();
 8590        assert_eq!(
 8591            languages,
 8592            &["HTML".into(), "JavaScript".into(), "HTML".into()]
 8593        );
 8594    });
 8595
 8596    // Angle brackets autoclose in HTML, but not JavaScript.
 8597    cx.update_editor(|editor, window, cx| {
 8598        editor.handle_input("<", window, cx);
 8599        editor.handle_input("a", window, cx);
 8600    });
 8601    cx.assert_editor_state(
 8602        &r#"
 8603            <body><aˇ>
 8604                <script>
 8605                    var x = 1;<aˇ
 8606                </script>
 8607            </body><aˇ>
 8608        "#
 8609        .unindent(),
 8610    );
 8611
 8612    // Curly braces and parens autoclose in both HTML and JavaScript.
 8613    cx.update_editor(|editor, window, cx| {
 8614        editor.handle_input(" b=", window, cx);
 8615        editor.handle_input("{", window, cx);
 8616        editor.handle_input("c", window, cx);
 8617        editor.handle_input("(", window, cx);
 8618    });
 8619    cx.assert_editor_state(
 8620        &r#"
 8621            <body><a b={c(ˇ)}>
 8622                <script>
 8623                    var x = 1;<a b={c(ˇ)}
 8624                </script>
 8625            </body><a b={c(ˇ)}>
 8626        "#
 8627        .unindent(),
 8628    );
 8629
 8630    // Brackets that were already autoclosed are skipped.
 8631    cx.update_editor(|editor, window, cx| {
 8632        editor.handle_input(")", window, cx);
 8633        editor.handle_input("d", window, cx);
 8634        editor.handle_input("}", window, cx);
 8635    });
 8636    cx.assert_editor_state(
 8637        &r#"
 8638            <body><a b={c()d}ˇ>
 8639                <script>
 8640                    var x = 1;<a b={c()d}ˇ
 8641                </script>
 8642            </body><a b={c()d}ˇ>
 8643        "#
 8644        .unindent(),
 8645    );
 8646    cx.update_editor(|editor, window, cx| {
 8647        editor.handle_input(">", window, cx);
 8648    });
 8649    cx.assert_editor_state(
 8650        &r#"
 8651            <body><a b={c()d}>ˇ
 8652                <script>
 8653                    var x = 1;<a b={c()d}>ˇ
 8654                </script>
 8655            </body><a b={c()d}>ˇ
 8656        "#
 8657        .unindent(),
 8658    );
 8659
 8660    // Reset
 8661    cx.set_state(
 8662        &r#"
 8663            <body>ˇ
 8664                <script>
 8665                    var x = 1;ˇ
 8666                </script>
 8667            </body>ˇ
 8668        "#
 8669        .unindent(),
 8670    );
 8671
 8672    cx.update_editor(|editor, window, cx| {
 8673        editor.handle_input("<", window, cx);
 8674    });
 8675    cx.assert_editor_state(
 8676        &r#"
 8677            <body><ˇ>
 8678                <script>
 8679                    var x = 1;<ˇ
 8680                </script>
 8681            </body><ˇ>
 8682        "#
 8683        .unindent(),
 8684    );
 8685
 8686    // When backspacing, the closing angle brackets are removed.
 8687    cx.update_editor(|editor, window, cx| {
 8688        editor.backspace(&Backspace, window, cx);
 8689    });
 8690    cx.assert_editor_state(
 8691        &r#"
 8692            <body>ˇ
 8693                <script>
 8694                    var x = 1;ˇ
 8695                </script>
 8696            </body>ˇ
 8697        "#
 8698        .unindent(),
 8699    );
 8700
 8701    // Block comments autoclose in JavaScript, but not HTML.
 8702    cx.update_editor(|editor, window, cx| {
 8703        editor.handle_input("/", window, cx);
 8704        editor.handle_input("*", window, cx);
 8705    });
 8706    cx.assert_editor_state(
 8707        &r#"
 8708            <body>/*ˇ
 8709                <script>
 8710                    var x = 1;/*ˇ */
 8711                </script>
 8712            </body>/*ˇ
 8713        "#
 8714        .unindent(),
 8715    );
 8716}
 8717
 8718#[gpui::test]
 8719async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
 8720    init_test(cx, |_| {});
 8721
 8722    let mut cx = EditorTestContext::new(cx).await;
 8723
 8724    let rust_language = Arc::new(
 8725        Language::new(
 8726            LanguageConfig {
 8727                name: "Rust".into(),
 8728                brackets: serde_json::from_value(json!([
 8729                    { "start": "{", "end": "}", "close": true, "newline": true },
 8730                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
 8731                ]))
 8732                .unwrap(),
 8733                autoclose_before: "})]>".into(),
 8734                ..Default::default()
 8735            },
 8736            Some(tree_sitter_rust::LANGUAGE.into()),
 8737        )
 8738        .with_override_query("(string_literal) @string")
 8739        .unwrap(),
 8740    );
 8741
 8742    cx.language_registry().add(rust_language.clone());
 8743    cx.update_buffer(|buffer, cx| {
 8744        buffer.set_language(Some(rust_language), cx);
 8745    });
 8746
 8747    cx.set_state(
 8748        &r#"
 8749            let x = ˇ
 8750        "#
 8751        .unindent(),
 8752    );
 8753
 8754    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
 8755    cx.update_editor(|editor, window, cx| {
 8756        editor.handle_input("\"", window, cx);
 8757    });
 8758    cx.assert_editor_state(
 8759        &r#"
 8760            let x = "ˇ"
 8761        "#
 8762        .unindent(),
 8763    );
 8764
 8765    // Inserting another quotation mark. The cursor moves across the existing
 8766    // automatically-inserted quotation mark.
 8767    cx.update_editor(|editor, window, cx| {
 8768        editor.handle_input("\"", window, cx);
 8769    });
 8770    cx.assert_editor_state(
 8771        &r#"
 8772            let x = ""ˇ
 8773        "#
 8774        .unindent(),
 8775    );
 8776
 8777    // Reset
 8778    cx.set_state(
 8779        &r#"
 8780            let x = ˇ
 8781        "#
 8782        .unindent(),
 8783    );
 8784
 8785    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
 8786    cx.update_editor(|editor, window, cx| {
 8787        editor.handle_input("\"", window, cx);
 8788        editor.handle_input(" ", window, cx);
 8789        editor.move_left(&Default::default(), window, cx);
 8790        editor.handle_input("\\", window, cx);
 8791        editor.handle_input("\"", window, cx);
 8792    });
 8793    cx.assert_editor_state(
 8794        &r#"
 8795            let x = "\"ˇ "
 8796        "#
 8797        .unindent(),
 8798    );
 8799
 8800    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
 8801    // mark. Nothing is inserted.
 8802    cx.update_editor(|editor, window, cx| {
 8803        editor.move_right(&Default::default(), window, cx);
 8804        editor.handle_input("\"", window, cx);
 8805    });
 8806    cx.assert_editor_state(
 8807        &r#"
 8808            let x = "\" "ˇ
 8809        "#
 8810        .unindent(),
 8811    );
 8812}
 8813
 8814#[gpui::test]
 8815async fn test_surround_with_pair(cx: &mut TestAppContext) {
 8816    init_test(cx, |_| {});
 8817
 8818    let language = Arc::new(Language::new(
 8819        LanguageConfig {
 8820            brackets: BracketPairConfig {
 8821                pairs: vec![
 8822                    BracketPair {
 8823                        start: "{".to_string(),
 8824                        end: "}".to_string(),
 8825                        close: true,
 8826                        surround: true,
 8827                        newline: true,
 8828                    },
 8829                    BracketPair {
 8830                        start: "/* ".to_string(),
 8831                        end: "*/".to_string(),
 8832                        close: true,
 8833                        surround: true,
 8834                        ..Default::default()
 8835                    },
 8836                ],
 8837                ..Default::default()
 8838            },
 8839            ..Default::default()
 8840        },
 8841        Some(tree_sitter_rust::LANGUAGE.into()),
 8842    ));
 8843
 8844    let text = r#"
 8845        a
 8846        b
 8847        c
 8848    "#
 8849    .unindent();
 8850
 8851    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8852    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8853    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8854    editor
 8855        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8856        .await;
 8857
 8858    editor.update_in(cx, |editor, window, cx| {
 8859        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8860            s.select_display_ranges([
 8861                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8862                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8863                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
 8864            ])
 8865        });
 8866
 8867        editor.handle_input("{", window, cx);
 8868        editor.handle_input("{", window, cx);
 8869        editor.handle_input("{", window, cx);
 8870        assert_eq!(
 8871            editor.text(cx),
 8872            "
 8873                {{{a}}}
 8874                {{{b}}}
 8875                {{{c}}}
 8876            "
 8877            .unindent()
 8878        );
 8879        assert_eq!(
 8880            editor.selections.display_ranges(cx),
 8881            [
 8882                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
 8883                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
 8884                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
 8885            ]
 8886        );
 8887
 8888        editor.undo(&Undo, window, cx);
 8889        editor.undo(&Undo, window, cx);
 8890        editor.undo(&Undo, window, cx);
 8891        assert_eq!(
 8892            editor.text(cx),
 8893            "
 8894                a
 8895                b
 8896                c
 8897            "
 8898            .unindent()
 8899        );
 8900        assert_eq!(
 8901            editor.selections.display_ranges(cx),
 8902            [
 8903                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8904                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8905                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 8906            ]
 8907        );
 8908
 8909        // Ensure inserting the first character of a multi-byte bracket pair
 8910        // doesn't surround the selections with the bracket.
 8911        editor.handle_input("/", window, cx);
 8912        assert_eq!(
 8913            editor.text(cx),
 8914            "
 8915                /
 8916                /
 8917                /
 8918            "
 8919            .unindent()
 8920        );
 8921        assert_eq!(
 8922            editor.selections.display_ranges(cx),
 8923            [
 8924                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 8925                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 8926                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 8927            ]
 8928        );
 8929
 8930        editor.undo(&Undo, window, cx);
 8931        assert_eq!(
 8932            editor.text(cx),
 8933            "
 8934                a
 8935                b
 8936                c
 8937            "
 8938            .unindent()
 8939        );
 8940        assert_eq!(
 8941            editor.selections.display_ranges(cx),
 8942            [
 8943                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8944                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8945                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 8946            ]
 8947        );
 8948
 8949        // Ensure inserting the last character of a multi-byte bracket pair
 8950        // doesn't surround the selections with the bracket.
 8951        editor.handle_input("*", window, cx);
 8952        assert_eq!(
 8953            editor.text(cx),
 8954            "
 8955                *
 8956                *
 8957                *
 8958            "
 8959            .unindent()
 8960        );
 8961        assert_eq!(
 8962            editor.selections.display_ranges(cx),
 8963            [
 8964                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 8965                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 8966                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 8967            ]
 8968        );
 8969    });
 8970}
 8971
 8972#[gpui::test]
 8973async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
 8974    init_test(cx, |_| {});
 8975
 8976    let language = Arc::new(Language::new(
 8977        LanguageConfig {
 8978            brackets: BracketPairConfig {
 8979                pairs: vec![BracketPair {
 8980                    start: "{".to_string(),
 8981                    end: "}".to_string(),
 8982                    close: true,
 8983                    surround: true,
 8984                    newline: true,
 8985                }],
 8986                ..Default::default()
 8987            },
 8988            autoclose_before: "}".to_string(),
 8989            ..Default::default()
 8990        },
 8991        Some(tree_sitter_rust::LANGUAGE.into()),
 8992    ));
 8993
 8994    let text = r#"
 8995        a
 8996        b
 8997        c
 8998    "#
 8999    .unindent();
 9000
 9001    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9002    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9003    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9004    editor
 9005        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9006        .await;
 9007
 9008    editor.update_in(cx, |editor, window, cx| {
 9009        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9010            s.select_ranges([
 9011                Point::new(0, 1)..Point::new(0, 1),
 9012                Point::new(1, 1)..Point::new(1, 1),
 9013                Point::new(2, 1)..Point::new(2, 1),
 9014            ])
 9015        });
 9016
 9017        editor.handle_input("{", window, cx);
 9018        editor.handle_input("{", window, cx);
 9019        editor.handle_input("_", window, cx);
 9020        assert_eq!(
 9021            editor.text(cx),
 9022            "
 9023                a{{_}}
 9024                b{{_}}
 9025                c{{_}}
 9026            "
 9027            .unindent()
 9028        );
 9029        assert_eq!(
 9030            editor.selections.ranges::<Point>(cx),
 9031            [
 9032                Point::new(0, 4)..Point::new(0, 4),
 9033                Point::new(1, 4)..Point::new(1, 4),
 9034                Point::new(2, 4)..Point::new(2, 4)
 9035            ]
 9036        );
 9037
 9038        editor.backspace(&Default::default(), window, cx);
 9039        editor.backspace(&Default::default(), window, cx);
 9040        assert_eq!(
 9041            editor.text(cx),
 9042            "
 9043                a{}
 9044                b{}
 9045                c{}
 9046            "
 9047            .unindent()
 9048        );
 9049        assert_eq!(
 9050            editor.selections.ranges::<Point>(cx),
 9051            [
 9052                Point::new(0, 2)..Point::new(0, 2),
 9053                Point::new(1, 2)..Point::new(1, 2),
 9054                Point::new(2, 2)..Point::new(2, 2)
 9055            ]
 9056        );
 9057
 9058        editor.delete_to_previous_word_start(&Default::default(), window, cx);
 9059        assert_eq!(
 9060            editor.text(cx),
 9061            "
 9062                a
 9063                b
 9064                c
 9065            "
 9066            .unindent()
 9067        );
 9068        assert_eq!(
 9069            editor.selections.ranges::<Point>(cx),
 9070            [
 9071                Point::new(0, 1)..Point::new(0, 1),
 9072                Point::new(1, 1)..Point::new(1, 1),
 9073                Point::new(2, 1)..Point::new(2, 1)
 9074            ]
 9075        );
 9076    });
 9077}
 9078
 9079#[gpui::test]
 9080async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
 9081    init_test(cx, |settings| {
 9082        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 9083    });
 9084
 9085    let mut cx = EditorTestContext::new(cx).await;
 9086
 9087    let language = Arc::new(Language::new(
 9088        LanguageConfig {
 9089            brackets: BracketPairConfig {
 9090                pairs: vec![
 9091                    BracketPair {
 9092                        start: "{".to_string(),
 9093                        end: "}".to_string(),
 9094                        close: true,
 9095                        surround: true,
 9096                        newline: true,
 9097                    },
 9098                    BracketPair {
 9099                        start: "(".to_string(),
 9100                        end: ")".to_string(),
 9101                        close: true,
 9102                        surround: true,
 9103                        newline: true,
 9104                    },
 9105                    BracketPair {
 9106                        start: "[".to_string(),
 9107                        end: "]".to_string(),
 9108                        close: false,
 9109                        surround: true,
 9110                        newline: true,
 9111                    },
 9112                ],
 9113                ..Default::default()
 9114            },
 9115            autoclose_before: "})]".to_string(),
 9116            ..Default::default()
 9117        },
 9118        Some(tree_sitter_rust::LANGUAGE.into()),
 9119    ));
 9120
 9121    cx.language_registry().add(language.clone());
 9122    cx.update_buffer(|buffer, cx| {
 9123        buffer.set_language(Some(language), cx);
 9124    });
 9125
 9126    cx.set_state(
 9127        &"
 9128            {(ˇ)}
 9129            [[ˇ]]
 9130            {(ˇ)}
 9131        "
 9132        .unindent(),
 9133    );
 9134
 9135    cx.update_editor(|editor, window, cx| {
 9136        editor.backspace(&Default::default(), window, cx);
 9137        editor.backspace(&Default::default(), window, cx);
 9138    });
 9139
 9140    cx.assert_editor_state(
 9141        &"
 9142            ˇ
 9143            ˇ]]
 9144            ˇ
 9145        "
 9146        .unindent(),
 9147    );
 9148
 9149    cx.update_editor(|editor, window, cx| {
 9150        editor.handle_input("{", window, cx);
 9151        editor.handle_input("{", window, cx);
 9152        editor.move_right(&MoveRight, window, cx);
 9153        editor.move_right(&MoveRight, window, cx);
 9154        editor.move_left(&MoveLeft, window, cx);
 9155        editor.move_left(&MoveLeft, window, cx);
 9156        editor.backspace(&Default::default(), window, cx);
 9157    });
 9158
 9159    cx.assert_editor_state(
 9160        &"
 9161            {ˇ}
 9162            {ˇ}]]
 9163            {ˇ}
 9164        "
 9165        .unindent(),
 9166    );
 9167
 9168    cx.update_editor(|editor, window, cx| {
 9169        editor.backspace(&Default::default(), window, cx);
 9170    });
 9171
 9172    cx.assert_editor_state(
 9173        &"
 9174            ˇ
 9175            ˇ]]
 9176            ˇ
 9177        "
 9178        .unindent(),
 9179    );
 9180}
 9181
 9182#[gpui::test]
 9183async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
 9184    init_test(cx, |_| {});
 9185
 9186    let language = Arc::new(Language::new(
 9187        LanguageConfig::default(),
 9188        Some(tree_sitter_rust::LANGUAGE.into()),
 9189    ));
 9190
 9191    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
 9192    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9193    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9194    editor
 9195        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9196        .await;
 9197
 9198    editor.update_in(cx, |editor, window, cx| {
 9199        editor.set_auto_replace_emoji_shortcode(true);
 9200
 9201        editor.handle_input("Hello ", window, cx);
 9202        editor.handle_input(":wave", window, cx);
 9203        assert_eq!(editor.text(cx), "Hello :wave".unindent());
 9204
 9205        editor.handle_input(":", window, cx);
 9206        assert_eq!(editor.text(cx), "Hello 👋".unindent());
 9207
 9208        editor.handle_input(" :smile", window, cx);
 9209        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
 9210
 9211        editor.handle_input(":", window, cx);
 9212        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
 9213
 9214        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
 9215        editor.handle_input(":wave", window, cx);
 9216        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
 9217
 9218        editor.handle_input(":", window, cx);
 9219        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
 9220
 9221        editor.handle_input(":1", window, cx);
 9222        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
 9223
 9224        editor.handle_input(":", window, cx);
 9225        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
 9226
 9227        // Ensure shortcode does not get replaced when it is part of a word
 9228        editor.handle_input(" Test:wave", window, cx);
 9229        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
 9230
 9231        editor.handle_input(":", window, cx);
 9232        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
 9233
 9234        editor.set_auto_replace_emoji_shortcode(false);
 9235
 9236        // Ensure shortcode does not get replaced when auto replace is off
 9237        editor.handle_input(" :wave", window, cx);
 9238        assert_eq!(
 9239            editor.text(cx),
 9240            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
 9241        );
 9242
 9243        editor.handle_input(":", window, cx);
 9244        assert_eq!(
 9245            editor.text(cx),
 9246            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
 9247        );
 9248    });
 9249}
 9250
 9251#[gpui::test]
 9252async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
 9253    init_test(cx, |_| {});
 9254
 9255    let (text, insertion_ranges) = marked_text_ranges(
 9256        indoc! {"
 9257            ˇ
 9258        "},
 9259        false,
 9260    );
 9261
 9262    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
 9263    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9264
 9265    _ = editor.update_in(cx, |editor, window, cx| {
 9266        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
 9267
 9268        editor
 9269            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9270            .unwrap();
 9271
 9272        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
 9273            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
 9274            assert_eq!(editor.text(cx), expected_text);
 9275            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
 9276        }
 9277
 9278        assert(
 9279            editor,
 9280            cx,
 9281            indoc! {"
 9282            type «» =•
 9283            "},
 9284        );
 9285
 9286        assert!(editor.context_menu_visible(), "There should be a matches");
 9287    });
 9288}
 9289
 9290#[gpui::test]
 9291async fn test_snippets(cx: &mut TestAppContext) {
 9292    init_test(cx, |_| {});
 9293
 9294    let mut cx = EditorTestContext::new(cx).await;
 9295
 9296    cx.set_state(indoc! {"
 9297        a.ˇ b
 9298        a.ˇ b
 9299        a.ˇ b
 9300    "});
 9301
 9302    cx.update_editor(|editor, window, cx| {
 9303        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
 9304        let insertion_ranges = editor
 9305            .selections
 9306            .all(cx)
 9307            .iter()
 9308            .map(|s| s.range().clone())
 9309            .collect::<Vec<_>>();
 9310        editor
 9311            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9312            .unwrap();
 9313    });
 9314
 9315    cx.assert_editor_state(indoc! {"
 9316        a.f(«oneˇ», two, «threeˇ») b
 9317        a.f(«oneˇ», two, «threeˇ») b
 9318        a.f(«oneˇ», two, «threeˇ») b
 9319    "});
 9320
 9321    // Can't move earlier than the first tab stop
 9322    cx.update_editor(|editor, window, cx| {
 9323        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 9324    });
 9325    cx.assert_editor_state(indoc! {"
 9326        a.f(«oneˇ», two, «threeˇ») b
 9327        a.f(«oneˇ», two, «threeˇ») b
 9328        a.f(«oneˇ», two, «threeˇ») b
 9329    "});
 9330
 9331    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9332    cx.assert_editor_state(indoc! {"
 9333        a.f(one, «twoˇ», three) b
 9334        a.f(one, «twoˇ», three) b
 9335        a.f(one, «twoˇ», three) b
 9336    "});
 9337
 9338    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
 9339    cx.assert_editor_state(indoc! {"
 9340        a.f(«oneˇ», two, «threeˇ») b
 9341        a.f(«oneˇ», two, «threeˇ») b
 9342        a.f(«oneˇ», two, «threeˇ») b
 9343    "});
 9344
 9345    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9346    cx.assert_editor_state(indoc! {"
 9347        a.f(one, «twoˇ», three) b
 9348        a.f(one, «twoˇ», three) b
 9349        a.f(one, «twoˇ», three) b
 9350    "});
 9351    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9352    cx.assert_editor_state(indoc! {"
 9353        a.f(one, two, three)ˇ b
 9354        a.f(one, two, three)ˇ b
 9355        a.f(one, two, three)ˇ b
 9356    "});
 9357
 9358    // As soon as the last tab stop is reached, snippet state is gone
 9359    cx.update_editor(|editor, window, cx| {
 9360        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 9361    });
 9362    cx.assert_editor_state(indoc! {"
 9363        a.f(one, two, three)ˇ b
 9364        a.f(one, two, three)ˇ b
 9365        a.f(one, two, three)ˇ b
 9366    "});
 9367}
 9368
 9369#[gpui::test]
 9370async fn test_snippet_indentation(cx: &mut TestAppContext) {
 9371    init_test(cx, |_| {});
 9372
 9373    let mut cx = EditorTestContext::new(cx).await;
 9374
 9375    cx.update_editor(|editor, window, cx| {
 9376        let snippet = Snippet::parse(indoc! {"
 9377            /*
 9378             * Multiline comment with leading indentation
 9379             *
 9380             * $1
 9381             */
 9382            $0"})
 9383        .unwrap();
 9384        let insertion_ranges = editor
 9385            .selections
 9386            .all(cx)
 9387            .iter()
 9388            .map(|s| s.range().clone())
 9389            .collect::<Vec<_>>();
 9390        editor
 9391            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9392            .unwrap();
 9393    });
 9394
 9395    cx.assert_editor_state(indoc! {"
 9396        /*
 9397         * Multiline comment with leading indentation
 9398         *
 9399         * ˇ
 9400         */
 9401    "});
 9402
 9403    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9404    cx.assert_editor_state(indoc! {"
 9405        /*
 9406         * Multiline comment with leading indentation
 9407         *
 9408         *•
 9409         */
 9410        ˇ"});
 9411}
 9412
 9413#[gpui::test]
 9414async fn test_document_format_during_save(cx: &mut TestAppContext) {
 9415    init_test(cx, |_| {});
 9416
 9417    let fs = FakeFs::new(cx.executor());
 9418    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9419
 9420    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
 9421
 9422    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9423    language_registry.add(rust_lang());
 9424    let mut fake_servers = language_registry.register_fake_lsp(
 9425        "Rust",
 9426        FakeLspAdapter {
 9427            capabilities: lsp::ServerCapabilities {
 9428                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9429                ..Default::default()
 9430            },
 9431            ..Default::default()
 9432        },
 9433    );
 9434
 9435    let buffer = project
 9436        .update(cx, |project, cx| {
 9437            project.open_local_buffer(path!("/file.rs"), cx)
 9438        })
 9439        .await
 9440        .unwrap();
 9441
 9442    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9443    let (editor, cx) = cx.add_window_view(|window, cx| {
 9444        build_editor_with_project(project.clone(), buffer, window, cx)
 9445    });
 9446    editor.update_in(cx, |editor, window, cx| {
 9447        editor.set_text("one\ntwo\nthree\n", window, cx)
 9448    });
 9449    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9450
 9451    cx.executor().start_waiting();
 9452    let fake_server = fake_servers.next().await.unwrap();
 9453
 9454    {
 9455        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9456            move |params, _| async move {
 9457                assert_eq!(
 9458                    params.text_document.uri,
 9459                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9460                );
 9461                assert_eq!(params.options.tab_size, 4);
 9462                Ok(Some(vec![lsp::TextEdit::new(
 9463                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9464                    ", ".to_string(),
 9465                )]))
 9466            },
 9467        );
 9468        let save = editor
 9469            .update_in(cx, |editor, window, cx| {
 9470                editor.save(
 9471                    SaveOptions {
 9472                        format: true,
 9473                        autosave: false,
 9474                    },
 9475                    project.clone(),
 9476                    window,
 9477                    cx,
 9478                )
 9479            })
 9480            .unwrap();
 9481        cx.executor().start_waiting();
 9482        save.await;
 9483
 9484        assert_eq!(
 9485            editor.update(cx, |editor, cx| editor.text(cx)),
 9486            "one, two\nthree\n"
 9487        );
 9488        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9489    }
 9490
 9491    {
 9492        editor.update_in(cx, |editor, window, cx| {
 9493            editor.set_text("one\ntwo\nthree\n", window, cx)
 9494        });
 9495        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9496
 9497        // Ensure we can still save even if formatting hangs.
 9498        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9499            move |params, _| async move {
 9500                assert_eq!(
 9501                    params.text_document.uri,
 9502                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9503                );
 9504                futures::future::pending::<()>().await;
 9505                unreachable!()
 9506            },
 9507        );
 9508        let save = editor
 9509            .update_in(cx, |editor, window, cx| {
 9510                editor.save(
 9511                    SaveOptions {
 9512                        format: true,
 9513                        autosave: false,
 9514                    },
 9515                    project.clone(),
 9516                    window,
 9517                    cx,
 9518                )
 9519            })
 9520            .unwrap();
 9521        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9522        cx.executor().start_waiting();
 9523        save.await;
 9524        assert_eq!(
 9525            editor.update(cx, |editor, cx| editor.text(cx)),
 9526            "one\ntwo\nthree\n"
 9527        );
 9528    }
 9529
 9530    // Set rust language override and assert overridden tabsize is sent to language server
 9531    update_test_language_settings(cx, |settings| {
 9532        settings.languages.0.insert(
 9533            "Rust".into(),
 9534            LanguageSettingsContent {
 9535                tab_size: NonZeroU32::new(8),
 9536                ..Default::default()
 9537            },
 9538        );
 9539    });
 9540
 9541    {
 9542        editor.update_in(cx, |editor, window, cx| {
 9543            editor.set_text("somehting_new\n", window, cx)
 9544        });
 9545        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9546        let _formatting_request_signal = fake_server
 9547            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9548                assert_eq!(
 9549                    params.text_document.uri,
 9550                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9551                );
 9552                assert_eq!(params.options.tab_size, 8);
 9553                Ok(Some(vec![]))
 9554            });
 9555        let save = editor
 9556            .update_in(cx, |editor, window, cx| {
 9557                editor.save(
 9558                    SaveOptions {
 9559                        format: true,
 9560                        autosave: false,
 9561                    },
 9562                    project.clone(),
 9563                    window,
 9564                    cx,
 9565                )
 9566            })
 9567            .unwrap();
 9568        cx.executor().start_waiting();
 9569        save.await;
 9570    }
 9571}
 9572
 9573#[gpui::test]
 9574async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
 9575    init_test(cx, |settings| {
 9576        settings.defaults.ensure_final_newline_on_save = Some(false);
 9577    });
 9578
 9579    let fs = FakeFs::new(cx.executor());
 9580    fs.insert_file(path!("/file.txt"), "foo".into()).await;
 9581
 9582    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
 9583
 9584    let buffer = project
 9585        .update(cx, |project, cx| {
 9586            project.open_local_buffer(path!("/file.txt"), cx)
 9587        })
 9588        .await
 9589        .unwrap();
 9590
 9591    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9592    let (editor, cx) = cx.add_window_view(|window, cx| {
 9593        build_editor_with_project(project.clone(), buffer, window, cx)
 9594    });
 9595    editor.update_in(cx, |editor, window, cx| {
 9596        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 9597            s.select_ranges([0..0])
 9598        });
 9599    });
 9600    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9601
 9602    editor.update_in(cx, |editor, window, cx| {
 9603        editor.handle_input("\n", window, cx)
 9604    });
 9605    cx.run_until_parked();
 9606    save(&editor, &project, cx).await;
 9607    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
 9608
 9609    editor.update_in(cx, |editor, window, cx| {
 9610        editor.undo(&Default::default(), window, cx);
 9611    });
 9612    save(&editor, &project, cx).await;
 9613    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
 9614
 9615    editor.update_in(cx, |editor, window, cx| {
 9616        editor.redo(&Default::default(), window, cx);
 9617    });
 9618    cx.run_until_parked();
 9619    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
 9620
 9621    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
 9622        let save = editor
 9623            .update_in(cx, |editor, window, cx| {
 9624                editor.save(
 9625                    SaveOptions {
 9626                        format: true,
 9627                        autosave: false,
 9628                    },
 9629                    project.clone(),
 9630                    window,
 9631                    cx,
 9632                )
 9633            })
 9634            .unwrap();
 9635        cx.executor().start_waiting();
 9636        save.await;
 9637        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9638    }
 9639}
 9640
 9641#[gpui::test]
 9642async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
 9643    init_test(cx, |_| {});
 9644
 9645    let cols = 4;
 9646    let rows = 10;
 9647    let sample_text_1 = sample_text(rows, cols, 'a');
 9648    assert_eq!(
 9649        sample_text_1,
 9650        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
 9651    );
 9652    let sample_text_2 = sample_text(rows, cols, 'l');
 9653    assert_eq!(
 9654        sample_text_2,
 9655        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
 9656    );
 9657    let sample_text_3 = sample_text(rows, cols, 'v');
 9658    assert_eq!(
 9659        sample_text_3,
 9660        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
 9661    );
 9662
 9663    let fs = FakeFs::new(cx.executor());
 9664    fs.insert_tree(
 9665        path!("/a"),
 9666        json!({
 9667            "main.rs": sample_text_1,
 9668            "other.rs": sample_text_2,
 9669            "lib.rs": sample_text_3,
 9670        }),
 9671    )
 9672    .await;
 9673
 9674    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
 9675    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9676    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 9677
 9678    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9679    language_registry.add(rust_lang());
 9680    let mut fake_servers = language_registry.register_fake_lsp(
 9681        "Rust",
 9682        FakeLspAdapter {
 9683            capabilities: lsp::ServerCapabilities {
 9684                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9685                ..Default::default()
 9686            },
 9687            ..Default::default()
 9688        },
 9689    );
 9690
 9691    let worktree = project.update(cx, |project, cx| {
 9692        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
 9693        assert_eq!(worktrees.len(), 1);
 9694        worktrees.pop().unwrap()
 9695    });
 9696    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 9697
 9698    let buffer_1 = project
 9699        .update(cx, |project, cx| {
 9700            project.open_buffer((worktree_id, "main.rs"), cx)
 9701        })
 9702        .await
 9703        .unwrap();
 9704    let buffer_2 = project
 9705        .update(cx, |project, cx| {
 9706            project.open_buffer((worktree_id, "other.rs"), cx)
 9707        })
 9708        .await
 9709        .unwrap();
 9710    let buffer_3 = project
 9711        .update(cx, |project, cx| {
 9712            project.open_buffer((worktree_id, "lib.rs"), cx)
 9713        })
 9714        .await
 9715        .unwrap();
 9716
 9717    let multi_buffer = cx.new(|cx| {
 9718        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 9719        multi_buffer.push_excerpts(
 9720            buffer_1.clone(),
 9721            [
 9722                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9723                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9724                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9725            ],
 9726            cx,
 9727        );
 9728        multi_buffer.push_excerpts(
 9729            buffer_2.clone(),
 9730            [
 9731                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9732                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9733                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9734            ],
 9735            cx,
 9736        );
 9737        multi_buffer.push_excerpts(
 9738            buffer_3.clone(),
 9739            [
 9740                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9741                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9742                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9743            ],
 9744            cx,
 9745        );
 9746        multi_buffer
 9747    });
 9748    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
 9749        Editor::new(
 9750            EditorMode::full(),
 9751            multi_buffer,
 9752            Some(project.clone()),
 9753            window,
 9754            cx,
 9755        )
 9756    });
 9757
 9758    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 9759        editor.change_selections(
 9760            SelectionEffects::scroll(Autoscroll::Next),
 9761            window,
 9762            cx,
 9763            |s| s.select_ranges(Some(1..2)),
 9764        );
 9765        editor.insert("|one|two|three|", window, cx);
 9766    });
 9767    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 9768    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 9769        editor.change_selections(
 9770            SelectionEffects::scroll(Autoscroll::Next),
 9771            window,
 9772            cx,
 9773            |s| s.select_ranges(Some(60..70)),
 9774        );
 9775        editor.insert("|four|five|six|", window, cx);
 9776    });
 9777    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 9778
 9779    // First two buffers should be edited, but not the third one.
 9780    assert_eq!(
 9781        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 9782        "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}",
 9783    );
 9784    buffer_1.update(cx, |buffer, _| {
 9785        assert!(buffer.is_dirty());
 9786        assert_eq!(
 9787            buffer.text(),
 9788            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
 9789        )
 9790    });
 9791    buffer_2.update(cx, |buffer, _| {
 9792        assert!(buffer.is_dirty());
 9793        assert_eq!(
 9794            buffer.text(),
 9795            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
 9796        )
 9797    });
 9798    buffer_3.update(cx, |buffer, _| {
 9799        assert!(!buffer.is_dirty());
 9800        assert_eq!(buffer.text(), sample_text_3,)
 9801    });
 9802    cx.executor().run_until_parked();
 9803
 9804    cx.executor().start_waiting();
 9805    let save = multi_buffer_editor
 9806        .update_in(cx, |editor, window, cx| {
 9807            editor.save(
 9808                SaveOptions {
 9809                    format: true,
 9810                    autosave: false,
 9811                },
 9812                project.clone(),
 9813                window,
 9814                cx,
 9815            )
 9816        })
 9817        .unwrap();
 9818
 9819    let fake_server = fake_servers.next().await.unwrap();
 9820    fake_server
 9821        .server
 9822        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9823            Ok(Some(vec![lsp::TextEdit::new(
 9824                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9825                format!("[{} formatted]", params.text_document.uri),
 9826            )]))
 9827        })
 9828        .detach();
 9829    save.await;
 9830
 9831    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
 9832    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
 9833    assert_eq!(
 9834        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 9835        uri!(
 9836            "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}"
 9837        ),
 9838    );
 9839    buffer_1.update(cx, |buffer, _| {
 9840        assert!(!buffer.is_dirty());
 9841        assert_eq!(
 9842            buffer.text(),
 9843            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
 9844        )
 9845    });
 9846    buffer_2.update(cx, |buffer, _| {
 9847        assert!(!buffer.is_dirty());
 9848        assert_eq!(
 9849            buffer.text(),
 9850            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
 9851        )
 9852    });
 9853    buffer_3.update(cx, |buffer, _| {
 9854        assert!(!buffer.is_dirty());
 9855        assert_eq!(buffer.text(), sample_text_3,)
 9856    });
 9857}
 9858
 9859#[gpui::test]
 9860async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
 9861    init_test(cx, |_| {});
 9862
 9863    let fs = FakeFs::new(cx.executor());
 9864    fs.insert_tree(
 9865        path!("/dir"),
 9866        json!({
 9867            "file1.rs": "fn main() { println!(\"hello\"); }",
 9868            "file2.rs": "fn test() { println!(\"test\"); }",
 9869            "file3.rs": "fn other() { println!(\"other\"); }\n",
 9870        }),
 9871    )
 9872    .await;
 9873
 9874    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
 9875    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9876    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 9877
 9878    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9879    language_registry.add(rust_lang());
 9880
 9881    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
 9882    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 9883
 9884    // Open three buffers
 9885    let buffer_1 = project
 9886        .update(cx, |project, cx| {
 9887            project.open_buffer((worktree_id, "file1.rs"), cx)
 9888        })
 9889        .await
 9890        .unwrap();
 9891    let buffer_2 = project
 9892        .update(cx, |project, cx| {
 9893            project.open_buffer((worktree_id, "file2.rs"), cx)
 9894        })
 9895        .await
 9896        .unwrap();
 9897    let buffer_3 = project
 9898        .update(cx, |project, cx| {
 9899            project.open_buffer((worktree_id, "file3.rs"), cx)
 9900        })
 9901        .await
 9902        .unwrap();
 9903
 9904    // Create a multi-buffer with all three buffers
 9905    let multi_buffer = cx.new(|cx| {
 9906        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 9907        multi_buffer.push_excerpts(
 9908            buffer_1.clone(),
 9909            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9910            cx,
 9911        );
 9912        multi_buffer.push_excerpts(
 9913            buffer_2.clone(),
 9914            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9915            cx,
 9916        );
 9917        multi_buffer.push_excerpts(
 9918            buffer_3.clone(),
 9919            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9920            cx,
 9921        );
 9922        multi_buffer
 9923    });
 9924
 9925    let editor = cx.new_window_entity(|window, cx| {
 9926        Editor::new(
 9927            EditorMode::full(),
 9928            multi_buffer,
 9929            Some(project.clone()),
 9930            window,
 9931            cx,
 9932        )
 9933    });
 9934
 9935    // Edit only the first buffer
 9936    editor.update_in(cx, |editor, window, cx| {
 9937        editor.change_selections(
 9938            SelectionEffects::scroll(Autoscroll::Next),
 9939            window,
 9940            cx,
 9941            |s| s.select_ranges(Some(10..10)),
 9942        );
 9943        editor.insert("// edited", window, cx);
 9944    });
 9945
 9946    // Verify that only buffer 1 is dirty
 9947    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
 9948    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9949    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9950
 9951    // Get write counts after file creation (files were created with initial content)
 9952    // We expect each file to have been written once during creation
 9953    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
 9954    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
 9955    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
 9956
 9957    // Perform autosave
 9958    let save_task = editor.update_in(cx, |editor, window, cx| {
 9959        editor.save(
 9960            SaveOptions {
 9961                format: true,
 9962                autosave: true,
 9963            },
 9964            project.clone(),
 9965            window,
 9966            cx,
 9967        )
 9968    });
 9969    save_task.await.unwrap();
 9970
 9971    // Only the dirty buffer should have been saved
 9972    assert_eq!(
 9973        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
 9974        1,
 9975        "Buffer 1 was dirty, so it should have been written once during autosave"
 9976    );
 9977    assert_eq!(
 9978        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
 9979        0,
 9980        "Buffer 2 was clean, so it should not have been written during autosave"
 9981    );
 9982    assert_eq!(
 9983        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
 9984        0,
 9985        "Buffer 3 was clean, so it should not have been written during autosave"
 9986    );
 9987
 9988    // Verify buffer states after autosave
 9989    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9990    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9991    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9992
 9993    // Now perform a manual save (format = true)
 9994    let save_task = editor.update_in(cx, |editor, window, cx| {
 9995        editor.save(
 9996            SaveOptions {
 9997                format: true,
 9998                autosave: false,
 9999            },
10000            project.clone(),
10001            window,
10002            cx,
10003        )
10004    });
10005    save_task.await.unwrap();
10006
10007    // During manual save, clean buffers don't get written to disk
10008    // They just get did_save called for language server notifications
10009    assert_eq!(
10010        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
10011        1,
10012        "Buffer 1 should only have been written once total (during autosave, not manual save)"
10013    );
10014    assert_eq!(
10015        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
10016        0,
10017        "Buffer 2 should not have been written at all"
10018    );
10019    assert_eq!(
10020        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
10021        0,
10022        "Buffer 3 should not have been written at all"
10023    );
10024}
10025
10026#[gpui::test]
10027async fn test_range_format_during_save(cx: &mut TestAppContext) {
10028    init_test(cx, |_| {});
10029
10030    let fs = FakeFs::new(cx.executor());
10031    fs.insert_file(path!("/file.rs"), Default::default()).await;
10032
10033    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10034
10035    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10036    language_registry.add(rust_lang());
10037    let mut fake_servers = language_registry.register_fake_lsp(
10038        "Rust",
10039        FakeLspAdapter {
10040            capabilities: lsp::ServerCapabilities {
10041                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
10042                ..Default::default()
10043            },
10044            ..Default::default()
10045        },
10046    );
10047
10048    let buffer = project
10049        .update(cx, |project, cx| {
10050            project.open_local_buffer(path!("/file.rs"), cx)
10051        })
10052        .await
10053        .unwrap();
10054
10055    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10056    let (editor, cx) = cx.add_window_view(|window, cx| {
10057        build_editor_with_project(project.clone(), buffer, window, cx)
10058    });
10059    editor.update_in(cx, |editor, window, cx| {
10060        editor.set_text("one\ntwo\nthree\n", window, cx)
10061    });
10062    assert!(cx.read(|cx| editor.is_dirty(cx)));
10063
10064    cx.executor().start_waiting();
10065    let fake_server = fake_servers.next().await.unwrap();
10066
10067    let save = editor
10068        .update_in(cx, |editor, window, cx| {
10069            editor.save(
10070                SaveOptions {
10071                    format: true,
10072                    autosave: false,
10073                },
10074                project.clone(),
10075                window,
10076                cx,
10077            )
10078        })
10079        .unwrap();
10080    fake_server
10081        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
10082            assert_eq!(
10083                params.text_document.uri,
10084                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10085            );
10086            assert_eq!(params.options.tab_size, 4);
10087            Ok(Some(vec![lsp::TextEdit::new(
10088                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10089                ", ".to_string(),
10090            )]))
10091        })
10092        .next()
10093        .await;
10094    cx.executor().start_waiting();
10095    save.await;
10096    assert_eq!(
10097        editor.update(cx, |editor, cx| editor.text(cx)),
10098        "one, two\nthree\n"
10099    );
10100    assert!(!cx.read(|cx| editor.is_dirty(cx)));
10101
10102    editor.update_in(cx, |editor, window, cx| {
10103        editor.set_text("one\ntwo\nthree\n", window, cx)
10104    });
10105    assert!(cx.read(|cx| editor.is_dirty(cx)));
10106
10107    // Ensure we can still save even if formatting hangs.
10108    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
10109        move |params, _| async move {
10110            assert_eq!(
10111                params.text_document.uri,
10112                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10113            );
10114            futures::future::pending::<()>().await;
10115            unreachable!()
10116        },
10117    );
10118    let save = editor
10119        .update_in(cx, |editor, window, cx| {
10120            editor.save(
10121                SaveOptions {
10122                    format: true,
10123                    autosave: false,
10124                },
10125                project.clone(),
10126                window,
10127                cx,
10128            )
10129        })
10130        .unwrap();
10131    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10132    cx.executor().start_waiting();
10133    save.await;
10134    assert_eq!(
10135        editor.update(cx, |editor, cx| editor.text(cx)),
10136        "one\ntwo\nthree\n"
10137    );
10138    assert!(!cx.read(|cx| editor.is_dirty(cx)));
10139
10140    // For non-dirty buffer, no formatting request should be sent
10141    let save = editor
10142        .update_in(cx, |editor, window, cx| {
10143            editor.save(
10144                SaveOptions {
10145                    format: false,
10146                    autosave: false,
10147                },
10148                project.clone(),
10149                window,
10150                cx,
10151            )
10152        })
10153        .unwrap();
10154    let _pending_format_request = fake_server
10155        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
10156            panic!("Should not be invoked");
10157        })
10158        .next();
10159    cx.executor().start_waiting();
10160    save.await;
10161
10162    // Set Rust language override and assert overridden tabsize is sent to language server
10163    update_test_language_settings(cx, |settings| {
10164        settings.languages.0.insert(
10165            "Rust".into(),
10166            LanguageSettingsContent {
10167                tab_size: NonZeroU32::new(8),
10168                ..Default::default()
10169            },
10170        );
10171    });
10172
10173    editor.update_in(cx, |editor, window, cx| {
10174        editor.set_text("somehting_new\n", window, cx)
10175    });
10176    assert!(cx.read(|cx| editor.is_dirty(cx)));
10177    let save = editor
10178        .update_in(cx, |editor, window, cx| {
10179            editor.save(
10180                SaveOptions {
10181                    format: true,
10182                    autosave: false,
10183                },
10184                project.clone(),
10185                window,
10186                cx,
10187            )
10188        })
10189        .unwrap();
10190    fake_server
10191        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
10192            assert_eq!(
10193                params.text_document.uri,
10194                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10195            );
10196            assert_eq!(params.options.tab_size, 8);
10197            Ok(Some(Vec::new()))
10198        })
10199        .next()
10200        .await;
10201    save.await;
10202}
10203
10204#[gpui::test]
10205async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
10206    init_test(cx, |settings| {
10207        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
10208            Formatter::LanguageServer { name: None },
10209        )))
10210    });
10211
10212    let fs = FakeFs::new(cx.executor());
10213    fs.insert_file(path!("/file.rs"), Default::default()).await;
10214
10215    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10216
10217    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10218    language_registry.add(Arc::new(Language::new(
10219        LanguageConfig {
10220            name: "Rust".into(),
10221            matcher: LanguageMatcher {
10222                path_suffixes: vec!["rs".to_string()],
10223                ..Default::default()
10224            },
10225            ..LanguageConfig::default()
10226        },
10227        Some(tree_sitter_rust::LANGUAGE.into()),
10228    )));
10229    update_test_language_settings(cx, |settings| {
10230        // Enable Prettier formatting for the same buffer, and ensure
10231        // LSP is called instead of Prettier.
10232        settings.defaults.prettier = Some(PrettierSettings {
10233            allowed: true,
10234            ..PrettierSettings::default()
10235        });
10236    });
10237    let mut fake_servers = language_registry.register_fake_lsp(
10238        "Rust",
10239        FakeLspAdapter {
10240            capabilities: lsp::ServerCapabilities {
10241                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10242                ..Default::default()
10243            },
10244            ..Default::default()
10245        },
10246    );
10247
10248    let buffer = project
10249        .update(cx, |project, cx| {
10250            project.open_local_buffer(path!("/file.rs"), cx)
10251        })
10252        .await
10253        .unwrap();
10254
10255    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10256    let (editor, cx) = cx.add_window_view(|window, cx| {
10257        build_editor_with_project(project.clone(), buffer, window, cx)
10258    });
10259    editor.update_in(cx, |editor, window, cx| {
10260        editor.set_text("one\ntwo\nthree\n", window, cx)
10261    });
10262
10263    cx.executor().start_waiting();
10264    let fake_server = fake_servers.next().await.unwrap();
10265
10266    let format = editor
10267        .update_in(cx, |editor, window, cx| {
10268            editor.perform_format(
10269                project.clone(),
10270                FormatTrigger::Manual,
10271                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10272                window,
10273                cx,
10274            )
10275        })
10276        .unwrap();
10277    fake_server
10278        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
10279            assert_eq!(
10280                params.text_document.uri,
10281                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10282            );
10283            assert_eq!(params.options.tab_size, 4);
10284            Ok(Some(vec![lsp::TextEdit::new(
10285                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10286                ", ".to_string(),
10287            )]))
10288        })
10289        .next()
10290        .await;
10291    cx.executor().start_waiting();
10292    format.await;
10293    assert_eq!(
10294        editor.update(cx, |editor, cx| editor.text(cx)),
10295        "one, two\nthree\n"
10296    );
10297
10298    editor.update_in(cx, |editor, window, cx| {
10299        editor.set_text("one\ntwo\nthree\n", window, cx)
10300    });
10301    // Ensure we don't lock if formatting hangs.
10302    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10303        move |params, _| async move {
10304            assert_eq!(
10305                params.text_document.uri,
10306                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10307            );
10308            futures::future::pending::<()>().await;
10309            unreachable!()
10310        },
10311    );
10312    let format = editor
10313        .update_in(cx, |editor, window, cx| {
10314            editor.perform_format(
10315                project,
10316                FormatTrigger::Manual,
10317                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10318                window,
10319                cx,
10320            )
10321        })
10322        .unwrap();
10323    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10324    cx.executor().start_waiting();
10325    format.await;
10326    assert_eq!(
10327        editor.update(cx, |editor, cx| editor.text(cx)),
10328        "one\ntwo\nthree\n"
10329    );
10330}
10331
10332#[gpui::test]
10333async fn test_multiple_formatters(cx: &mut TestAppContext) {
10334    init_test(cx, |settings| {
10335        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
10336        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
10337            Formatter::LanguageServer { name: None },
10338            Formatter::CodeActions(
10339                [
10340                    ("code-action-1".into(), true),
10341                    ("code-action-2".into(), true),
10342                ]
10343                .into_iter()
10344                .collect(),
10345            ),
10346        ])))
10347    });
10348
10349    let fs = FakeFs::new(cx.executor());
10350    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
10351        .await;
10352
10353    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10354    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10355    language_registry.add(rust_lang());
10356
10357    let mut fake_servers = language_registry.register_fake_lsp(
10358        "Rust",
10359        FakeLspAdapter {
10360            capabilities: lsp::ServerCapabilities {
10361                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10362                execute_command_provider: Some(lsp::ExecuteCommandOptions {
10363                    commands: vec!["the-command-for-code-action-1".into()],
10364                    ..Default::default()
10365                }),
10366                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10367                ..Default::default()
10368            },
10369            ..Default::default()
10370        },
10371    );
10372
10373    let buffer = project
10374        .update(cx, |project, cx| {
10375            project.open_local_buffer(path!("/file.rs"), cx)
10376        })
10377        .await
10378        .unwrap();
10379
10380    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10381    let (editor, cx) = cx.add_window_view(|window, cx| {
10382        build_editor_with_project(project.clone(), buffer, window, cx)
10383    });
10384
10385    cx.executor().start_waiting();
10386
10387    let fake_server = fake_servers.next().await.unwrap();
10388    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10389        move |_params, _| async move {
10390            Ok(Some(vec![lsp::TextEdit::new(
10391                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10392                "applied-formatting\n".to_string(),
10393            )]))
10394        },
10395    );
10396    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
10397        move |params, _| async move {
10398            assert_eq!(
10399                params.context.only,
10400                Some(vec!["code-action-1".into(), "code-action-2".into()])
10401            );
10402            let uri = lsp::Url::from_file_path(path!("/file.rs")).unwrap();
10403            Ok(Some(vec![
10404                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10405                    kind: Some("code-action-1".into()),
10406                    edit: Some(lsp::WorkspaceEdit::new(
10407                        [(
10408                            uri.clone(),
10409                            vec![lsp::TextEdit::new(
10410                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10411                                "applied-code-action-1-edit\n".to_string(),
10412                            )],
10413                        )]
10414                        .into_iter()
10415                        .collect(),
10416                    )),
10417                    command: Some(lsp::Command {
10418                        command: "the-command-for-code-action-1".into(),
10419                        ..Default::default()
10420                    }),
10421                    ..Default::default()
10422                }),
10423                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10424                    kind: Some("code-action-2".into()),
10425                    edit: Some(lsp::WorkspaceEdit::new(
10426                        [(
10427                            uri.clone(),
10428                            vec![lsp::TextEdit::new(
10429                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10430                                "applied-code-action-2-edit\n".to_string(),
10431                            )],
10432                        )]
10433                        .into_iter()
10434                        .collect(),
10435                    )),
10436                    ..Default::default()
10437                }),
10438            ]))
10439        },
10440    );
10441
10442    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
10443        move |params, _| async move { Ok(params) }
10444    });
10445
10446    let command_lock = Arc::new(futures::lock::Mutex::new(()));
10447    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
10448        let fake = fake_server.clone();
10449        let lock = command_lock.clone();
10450        move |params, _| {
10451            assert_eq!(params.command, "the-command-for-code-action-1");
10452            let fake = fake.clone();
10453            let lock = lock.clone();
10454            async move {
10455                lock.lock().await;
10456                fake.server
10457                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
10458                        label: None,
10459                        edit: lsp::WorkspaceEdit {
10460                            changes: Some(
10461                                [(
10462                                    lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
10463                                    vec![lsp::TextEdit {
10464                                        range: lsp::Range::new(
10465                                            lsp::Position::new(0, 0),
10466                                            lsp::Position::new(0, 0),
10467                                        ),
10468                                        new_text: "applied-code-action-1-command\n".into(),
10469                                    }],
10470                                )]
10471                                .into_iter()
10472                                .collect(),
10473                            ),
10474                            ..Default::default()
10475                        },
10476                    })
10477                    .await
10478                    .into_response()
10479                    .unwrap();
10480                Ok(Some(json!(null)))
10481            }
10482        }
10483    });
10484
10485    cx.executor().start_waiting();
10486    editor
10487        .update_in(cx, |editor, window, cx| {
10488            editor.perform_format(
10489                project.clone(),
10490                FormatTrigger::Manual,
10491                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10492                window,
10493                cx,
10494            )
10495        })
10496        .unwrap()
10497        .await;
10498    editor.update(cx, |editor, cx| {
10499        assert_eq!(
10500            editor.text(cx),
10501            r#"
10502                applied-code-action-2-edit
10503                applied-code-action-1-command
10504                applied-code-action-1-edit
10505                applied-formatting
10506                one
10507                two
10508                three
10509            "#
10510            .unindent()
10511        );
10512    });
10513
10514    editor.update_in(cx, |editor, window, cx| {
10515        editor.undo(&Default::default(), window, cx);
10516        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10517    });
10518
10519    // Perform a manual edit while waiting for an LSP command
10520    // that's being run as part of a formatting code action.
10521    let lock_guard = command_lock.lock().await;
10522    let format = editor
10523        .update_in(cx, |editor, window, cx| {
10524            editor.perform_format(
10525                project.clone(),
10526                FormatTrigger::Manual,
10527                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10528                window,
10529                cx,
10530            )
10531        })
10532        .unwrap();
10533    cx.run_until_parked();
10534    editor.update(cx, |editor, cx| {
10535        assert_eq!(
10536            editor.text(cx),
10537            r#"
10538                applied-code-action-1-edit
10539                applied-formatting
10540                one
10541                two
10542                three
10543            "#
10544            .unindent()
10545        );
10546
10547        editor.buffer.update(cx, |buffer, cx| {
10548            let ix = buffer.len(cx);
10549            buffer.edit([(ix..ix, "edited\n")], None, cx);
10550        });
10551    });
10552
10553    // Allow the LSP command to proceed. Because the buffer was edited,
10554    // the second code action will not be run.
10555    drop(lock_guard);
10556    format.await;
10557    editor.update_in(cx, |editor, window, cx| {
10558        assert_eq!(
10559            editor.text(cx),
10560            r#"
10561                applied-code-action-1-command
10562                applied-code-action-1-edit
10563                applied-formatting
10564                one
10565                two
10566                three
10567                edited
10568            "#
10569            .unindent()
10570        );
10571
10572        // The manual edit is undone first, because it is the last thing the user did
10573        // (even though the command completed afterwards).
10574        editor.undo(&Default::default(), window, cx);
10575        assert_eq!(
10576            editor.text(cx),
10577            r#"
10578                applied-code-action-1-command
10579                applied-code-action-1-edit
10580                applied-formatting
10581                one
10582                two
10583                three
10584            "#
10585            .unindent()
10586        );
10587
10588        // All the formatting (including the command, which completed after the manual edit)
10589        // is undone together.
10590        editor.undo(&Default::default(), window, cx);
10591        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10592    });
10593}
10594
10595#[gpui::test]
10596async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
10597    init_test(cx, |settings| {
10598        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
10599            Formatter::LanguageServer { name: None },
10600        ])))
10601    });
10602
10603    let fs = FakeFs::new(cx.executor());
10604    fs.insert_file(path!("/file.ts"), Default::default()).await;
10605
10606    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10607
10608    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10609    language_registry.add(Arc::new(Language::new(
10610        LanguageConfig {
10611            name: "TypeScript".into(),
10612            matcher: LanguageMatcher {
10613                path_suffixes: vec!["ts".to_string()],
10614                ..Default::default()
10615            },
10616            ..LanguageConfig::default()
10617        },
10618        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
10619    )));
10620    update_test_language_settings(cx, |settings| {
10621        settings.defaults.prettier = Some(PrettierSettings {
10622            allowed: true,
10623            ..PrettierSettings::default()
10624        });
10625    });
10626    let mut fake_servers = language_registry.register_fake_lsp(
10627        "TypeScript",
10628        FakeLspAdapter {
10629            capabilities: lsp::ServerCapabilities {
10630                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10631                ..Default::default()
10632            },
10633            ..Default::default()
10634        },
10635    );
10636
10637    let buffer = project
10638        .update(cx, |project, cx| {
10639            project.open_local_buffer(path!("/file.ts"), cx)
10640        })
10641        .await
10642        .unwrap();
10643
10644    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10645    let (editor, cx) = cx.add_window_view(|window, cx| {
10646        build_editor_with_project(project.clone(), buffer, window, cx)
10647    });
10648    editor.update_in(cx, |editor, window, cx| {
10649        editor.set_text(
10650            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
10651            window,
10652            cx,
10653        )
10654    });
10655
10656    cx.executor().start_waiting();
10657    let fake_server = fake_servers.next().await.unwrap();
10658
10659    let format = editor
10660        .update_in(cx, |editor, window, cx| {
10661            editor.perform_code_action_kind(
10662                project.clone(),
10663                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
10664                window,
10665                cx,
10666            )
10667        })
10668        .unwrap();
10669    fake_server
10670        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
10671            assert_eq!(
10672                params.text_document.uri,
10673                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
10674            );
10675            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
10676                lsp::CodeAction {
10677                    title: "Organize Imports".to_string(),
10678                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
10679                    edit: Some(lsp::WorkspaceEdit {
10680                        changes: Some(
10681                            [(
10682                                params.text_document.uri.clone(),
10683                                vec![lsp::TextEdit::new(
10684                                    lsp::Range::new(
10685                                        lsp::Position::new(1, 0),
10686                                        lsp::Position::new(2, 0),
10687                                    ),
10688                                    "".to_string(),
10689                                )],
10690                            )]
10691                            .into_iter()
10692                            .collect(),
10693                        ),
10694                        ..Default::default()
10695                    }),
10696                    ..Default::default()
10697                },
10698            )]))
10699        })
10700        .next()
10701        .await;
10702    cx.executor().start_waiting();
10703    format.await;
10704    assert_eq!(
10705        editor.update(cx, |editor, cx| editor.text(cx)),
10706        "import { a } from 'module';\n\nconst x = a;\n"
10707    );
10708
10709    editor.update_in(cx, |editor, window, cx| {
10710        editor.set_text(
10711            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
10712            window,
10713            cx,
10714        )
10715    });
10716    // Ensure we don't lock if code action hangs.
10717    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
10718        move |params, _| async move {
10719            assert_eq!(
10720                params.text_document.uri,
10721                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
10722            );
10723            futures::future::pending::<()>().await;
10724            unreachable!()
10725        },
10726    );
10727    let format = editor
10728        .update_in(cx, |editor, window, cx| {
10729            editor.perform_code_action_kind(
10730                project,
10731                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
10732                window,
10733                cx,
10734            )
10735        })
10736        .unwrap();
10737    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
10738    cx.executor().start_waiting();
10739    format.await;
10740    assert_eq!(
10741        editor.update(cx, |editor, cx| editor.text(cx)),
10742        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
10743    );
10744}
10745
10746#[gpui::test]
10747async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
10748    init_test(cx, |_| {});
10749
10750    let mut cx = EditorLspTestContext::new_rust(
10751        lsp::ServerCapabilities {
10752            document_formatting_provider: Some(lsp::OneOf::Left(true)),
10753            ..Default::default()
10754        },
10755        cx,
10756    )
10757    .await;
10758
10759    cx.set_state(indoc! {"
10760        one.twoˇ
10761    "});
10762
10763    // The format request takes a long time. When it completes, it inserts
10764    // a newline and an indent before the `.`
10765    cx.lsp
10766        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
10767            let executor = cx.background_executor().clone();
10768            async move {
10769                executor.timer(Duration::from_millis(100)).await;
10770                Ok(Some(vec![lsp::TextEdit {
10771                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
10772                    new_text: "\n    ".into(),
10773                }]))
10774            }
10775        });
10776
10777    // Submit a format request.
10778    let format_1 = cx
10779        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10780        .unwrap();
10781    cx.executor().run_until_parked();
10782
10783    // Submit a second format request.
10784    let format_2 = cx
10785        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10786        .unwrap();
10787    cx.executor().run_until_parked();
10788
10789    // Wait for both format requests to complete
10790    cx.executor().advance_clock(Duration::from_millis(200));
10791    cx.executor().start_waiting();
10792    format_1.await.unwrap();
10793    cx.executor().start_waiting();
10794    format_2.await.unwrap();
10795
10796    // The formatting edits only happens once.
10797    cx.assert_editor_state(indoc! {"
10798        one
10799            .twoˇ
10800    "});
10801}
10802
10803#[gpui::test]
10804async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
10805    init_test(cx, |settings| {
10806        settings.defaults.formatter = Some(SelectedFormatter::Auto)
10807    });
10808
10809    let mut cx = EditorLspTestContext::new_rust(
10810        lsp::ServerCapabilities {
10811            document_formatting_provider: Some(lsp::OneOf::Left(true)),
10812            ..Default::default()
10813        },
10814        cx,
10815    )
10816    .await;
10817
10818    // Set up a buffer white some trailing whitespace and no trailing newline.
10819    cx.set_state(
10820        &[
10821            "one ",   //
10822            "twoˇ",   //
10823            "three ", //
10824            "four",   //
10825        ]
10826        .join("\n"),
10827    );
10828
10829    // Submit a format request.
10830    let format = cx
10831        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10832        .unwrap();
10833
10834    // Record which buffer changes have been sent to the language server
10835    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
10836    cx.lsp
10837        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
10838            let buffer_changes = buffer_changes.clone();
10839            move |params, _| {
10840                buffer_changes.lock().extend(
10841                    params
10842                        .content_changes
10843                        .into_iter()
10844                        .map(|e| (e.range.unwrap(), e.text)),
10845                );
10846            }
10847        });
10848
10849    // Handle formatting requests to the language server.
10850    cx.lsp
10851        .set_request_handler::<lsp::request::Formatting, _, _>({
10852            let buffer_changes = buffer_changes.clone();
10853            move |_, _| {
10854                // When formatting is requested, trailing whitespace has already been stripped,
10855                // and the trailing newline has already been added.
10856                assert_eq!(
10857                    &buffer_changes.lock()[1..],
10858                    &[
10859                        (
10860                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
10861                            "".into()
10862                        ),
10863                        (
10864                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
10865                            "".into()
10866                        ),
10867                        (
10868                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
10869                            "\n".into()
10870                        ),
10871                    ]
10872                );
10873
10874                // Insert blank lines between each line of the buffer.
10875                async move {
10876                    Ok(Some(vec![
10877                        lsp::TextEdit {
10878                            range: lsp::Range::new(
10879                                lsp::Position::new(1, 0),
10880                                lsp::Position::new(1, 0),
10881                            ),
10882                            new_text: "\n".into(),
10883                        },
10884                        lsp::TextEdit {
10885                            range: lsp::Range::new(
10886                                lsp::Position::new(2, 0),
10887                                lsp::Position::new(2, 0),
10888                            ),
10889                            new_text: "\n".into(),
10890                        },
10891                    ]))
10892                }
10893            }
10894        });
10895
10896    // After formatting the buffer, the trailing whitespace is stripped,
10897    // a newline is appended, and the edits provided by the language server
10898    // have been applied.
10899    format.await.unwrap();
10900    cx.assert_editor_state(
10901        &[
10902            "one",   //
10903            "",      //
10904            "twoˇ",  //
10905            "",      //
10906            "three", //
10907            "four",  //
10908            "",      //
10909        ]
10910        .join("\n"),
10911    );
10912
10913    // Undoing the formatting undoes the trailing whitespace removal, the
10914    // trailing newline, and the LSP edits.
10915    cx.update_buffer(|buffer, cx| buffer.undo(cx));
10916    cx.assert_editor_state(
10917        &[
10918            "one ",   //
10919            "twoˇ",   //
10920            "three ", //
10921            "four",   //
10922        ]
10923        .join("\n"),
10924    );
10925}
10926
10927#[gpui::test]
10928async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
10929    cx: &mut TestAppContext,
10930) {
10931    init_test(cx, |_| {});
10932
10933    cx.update(|cx| {
10934        cx.update_global::<SettingsStore, _>(|settings, cx| {
10935            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10936                settings.auto_signature_help = Some(true);
10937            });
10938        });
10939    });
10940
10941    let mut cx = EditorLspTestContext::new_rust(
10942        lsp::ServerCapabilities {
10943            signature_help_provider: Some(lsp::SignatureHelpOptions {
10944                ..Default::default()
10945            }),
10946            ..Default::default()
10947        },
10948        cx,
10949    )
10950    .await;
10951
10952    let language = Language::new(
10953        LanguageConfig {
10954            name: "Rust".into(),
10955            brackets: BracketPairConfig {
10956                pairs: vec![
10957                    BracketPair {
10958                        start: "{".to_string(),
10959                        end: "}".to_string(),
10960                        close: true,
10961                        surround: true,
10962                        newline: true,
10963                    },
10964                    BracketPair {
10965                        start: "(".to_string(),
10966                        end: ")".to_string(),
10967                        close: true,
10968                        surround: true,
10969                        newline: true,
10970                    },
10971                    BracketPair {
10972                        start: "/*".to_string(),
10973                        end: " */".to_string(),
10974                        close: true,
10975                        surround: true,
10976                        newline: true,
10977                    },
10978                    BracketPair {
10979                        start: "[".to_string(),
10980                        end: "]".to_string(),
10981                        close: false,
10982                        surround: false,
10983                        newline: true,
10984                    },
10985                    BracketPair {
10986                        start: "\"".to_string(),
10987                        end: "\"".to_string(),
10988                        close: true,
10989                        surround: true,
10990                        newline: false,
10991                    },
10992                    BracketPair {
10993                        start: "<".to_string(),
10994                        end: ">".to_string(),
10995                        close: false,
10996                        surround: true,
10997                        newline: true,
10998                    },
10999                ],
11000                ..Default::default()
11001            },
11002            autoclose_before: "})]".to_string(),
11003            ..Default::default()
11004        },
11005        Some(tree_sitter_rust::LANGUAGE.into()),
11006    );
11007    let language = Arc::new(language);
11008
11009    cx.language_registry().add(language.clone());
11010    cx.update_buffer(|buffer, cx| {
11011        buffer.set_language(Some(language), cx);
11012    });
11013
11014    cx.set_state(
11015        &r#"
11016            fn main() {
11017                sampleˇ
11018            }
11019        "#
11020        .unindent(),
11021    );
11022
11023    cx.update_editor(|editor, window, cx| {
11024        editor.handle_input("(", window, cx);
11025    });
11026    cx.assert_editor_state(
11027        &"
11028            fn main() {
11029                sample(ˇ)
11030            }
11031        "
11032        .unindent(),
11033    );
11034
11035    let mocked_response = lsp::SignatureHelp {
11036        signatures: vec![lsp::SignatureInformation {
11037            label: "fn sample(param1: u8, param2: u8)".to_string(),
11038            documentation: None,
11039            parameters: Some(vec![
11040                lsp::ParameterInformation {
11041                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11042                    documentation: None,
11043                },
11044                lsp::ParameterInformation {
11045                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11046                    documentation: None,
11047                },
11048            ]),
11049            active_parameter: None,
11050        }],
11051        active_signature: Some(0),
11052        active_parameter: Some(0),
11053    };
11054    handle_signature_help_request(&mut cx, mocked_response).await;
11055
11056    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11057        .await;
11058
11059    cx.editor(|editor, _, _| {
11060        let signature_help_state = editor.signature_help_state.popover().cloned();
11061        let signature = signature_help_state.unwrap();
11062        assert_eq!(
11063            signature.signatures[signature.current_signature].label,
11064            "fn sample(param1: u8, param2: u8)"
11065        );
11066    });
11067}
11068
11069#[gpui::test]
11070async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
11071    init_test(cx, |_| {});
11072
11073    cx.update(|cx| {
11074        cx.update_global::<SettingsStore, _>(|settings, cx| {
11075            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11076                settings.auto_signature_help = Some(false);
11077                settings.show_signature_help_after_edits = Some(false);
11078            });
11079        });
11080    });
11081
11082    let mut cx = EditorLspTestContext::new_rust(
11083        lsp::ServerCapabilities {
11084            signature_help_provider: Some(lsp::SignatureHelpOptions {
11085                ..Default::default()
11086            }),
11087            ..Default::default()
11088        },
11089        cx,
11090    )
11091    .await;
11092
11093    let language = Language::new(
11094        LanguageConfig {
11095            name: "Rust".into(),
11096            brackets: BracketPairConfig {
11097                pairs: vec![
11098                    BracketPair {
11099                        start: "{".to_string(),
11100                        end: "}".to_string(),
11101                        close: true,
11102                        surround: true,
11103                        newline: true,
11104                    },
11105                    BracketPair {
11106                        start: "(".to_string(),
11107                        end: ")".to_string(),
11108                        close: true,
11109                        surround: true,
11110                        newline: true,
11111                    },
11112                    BracketPair {
11113                        start: "/*".to_string(),
11114                        end: " */".to_string(),
11115                        close: true,
11116                        surround: true,
11117                        newline: true,
11118                    },
11119                    BracketPair {
11120                        start: "[".to_string(),
11121                        end: "]".to_string(),
11122                        close: false,
11123                        surround: false,
11124                        newline: true,
11125                    },
11126                    BracketPair {
11127                        start: "\"".to_string(),
11128                        end: "\"".to_string(),
11129                        close: true,
11130                        surround: true,
11131                        newline: false,
11132                    },
11133                    BracketPair {
11134                        start: "<".to_string(),
11135                        end: ">".to_string(),
11136                        close: false,
11137                        surround: true,
11138                        newline: true,
11139                    },
11140                ],
11141                ..Default::default()
11142            },
11143            autoclose_before: "})]".to_string(),
11144            ..Default::default()
11145        },
11146        Some(tree_sitter_rust::LANGUAGE.into()),
11147    );
11148    let language = Arc::new(language);
11149
11150    cx.language_registry().add(language.clone());
11151    cx.update_buffer(|buffer, cx| {
11152        buffer.set_language(Some(language), cx);
11153    });
11154
11155    // Ensure that signature_help is not called when no signature help is enabled.
11156    cx.set_state(
11157        &r#"
11158            fn main() {
11159                sampleˇ
11160            }
11161        "#
11162        .unindent(),
11163    );
11164    cx.update_editor(|editor, window, cx| {
11165        editor.handle_input("(", window, cx);
11166    });
11167    cx.assert_editor_state(
11168        &"
11169            fn main() {
11170                sample(ˇ)
11171            }
11172        "
11173        .unindent(),
11174    );
11175    cx.editor(|editor, _, _| {
11176        assert!(editor.signature_help_state.task().is_none());
11177    });
11178
11179    let mocked_response = lsp::SignatureHelp {
11180        signatures: vec![lsp::SignatureInformation {
11181            label: "fn sample(param1: u8, param2: u8)".to_string(),
11182            documentation: None,
11183            parameters: Some(vec![
11184                lsp::ParameterInformation {
11185                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11186                    documentation: None,
11187                },
11188                lsp::ParameterInformation {
11189                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11190                    documentation: None,
11191                },
11192            ]),
11193            active_parameter: None,
11194        }],
11195        active_signature: Some(0),
11196        active_parameter: Some(0),
11197    };
11198
11199    // Ensure that signature_help is called when enabled afte edits
11200    cx.update(|_, cx| {
11201        cx.update_global::<SettingsStore, _>(|settings, cx| {
11202            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11203                settings.auto_signature_help = Some(false);
11204                settings.show_signature_help_after_edits = Some(true);
11205            });
11206        });
11207    });
11208    cx.set_state(
11209        &r#"
11210            fn main() {
11211                sampleˇ
11212            }
11213        "#
11214        .unindent(),
11215    );
11216    cx.update_editor(|editor, window, cx| {
11217        editor.handle_input("(", window, cx);
11218    });
11219    cx.assert_editor_state(
11220        &"
11221            fn main() {
11222                sample(ˇ)
11223            }
11224        "
11225        .unindent(),
11226    );
11227    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11228    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11229        .await;
11230    cx.update_editor(|editor, _, _| {
11231        let signature_help_state = editor.signature_help_state.popover().cloned();
11232        assert!(signature_help_state.is_some());
11233        let signature = signature_help_state.unwrap();
11234        assert_eq!(
11235            signature.signatures[signature.current_signature].label,
11236            "fn sample(param1: u8, param2: u8)"
11237        );
11238        editor.signature_help_state = SignatureHelpState::default();
11239    });
11240
11241    // Ensure that signature_help is called when auto signature help override is enabled
11242    cx.update(|_, cx| {
11243        cx.update_global::<SettingsStore, _>(|settings, cx| {
11244            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11245                settings.auto_signature_help = Some(true);
11246                settings.show_signature_help_after_edits = Some(false);
11247            });
11248        });
11249    });
11250    cx.set_state(
11251        &r#"
11252            fn main() {
11253                sampleˇ
11254            }
11255        "#
11256        .unindent(),
11257    );
11258    cx.update_editor(|editor, window, cx| {
11259        editor.handle_input("(", window, cx);
11260    });
11261    cx.assert_editor_state(
11262        &"
11263            fn main() {
11264                sample(ˇ)
11265            }
11266        "
11267        .unindent(),
11268    );
11269    handle_signature_help_request(&mut cx, mocked_response).await;
11270    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11271        .await;
11272    cx.editor(|editor, _, _| {
11273        let signature_help_state = editor.signature_help_state.popover().cloned();
11274        assert!(signature_help_state.is_some());
11275        let signature = signature_help_state.unwrap();
11276        assert_eq!(
11277            signature.signatures[signature.current_signature].label,
11278            "fn sample(param1: u8, param2: u8)"
11279        );
11280    });
11281}
11282
11283#[gpui::test]
11284async fn test_signature_help(cx: &mut TestAppContext) {
11285    init_test(cx, |_| {});
11286    cx.update(|cx| {
11287        cx.update_global::<SettingsStore, _>(|settings, cx| {
11288            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11289                settings.auto_signature_help = Some(true);
11290            });
11291        });
11292    });
11293
11294    let mut cx = EditorLspTestContext::new_rust(
11295        lsp::ServerCapabilities {
11296            signature_help_provider: Some(lsp::SignatureHelpOptions {
11297                ..Default::default()
11298            }),
11299            ..Default::default()
11300        },
11301        cx,
11302    )
11303    .await;
11304
11305    // A test that directly calls `show_signature_help`
11306    cx.update_editor(|editor, window, cx| {
11307        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11308    });
11309
11310    let mocked_response = lsp::SignatureHelp {
11311        signatures: vec![lsp::SignatureInformation {
11312            label: "fn sample(param1: u8, param2: u8)".to_string(),
11313            documentation: None,
11314            parameters: Some(vec![
11315                lsp::ParameterInformation {
11316                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11317                    documentation: None,
11318                },
11319                lsp::ParameterInformation {
11320                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11321                    documentation: None,
11322                },
11323            ]),
11324            active_parameter: None,
11325        }],
11326        active_signature: Some(0),
11327        active_parameter: Some(0),
11328    };
11329    handle_signature_help_request(&mut cx, mocked_response).await;
11330
11331    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11332        .await;
11333
11334    cx.editor(|editor, _, _| {
11335        let signature_help_state = editor.signature_help_state.popover().cloned();
11336        assert!(signature_help_state.is_some());
11337        let signature = signature_help_state.unwrap();
11338        assert_eq!(
11339            signature.signatures[signature.current_signature].label,
11340            "fn sample(param1: u8, param2: u8)"
11341        );
11342    });
11343
11344    // When exiting outside from inside the brackets, `signature_help` is closed.
11345    cx.set_state(indoc! {"
11346        fn main() {
11347            sample(ˇ);
11348        }
11349
11350        fn sample(param1: u8, param2: u8) {}
11351    "});
11352
11353    cx.update_editor(|editor, window, cx| {
11354        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11355            s.select_ranges([0..0])
11356        });
11357    });
11358
11359    let mocked_response = lsp::SignatureHelp {
11360        signatures: Vec::new(),
11361        active_signature: None,
11362        active_parameter: None,
11363    };
11364    handle_signature_help_request(&mut cx, mocked_response).await;
11365
11366    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11367        .await;
11368
11369    cx.editor(|editor, _, _| {
11370        assert!(!editor.signature_help_state.is_shown());
11371    });
11372
11373    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
11374    cx.set_state(indoc! {"
11375        fn main() {
11376            sample(ˇ);
11377        }
11378
11379        fn sample(param1: u8, param2: u8) {}
11380    "});
11381
11382    let mocked_response = lsp::SignatureHelp {
11383        signatures: vec![lsp::SignatureInformation {
11384            label: "fn sample(param1: u8, param2: u8)".to_string(),
11385            documentation: None,
11386            parameters: Some(vec![
11387                lsp::ParameterInformation {
11388                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11389                    documentation: None,
11390                },
11391                lsp::ParameterInformation {
11392                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11393                    documentation: None,
11394                },
11395            ]),
11396            active_parameter: None,
11397        }],
11398        active_signature: Some(0),
11399        active_parameter: Some(0),
11400    };
11401    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11402    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11403        .await;
11404    cx.editor(|editor, _, _| {
11405        assert!(editor.signature_help_state.is_shown());
11406    });
11407
11408    // Restore the popover with more parameter input
11409    cx.set_state(indoc! {"
11410        fn main() {
11411            sample(param1, param2ˇ);
11412        }
11413
11414        fn sample(param1: u8, param2: u8) {}
11415    "});
11416
11417    let mocked_response = lsp::SignatureHelp {
11418        signatures: vec![lsp::SignatureInformation {
11419            label: "fn sample(param1: u8, param2: u8)".to_string(),
11420            documentation: None,
11421            parameters: Some(vec![
11422                lsp::ParameterInformation {
11423                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11424                    documentation: None,
11425                },
11426                lsp::ParameterInformation {
11427                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11428                    documentation: None,
11429                },
11430            ]),
11431            active_parameter: None,
11432        }],
11433        active_signature: Some(0),
11434        active_parameter: Some(1),
11435    };
11436    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11437    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11438        .await;
11439
11440    // When selecting a range, the popover is gone.
11441    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
11442    cx.update_editor(|editor, window, cx| {
11443        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11444            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11445        })
11446    });
11447    cx.assert_editor_state(indoc! {"
11448        fn main() {
11449            sample(param1, «ˇparam2»);
11450        }
11451
11452        fn sample(param1: u8, param2: u8) {}
11453    "});
11454    cx.editor(|editor, _, _| {
11455        assert!(!editor.signature_help_state.is_shown());
11456    });
11457
11458    // When unselecting again, the popover is back if within the brackets.
11459    cx.update_editor(|editor, window, cx| {
11460        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11461            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11462        })
11463    });
11464    cx.assert_editor_state(indoc! {"
11465        fn main() {
11466            sample(param1, ˇparam2);
11467        }
11468
11469        fn sample(param1: u8, param2: u8) {}
11470    "});
11471    handle_signature_help_request(&mut cx, mocked_response).await;
11472    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11473        .await;
11474    cx.editor(|editor, _, _| {
11475        assert!(editor.signature_help_state.is_shown());
11476    });
11477
11478    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
11479    cx.update_editor(|editor, window, cx| {
11480        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11481            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
11482            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11483        })
11484    });
11485    cx.assert_editor_state(indoc! {"
11486        fn main() {
11487            sample(param1, ˇparam2);
11488        }
11489
11490        fn sample(param1: u8, param2: u8) {}
11491    "});
11492
11493    let mocked_response = lsp::SignatureHelp {
11494        signatures: vec![lsp::SignatureInformation {
11495            label: "fn sample(param1: u8, param2: u8)".to_string(),
11496            documentation: None,
11497            parameters: Some(vec![
11498                lsp::ParameterInformation {
11499                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11500                    documentation: None,
11501                },
11502                lsp::ParameterInformation {
11503                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11504                    documentation: None,
11505                },
11506            ]),
11507            active_parameter: None,
11508        }],
11509        active_signature: Some(0),
11510        active_parameter: Some(1),
11511    };
11512    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11513    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11514        .await;
11515    cx.update_editor(|editor, _, cx| {
11516        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
11517    });
11518    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11519        .await;
11520    cx.update_editor(|editor, window, cx| {
11521        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11522            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11523        })
11524    });
11525    cx.assert_editor_state(indoc! {"
11526        fn main() {
11527            sample(param1, «ˇparam2»);
11528        }
11529
11530        fn sample(param1: u8, param2: u8) {}
11531    "});
11532    cx.update_editor(|editor, window, cx| {
11533        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11534            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11535        })
11536    });
11537    cx.assert_editor_state(indoc! {"
11538        fn main() {
11539            sample(param1, ˇparam2);
11540        }
11541
11542        fn sample(param1: u8, param2: u8) {}
11543    "});
11544    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
11545        .await;
11546}
11547
11548#[gpui::test]
11549async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
11550    init_test(cx, |_| {});
11551
11552    let mut cx = EditorLspTestContext::new_rust(
11553        lsp::ServerCapabilities {
11554            signature_help_provider: Some(lsp::SignatureHelpOptions {
11555                ..Default::default()
11556            }),
11557            ..Default::default()
11558        },
11559        cx,
11560    )
11561    .await;
11562
11563    cx.set_state(indoc! {"
11564        fn main() {
11565            overloadedˇ
11566        }
11567    "});
11568
11569    cx.update_editor(|editor, window, cx| {
11570        editor.handle_input("(", window, cx);
11571        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11572    });
11573
11574    // Mock response with 3 signatures
11575    let mocked_response = lsp::SignatureHelp {
11576        signatures: vec![
11577            lsp::SignatureInformation {
11578                label: "fn overloaded(x: i32)".to_string(),
11579                documentation: None,
11580                parameters: Some(vec![lsp::ParameterInformation {
11581                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11582                    documentation: None,
11583                }]),
11584                active_parameter: None,
11585            },
11586            lsp::SignatureInformation {
11587                label: "fn overloaded(x: i32, y: i32)".to_string(),
11588                documentation: None,
11589                parameters: Some(vec![
11590                    lsp::ParameterInformation {
11591                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11592                        documentation: None,
11593                    },
11594                    lsp::ParameterInformation {
11595                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
11596                        documentation: None,
11597                    },
11598                ]),
11599                active_parameter: None,
11600            },
11601            lsp::SignatureInformation {
11602                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
11603                documentation: None,
11604                parameters: Some(vec![
11605                    lsp::ParameterInformation {
11606                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11607                        documentation: None,
11608                    },
11609                    lsp::ParameterInformation {
11610                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
11611                        documentation: None,
11612                    },
11613                    lsp::ParameterInformation {
11614                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
11615                        documentation: None,
11616                    },
11617                ]),
11618                active_parameter: None,
11619            },
11620        ],
11621        active_signature: Some(1),
11622        active_parameter: Some(0),
11623    };
11624    handle_signature_help_request(&mut cx, mocked_response).await;
11625
11626    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11627        .await;
11628
11629    // Verify we have multiple signatures and the right one is selected
11630    cx.editor(|editor, _, _| {
11631        let popover = editor.signature_help_state.popover().cloned().unwrap();
11632        assert_eq!(popover.signatures.len(), 3);
11633        // active_signature was 1, so that should be the current
11634        assert_eq!(popover.current_signature, 1);
11635        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
11636        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
11637        assert_eq!(
11638            popover.signatures[2].label,
11639            "fn overloaded(x: i32, y: i32, z: i32)"
11640        );
11641    });
11642
11643    // Test navigation functionality
11644    cx.update_editor(|editor, window, cx| {
11645        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
11646    });
11647
11648    cx.editor(|editor, _, _| {
11649        let popover = editor.signature_help_state.popover().cloned().unwrap();
11650        assert_eq!(popover.current_signature, 2);
11651    });
11652
11653    // Test wrap around
11654    cx.update_editor(|editor, window, cx| {
11655        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
11656    });
11657
11658    cx.editor(|editor, _, _| {
11659        let popover = editor.signature_help_state.popover().cloned().unwrap();
11660        assert_eq!(popover.current_signature, 0);
11661    });
11662
11663    // Test previous navigation
11664    cx.update_editor(|editor, window, cx| {
11665        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
11666    });
11667
11668    cx.editor(|editor, _, _| {
11669        let popover = editor.signature_help_state.popover().cloned().unwrap();
11670        assert_eq!(popover.current_signature, 2);
11671    });
11672}
11673
11674#[gpui::test]
11675async fn test_completion_mode(cx: &mut TestAppContext) {
11676    init_test(cx, |_| {});
11677    let mut cx = EditorLspTestContext::new_rust(
11678        lsp::ServerCapabilities {
11679            completion_provider: Some(lsp::CompletionOptions {
11680                resolve_provider: Some(true),
11681                ..Default::default()
11682            }),
11683            ..Default::default()
11684        },
11685        cx,
11686    )
11687    .await;
11688
11689    struct Run {
11690        run_description: &'static str,
11691        initial_state: String,
11692        buffer_marked_text: String,
11693        completion_label: &'static str,
11694        completion_text: &'static str,
11695        expected_with_insert_mode: String,
11696        expected_with_replace_mode: String,
11697        expected_with_replace_subsequence_mode: String,
11698        expected_with_replace_suffix_mode: String,
11699    }
11700
11701    let runs = [
11702        Run {
11703            run_description: "Start of word matches completion text",
11704            initial_state: "before ediˇ after".into(),
11705            buffer_marked_text: "before <edi|> after".into(),
11706            completion_label: "editor",
11707            completion_text: "editor",
11708            expected_with_insert_mode: "before editorˇ after".into(),
11709            expected_with_replace_mode: "before editorˇ after".into(),
11710            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11711            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11712        },
11713        Run {
11714            run_description: "Accept same text at the middle of the word",
11715            initial_state: "before ediˇtor after".into(),
11716            buffer_marked_text: "before <edi|tor> after".into(),
11717            completion_label: "editor",
11718            completion_text: "editor",
11719            expected_with_insert_mode: "before editorˇtor after".into(),
11720            expected_with_replace_mode: "before editorˇ after".into(),
11721            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11722            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11723        },
11724        Run {
11725            run_description: "End of word matches completion text -- cursor at end",
11726            initial_state: "before torˇ after".into(),
11727            buffer_marked_text: "before <tor|> after".into(),
11728            completion_label: "editor",
11729            completion_text: "editor",
11730            expected_with_insert_mode: "before editorˇ after".into(),
11731            expected_with_replace_mode: "before editorˇ after".into(),
11732            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11733            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11734        },
11735        Run {
11736            run_description: "End of word matches completion text -- cursor at start",
11737            initial_state: "before ˇtor after".into(),
11738            buffer_marked_text: "before <|tor> after".into(),
11739            completion_label: "editor",
11740            completion_text: "editor",
11741            expected_with_insert_mode: "before editorˇtor after".into(),
11742            expected_with_replace_mode: "before editorˇ after".into(),
11743            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11744            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11745        },
11746        Run {
11747            run_description: "Prepend text containing whitespace",
11748            initial_state: "pˇfield: bool".into(),
11749            buffer_marked_text: "<p|field>: bool".into(),
11750            completion_label: "pub ",
11751            completion_text: "pub ",
11752            expected_with_insert_mode: "pub ˇfield: bool".into(),
11753            expected_with_replace_mode: "pub ˇ: bool".into(),
11754            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
11755            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
11756        },
11757        Run {
11758            run_description: "Add element to start of list",
11759            initial_state: "[element_ˇelement_2]".into(),
11760            buffer_marked_text: "[<element_|element_2>]".into(),
11761            completion_label: "element_1",
11762            completion_text: "element_1",
11763            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
11764            expected_with_replace_mode: "[element_1ˇ]".into(),
11765            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
11766            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
11767        },
11768        Run {
11769            run_description: "Add element to start of list -- first and second elements are equal",
11770            initial_state: "[elˇelement]".into(),
11771            buffer_marked_text: "[<el|element>]".into(),
11772            completion_label: "element",
11773            completion_text: "element",
11774            expected_with_insert_mode: "[elementˇelement]".into(),
11775            expected_with_replace_mode: "[elementˇ]".into(),
11776            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
11777            expected_with_replace_suffix_mode: "[elementˇ]".into(),
11778        },
11779        Run {
11780            run_description: "Ends with matching suffix",
11781            initial_state: "SubˇError".into(),
11782            buffer_marked_text: "<Sub|Error>".into(),
11783            completion_label: "SubscriptionError",
11784            completion_text: "SubscriptionError",
11785            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
11786            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11787            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11788            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
11789        },
11790        Run {
11791            run_description: "Suffix is a subsequence -- contiguous",
11792            initial_state: "SubˇErr".into(),
11793            buffer_marked_text: "<Sub|Err>".into(),
11794            completion_label: "SubscriptionError",
11795            completion_text: "SubscriptionError",
11796            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
11797            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11798            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11799            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
11800        },
11801        Run {
11802            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
11803            initial_state: "Suˇscrirr".into(),
11804            buffer_marked_text: "<Su|scrirr>".into(),
11805            completion_label: "SubscriptionError",
11806            completion_text: "SubscriptionError",
11807            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
11808            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11809            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11810            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
11811        },
11812        Run {
11813            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
11814            initial_state: "foo(indˇix)".into(),
11815            buffer_marked_text: "foo(<ind|ix>)".into(),
11816            completion_label: "node_index",
11817            completion_text: "node_index",
11818            expected_with_insert_mode: "foo(node_indexˇix)".into(),
11819            expected_with_replace_mode: "foo(node_indexˇ)".into(),
11820            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
11821            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
11822        },
11823        Run {
11824            run_description: "Replace range ends before cursor - should extend to cursor",
11825            initial_state: "before editˇo after".into(),
11826            buffer_marked_text: "before <{ed}>it|o after".into(),
11827            completion_label: "editor",
11828            completion_text: "editor",
11829            expected_with_insert_mode: "before editorˇo after".into(),
11830            expected_with_replace_mode: "before editorˇo after".into(),
11831            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
11832            expected_with_replace_suffix_mode: "before editorˇo after".into(),
11833        },
11834        Run {
11835            run_description: "Uses label for suffix matching",
11836            initial_state: "before ediˇtor after".into(),
11837            buffer_marked_text: "before <edi|tor> after".into(),
11838            completion_label: "editor",
11839            completion_text: "editor()",
11840            expected_with_insert_mode: "before editor()ˇtor after".into(),
11841            expected_with_replace_mode: "before editor()ˇ after".into(),
11842            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
11843            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
11844        },
11845        Run {
11846            run_description: "Case insensitive subsequence and suffix matching",
11847            initial_state: "before EDiˇtoR after".into(),
11848            buffer_marked_text: "before <EDi|toR> after".into(),
11849            completion_label: "editor",
11850            completion_text: "editor",
11851            expected_with_insert_mode: "before editorˇtoR after".into(),
11852            expected_with_replace_mode: "before editorˇ after".into(),
11853            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11854            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11855        },
11856    ];
11857
11858    for run in runs {
11859        let run_variations = [
11860            (LspInsertMode::Insert, run.expected_with_insert_mode),
11861            (LspInsertMode::Replace, run.expected_with_replace_mode),
11862            (
11863                LspInsertMode::ReplaceSubsequence,
11864                run.expected_with_replace_subsequence_mode,
11865            ),
11866            (
11867                LspInsertMode::ReplaceSuffix,
11868                run.expected_with_replace_suffix_mode,
11869            ),
11870        ];
11871
11872        for (lsp_insert_mode, expected_text) in run_variations {
11873            eprintln!(
11874                "run = {:?}, mode = {lsp_insert_mode:.?}",
11875                run.run_description,
11876            );
11877
11878            update_test_language_settings(&mut cx, |settings| {
11879                settings.defaults.completions = Some(CompletionSettings {
11880                    lsp_insert_mode,
11881                    words: WordsCompletionMode::Disabled,
11882                    lsp: true,
11883                    lsp_fetch_timeout_ms: 0,
11884                });
11885            });
11886
11887            cx.set_state(&run.initial_state);
11888            cx.update_editor(|editor, window, cx| {
11889                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11890            });
11891
11892            let counter = Arc::new(AtomicUsize::new(0));
11893            handle_completion_request_with_insert_and_replace(
11894                &mut cx,
11895                &run.buffer_marked_text,
11896                vec![(run.completion_label, run.completion_text)],
11897                counter.clone(),
11898            )
11899            .await;
11900            cx.condition(|editor, _| editor.context_menu_visible())
11901                .await;
11902            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11903
11904            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11905                editor
11906                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
11907                    .unwrap()
11908            });
11909            cx.assert_editor_state(&expected_text);
11910            handle_resolve_completion_request(&mut cx, None).await;
11911            apply_additional_edits.await.unwrap();
11912        }
11913    }
11914}
11915
11916#[gpui::test]
11917async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
11918    init_test(cx, |_| {});
11919    let mut cx = EditorLspTestContext::new_rust(
11920        lsp::ServerCapabilities {
11921            completion_provider: Some(lsp::CompletionOptions {
11922                resolve_provider: Some(true),
11923                ..Default::default()
11924            }),
11925            ..Default::default()
11926        },
11927        cx,
11928    )
11929    .await;
11930
11931    let initial_state = "SubˇError";
11932    let buffer_marked_text = "<Sub|Error>";
11933    let completion_text = "SubscriptionError";
11934    let expected_with_insert_mode = "SubscriptionErrorˇError";
11935    let expected_with_replace_mode = "SubscriptionErrorˇ";
11936
11937    update_test_language_settings(&mut cx, |settings| {
11938        settings.defaults.completions = Some(CompletionSettings {
11939            words: WordsCompletionMode::Disabled,
11940            // set the opposite here to ensure that the action is overriding the default behavior
11941            lsp_insert_mode: LspInsertMode::Insert,
11942            lsp: true,
11943            lsp_fetch_timeout_ms: 0,
11944        });
11945    });
11946
11947    cx.set_state(initial_state);
11948    cx.update_editor(|editor, window, cx| {
11949        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11950    });
11951
11952    let counter = Arc::new(AtomicUsize::new(0));
11953    handle_completion_request_with_insert_and_replace(
11954        &mut cx,
11955        &buffer_marked_text,
11956        vec![(completion_text, completion_text)],
11957        counter.clone(),
11958    )
11959    .await;
11960    cx.condition(|editor, _| editor.context_menu_visible())
11961        .await;
11962    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11963
11964    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11965        editor
11966            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11967            .unwrap()
11968    });
11969    cx.assert_editor_state(&expected_with_replace_mode);
11970    handle_resolve_completion_request(&mut cx, None).await;
11971    apply_additional_edits.await.unwrap();
11972
11973    update_test_language_settings(&mut cx, |settings| {
11974        settings.defaults.completions = Some(CompletionSettings {
11975            words: WordsCompletionMode::Disabled,
11976            // set the opposite here to ensure that the action is overriding the default behavior
11977            lsp_insert_mode: LspInsertMode::Replace,
11978            lsp: true,
11979            lsp_fetch_timeout_ms: 0,
11980        });
11981    });
11982
11983    cx.set_state(initial_state);
11984    cx.update_editor(|editor, window, cx| {
11985        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11986    });
11987    handle_completion_request_with_insert_and_replace(
11988        &mut cx,
11989        &buffer_marked_text,
11990        vec![(completion_text, completion_text)],
11991        counter.clone(),
11992    )
11993    .await;
11994    cx.condition(|editor, _| editor.context_menu_visible())
11995        .await;
11996    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
11997
11998    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11999        editor
12000            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
12001            .unwrap()
12002    });
12003    cx.assert_editor_state(&expected_with_insert_mode);
12004    handle_resolve_completion_request(&mut cx, None).await;
12005    apply_additional_edits.await.unwrap();
12006}
12007
12008#[gpui::test]
12009async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
12010    init_test(cx, |_| {});
12011    let mut cx = EditorLspTestContext::new_rust(
12012        lsp::ServerCapabilities {
12013            completion_provider: Some(lsp::CompletionOptions {
12014                resolve_provider: Some(true),
12015                ..Default::default()
12016            }),
12017            ..Default::default()
12018        },
12019        cx,
12020    )
12021    .await;
12022
12023    // scenario: surrounding text matches completion text
12024    let completion_text = "to_offset";
12025    let initial_state = indoc! {"
12026        1. buf.to_offˇsuffix
12027        2. buf.to_offˇsuf
12028        3. buf.to_offˇfix
12029        4. buf.to_offˇ
12030        5. into_offˇensive
12031        6. ˇsuffix
12032        7. let ˇ //
12033        8. aaˇzz
12034        9. buf.to_off«zzzzzˇ»suffix
12035        10. buf.«ˇzzzzz»suffix
12036        11. to_off«ˇzzzzz»
12037
12038        buf.to_offˇsuffix  // newest cursor
12039    "};
12040    let completion_marked_buffer = indoc! {"
12041        1. buf.to_offsuffix
12042        2. buf.to_offsuf
12043        3. buf.to_offfix
12044        4. buf.to_off
12045        5. into_offensive
12046        6. suffix
12047        7. let  //
12048        8. aazz
12049        9. buf.to_offzzzzzsuffix
12050        10. buf.zzzzzsuffix
12051        11. to_offzzzzz
12052
12053        buf.<to_off|suffix>  // newest cursor
12054    "};
12055    let expected = indoc! {"
12056        1. buf.to_offsetˇ
12057        2. buf.to_offsetˇsuf
12058        3. buf.to_offsetˇfix
12059        4. buf.to_offsetˇ
12060        5. into_offsetˇensive
12061        6. to_offsetˇsuffix
12062        7. let to_offsetˇ //
12063        8. aato_offsetˇzz
12064        9. buf.to_offsetˇ
12065        10. buf.to_offsetˇsuffix
12066        11. to_offsetˇ
12067
12068        buf.to_offsetˇ  // newest cursor
12069    "};
12070    cx.set_state(initial_state);
12071    cx.update_editor(|editor, window, cx| {
12072        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12073    });
12074    handle_completion_request_with_insert_and_replace(
12075        &mut cx,
12076        completion_marked_buffer,
12077        vec![(completion_text, completion_text)],
12078        Arc::new(AtomicUsize::new(0)),
12079    )
12080    .await;
12081    cx.condition(|editor, _| editor.context_menu_visible())
12082        .await;
12083    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12084        editor
12085            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12086            .unwrap()
12087    });
12088    cx.assert_editor_state(expected);
12089    handle_resolve_completion_request(&mut cx, None).await;
12090    apply_additional_edits.await.unwrap();
12091
12092    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
12093    let completion_text = "foo_and_bar";
12094    let initial_state = indoc! {"
12095        1. ooanbˇ
12096        2. zooanbˇ
12097        3. ooanbˇz
12098        4. zooanbˇz
12099        5. ooanˇ
12100        6. oanbˇ
12101
12102        ooanbˇ
12103    "};
12104    let completion_marked_buffer = indoc! {"
12105        1. ooanb
12106        2. zooanb
12107        3. ooanbz
12108        4. zooanbz
12109        5. ooan
12110        6. oanb
12111
12112        <ooanb|>
12113    "};
12114    let expected = indoc! {"
12115        1. foo_and_barˇ
12116        2. zfoo_and_barˇ
12117        3. foo_and_barˇz
12118        4. zfoo_and_barˇz
12119        5. ooanfoo_and_barˇ
12120        6. oanbfoo_and_barˇ
12121
12122        foo_and_barˇ
12123    "};
12124    cx.set_state(initial_state);
12125    cx.update_editor(|editor, window, cx| {
12126        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12127    });
12128    handle_completion_request_with_insert_and_replace(
12129        &mut cx,
12130        completion_marked_buffer,
12131        vec![(completion_text, completion_text)],
12132        Arc::new(AtomicUsize::new(0)),
12133    )
12134    .await;
12135    cx.condition(|editor, _| editor.context_menu_visible())
12136        .await;
12137    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12138        editor
12139            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12140            .unwrap()
12141    });
12142    cx.assert_editor_state(expected);
12143    handle_resolve_completion_request(&mut cx, None).await;
12144    apply_additional_edits.await.unwrap();
12145
12146    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
12147    // (expects the same as if it was inserted at the end)
12148    let completion_text = "foo_and_bar";
12149    let initial_state = indoc! {"
12150        1. ooˇanb
12151        2. zooˇanb
12152        3. ooˇanbz
12153        4. zooˇanbz
12154
12155        ooˇanb
12156    "};
12157    let completion_marked_buffer = indoc! {"
12158        1. ooanb
12159        2. zooanb
12160        3. ooanbz
12161        4. zooanbz
12162
12163        <oo|anb>
12164    "};
12165    let expected = indoc! {"
12166        1. foo_and_barˇ
12167        2. zfoo_and_barˇ
12168        3. foo_and_barˇz
12169        4. zfoo_and_barˇz
12170
12171        foo_and_barˇ
12172    "};
12173    cx.set_state(initial_state);
12174    cx.update_editor(|editor, window, cx| {
12175        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12176    });
12177    handle_completion_request_with_insert_and_replace(
12178        &mut cx,
12179        completion_marked_buffer,
12180        vec![(completion_text, completion_text)],
12181        Arc::new(AtomicUsize::new(0)),
12182    )
12183    .await;
12184    cx.condition(|editor, _| editor.context_menu_visible())
12185        .await;
12186    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12187        editor
12188            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12189            .unwrap()
12190    });
12191    cx.assert_editor_state(expected);
12192    handle_resolve_completion_request(&mut cx, None).await;
12193    apply_additional_edits.await.unwrap();
12194}
12195
12196// This used to crash
12197#[gpui::test]
12198async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
12199    init_test(cx, |_| {});
12200
12201    let buffer_text = indoc! {"
12202        fn main() {
12203            10.satu;
12204
12205            //
12206            // separate cursors so they open in different excerpts (manually reproducible)
12207            //
12208
12209            10.satu20;
12210        }
12211    "};
12212    let multibuffer_text_with_selections = indoc! {"
12213        fn main() {
12214            10.satuˇ;
12215
12216            //
12217
12218            //
12219
12220            10.satuˇ20;
12221        }
12222    "};
12223    let expected_multibuffer = indoc! {"
12224        fn main() {
12225            10.saturating_sub()ˇ;
12226
12227            //
12228
12229            //
12230
12231            10.saturating_sub()ˇ;
12232        }
12233    "};
12234
12235    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
12236    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
12237
12238    let fs = FakeFs::new(cx.executor());
12239    fs.insert_tree(
12240        path!("/a"),
12241        json!({
12242            "main.rs": buffer_text,
12243        }),
12244    )
12245    .await;
12246
12247    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12248    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12249    language_registry.add(rust_lang());
12250    let mut fake_servers = language_registry.register_fake_lsp(
12251        "Rust",
12252        FakeLspAdapter {
12253            capabilities: lsp::ServerCapabilities {
12254                completion_provider: Some(lsp::CompletionOptions {
12255                    resolve_provider: None,
12256                    ..lsp::CompletionOptions::default()
12257                }),
12258                ..lsp::ServerCapabilities::default()
12259            },
12260            ..FakeLspAdapter::default()
12261        },
12262    );
12263    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12264    let cx = &mut VisualTestContext::from_window(*workspace, cx);
12265    let buffer = project
12266        .update(cx, |project, cx| {
12267            project.open_local_buffer(path!("/a/main.rs"), cx)
12268        })
12269        .await
12270        .unwrap();
12271
12272    let multi_buffer = cx.new(|cx| {
12273        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
12274        multi_buffer.push_excerpts(
12275            buffer.clone(),
12276            [ExcerptRange::new(0..first_excerpt_end)],
12277            cx,
12278        );
12279        multi_buffer.push_excerpts(
12280            buffer.clone(),
12281            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
12282            cx,
12283        );
12284        multi_buffer
12285    });
12286
12287    let editor = workspace
12288        .update(cx, |_, window, cx| {
12289            cx.new(|cx| {
12290                Editor::new(
12291                    EditorMode::Full {
12292                        scale_ui_elements_with_buffer_font_size: false,
12293                        show_active_line_background: false,
12294                        sized_by_content: false,
12295                    },
12296                    multi_buffer.clone(),
12297                    Some(project.clone()),
12298                    window,
12299                    cx,
12300                )
12301            })
12302        })
12303        .unwrap();
12304
12305    let pane = workspace
12306        .update(cx, |workspace, _, _| workspace.active_pane().clone())
12307        .unwrap();
12308    pane.update_in(cx, |pane, window, cx| {
12309        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
12310    });
12311
12312    let fake_server = fake_servers.next().await.unwrap();
12313
12314    editor.update_in(cx, |editor, window, cx| {
12315        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12316            s.select_ranges([
12317                Point::new(1, 11)..Point::new(1, 11),
12318                Point::new(7, 11)..Point::new(7, 11),
12319            ])
12320        });
12321
12322        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
12323    });
12324
12325    editor.update_in(cx, |editor, window, cx| {
12326        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12327    });
12328
12329    fake_server
12330        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
12331            let completion_item = lsp::CompletionItem {
12332                label: "saturating_sub()".into(),
12333                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
12334                    lsp::InsertReplaceEdit {
12335                        new_text: "saturating_sub()".to_owned(),
12336                        insert: lsp::Range::new(
12337                            lsp::Position::new(7, 7),
12338                            lsp::Position::new(7, 11),
12339                        ),
12340                        replace: lsp::Range::new(
12341                            lsp::Position::new(7, 7),
12342                            lsp::Position::new(7, 13),
12343                        ),
12344                    },
12345                )),
12346                ..lsp::CompletionItem::default()
12347            };
12348
12349            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
12350        })
12351        .next()
12352        .await
12353        .unwrap();
12354
12355    cx.condition(&editor, |editor, _| editor.context_menu_visible())
12356        .await;
12357
12358    editor
12359        .update_in(cx, |editor, window, cx| {
12360            editor
12361                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12362                .unwrap()
12363        })
12364        .await
12365        .unwrap();
12366
12367    editor.update(cx, |editor, cx| {
12368        assert_text_with_selections(editor, expected_multibuffer, cx);
12369    })
12370}
12371
12372#[gpui::test]
12373async fn test_completion(cx: &mut TestAppContext) {
12374    init_test(cx, |_| {});
12375
12376    let mut cx = EditorLspTestContext::new_rust(
12377        lsp::ServerCapabilities {
12378            completion_provider: Some(lsp::CompletionOptions {
12379                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12380                resolve_provider: Some(true),
12381                ..Default::default()
12382            }),
12383            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12384            ..Default::default()
12385        },
12386        cx,
12387    )
12388    .await;
12389    let counter = Arc::new(AtomicUsize::new(0));
12390
12391    cx.set_state(indoc! {"
12392        oneˇ
12393        two
12394        three
12395    "});
12396    cx.simulate_keystroke(".");
12397    handle_completion_request(
12398        indoc! {"
12399            one.|<>
12400            two
12401            three
12402        "},
12403        vec!["first_completion", "second_completion"],
12404        true,
12405        counter.clone(),
12406        &mut cx,
12407    )
12408    .await;
12409    cx.condition(|editor, _| editor.context_menu_visible())
12410        .await;
12411    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12412
12413    let _handler = handle_signature_help_request(
12414        &mut cx,
12415        lsp::SignatureHelp {
12416            signatures: vec![lsp::SignatureInformation {
12417                label: "test signature".to_string(),
12418                documentation: None,
12419                parameters: Some(vec![lsp::ParameterInformation {
12420                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
12421                    documentation: None,
12422                }]),
12423                active_parameter: None,
12424            }],
12425            active_signature: None,
12426            active_parameter: None,
12427        },
12428    );
12429    cx.update_editor(|editor, window, cx| {
12430        assert!(
12431            !editor.signature_help_state.is_shown(),
12432            "No signature help was called for"
12433        );
12434        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12435    });
12436    cx.run_until_parked();
12437    cx.update_editor(|editor, _, _| {
12438        assert!(
12439            !editor.signature_help_state.is_shown(),
12440            "No signature help should be shown when completions menu is open"
12441        );
12442    });
12443
12444    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12445        editor.context_menu_next(&Default::default(), window, cx);
12446        editor
12447            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12448            .unwrap()
12449    });
12450    cx.assert_editor_state(indoc! {"
12451        one.second_completionˇ
12452        two
12453        three
12454    "});
12455
12456    handle_resolve_completion_request(
12457        &mut cx,
12458        Some(vec![
12459            (
12460                //This overlaps with the primary completion edit which is
12461                //misbehavior from the LSP spec, test that we filter it out
12462                indoc! {"
12463                    one.second_ˇcompletion
12464                    two
12465                    threeˇ
12466                "},
12467                "overlapping additional edit",
12468            ),
12469            (
12470                indoc! {"
12471                    one.second_completion
12472                    two
12473                    threeˇ
12474                "},
12475                "\nadditional edit",
12476            ),
12477        ]),
12478    )
12479    .await;
12480    apply_additional_edits.await.unwrap();
12481    cx.assert_editor_state(indoc! {"
12482        one.second_completionˇ
12483        two
12484        three
12485        additional edit
12486    "});
12487
12488    cx.set_state(indoc! {"
12489        one.second_completion
12490        twoˇ
12491        threeˇ
12492        additional edit
12493    "});
12494    cx.simulate_keystroke(" ");
12495    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12496    cx.simulate_keystroke("s");
12497    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12498
12499    cx.assert_editor_state(indoc! {"
12500        one.second_completion
12501        two sˇ
12502        three sˇ
12503        additional edit
12504    "});
12505    handle_completion_request(
12506        indoc! {"
12507            one.second_completion
12508            two s
12509            three <s|>
12510            additional edit
12511        "},
12512        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
12513        true,
12514        counter.clone(),
12515        &mut cx,
12516    )
12517    .await;
12518    cx.condition(|editor, _| editor.context_menu_visible())
12519        .await;
12520    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12521
12522    cx.simulate_keystroke("i");
12523
12524    handle_completion_request(
12525        indoc! {"
12526            one.second_completion
12527            two si
12528            three <si|>
12529            additional edit
12530        "},
12531        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
12532        true,
12533        counter.clone(),
12534        &mut cx,
12535    )
12536    .await;
12537    cx.condition(|editor, _| editor.context_menu_visible())
12538        .await;
12539    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12540
12541    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12542        editor
12543            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12544            .unwrap()
12545    });
12546    cx.assert_editor_state(indoc! {"
12547        one.second_completion
12548        two sixth_completionˇ
12549        three sixth_completionˇ
12550        additional edit
12551    "});
12552
12553    apply_additional_edits.await.unwrap();
12554
12555    update_test_language_settings(&mut cx, |settings| {
12556        settings.defaults.show_completions_on_input = Some(false);
12557    });
12558    cx.set_state("editorˇ");
12559    cx.simulate_keystroke(".");
12560    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12561    cx.simulate_keystrokes("c l o");
12562    cx.assert_editor_state("editor.cloˇ");
12563    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12564    cx.update_editor(|editor, window, cx| {
12565        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12566    });
12567    handle_completion_request(
12568        "editor.<clo|>",
12569        vec!["close", "clobber"],
12570        true,
12571        counter.clone(),
12572        &mut cx,
12573    )
12574    .await;
12575    cx.condition(|editor, _| editor.context_menu_visible())
12576        .await;
12577    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
12578
12579    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12580        editor
12581            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12582            .unwrap()
12583    });
12584    cx.assert_editor_state("editor.clobberˇ");
12585    handle_resolve_completion_request(&mut cx, None).await;
12586    apply_additional_edits.await.unwrap();
12587}
12588
12589#[gpui::test]
12590async fn test_completion_reuse(cx: &mut TestAppContext) {
12591    init_test(cx, |_| {});
12592
12593    let mut cx = EditorLspTestContext::new_rust(
12594        lsp::ServerCapabilities {
12595            completion_provider: Some(lsp::CompletionOptions {
12596                trigger_characters: Some(vec![".".to_string()]),
12597                ..Default::default()
12598            }),
12599            ..Default::default()
12600        },
12601        cx,
12602    )
12603    .await;
12604
12605    let counter = Arc::new(AtomicUsize::new(0));
12606    cx.set_state("objˇ");
12607    cx.simulate_keystroke(".");
12608
12609    // Initial completion request returns complete results
12610    let is_incomplete = false;
12611    handle_completion_request(
12612        "obj.|<>",
12613        vec!["a", "ab", "abc"],
12614        is_incomplete,
12615        counter.clone(),
12616        &mut cx,
12617    )
12618    .await;
12619    cx.run_until_parked();
12620    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12621    cx.assert_editor_state("obj.ˇ");
12622    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12623
12624    // Type "a" - filters existing completions
12625    cx.simulate_keystroke("a");
12626    cx.run_until_parked();
12627    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12628    cx.assert_editor_state("obj.aˇ");
12629    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12630
12631    // Type "b" - filters existing completions
12632    cx.simulate_keystroke("b");
12633    cx.run_until_parked();
12634    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12635    cx.assert_editor_state("obj.abˇ");
12636    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12637
12638    // Type "c" - filters existing completions
12639    cx.simulate_keystroke("c");
12640    cx.run_until_parked();
12641    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12642    cx.assert_editor_state("obj.abcˇ");
12643    check_displayed_completions(vec!["abc"], &mut cx);
12644
12645    // Backspace to delete "c" - filters existing completions
12646    cx.update_editor(|editor, window, cx| {
12647        editor.backspace(&Backspace, window, cx);
12648    });
12649    cx.run_until_parked();
12650    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12651    cx.assert_editor_state("obj.abˇ");
12652    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12653
12654    // Moving cursor to the left dismisses menu.
12655    cx.update_editor(|editor, window, cx| {
12656        editor.move_left(&MoveLeft, window, cx);
12657    });
12658    cx.run_until_parked();
12659    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12660    cx.assert_editor_state("obj.aˇb");
12661    cx.update_editor(|editor, _, _| {
12662        assert_eq!(editor.context_menu_visible(), false);
12663    });
12664
12665    // Type "b" - new request
12666    cx.simulate_keystroke("b");
12667    let is_incomplete = false;
12668    handle_completion_request(
12669        "obj.<ab|>a",
12670        vec!["ab", "abc"],
12671        is_incomplete,
12672        counter.clone(),
12673        &mut cx,
12674    )
12675    .await;
12676    cx.run_until_parked();
12677    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12678    cx.assert_editor_state("obj.abˇb");
12679    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12680
12681    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
12682    cx.update_editor(|editor, window, cx| {
12683        editor.backspace(&Backspace, window, cx);
12684    });
12685    let is_incomplete = false;
12686    handle_completion_request(
12687        "obj.<a|>b",
12688        vec!["a", "ab", "abc"],
12689        is_incomplete,
12690        counter.clone(),
12691        &mut cx,
12692    )
12693    .await;
12694    cx.run_until_parked();
12695    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12696    cx.assert_editor_state("obj.aˇb");
12697    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12698
12699    // Backspace to delete "a" - dismisses menu.
12700    cx.update_editor(|editor, window, cx| {
12701        editor.backspace(&Backspace, window, cx);
12702    });
12703    cx.run_until_parked();
12704    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12705    cx.assert_editor_state("obj.ˇb");
12706    cx.update_editor(|editor, _, _| {
12707        assert_eq!(editor.context_menu_visible(), false);
12708    });
12709}
12710
12711#[gpui::test]
12712async fn test_word_completion(cx: &mut TestAppContext) {
12713    let lsp_fetch_timeout_ms = 10;
12714    init_test(cx, |language_settings| {
12715        language_settings.defaults.completions = Some(CompletionSettings {
12716            words: WordsCompletionMode::Fallback,
12717            lsp: true,
12718            lsp_fetch_timeout_ms: 10,
12719            lsp_insert_mode: LspInsertMode::Insert,
12720        });
12721    });
12722
12723    let mut cx = EditorLspTestContext::new_rust(
12724        lsp::ServerCapabilities {
12725            completion_provider: Some(lsp::CompletionOptions {
12726                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12727                ..lsp::CompletionOptions::default()
12728            }),
12729            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12730            ..lsp::ServerCapabilities::default()
12731        },
12732        cx,
12733    )
12734    .await;
12735
12736    let throttle_completions = Arc::new(AtomicBool::new(false));
12737
12738    let lsp_throttle_completions = throttle_completions.clone();
12739    let _completion_requests_handler =
12740        cx.lsp
12741            .server
12742            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
12743                let lsp_throttle_completions = lsp_throttle_completions.clone();
12744                let cx = cx.clone();
12745                async move {
12746                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
12747                        cx.background_executor()
12748                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
12749                            .await;
12750                    }
12751                    Ok(Some(lsp::CompletionResponse::Array(vec![
12752                        lsp::CompletionItem {
12753                            label: "first".into(),
12754                            ..lsp::CompletionItem::default()
12755                        },
12756                        lsp::CompletionItem {
12757                            label: "last".into(),
12758                            ..lsp::CompletionItem::default()
12759                        },
12760                    ])))
12761                }
12762            });
12763
12764    cx.set_state(indoc! {"
12765        oneˇ
12766        two
12767        three
12768    "});
12769    cx.simulate_keystroke(".");
12770    cx.executor().run_until_parked();
12771    cx.condition(|editor, _| editor.context_menu_visible())
12772        .await;
12773    cx.update_editor(|editor, window, cx| {
12774        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12775        {
12776            assert_eq!(
12777                completion_menu_entries(&menu),
12778                &["first", "last"],
12779                "When LSP server is fast to reply, no fallback word completions are used"
12780            );
12781        } else {
12782            panic!("expected completion menu to be open");
12783        }
12784        editor.cancel(&Cancel, window, cx);
12785    });
12786    cx.executor().run_until_parked();
12787    cx.condition(|editor, _| !editor.context_menu_visible())
12788        .await;
12789
12790    throttle_completions.store(true, atomic::Ordering::Release);
12791    cx.simulate_keystroke(".");
12792    cx.executor()
12793        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
12794    cx.executor().run_until_parked();
12795    cx.condition(|editor, _| editor.context_menu_visible())
12796        .await;
12797    cx.update_editor(|editor, _, _| {
12798        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12799        {
12800            assert_eq!(completion_menu_entries(&menu), &["one", "three", "two"],
12801                "When LSP server is slow, document words can be shown instead, if configured accordingly");
12802        } else {
12803            panic!("expected completion menu to be open");
12804        }
12805    });
12806}
12807
12808#[gpui::test]
12809async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
12810    init_test(cx, |language_settings| {
12811        language_settings.defaults.completions = Some(CompletionSettings {
12812            words: WordsCompletionMode::Enabled,
12813            lsp: true,
12814            lsp_fetch_timeout_ms: 0,
12815            lsp_insert_mode: LspInsertMode::Insert,
12816        });
12817    });
12818
12819    let mut cx = EditorLspTestContext::new_rust(
12820        lsp::ServerCapabilities {
12821            completion_provider: Some(lsp::CompletionOptions {
12822                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12823                ..lsp::CompletionOptions::default()
12824            }),
12825            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12826            ..lsp::ServerCapabilities::default()
12827        },
12828        cx,
12829    )
12830    .await;
12831
12832    let _completion_requests_handler =
12833        cx.lsp
12834            .server
12835            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12836                Ok(Some(lsp::CompletionResponse::Array(vec![
12837                    lsp::CompletionItem {
12838                        label: "first".into(),
12839                        ..lsp::CompletionItem::default()
12840                    },
12841                    lsp::CompletionItem {
12842                        label: "last".into(),
12843                        ..lsp::CompletionItem::default()
12844                    },
12845                ])))
12846            });
12847
12848    cx.set_state(indoc! {"ˇ
12849        first
12850        last
12851        second
12852    "});
12853    cx.simulate_keystroke(".");
12854    cx.executor().run_until_parked();
12855    cx.condition(|editor, _| editor.context_menu_visible())
12856        .await;
12857    cx.update_editor(|editor, _, _| {
12858        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12859        {
12860            assert_eq!(
12861                completion_menu_entries(&menu),
12862                &["first", "last", "second"],
12863                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
12864            );
12865        } else {
12866            panic!("expected completion menu to be open");
12867        }
12868    });
12869}
12870
12871#[gpui::test]
12872async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
12873    init_test(cx, |language_settings| {
12874        language_settings.defaults.completions = Some(CompletionSettings {
12875            words: WordsCompletionMode::Disabled,
12876            lsp: true,
12877            lsp_fetch_timeout_ms: 0,
12878            lsp_insert_mode: LspInsertMode::Insert,
12879        });
12880    });
12881
12882    let mut cx = EditorLspTestContext::new_rust(
12883        lsp::ServerCapabilities {
12884            completion_provider: Some(lsp::CompletionOptions {
12885                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12886                ..lsp::CompletionOptions::default()
12887            }),
12888            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12889            ..lsp::ServerCapabilities::default()
12890        },
12891        cx,
12892    )
12893    .await;
12894
12895    let _completion_requests_handler =
12896        cx.lsp
12897            .server
12898            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12899                panic!("LSP completions should not be queried when dealing with word completions")
12900            });
12901
12902    cx.set_state(indoc! {"ˇ
12903        first
12904        last
12905        second
12906    "});
12907    cx.update_editor(|editor, window, cx| {
12908        editor.show_word_completions(&ShowWordCompletions, window, cx);
12909    });
12910    cx.executor().run_until_parked();
12911    cx.condition(|editor, _| editor.context_menu_visible())
12912        .await;
12913    cx.update_editor(|editor, _, _| {
12914        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12915        {
12916            assert_eq!(
12917                completion_menu_entries(&menu),
12918                &["first", "last", "second"],
12919                "`ShowWordCompletions` action should show word completions"
12920            );
12921        } else {
12922            panic!("expected completion menu to be open");
12923        }
12924    });
12925
12926    cx.simulate_keystroke("l");
12927    cx.executor().run_until_parked();
12928    cx.condition(|editor, _| editor.context_menu_visible())
12929        .await;
12930    cx.update_editor(|editor, _, _| {
12931        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12932        {
12933            assert_eq!(
12934                completion_menu_entries(&menu),
12935                &["last"],
12936                "After showing word completions, further editing should filter them and not query the LSP"
12937            );
12938        } else {
12939            panic!("expected completion menu to be open");
12940        }
12941    });
12942}
12943
12944#[gpui::test]
12945async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
12946    init_test(cx, |language_settings| {
12947        language_settings.defaults.completions = Some(CompletionSettings {
12948            words: WordsCompletionMode::Fallback,
12949            lsp: false,
12950            lsp_fetch_timeout_ms: 0,
12951            lsp_insert_mode: LspInsertMode::Insert,
12952        });
12953    });
12954
12955    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12956
12957    cx.set_state(indoc! {"ˇ
12958        0_usize
12959        let
12960        33
12961        4.5f32
12962    "});
12963    cx.update_editor(|editor, window, cx| {
12964        editor.show_completions(&ShowCompletions::default(), window, cx);
12965    });
12966    cx.executor().run_until_parked();
12967    cx.condition(|editor, _| editor.context_menu_visible())
12968        .await;
12969    cx.update_editor(|editor, window, cx| {
12970        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12971        {
12972            assert_eq!(
12973                completion_menu_entries(&menu),
12974                &["let"],
12975                "With no digits in the completion query, no digits should be in the word completions"
12976            );
12977        } else {
12978            panic!("expected completion menu to be open");
12979        }
12980        editor.cancel(&Cancel, window, cx);
12981    });
12982
12983    cx.set_state(indoc! {"12984        0_usize
12985        let
12986        3
12987        33.35f32
12988    "});
12989    cx.update_editor(|editor, window, cx| {
12990        editor.show_completions(&ShowCompletions::default(), window, cx);
12991    });
12992    cx.executor().run_until_parked();
12993    cx.condition(|editor, _| editor.context_menu_visible())
12994        .await;
12995    cx.update_editor(|editor, _, _| {
12996        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12997        {
12998            assert_eq!(completion_menu_entries(&menu), &["33", "35f32"], "The digit is in the completion query, \
12999                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
13000        } else {
13001            panic!("expected completion menu to be open");
13002        }
13003    });
13004}
13005
13006fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
13007    let position = || lsp::Position {
13008        line: params.text_document_position.position.line,
13009        character: params.text_document_position.position.character,
13010    };
13011    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13012        range: lsp::Range {
13013            start: position(),
13014            end: position(),
13015        },
13016        new_text: text.to_string(),
13017    }))
13018}
13019
13020#[gpui::test]
13021async fn test_multiline_completion(cx: &mut TestAppContext) {
13022    init_test(cx, |_| {});
13023
13024    let fs = FakeFs::new(cx.executor());
13025    fs.insert_tree(
13026        path!("/a"),
13027        json!({
13028            "main.ts": "a",
13029        }),
13030    )
13031    .await;
13032
13033    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13034    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13035    let typescript_language = Arc::new(Language::new(
13036        LanguageConfig {
13037            name: "TypeScript".into(),
13038            matcher: LanguageMatcher {
13039                path_suffixes: vec!["ts".to_string()],
13040                ..LanguageMatcher::default()
13041            },
13042            line_comments: vec!["// ".into()],
13043            ..LanguageConfig::default()
13044        },
13045        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
13046    ));
13047    language_registry.add(typescript_language.clone());
13048    let mut fake_servers = language_registry.register_fake_lsp(
13049        "TypeScript",
13050        FakeLspAdapter {
13051            capabilities: lsp::ServerCapabilities {
13052                completion_provider: Some(lsp::CompletionOptions {
13053                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13054                    ..lsp::CompletionOptions::default()
13055                }),
13056                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13057                ..lsp::ServerCapabilities::default()
13058            },
13059            // Emulate vtsls label generation
13060            label_for_completion: Some(Box::new(|item, _| {
13061                let text = if let Some(description) = item
13062                    .label_details
13063                    .as_ref()
13064                    .and_then(|label_details| label_details.description.as_ref())
13065                {
13066                    format!("{} {}", item.label, description)
13067                } else if let Some(detail) = &item.detail {
13068                    format!("{} {}", item.label, detail)
13069                } else {
13070                    item.label.clone()
13071                };
13072                let len = text.len();
13073                Some(language::CodeLabel {
13074                    text,
13075                    runs: Vec::new(),
13076                    filter_range: 0..len,
13077                })
13078            })),
13079            ..FakeLspAdapter::default()
13080        },
13081    );
13082    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13083    let cx = &mut VisualTestContext::from_window(*workspace, cx);
13084    let worktree_id = workspace
13085        .update(cx, |workspace, _window, cx| {
13086            workspace.project().update(cx, |project, cx| {
13087                project.worktrees(cx).next().unwrap().read(cx).id()
13088            })
13089        })
13090        .unwrap();
13091    let _buffer = project
13092        .update(cx, |project, cx| {
13093            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
13094        })
13095        .await
13096        .unwrap();
13097    let editor = workspace
13098        .update(cx, |workspace, window, cx| {
13099            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
13100        })
13101        .unwrap()
13102        .await
13103        .unwrap()
13104        .downcast::<Editor>()
13105        .unwrap();
13106    let fake_server = fake_servers.next().await.unwrap();
13107
13108    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
13109    let multiline_label_2 = "a\nb\nc\n";
13110    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
13111    let multiline_description = "d\ne\nf\n";
13112    let multiline_detail_2 = "g\nh\ni\n";
13113
13114    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
13115        move |params, _| async move {
13116            Ok(Some(lsp::CompletionResponse::Array(vec![
13117                lsp::CompletionItem {
13118                    label: multiline_label.to_string(),
13119                    text_edit: gen_text_edit(&params, "new_text_1"),
13120                    ..lsp::CompletionItem::default()
13121                },
13122                lsp::CompletionItem {
13123                    label: "single line label 1".to_string(),
13124                    detail: Some(multiline_detail.to_string()),
13125                    text_edit: gen_text_edit(&params, "new_text_2"),
13126                    ..lsp::CompletionItem::default()
13127                },
13128                lsp::CompletionItem {
13129                    label: "single line label 2".to_string(),
13130                    label_details: Some(lsp::CompletionItemLabelDetails {
13131                        description: Some(multiline_description.to_string()),
13132                        detail: None,
13133                    }),
13134                    text_edit: gen_text_edit(&params, "new_text_2"),
13135                    ..lsp::CompletionItem::default()
13136                },
13137                lsp::CompletionItem {
13138                    label: multiline_label_2.to_string(),
13139                    detail: Some(multiline_detail_2.to_string()),
13140                    text_edit: gen_text_edit(&params, "new_text_3"),
13141                    ..lsp::CompletionItem::default()
13142                },
13143                lsp::CompletionItem {
13144                    label: "Label with many     spaces and \t but without newlines".to_string(),
13145                    detail: Some(
13146                        "Details with many     spaces and \t but without newlines".to_string(),
13147                    ),
13148                    text_edit: gen_text_edit(&params, "new_text_4"),
13149                    ..lsp::CompletionItem::default()
13150                },
13151            ])))
13152        },
13153    );
13154
13155    editor.update_in(cx, |editor, window, cx| {
13156        cx.focus_self(window);
13157        editor.move_to_end(&MoveToEnd, window, cx);
13158        editor.handle_input(".", window, cx);
13159    });
13160    cx.run_until_parked();
13161    completion_handle.next().await.unwrap();
13162
13163    editor.update(cx, |editor, _| {
13164        assert!(editor.context_menu_visible());
13165        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13166        {
13167            let completion_labels = menu
13168                .completions
13169                .borrow()
13170                .iter()
13171                .map(|c| c.label.text.clone())
13172                .collect::<Vec<_>>();
13173            assert_eq!(
13174                completion_labels,
13175                &[
13176                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
13177                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
13178                    "single line label 2 d e f ",
13179                    "a b c g h i ",
13180                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
13181                ],
13182                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
13183            );
13184
13185            for completion in menu
13186                .completions
13187                .borrow()
13188                .iter() {
13189                    assert_eq!(
13190                        completion.label.filter_range,
13191                        0..completion.label.text.len(),
13192                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
13193                    );
13194                }
13195        } else {
13196            panic!("expected completion menu to be open");
13197        }
13198    });
13199}
13200
13201#[gpui::test]
13202async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
13203    init_test(cx, |_| {});
13204    let mut cx = EditorLspTestContext::new_rust(
13205        lsp::ServerCapabilities {
13206            completion_provider: Some(lsp::CompletionOptions {
13207                trigger_characters: Some(vec![".".to_string()]),
13208                ..Default::default()
13209            }),
13210            ..Default::default()
13211        },
13212        cx,
13213    )
13214    .await;
13215    cx.lsp
13216        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13217            Ok(Some(lsp::CompletionResponse::Array(vec![
13218                lsp::CompletionItem {
13219                    label: "first".into(),
13220                    ..Default::default()
13221                },
13222                lsp::CompletionItem {
13223                    label: "last".into(),
13224                    ..Default::default()
13225                },
13226            ])))
13227        });
13228    cx.set_state("variableˇ");
13229    cx.simulate_keystroke(".");
13230    cx.executor().run_until_parked();
13231
13232    cx.update_editor(|editor, _, _| {
13233        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13234        {
13235            assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
13236        } else {
13237            panic!("expected completion menu to be open");
13238        }
13239    });
13240
13241    cx.update_editor(|editor, window, cx| {
13242        editor.move_page_down(&MovePageDown::default(), window, cx);
13243        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13244        {
13245            assert!(
13246                menu.selected_item == 1,
13247                "expected PageDown to select the last item from the context menu"
13248            );
13249        } else {
13250            panic!("expected completion menu to stay open after PageDown");
13251        }
13252    });
13253
13254    cx.update_editor(|editor, window, cx| {
13255        editor.move_page_up(&MovePageUp::default(), window, cx);
13256        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13257        {
13258            assert!(
13259                menu.selected_item == 0,
13260                "expected PageUp to select the first item from the context menu"
13261            );
13262        } else {
13263            panic!("expected completion menu to stay open after PageUp");
13264        }
13265    });
13266}
13267
13268#[gpui::test]
13269async fn test_as_is_completions(cx: &mut TestAppContext) {
13270    init_test(cx, |_| {});
13271    let mut cx = EditorLspTestContext::new_rust(
13272        lsp::ServerCapabilities {
13273            completion_provider: Some(lsp::CompletionOptions {
13274                ..Default::default()
13275            }),
13276            ..Default::default()
13277        },
13278        cx,
13279    )
13280    .await;
13281    cx.lsp
13282        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13283            Ok(Some(lsp::CompletionResponse::Array(vec![
13284                lsp::CompletionItem {
13285                    label: "unsafe".into(),
13286                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13287                        range: lsp::Range {
13288                            start: lsp::Position {
13289                                line: 1,
13290                                character: 2,
13291                            },
13292                            end: lsp::Position {
13293                                line: 1,
13294                                character: 3,
13295                            },
13296                        },
13297                        new_text: "unsafe".to_string(),
13298                    })),
13299                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
13300                    ..Default::default()
13301                },
13302            ])))
13303        });
13304    cx.set_state("fn a() {}\n");
13305    cx.executor().run_until_parked();
13306    cx.update_editor(|editor, window, cx| {
13307        editor.show_completions(
13308            &ShowCompletions {
13309                trigger: Some("\n".into()),
13310            },
13311            window,
13312            cx,
13313        );
13314    });
13315    cx.executor().run_until_parked();
13316
13317    cx.update_editor(|editor, window, cx| {
13318        editor.confirm_completion(&Default::default(), window, cx)
13319    });
13320    cx.executor().run_until_parked();
13321    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
13322}
13323
13324#[gpui::test]
13325async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
13326    init_test(cx, |_| {});
13327
13328    let mut cx = EditorLspTestContext::new_rust(
13329        lsp::ServerCapabilities {
13330            completion_provider: Some(lsp::CompletionOptions {
13331                trigger_characters: Some(vec![".".to_string()]),
13332                resolve_provider: Some(true),
13333                ..Default::default()
13334            }),
13335            ..Default::default()
13336        },
13337        cx,
13338    )
13339    .await;
13340
13341    cx.set_state("fn main() { let a = 2ˇ; }");
13342    cx.simulate_keystroke(".");
13343    let completion_item = lsp::CompletionItem {
13344        label: "Some".into(),
13345        kind: Some(lsp::CompletionItemKind::SNIPPET),
13346        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
13347        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
13348            kind: lsp::MarkupKind::Markdown,
13349            value: "```rust\nSome(2)\n```".to_string(),
13350        })),
13351        deprecated: Some(false),
13352        sort_text: Some("Some".to_string()),
13353        filter_text: Some("Some".to_string()),
13354        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
13355        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13356            range: lsp::Range {
13357                start: lsp::Position {
13358                    line: 0,
13359                    character: 22,
13360                },
13361                end: lsp::Position {
13362                    line: 0,
13363                    character: 22,
13364                },
13365            },
13366            new_text: "Some(2)".to_string(),
13367        })),
13368        additional_text_edits: Some(vec![lsp::TextEdit {
13369            range: lsp::Range {
13370                start: lsp::Position {
13371                    line: 0,
13372                    character: 20,
13373                },
13374                end: lsp::Position {
13375                    line: 0,
13376                    character: 22,
13377                },
13378            },
13379            new_text: "".to_string(),
13380        }]),
13381        ..Default::default()
13382    };
13383
13384    let closure_completion_item = completion_item.clone();
13385    let counter = Arc::new(AtomicUsize::new(0));
13386    let counter_clone = counter.clone();
13387    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
13388        let task_completion_item = closure_completion_item.clone();
13389        counter_clone.fetch_add(1, atomic::Ordering::Release);
13390        async move {
13391            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
13392                is_incomplete: true,
13393                item_defaults: None,
13394                items: vec![task_completion_item],
13395            })))
13396        }
13397    });
13398
13399    cx.condition(|editor, _| editor.context_menu_visible())
13400        .await;
13401    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
13402    assert!(request.next().await.is_some());
13403    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13404
13405    cx.simulate_keystrokes("S o m");
13406    cx.condition(|editor, _| editor.context_menu_visible())
13407        .await;
13408    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
13409    assert!(request.next().await.is_some());
13410    assert!(request.next().await.is_some());
13411    assert!(request.next().await.is_some());
13412    request.close();
13413    assert!(request.next().await.is_none());
13414    assert_eq!(
13415        counter.load(atomic::Ordering::Acquire),
13416        4,
13417        "With the completions menu open, only one LSP request should happen per input"
13418    );
13419}
13420
13421#[gpui::test]
13422async fn test_toggle_comment(cx: &mut TestAppContext) {
13423    init_test(cx, |_| {});
13424    let mut cx = EditorTestContext::new(cx).await;
13425    let language = Arc::new(Language::new(
13426        LanguageConfig {
13427            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
13428            ..Default::default()
13429        },
13430        Some(tree_sitter_rust::LANGUAGE.into()),
13431    ));
13432    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
13433
13434    // If multiple selections intersect a line, the line is only toggled once.
13435    cx.set_state(indoc! {"
13436        fn a() {
13437            «//b();
13438            ˇ»// «c();
13439            //ˇ»  d();
13440        }
13441    "});
13442
13443    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13444
13445    cx.assert_editor_state(indoc! {"
13446        fn a() {
13447            «b();
13448            c();
13449            ˇ» d();
13450        }
13451    "});
13452
13453    // The comment prefix is inserted at the same column for every line in a
13454    // selection.
13455    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13456
13457    cx.assert_editor_state(indoc! {"
13458        fn a() {
13459            // «b();
13460            // c();
13461            ˇ»//  d();
13462        }
13463    "});
13464
13465    // If a selection ends at the beginning of a line, that line is not toggled.
13466    cx.set_selections_state(indoc! {"
13467        fn a() {
13468            // b();
13469            «// c();
13470        ˇ»    //  d();
13471        }
13472    "});
13473
13474    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13475
13476    cx.assert_editor_state(indoc! {"
13477        fn a() {
13478            // b();
13479            «c();
13480        ˇ»    //  d();
13481        }
13482    "});
13483
13484    // If a selection span a single line and is empty, the line is toggled.
13485    cx.set_state(indoc! {"
13486        fn a() {
13487            a();
13488            b();
13489        ˇ
13490        }
13491    "});
13492
13493    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13494
13495    cx.assert_editor_state(indoc! {"
13496        fn a() {
13497            a();
13498            b();
13499        //•ˇ
13500        }
13501    "});
13502
13503    // If a selection span multiple lines, empty lines are not toggled.
13504    cx.set_state(indoc! {"
13505        fn a() {
13506            «a();
13507
13508            c();ˇ»
13509        }
13510    "});
13511
13512    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13513
13514    cx.assert_editor_state(indoc! {"
13515        fn a() {
13516            // «a();
13517
13518            // c();ˇ»
13519        }
13520    "});
13521
13522    // If a selection includes multiple comment prefixes, all lines are uncommented.
13523    cx.set_state(indoc! {"
13524        fn a() {
13525            «// a();
13526            /// b();
13527            //! c();ˇ»
13528        }
13529    "});
13530
13531    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13532
13533    cx.assert_editor_state(indoc! {"
13534        fn a() {
13535            «a();
13536            b();
13537            c();ˇ»
13538        }
13539    "});
13540}
13541
13542#[gpui::test]
13543async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
13544    init_test(cx, |_| {});
13545    let mut cx = EditorTestContext::new(cx).await;
13546    let language = Arc::new(Language::new(
13547        LanguageConfig {
13548            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
13549            ..Default::default()
13550        },
13551        Some(tree_sitter_rust::LANGUAGE.into()),
13552    ));
13553    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
13554
13555    let toggle_comments = &ToggleComments {
13556        advance_downwards: false,
13557        ignore_indent: true,
13558    };
13559
13560    // If multiple selections intersect a line, the line is only toggled once.
13561    cx.set_state(indoc! {"
13562        fn a() {
13563        //    «b();
13564        //    c();
13565        //    ˇ» d();
13566        }
13567    "});
13568
13569    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13570
13571    cx.assert_editor_state(indoc! {"
13572        fn a() {
13573            «b();
13574            c();
13575            ˇ» d();
13576        }
13577    "});
13578
13579    // The comment prefix is inserted at the beginning of each line
13580    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13581
13582    cx.assert_editor_state(indoc! {"
13583        fn a() {
13584        //    «b();
13585        //    c();
13586        //    ˇ» d();
13587        }
13588    "});
13589
13590    // If a selection ends at the beginning of a line, that line is not toggled.
13591    cx.set_selections_state(indoc! {"
13592        fn a() {
13593        //    b();
13594        //    «c();
13595        ˇ»//     d();
13596        }
13597    "});
13598
13599    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13600
13601    cx.assert_editor_state(indoc! {"
13602        fn a() {
13603        //    b();
13604            «c();
13605        ˇ»//     d();
13606        }
13607    "});
13608
13609    // If a selection span a single line and is empty, the line is toggled.
13610    cx.set_state(indoc! {"
13611        fn a() {
13612            a();
13613            b();
13614        ˇ
13615        }
13616    "});
13617
13618    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13619
13620    cx.assert_editor_state(indoc! {"
13621        fn a() {
13622            a();
13623            b();
13624        //ˇ
13625        }
13626    "});
13627
13628    // If a selection span multiple lines, empty lines are not toggled.
13629    cx.set_state(indoc! {"
13630        fn a() {
13631            «a();
13632
13633            c();ˇ»
13634        }
13635    "});
13636
13637    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13638
13639    cx.assert_editor_state(indoc! {"
13640        fn a() {
13641        //    «a();
13642
13643        //    c();ˇ»
13644        }
13645    "});
13646
13647    // If a selection includes multiple comment prefixes, all lines are uncommented.
13648    cx.set_state(indoc! {"
13649        fn a() {
13650        //    «a();
13651        ///    b();
13652        //!    c();ˇ»
13653        }
13654    "});
13655
13656    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13657
13658    cx.assert_editor_state(indoc! {"
13659        fn a() {
13660            «a();
13661            b();
13662            c();ˇ»
13663        }
13664    "});
13665}
13666
13667#[gpui::test]
13668async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
13669    init_test(cx, |_| {});
13670
13671    let language = Arc::new(Language::new(
13672        LanguageConfig {
13673            line_comments: vec!["// ".into()],
13674            ..Default::default()
13675        },
13676        Some(tree_sitter_rust::LANGUAGE.into()),
13677    ));
13678
13679    let mut cx = EditorTestContext::new(cx).await;
13680
13681    cx.language_registry().add(language.clone());
13682    cx.update_buffer(|buffer, cx| {
13683        buffer.set_language(Some(language), cx);
13684    });
13685
13686    let toggle_comments = &ToggleComments {
13687        advance_downwards: true,
13688        ignore_indent: false,
13689    };
13690
13691    // Single cursor on one line -> advance
13692    // Cursor moves horizontally 3 characters as well on non-blank line
13693    cx.set_state(indoc!(
13694        "fn a() {
13695             ˇdog();
13696             cat();
13697        }"
13698    ));
13699    cx.update_editor(|editor, window, cx| {
13700        editor.toggle_comments(toggle_comments, window, cx);
13701    });
13702    cx.assert_editor_state(indoc!(
13703        "fn a() {
13704             // dog();
13705             catˇ();
13706        }"
13707    ));
13708
13709    // Single selection on one line -> don't advance
13710    cx.set_state(indoc!(
13711        "fn a() {
13712             «dog()ˇ»;
13713             cat();
13714        }"
13715    ));
13716    cx.update_editor(|editor, window, cx| {
13717        editor.toggle_comments(toggle_comments, window, cx);
13718    });
13719    cx.assert_editor_state(indoc!(
13720        "fn a() {
13721             // «dog()ˇ»;
13722             cat();
13723        }"
13724    ));
13725
13726    // Multiple cursors on one line -> advance
13727    cx.set_state(indoc!(
13728        "fn a() {
13729             ˇdˇog();
13730             cat();
13731        }"
13732    ));
13733    cx.update_editor(|editor, window, cx| {
13734        editor.toggle_comments(toggle_comments, window, cx);
13735    });
13736    cx.assert_editor_state(indoc!(
13737        "fn a() {
13738             // dog();
13739             catˇ(ˇ);
13740        }"
13741    ));
13742
13743    // Multiple cursors on one line, with selection -> don't advance
13744    cx.set_state(indoc!(
13745        "fn a() {
13746             ˇdˇog«()ˇ»;
13747             cat();
13748        }"
13749    ));
13750    cx.update_editor(|editor, window, cx| {
13751        editor.toggle_comments(toggle_comments, window, cx);
13752    });
13753    cx.assert_editor_state(indoc!(
13754        "fn a() {
13755             // ˇdˇog«()ˇ»;
13756             cat();
13757        }"
13758    ));
13759
13760    // Single cursor on one line -> advance
13761    // Cursor moves to column 0 on blank line
13762    cx.set_state(indoc!(
13763        "fn a() {
13764             ˇdog();
13765
13766             cat();
13767        }"
13768    ));
13769    cx.update_editor(|editor, window, cx| {
13770        editor.toggle_comments(toggle_comments, window, cx);
13771    });
13772    cx.assert_editor_state(indoc!(
13773        "fn a() {
13774             // dog();
13775        ˇ
13776             cat();
13777        }"
13778    ));
13779
13780    // Single cursor on one line -> advance
13781    // Cursor starts and ends at column 0
13782    cx.set_state(indoc!(
13783        "fn a() {
13784         ˇ    dog();
13785             cat();
13786        }"
13787    ));
13788    cx.update_editor(|editor, window, cx| {
13789        editor.toggle_comments(toggle_comments, window, cx);
13790    });
13791    cx.assert_editor_state(indoc!(
13792        "fn a() {
13793             // dog();
13794         ˇ    cat();
13795        }"
13796    ));
13797}
13798
13799#[gpui::test]
13800async fn test_toggle_block_comment(cx: &mut TestAppContext) {
13801    init_test(cx, |_| {});
13802
13803    let mut cx = EditorTestContext::new(cx).await;
13804
13805    let html_language = Arc::new(
13806        Language::new(
13807            LanguageConfig {
13808                name: "HTML".into(),
13809                block_comment: Some(("<!-- ".into(), " -->".into())),
13810                ..Default::default()
13811            },
13812            Some(tree_sitter_html::LANGUAGE.into()),
13813        )
13814        .with_injection_query(
13815            r#"
13816            (script_element
13817                (raw_text) @injection.content
13818                (#set! injection.language "javascript"))
13819            "#,
13820        )
13821        .unwrap(),
13822    );
13823
13824    let javascript_language = Arc::new(Language::new(
13825        LanguageConfig {
13826            name: "JavaScript".into(),
13827            line_comments: vec!["// ".into()],
13828            ..Default::default()
13829        },
13830        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
13831    ));
13832
13833    cx.language_registry().add(html_language.clone());
13834    cx.language_registry().add(javascript_language.clone());
13835    cx.update_buffer(|buffer, cx| {
13836        buffer.set_language(Some(html_language), cx);
13837    });
13838
13839    // Toggle comments for empty selections
13840    cx.set_state(
13841        &r#"
13842            <p>A</p>ˇ
13843            <p>B</p>ˇ
13844            <p>C</p>ˇ
13845        "#
13846        .unindent(),
13847    );
13848    cx.update_editor(|editor, window, cx| {
13849        editor.toggle_comments(&ToggleComments::default(), window, cx)
13850    });
13851    cx.assert_editor_state(
13852        &r#"
13853            <!-- <p>A</p>ˇ -->
13854            <!-- <p>B</p>ˇ -->
13855            <!-- <p>C</p>ˇ -->
13856        "#
13857        .unindent(),
13858    );
13859    cx.update_editor(|editor, window, cx| {
13860        editor.toggle_comments(&ToggleComments::default(), window, cx)
13861    });
13862    cx.assert_editor_state(
13863        &r#"
13864            <p>A</p>ˇ
13865            <p>B</p>ˇ
13866            <p>C</p>ˇ
13867        "#
13868        .unindent(),
13869    );
13870
13871    // Toggle comments for mixture of empty and non-empty selections, where
13872    // multiple selections occupy a given line.
13873    cx.set_state(
13874        &r#"
13875            <p>A«</p>
13876            <p>ˇ»B</p>ˇ
13877            <p>C«</p>
13878            <p>ˇ»D</p>ˇ
13879        "#
13880        .unindent(),
13881    );
13882
13883    cx.update_editor(|editor, window, cx| {
13884        editor.toggle_comments(&ToggleComments::default(), window, cx)
13885    });
13886    cx.assert_editor_state(
13887        &r#"
13888            <!-- <p>A«</p>
13889            <p>ˇ»B</p>ˇ -->
13890            <!-- <p>C«</p>
13891            <p>ˇ»D</p>ˇ -->
13892        "#
13893        .unindent(),
13894    );
13895    cx.update_editor(|editor, window, cx| {
13896        editor.toggle_comments(&ToggleComments::default(), window, cx)
13897    });
13898    cx.assert_editor_state(
13899        &r#"
13900            <p>A«</p>
13901            <p>ˇ»B</p>ˇ
13902            <p>C«</p>
13903            <p>ˇ»D</p>ˇ
13904        "#
13905        .unindent(),
13906    );
13907
13908    // Toggle comments when different languages are active for different
13909    // selections.
13910    cx.set_state(
13911        &r#"
13912            ˇ<script>
13913                ˇvar x = new Y();
13914            ˇ</script>
13915        "#
13916        .unindent(),
13917    );
13918    cx.executor().run_until_parked();
13919    cx.update_editor(|editor, window, cx| {
13920        editor.toggle_comments(&ToggleComments::default(), window, cx)
13921    });
13922    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
13923    // Uncommenting and commenting from this position brings in even more wrong artifacts.
13924    cx.assert_editor_state(
13925        &r#"
13926            <!-- ˇ<script> -->
13927                // ˇvar x = new Y();
13928            <!-- ˇ</script> -->
13929        "#
13930        .unindent(),
13931    );
13932}
13933
13934#[gpui::test]
13935fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
13936    init_test(cx, |_| {});
13937
13938    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
13939    let multibuffer = cx.new(|cx| {
13940        let mut multibuffer = MultiBuffer::new(ReadWrite);
13941        multibuffer.push_excerpts(
13942            buffer.clone(),
13943            [
13944                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
13945                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
13946            ],
13947            cx,
13948        );
13949        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
13950        multibuffer
13951    });
13952
13953    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
13954    editor.update_in(cx, |editor, window, cx| {
13955        assert_eq!(editor.text(cx), "aaaa\nbbbb");
13956        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13957            s.select_ranges([
13958                Point::new(0, 0)..Point::new(0, 0),
13959                Point::new(1, 0)..Point::new(1, 0),
13960            ])
13961        });
13962
13963        editor.handle_input("X", window, cx);
13964        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
13965        assert_eq!(
13966            editor.selections.ranges(cx),
13967            [
13968                Point::new(0, 1)..Point::new(0, 1),
13969                Point::new(1, 1)..Point::new(1, 1),
13970            ]
13971        );
13972
13973        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
13974        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13975            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
13976        });
13977        editor.backspace(&Default::default(), window, cx);
13978        assert_eq!(editor.text(cx), "Xa\nbbb");
13979        assert_eq!(
13980            editor.selections.ranges(cx),
13981            [Point::new(1, 0)..Point::new(1, 0)]
13982        );
13983
13984        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13985            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
13986        });
13987        editor.backspace(&Default::default(), window, cx);
13988        assert_eq!(editor.text(cx), "X\nbb");
13989        assert_eq!(
13990            editor.selections.ranges(cx),
13991            [Point::new(0, 1)..Point::new(0, 1)]
13992        );
13993    });
13994}
13995
13996#[gpui::test]
13997fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
13998    init_test(cx, |_| {});
13999
14000    let markers = vec![('[', ']').into(), ('(', ')').into()];
14001    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
14002        indoc! {"
14003            [aaaa
14004            (bbbb]
14005            cccc)",
14006        },
14007        markers.clone(),
14008    );
14009    let excerpt_ranges = markers.into_iter().map(|marker| {
14010        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
14011        ExcerptRange::new(context.clone())
14012    });
14013    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
14014    let multibuffer = cx.new(|cx| {
14015        let mut multibuffer = MultiBuffer::new(ReadWrite);
14016        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
14017        multibuffer
14018    });
14019
14020    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
14021    editor.update_in(cx, |editor, window, cx| {
14022        let (expected_text, selection_ranges) = marked_text_ranges(
14023            indoc! {"
14024                aaaa
14025                bˇbbb
14026                bˇbbˇb
14027                cccc"
14028            },
14029            true,
14030        );
14031        assert_eq!(editor.text(cx), expected_text);
14032        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14033            s.select_ranges(selection_ranges)
14034        });
14035
14036        editor.handle_input("X", window, cx);
14037
14038        let (expected_text, expected_selections) = marked_text_ranges(
14039            indoc! {"
14040                aaaa
14041                bXˇbbXb
14042                bXˇbbXˇb
14043                cccc"
14044            },
14045            false,
14046        );
14047        assert_eq!(editor.text(cx), expected_text);
14048        assert_eq!(editor.selections.ranges(cx), expected_selections);
14049
14050        editor.newline(&Newline, window, cx);
14051        let (expected_text, expected_selections) = marked_text_ranges(
14052            indoc! {"
14053                aaaa
14054                bX
14055                ˇbbX
14056                b
14057                bX
14058                ˇbbX
14059                ˇb
14060                cccc"
14061            },
14062            false,
14063        );
14064        assert_eq!(editor.text(cx), expected_text);
14065        assert_eq!(editor.selections.ranges(cx), expected_selections);
14066    });
14067}
14068
14069#[gpui::test]
14070fn test_refresh_selections(cx: &mut TestAppContext) {
14071    init_test(cx, |_| {});
14072
14073    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
14074    let mut excerpt1_id = None;
14075    let multibuffer = cx.new(|cx| {
14076        let mut multibuffer = MultiBuffer::new(ReadWrite);
14077        excerpt1_id = multibuffer
14078            .push_excerpts(
14079                buffer.clone(),
14080                [
14081                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
14082                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
14083                ],
14084                cx,
14085            )
14086            .into_iter()
14087            .next();
14088        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
14089        multibuffer
14090    });
14091
14092    let editor = cx.add_window(|window, cx| {
14093        let mut editor = build_editor(multibuffer.clone(), window, cx);
14094        let snapshot = editor.snapshot(window, cx);
14095        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14096            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
14097        });
14098        editor.begin_selection(
14099            Point::new(2, 1).to_display_point(&snapshot),
14100            true,
14101            1,
14102            window,
14103            cx,
14104        );
14105        assert_eq!(
14106            editor.selections.ranges(cx),
14107            [
14108                Point::new(1, 3)..Point::new(1, 3),
14109                Point::new(2, 1)..Point::new(2, 1),
14110            ]
14111        );
14112        editor
14113    });
14114
14115    // Refreshing selections is a no-op when excerpts haven't changed.
14116    _ = editor.update(cx, |editor, window, cx| {
14117        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14118        assert_eq!(
14119            editor.selections.ranges(cx),
14120            [
14121                Point::new(1, 3)..Point::new(1, 3),
14122                Point::new(2, 1)..Point::new(2, 1),
14123            ]
14124        );
14125    });
14126
14127    multibuffer.update(cx, |multibuffer, cx| {
14128        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
14129    });
14130    _ = editor.update(cx, |editor, window, cx| {
14131        // Removing an excerpt causes the first selection to become degenerate.
14132        assert_eq!(
14133            editor.selections.ranges(cx),
14134            [
14135                Point::new(0, 0)..Point::new(0, 0),
14136                Point::new(0, 1)..Point::new(0, 1)
14137            ]
14138        );
14139
14140        // Refreshing selections will relocate the first selection to the original buffer
14141        // location.
14142        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14143        assert_eq!(
14144            editor.selections.ranges(cx),
14145            [
14146                Point::new(0, 1)..Point::new(0, 1),
14147                Point::new(0, 3)..Point::new(0, 3)
14148            ]
14149        );
14150        assert!(editor.selections.pending_anchor().is_some());
14151    });
14152}
14153
14154#[gpui::test]
14155fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
14156    init_test(cx, |_| {});
14157
14158    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
14159    let mut excerpt1_id = None;
14160    let multibuffer = cx.new(|cx| {
14161        let mut multibuffer = MultiBuffer::new(ReadWrite);
14162        excerpt1_id = multibuffer
14163            .push_excerpts(
14164                buffer.clone(),
14165                [
14166                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
14167                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
14168                ],
14169                cx,
14170            )
14171            .into_iter()
14172            .next();
14173        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
14174        multibuffer
14175    });
14176
14177    let editor = cx.add_window(|window, cx| {
14178        let mut editor = build_editor(multibuffer.clone(), window, cx);
14179        let snapshot = editor.snapshot(window, cx);
14180        editor.begin_selection(
14181            Point::new(1, 3).to_display_point(&snapshot),
14182            false,
14183            1,
14184            window,
14185            cx,
14186        );
14187        assert_eq!(
14188            editor.selections.ranges(cx),
14189            [Point::new(1, 3)..Point::new(1, 3)]
14190        );
14191        editor
14192    });
14193
14194    multibuffer.update(cx, |multibuffer, cx| {
14195        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
14196    });
14197    _ = editor.update(cx, |editor, window, cx| {
14198        assert_eq!(
14199            editor.selections.ranges(cx),
14200            [Point::new(0, 0)..Point::new(0, 0)]
14201        );
14202
14203        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
14204        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14205        assert_eq!(
14206            editor.selections.ranges(cx),
14207            [Point::new(0, 3)..Point::new(0, 3)]
14208        );
14209        assert!(editor.selections.pending_anchor().is_some());
14210    });
14211}
14212
14213#[gpui::test]
14214async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
14215    init_test(cx, |_| {});
14216
14217    let language = Arc::new(
14218        Language::new(
14219            LanguageConfig {
14220                brackets: BracketPairConfig {
14221                    pairs: vec![
14222                        BracketPair {
14223                            start: "{".to_string(),
14224                            end: "}".to_string(),
14225                            close: true,
14226                            surround: true,
14227                            newline: true,
14228                        },
14229                        BracketPair {
14230                            start: "/* ".to_string(),
14231                            end: " */".to_string(),
14232                            close: true,
14233                            surround: true,
14234                            newline: true,
14235                        },
14236                    ],
14237                    ..Default::default()
14238                },
14239                ..Default::default()
14240            },
14241            Some(tree_sitter_rust::LANGUAGE.into()),
14242        )
14243        .with_indents_query("")
14244        .unwrap(),
14245    );
14246
14247    let text = concat!(
14248        "{   }\n",     //
14249        "  x\n",       //
14250        "  /*   */\n", //
14251        "x\n",         //
14252        "{{} }\n",     //
14253    );
14254
14255    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
14256    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
14257    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
14258    editor
14259        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
14260        .await;
14261
14262    editor.update_in(cx, |editor, window, cx| {
14263        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14264            s.select_display_ranges([
14265                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
14266                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
14267                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
14268            ])
14269        });
14270        editor.newline(&Newline, window, cx);
14271
14272        assert_eq!(
14273            editor.buffer().read(cx).read(cx).text(),
14274            concat!(
14275                "{ \n",    // Suppress rustfmt
14276                "\n",      //
14277                "}\n",     //
14278                "  x\n",   //
14279                "  /* \n", //
14280                "  \n",    //
14281                "  */\n",  //
14282                "x\n",     //
14283                "{{} \n",  //
14284                "}\n",     //
14285            )
14286        );
14287    });
14288}
14289
14290#[gpui::test]
14291fn test_highlighted_ranges(cx: &mut TestAppContext) {
14292    init_test(cx, |_| {});
14293
14294    let editor = cx.add_window(|window, cx| {
14295        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
14296        build_editor(buffer.clone(), window, cx)
14297    });
14298
14299    _ = editor.update(cx, |editor, window, cx| {
14300        struct Type1;
14301        struct Type2;
14302
14303        let buffer = editor.buffer.read(cx).snapshot(cx);
14304
14305        let anchor_range =
14306            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
14307
14308        editor.highlight_background::<Type1>(
14309            &[
14310                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
14311                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
14312                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
14313                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
14314            ],
14315            |_| Hsla::red(),
14316            cx,
14317        );
14318        editor.highlight_background::<Type2>(
14319            &[
14320                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
14321                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
14322                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
14323                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
14324            ],
14325            |_| Hsla::green(),
14326            cx,
14327        );
14328
14329        let snapshot = editor.snapshot(window, cx);
14330        let mut highlighted_ranges = editor.background_highlights_in_range(
14331            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
14332            &snapshot,
14333            cx.theme(),
14334        );
14335        // Enforce a consistent ordering based on color without relying on the ordering of the
14336        // highlight's `TypeId` which is non-executor.
14337        highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
14338        assert_eq!(
14339            highlighted_ranges,
14340            &[
14341                (
14342                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
14343                    Hsla::red(),
14344                ),
14345                (
14346                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
14347                    Hsla::red(),
14348                ),
14349                (
14350                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
14351                    Hsla::green(),
14352                ),
14353                (
14354                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
14355                    Hsla::green(),
14356                ),
14357            ]
14358        );
14359        assert_eq!(
14360            editor.background_highlights_in_range(
14361                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
14362                &snapshot,
14363                cx.theme(),
14364            ),
14365            &[(
14366                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
14367                Hsla::red(),
14368            )]
14369        );
14370    });
14371}
14372
14373#[gpui::test]
14374async fn test_following(cx: &mut TestAppContext) {
14375    init_test(cx, |_| {});
14376
14377    let fs = FakeFs::new(cx.executor());
14378    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
14379
14380    let buffer = project.update(cx, |project, cx| {
14381        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
14382        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
14383    });
14384    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
14385    let follower = cx.update(|cx| {
14386        cx.open_window(
14387            WindowOptions {
14388                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
14389                    gpui::Point::new(px(0.), px(0.)),
14390                    gpui::Point::new(px(10.), px(80.)),
14391                ))),
14392                ..Default::default()
14393            },
14394            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
14395        )
14396        .unwrap()
14397    });
14398
14399    let is_still_following = Rc::new(RefCell::new(true));
14400    let follower_edit_event_count = Rc::new(RefCell::new(0));
14401    let pending_update = Rc::new(RefCell::new(None));
14402    let leader_entity = leader.root(cx).unwrap();
14403    let follower_entity = follower.root(cx).unwrap();
14404    _ = follower.update(cx, {
14405        let update = pending_update.clone();
14406        let is_still_following = is_still_following.clone();
14407        let follower_edit_event_count = follower_edit_event_count.clone();
14408        |_, window, cx| {
14409            cx.subscribe_in(
14410                &leader_entity,
14411                window,
14412                move |_, leader, event, window, cx| {
14413                    leader.read(cx).add_event_to_update_proto(
14414                        event,
14415                        &mut update.borrow_mut(),
14416                        window,
14417                        cx,
14418                    );
14419                },
14420            )
14421            .detach();
14422
14423            cx.subscribe_in(
14424                &follower_entity,
14425                window,
14426                move |_, _, event: &EditorEvent, _window, _cx| {
14427                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
14428                        *is_still_following.borrow_mut() = false;
14429                    }
14430
14431                    if let EditorEvent::BufferEdited = event {
14432                        *follower_edit_event_count.borrow_mut() += 1;
14433                    }
14434                },
14435            )
14436            .detach();
14437        }
14438    });
14439
14440    // Update the selections only
14441    _ = leader.update(cx, |leader, window, cx| {
14442        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14443            s.select_ranges([1..1])
14444        });
14445    });
14446    follower
14447        .update(cx, |follower, window, cx| {
14448            follower.apply_update_proto(
14449                &project,
14450                pending_update.borrow_mut().take().unwrap(),
14451                window,
14452                cx,
14453            )
14454        })
14455        .unwrap()
14456        .await
14457        .unwrap();
14458    _ = follower.update(cx, |follower, _, cx| {
14459        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
14460    });
14461    assert!(*is_still_following.borrow());
14462    assert_eq!(*follower_edit_event_count.borrow(), 0);
14463
14464    // Update the scroll position only
14465    _ = leader.update(cx, |leader, window, cx| {
14466        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
14467    });
14468    follower
14469        .update(cx, |follower, window, cx| {
14470            follower.apply_update_proto(
14471                &project,
14472                pending_update.borrow_mut().take().unwrap(),
14473                window,
14474                cx,
14475            )
14476        })
14477        .unwrap()
14478        .await
14479        .unwrap();
14480    assert_eq!(
14481        follower
14482            .update(cx, |follower, _, cx| follower.scroll_position(cx))
14483            .unwrap(),
14484        gpui::Point::new(1.5, 3.5)
14485    );
14486    assert!(*is_still_following.borrow());
14487    assert_eq!(*follower_edit_event_count.borrow(), 0);
14488
14489    // Update the selections and scroll position. The follower's scroll position is updated
14490    // via autoscroll, not via the leader's exact scroll position.
14491    _ = leader.update(cx, |leader, window, cx| {
14492        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14493            s.select_ranges([0..0])
14494        });
14495        leader.request_autoscroll(Autoscroll::newest(), cx);
14496        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
14497    });
14498    follower
14499        .update(cx, |follower, window, cx| {
14500            follower.apply_update_proto(
14501                &project,
14502                pending_update.borrow_mut().take().unwrap(),
14503                window,
14504                cx,
14505            )
14506        })
14507        .unwrap()
14508        .await
14509        .unwrap();
14510    _ = follower.update(cx, |follower, _, cx| {
14511        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
14512        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
14513    });
14514    assert!(*is_still_following.borrow());
14515
14516    // Creating a pending selection that precedes another selection
14517    _ = leader.update(cx, |leader, window, cx| {
14518        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14519            s.select_ranges([1..1])
14520        });
14521        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
14522    });
14523    follower
14524        .update(cx, |follower, window, cx| {
14525            follower.apply_update_proto(
14526                &project,
14527                pending_update.borrow_mut().take().unwrap(),
14528                window,
14529                cx,
14530            )
14531        })
14532        .unwrap()
14533        .await
14534        .unwrap();
14535    _ = follower.update(cx, |follower, _, cx| {
14536        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
14537    });
14538    assert!(*is_still_following.borrow());
14539
14540    // Extend the pending selection so that it surrounds another selection
14541    _ = leader.update(cx, |leader, window, cx| {
14542        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
14543    });
14544    follower
14545        .update(cx, |follower, window, cx| {
14546            follower.apply_update_proto(
14547                &project,
14548                pending_update.borrow_mut().take().unwrap(),
14549                window,
14550                cx,
14551            )
14552        })
14553        .unwrap()
14554        .await
14555        .unwrap();
14556    _ = follower.update(cx, |follower, _, cx| {
14557        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
14558    });
14559
14560    // Scrolling locally breaks the follow
14561    _ = follower.update(cx, |follower, window, cx| {
14562        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
14563        follower.set_scroll_anchor(
14564            ScrollAnchor {
14565                anchor: top_anchor,
14566                offset: gpui::Point::new(0.0, 0.5),
14567            },
14568            window,
14569            cx,
14570        );
14571    });
14572    assert!(!(*is_still_following.borrow()));
14573}
14574
14575#[gpui::test]
14576async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
14577    init_test(cx, |_| {});
14578
14579    let fs = FakeFs::new(cx.executor());
14580    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
14581    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14582    let pane = workspace
14583        .update(cx, |workspace, _, _| workspace.active_pane().clone())
14584        .unwrap();
14585
14586    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14587
14588    let leader = pane.update_in(cx, |_, window, cx| {
14589        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
14590        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
14591    });
14592
14593    // Start following the editor when it has no excerpts.
14594    let mut state_message =
14595        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
14596    let workspace_entity = workspace.root(cx).unwrap();
14597    let follower_1 = cx
14598        .update_window(*workspace.deref(), |_, window, cx| {
14599            Editor::from_state_proto(
14600                workspace_entity,
14601                ViewId {
14602                    creator: CollaboratorId::PeerId(PeerId::default()),
14603                    id: 0,
14604                },
14605                &mut state_message,
14606                window,
14607                cx,
14608            )
14609        })
14610        .unwrap()
14611        .unwrap()
14612        .await
14613        .unwrap();
14614
14615    let update_message = Rc::new(RefCell::new(None));
14616    follower_1.update_in(cx, {
14617        let update = update_message.clone();
14618        |_, window, cx| {
14619            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
14620                leader.read(cx).add_event_to_update_proto(
14621                    event,
14622                    &mut update.borrow_mut(),
14623                    window,
14624                    cx,
14625                );
14626            })
14627            .detach();
14628        }
14629    });
14630
14631    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
14632        (
14633            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
14634            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
14635        )
14636    });
14637
14638    // Insert some excerpts.
14639    leader.update(cx, |leader, cx| {
14640        leader.buffer.update(cx, |multibuffer, cx| {
14641            multibuffer.set_excerpts_for_path(
14642                PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
14643                buffer_1.clone(),
14644                vec![
14645                    Point::row_range(0..3),
14646                    Point::row_range(1..6),
14647                    Point::row_range(12..15),
14648                ],
14649                0,
14650                cx,
14651            );
14652            multibuffer.set_excerpts_for_path(
14653                PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
14654                buffer_2.clone(),
14655                vec![Point::row_range(0..6), Point::row_range(8..12)],
14656                0,
14657                cx,
14658            );
14659        });
14660    });
14661
14662    // Apply the update of adding the excerpts.
14663    follower_1
14664        .update_in(cx, |follower, window, cx| {
14665            follower.apply_update_proto(
14666                &project,
14667                update_message.borrow().clone().unwrap(),
14668                window,
14669                cx,
14670            )
14671        })
14672        .await
14673        .unwrap();
14674    assert_eq!(
14675        follower_1.update(cx, |editor, cx| editor.text(cx)),
14676        leader.update(cx, |editor, cx| editor.text(cx))
14677    );
14678    update_message.borrow_mut().take();
14679
14680    // Start following separately after it already has excerpts.
14681    let mut state_message =
14682        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
14683    let workspace_entity = workspace.root(cx).unwrap();
14684    let follower_2 = cx
14685        .update_window(*workspace.deref(), |_, window, cx| {
14686            Editor::from_state_proto(
14687                workspace_entity,
14688                ViewId {
14689                    creator: CollaboratorId::PeerId(PeerId::default()),
14690                    id: 0,
14691                },
14692                &mut state_message,
14693                window,
14694                cx,
14695            )
14696        })
14697        .unwrap()
14698        .unwrap()
14699        .await
14700        .unwrap();
14701    assert_eq!(
14702        follower_2.update(cx, |editor, cx| editor.text(cx)),
14703        leader.update(cx, |editor, cx| editor.text(cx))
14704    );
14705
14706    // Remove some excerpts.
14707    leader.update(cx, |leader, cx| {
14708        leader.buffer.update(cx, |multibuffer, cx| {
14709            let excerpt_ids = multibuffer.excerpt_ids();
14710            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
14711            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
14712        });
14713    });
14714
14715    // Apply the update of removing the excerpts.
14716    follower_1
14717        .update_in(cx, |follower, window, cx| {
14718            follower.apply_update_proto(
14719                &project,
14720                update_message.borrow().clone().unwrap(),
14721                window,
14722                cx,
14723            )
14724        })
14725        .await
14726        .unwrap();
14727    follower_2
14728        .update_in(cx, |follower, window, cx| {
14729            follower.apply_update_proto(
14730                &project,
14731                update_message.borrow().clone().unwrap(),
14732                window,
14733                cx,
14734            )
14735        })
14736        .await
14737        .unwrap();
14738    update_message.borrow_mut().take();
14739    assert_eq!(
14740        follower_1.update(cx, |editor, cx| editor.text(cx)),
14741        leader.update(cx, |editor, cx| editor.text(cx))
14742    );
14743}
14744
14745#[gpui::test]
14746async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14747    init_test(cx, |_| {});
14748
14749    let mut cx = EditorTestContext::new(cx).await;
14750    let lsp_store =
14751        cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
14752
14753    cx.set_state(indoc! {"
14754        ˇfn func(abc def: i32) -> u32 {
14755        }
14756    "});
14757
14758    cx.update(|_, cx| {
14759        lsp_store.update(cx, |lsp_store, cx| {
14760            lsp_store
14761                .update_diagnostics(
14762                    LanguageServerId(0),
14763                    lsp::PublishDiagnosticsParams {
14764                        uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
14765                        version: None,
14766                        diagnostics: vec![
14767                            lsp::Diagnostic {
14768                                range: lsp::Range::new(
14769                                    lsp::Position::new(0, 11),
14770                                    lsp::Position::new(0, 12),
14771                                ),
14772                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14773                                ..Default::default()
14774                            },
14775                            lsp::Diagnostic {
14776                                range: lsp::Range::new(
14777                                    lsp::Position::new(0, 12),
14778                                    lsp::Position::new(0, 15),
14779                                ),
14780                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14781                                ..Default::default()
14782                            },
14783                            lsp::Diagnostic {
14784                                range: lsp::Range::new(
14785                                    lsp::Position::new(0, 25),
14786                                    lsp::Position::new(0, 28),
14787                                ),
14788                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14789                                ..Default::default()
14790                            },
14791                        ],
14792                    },
14793                    None,
14794                    DiagnosticSourceKind::Pushed,
14795                    &[],
14796                    cx,
14797                )
14798                .unwrap()
14799        });
14800    });
14801
14802    executor.run_until_parked();
14803
14804    cx.update_editor(|editor, window, cx| {
14805        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
14806    });
14807
14808    cx.assert_editor_state(indoc! {"
14809        fn func(abc def: i32) -> ˇu32 {
14810        }
14811    "});
14812
14813    cx.update_editor(|editor, window, cx| {
14814        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
14815    });
14816
14817    cx.assert_editor_state(indoc! {"
14818        fn func(abc ˇdef: i32) -> u32 {
14819        }
14820    "});
14821
14822    cx.update_editor(|editor, window, cx| {
14823        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
14824    });
14825
14826    cx.assert_editor_state(indoc! {"
14827        fn func(abcˇ def: i32) -> u32 {
14828        }
14829    "});
14830
14831    cx.update_editor(|editor, window, cx| {
14832        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
14833    });
14834
14835    cx.assert_editor_state(indoc! {"
14836        fn func(abc def: i32) -> ˇu32 {
14837        }
14838    "});
14839}
14840
14841#[gpui::test]
14842async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14843    init_test(cx, |_| {});
14844
14845    let mut cx = EditorTestContext::new(cx).await;
14846
14847    let diff_base = r#"
14848        use some::mod;
14849
14850        const A: u32 = 42;
14851
14852        fn main() {
14853            println!("hello");
14854
14855            println!("world");
14856        }
14857        "#
14858    .unindent();
14859
14860    // Edits are modified, removed, modified, added
14861    cx.set_state(
14862        &r#"
14863        use some::modified;
14864
14865        ˇ
14866        fn main() {
14867            println!("hello there");
14868
14869            println!("around the");
14870            println!("world");
14871        }
14872        "#
14873        .unindent(),
14874    );
14875
14876    cx.set_head_text(&diff_base);
14877    executor.run_until_parked();
14878
14879    cx.update_editor(|editor, window, cx| {
14880        //Wrap around the bottom of the buffer
14881        for _ in 0..3 {
14882            editor.go_to_next_hunk(&GoToHunk, window, cx);
14883        }
14884    });
14885
14886    cx.assert_editor_state(
14887        &r#"
14888        ˇuse some::modified;
14889
14890
14891        fn main() {
14892            println!("hello there");
14893
14894            println!("around the");
14895            println!("world");
14896        }
14897        "#
14898        .unindent(),
14899    );
14900
14901    cx.update_editor(|editor, window, cx| {
14902        //Wrap around the top of the buffer
14903        for _ in 0..2 {
14904            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14905        }
14906    });
14907
14908    cx.assert_editor_state(
14909        &r#"
14910        use some::modified;
14911
14912
14913        fn main() {
14914        ˇ    println!("hello there");
14915
14916            println!("around the");
14917            println!("world");
14918        }
14919        "#
14920        .unindent(),
14921    );
14922
14923    cx.update_editor(|editor, window, cx| {
14924        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14925    });
14926
14927    cx.assert_editor_state(
14928        &r#"
14929        use some::modified;
14930
14931        ˇ
14932        fn main() {
14933            println!("hello there");
14934
14935            println!("around the");
14936            println!("world");
14937        }
14938        "#
14939        .unindent(),
14940    );
14941
14942    cx.update_editor(|editor, window, cx| {
14943        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14944    });
14945
14946    cx.assert_editor_state(
14947        &r#"
14948        ˇuse some::modified;
14949
14950
14951        fn main() {
14952            println!("hello there");
14953
14954            println!("around the");
14955            println!("world");
14956        }
14957        "#
14958        .unindent(),
14959    );
14960
14961    cx.update_editor(|editor, window, cx| {
14962        for _ in 0..2 {
14963            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14964        }
14965    });
14966
14967    cx.assert_editor_state(
14968        &r#"
14969        use some::modified;
14970
14971
14972        fn main() {
14973        ˇ    println!("hello there");
14974
14975            println!("around the");
14976            println!("world");
14977        }
14978        "#
14979        .unindent(),
14980    );
14981
14982    cx.update_editor(|editor, window, cx| {
14983        editor.fold(&Fold, window, cx);
14984    });
14985
14986    cx.update_editor(|editor, window, cx| {
14987        editor.go_to_next_hunk(&GoToHunk, window, cx);
14988    });
14989
14990    cx.assert_editor_state(
14991        &r#"
14992        ˇuse some::modified;
14993
14994
14995        fn main() {
14996            println!("hello there");
14997
14998            println!("around the");
14999            println!("world");
15000        }
15001        "#
15002        .unindent(),
15003    );
15004}
15005
15006#[test]
15007fn test_split_words() {
15008    fn split(text: &str) -> Vec<&str> {
15009        split_words(text).collect()
15010    }
15011
15012    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
15013    assert_eq!(split("hello_world"), &["hello_", "world"]);
15014    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
15015    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
15016    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
15017    assert_eq!(split("helloworld"), &["helloworld"]);
15018
15019    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
15020}
15021
15022#[gpui::test]
15023async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
15024    init_test(cx, |_| {});
15025
15026    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
15027    let mut assert = |before, after| {
15028        let _state_context = cx.set_state(before);
15029        cx.run_until_parked();
15030        cx.update_editor(|editor, window, cx| {
15031            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
15032        });
15033        cx.run_until_parked();
15034        cx.assert_editor_state(after);
15035    };
15036
15037    // Outside bracket jumps to outside of matching bracket
15038    assert("console.logˇ(var);", "console.log(var)ˇ;");
15039    assert("console.log(var)ˇ;", "console.logˇ(var);");
15040
15041    // Inside bracket jumps to inside of matching bracket
15042    assert("console.log(ˇvar);", "console.log(varˇ);");
15043    assert("console.log(varˇ);", "console.log(ˇvar);");
15044
15045    // When outside a bracket and inside, favor jumping to the inside bracket
15046    assert(
15047        "console.log('foo', [1, 2, 3]ˇ);",
15048        "console.log(ˇ'foo', [1, 2, 3]);",
15049    );
15050    assert(
15051        "console.log(ˇ'foo', [1, 2, 3]);",
15052        "console.log('foo', [1, 2, 3]ˇ);",
15053    );
15054
15055    // Bias forward if two options are equally likely
15056    assert(
15057        "let result = curried_fun()ˇ();",
15058        "let result = curried_fun()()ˇ;",
15059    );
15060
15061    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
15062    assert(
15063        indoc! {"
15064            function test() {
15065                console.log('test')ˇ
15066            }"},
15067        indoc! {"
15068            function test() {
15069                console.logˇ('test')
15070            }"},
15071    );
15072}
15073
15074#[gpui::test]
15075async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
15076    init_test(cx, |_| {});
15077
15078    let fs = FakeFs::new(cx.executor());
15079    fs.insert_tree(
15080        path!("/a"),
15081        json!({
15082            "main.rs": "fn main() { let a = 5; }",
15083            "other.rs": "// Test file",
15084        }),
15085    )
15086    .await;
15087    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15088
15089    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15090    language_registry.add(Arc::new(Language::new(
15091        LanguageConfig {
15092            name: "Rust".into(),
15093            matcher: LanguageMatcher {
15094                path_suffixes: vec!["rs".to_string()],
15095                ..Default::default()
15096            },
15097            brackets: BracketPairConfig {
15098                pairs: vec![BracketPair {
15099                    start: "{".to_string(),
15100                    end: "}".to_string(),
15101                    close: true,
15102                    surround: true,
15103                    newline: true,
15104                }],
15105                disabled_scopes_by_bracket_ix: Vec::new(),
15106            },
15107            ..Default::default()
15108        },
15109        Some(tree_sitter_rust::LANGUAGE.into()),
15110    )));
15111    let mut fake_servers = language_registry.register_fake_lsp(
15112        "Rust",
15113        FakeLspAdapter {
15114            capabilities: lsp::ServerCapabilities {
15115                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
15116                    first_trigger_character: "{".to_string(),
15117                    more_trigger_character: None,
15118                }),
15119                ..Default::default()
15120            },
15121            ..Default::default()
15122        },
15123    );
15124
15125    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15126
15127    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15128
15129    let worktree_id = workspace
15130        .update(cx, |workspace, _, cx| {
15131            workspace.project().update(cx, |project, cx| {
15132                project.worktrees(cx).next().unwrap().read(cx).id()
15133            })
15134        })
15135        .unwrap();
15136
15137    let buffer = project
15138        .update(cx, |project, cx| {
15139            project.open_local_buffer(path!("/a/main.rs"), cx)
15140        })
15141        .await
15142        .unwrap();
15143    let editor_handle = workspace
15144        .update(cx, |workspace, window, cx| {
15145            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
15146        })
15147        .unwrap()
15148        .await
15149        .unwrap()
15150        .downcast::<Editor>()
15151        .unwrap();
15152
15153    cx.executor().start_waiting();
15154    let fake_server = fake_servers.next().await.unwrap();
15155
15156    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
15157        |params, _| async move {
15158            assert_eq!(
15159                params.text_document_position.text_document.uri,
15160                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
15161            );
15162            assert_eq!(
15163                params.text_document_position.position,
15164                lsp::Position::new(0, 21),
15165            );
15166
15167            Ok(Some(vec![lsp::TextEdit {
15168                new_text: "]".to_string(),
15169                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15170            }]))
15171        },
15172    );
15173
15174    editor_handle.update_in(cx, |editor, window, cx| {
15175        window.focus(&editor.focus_handle(cx));
15176        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15177            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
15178        });
15179        editor.handle_input("{", window, cx);
15180    });
15181
15182    cx.executor().run_until_parked();
15183
15184    buffer.update(cx, |buffer, _| {
15185        assert_eq!(
15186            buffer.text(),
15187            "fn main() { let a = {5}; }",
15188            "No extra braces from on type formatting should appear in the buffer"
15189        )
15190    });
15191}
15192
15193#[gpui::test(iterations = 20, seeds(31))]
15194async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
15195    init_test(cx, |_| {});
15196
15197    let mut cx = EditorLspTestContext::new_rust(
15198        lsp::ServerCapabilities {
15199            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
15200                first_trigger_character: ".".to_string(),
15201                more_trigger_character: None,
15202            }),
15203            ..Default::default()
15204        },
15205        cx,
15206    )
15207    .await;
15208
15209    cx.update_buffer(|buffer, _| {
15210        // This causes autoindent to be async.
15211        buffer.set_sync_parse_timeout(Duration::ZERO)
15212    });
15213
15214    cx.set_state("fn c() {\n    d()ˇ\n}\n");
15215    cx.simulate_keystroke("\n");
15216    cx.run_until_parked();
15217
15218    let buffer_cloned =
15219        cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap().clone());
15220    let mut request =
15221        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
15222            let buffer_cloned = buffer_cloned.clone();
15223            async move {
15224                buffer_cloned.update(&mut cx, |buffer, _| {
15225                    assert_eq!(
15226                        buffer.text(),
15227                        "fn c() {\n    d()\n        .\n}\n",
15228                        "OnTypeFormatting should triggered after autoindent applied"
15229                    )
15230                })?;
15231
15232                Ok(Some(vec![]))
15233            }
15234        });
15235
15236    cx.simulate_keystroke(".");
15237    cx.run_until_parked();
15238
15239    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
15240    assert!(request.next().await.is_some());
15241    request.close();
15242    assert!(request.next().await.is_none());
15243}
15244
15245#[gpui::test]
15246async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
15247    init_test(cx, |_| {});
15248
15249    let fs = FakeFs::new(cx.executor());
15250    fs.insert_tree(
15251        path!("/a"),
15252        json!({
15253            "main.rs": "fn main() { let a = 5; }",
15254            "other.rs": "// Test file",
15255        }),
15256    )
15257    .await;
15258
15259    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15260
15261    let server_restarts = Arc::new(AtomicUsize::new(0));
15262    let closure_restarts = Arc::clone(&server_restarts);
15263    let language_server_name = "test language server";
15264    let language_name: LanguageName = "Rust".into();
15265
15266    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15267    language_registry.add(Arc::new(Language::new(
15268        LanguageConfig {
15269            name: language_name.clone(),
15270            matcher: LanguageMatcher {
15271                path_suffixes: vec!["rs".to_string()],
15272                ..Default::default()
15273            },
15274            ..Default::default()
15275        },
15276        Some(tree_sitter_rust::LANGUAGE.into()),
15277    )));
15278    let mut fake_servers = language_registry.register_fake_lsp(
15279        "Rust",
15280        FakeLspAdapter {
15281            name: language_server_name,
15282            initialization_options: Some(json!({
15283                "testOptionValue": true
15284            })),
15285            initializer: Some(Box::new(move |fake_server| {
15286                let task_restarts = Arc::clone(&closure_restarts);
15287                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
15288                    task_restarts.fetch_add(1, atomic::Ordering::Release);
15289                    futures::future::ready(Ok(()))
15290                });
15291            })),
15292            ..Default::default()
15293        },
15294    );
15295
15296    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15297    let _buffer = project
15298        .update(cx, |project, cx| {
15299            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
15300        })
15301        .await
15302        .unwrap();
15303    let _fake_server = fake_servers.next().await.unwrap();
15304    update_test_language_settings(cx, |language_settings| {
15305        language_settings.languages.0.insert(
15306            language_name.clone(),
15307            LanguageSettingsContent {
15308                tab_size: NonZeroU32::new(8),
15309                ..Default::default()
15310            },
15311        );
15312    });
15313    cx.executor().run_until_parked();
15314    assert_eq!(
15315        server_restarts.load(atomic::Ordering::Acquire),
15316        0,
15317        "Should not restart LSP server on an unrelated change"
15318    );
15319
15320    update_test_project_settings(cx, |project_settings| {
15321        project_settings.lsp.insert(
15322            "Some other server name".into(),
15323            LspSettings {
15324                binary: None,
15325                settings: None,
15326                initialization_options: Some(json!({
15327                    "some other init value": false
15328                })),
15329                enable_lsp_tasks: false,
15330            },
15331        );
15332    });
15333    cx.executor().run_until_parked();
15334    assert_eq!(
15335        server_restarts.load(atomic::Ordering::Acquire),
15336        0,
15337        "Should not restart LSP server on an unrelated LSP settings change"
15338    );
15339
15340    update_test_project_settings(cx, |project_settings| {
15341        project_settings.lsp.insert(
15342            language_server_name.into(),
15343            LspSettings {
15344                binary: None,
15345                settings: None,
15346                initialization_options: Some(json!({
15347                    "anotherInitValue": false
15348                })),
15349                enable_lsp_tasks: false,
15350            },
15351        );
15352    });
15353    cx.executor().run_until_parked();
15354    assert_eq!(
15355        server_restarts.load(atomic::Ordering::Acquire),
15356        1,
15357        "Should restart LSP server on a related LSP settings change"
15358    );
15359
15360    update_test_project_settings(cx, |project_settings| {
15361        project_settings.lsp.insert(
15362            language_server_name.into(),
15363            LspSettings {
15364                binary: None,
15365                settings: None,
15366                initialization_options: Some(json!({
15367                    "anotherInitValue": false
15368                })),
15369                enable_lsp_tasks: false,
15370            },
15371        );
15372    });
15373    cx.executor().run_until_parked();
15374    assert_eq!(
15375        server_restarts.load(atomic::Ordering::Acquire),
15376        1,
15377        "Should not restart LSP server on a related LSP settings change that is the same"
15378    );
15379
15380    update_test_project_settings(cx, |project_settings| {
15381        project_settings.lsp.insert(
15382            language_server_name.into(),
15383            LspSettings {
15384                binary: None,
15385                settings: None,
15386                initialization_options: None,
15387                enable_lsp_tasks: false,
15388            },
15389        );
15390    });
15391    cx.executor().run_until_parked();
15392    assert_eq!(
15393        server_restarts.load(atomic::Ordering::Acquire),
15394        2,
15395        "Should restart LSP server on another related LSP settings change"
15396    );
15397}
15398
15399#[gpui::test]
15400async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
15401    init_test(cx, |_| {});
15402
15403    let mut cx = EditorLspTestContext::new_rust(
15404        lsp::ServerCapabilities {
15405            completion_provider: Some(lsp::CompletionOptions {
15406                trigger_characters: Some(vec![".".to_string()]),
15407                resolve_provider: Some(true),
15408                ..Default::default()
15409            }),
15410            ..Default::default()
15411        },
15412        cx,
15413    )
15414    .await;
15415
15416    cx.set_state("fn main() { let a = 2ˇ; }");
15417    cx.simulate_keystroke(".");
15418    let completion_item = lsp::CompletionItem {
15419        label: "some".into(),
15420        kind: Some(lsp::CompletionItemKind::SNIPPET),
15421        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15422        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15423            kind: lsp::MarkupKind::Markdown,
15424            value: "```rust\nSome(2)\n```".to_string(),
15425        })),
15426        deprecated: Some(false),
15427        sort_text: Some("fffffff2".to_string()),
15428        filter_text: Some("some".to_string()),
15429        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15430        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15431            range: lsp::Range {
15432                start: lsp::Position {
15433                    line: 0,
15434                    character: 22,
15435                },
15436                end: lsp::Position {
15437                    line: 0,
15438                    character: 22,
15439                },
15440            },
15441            new_text: "Some(2)".to_string(),
15442        })),
15443        additional_text_edits: Some(vec![lsp::TextEdit {
15444            range: lsp::Range {
15445                start: lsp::Position {
15446                    line: 0,
15447                    character: 20,
15448                },
15449                end: lsp::Position {
15450                    line: 0,
15451                    character: 22,
15452                },
15453            },
15454            new_text: "".to_string(),
15455        }]),
15456        ..Default::default()
15457    };
15458
15459    let closure_completion_item = completion_item.clone();
15460    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15461        let task_completion_item = closure_completion_item.clone();
15462        async move {
15463            Ok(Some(lsp::CompletionResponse::Array(vec![
15464                task_completion_item,
15465            ])))
15466        }
15467    });
15468
15469    request.next().await;
15470
15471    cx.condition(|editor, _| editor.context_menu_visible())
15472        .await;
15473    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15474        editor
15475            .confirm_completion(&ConfirmCompletion::default(), window, cx)
15476            .unwrap()
15477    });
15478    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
15479
15480    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
15481        let task_completion_item = completion_item.clone();
15482        async move { Ok(task_completion_item) }
15483    })
15484    .next()
15485    .await
15486    .unwrap();
15487    apply_additional_edits.await.unwrap();
15488    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
15489}
15490
15491#[gpui::test]
15492async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
15493    init_test(cx, |_| {});
15494
15495    let mut cx = EditorLspTestContext::new_rust(
15496        lsp::ServerCapabilities {
15497            completion_provider: Some(lsp::CompletionOptions {
15498                trigger_characters: Some(vec![".".to_string()]),
15499                resolve_provider: Some(true),
15500                ..Default::default()
15501            }),
15502            ..Default::default()
15503        },
15504        cx,
15505    )
15506    .await;
15507
15508    cx.set_state("fn main() { let a = 2ˇ; }");
15509    cx.simulate_keystroke(".");
15510
15511    let item1 = lsp::CompletionItem {
15512        label: "method id()".to_string(),
15513        filter_text: Some("id".to_string()),
15514        detail: None,
15515        documentation: None,
15516        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15517            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15518            new_text: ".id".to_string(),
15519        })),
15520        ..lsp::CompletionItem::default()
15521    };
15522
15523    let item2 = lsp::CompletionItem {
15524        label: "other".to_string(),
15525        filter_text: Some("other".to_string()),
15526        detail: None,
15527        documentation: None,
15528        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15529            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15530            new_text: ".other".to_string(),
15531        })),
15532        ..lsp::CompletionItem::default()
15533    };
15534
15535    let item1 = item1.clone();
15536    cx.set_request_handler::<lsp::request::Completion, _, _>({
15537        let item1 = item1.clone();
15538        move |_, _, _| {
15539            let item1 = item1.clone();
15540            let item2 = item2.clone();
15541            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
15542        }
15543    })
15544    .next()
15545    .await;
15546
15547    cx.condition(|editor, _| editor.context_menu_visible())
15548        .await;
15549    cx.update_editor(|editor, _, _| {
15550        let context_menu = editor.context_menu.borrow_mut();
15551        let context_menu = context_menu
15552            .as_ref()
15553            .expect("Should have the context menu deployed");
15554        match context_menu {
15555            CodeContextMenu::Completions(completions_menu) => {
15556                let completions = completions_menu.completions.borrow_mut();
15557                assert_eq!(
15558                    completions
15559                        .iter()
15560                        .map(|completion| &completion.label.text)
15561                        .collect::<Vec<_>>(),
15562                    vec!["method id()", "other"]
15563                )
15564            }
15565            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15566        }
15567    });
15568
15569    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
15570        let item1 = item1.clone();
15571        move |_, item_to_resolve, _| {
15572            let item1 = item1.clone();
15573            async move {
15574                if item1 == item_to_resolve {
15575                    Ok(lsp::CompletionItem {
15576                        label: "method id()".to_string(),
15577                        filter_text: Some("id".to_string()),
15578                        detail: Some("Now resolved!".to_string()),
15579                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
15580                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15581                            range: lsp::Range::new(
15582                                lsp::Position::new(0, 22),
15583                                lsp::Position::new(0, 22),
15584                            ),
15585                            new_text: ".id".to_string(),
15586                        })),
15587                        ..lsp::CompletionItem::default()
15588                    })
15589                } else {
15590                    Ok(item_to_resolve)
15591                }
15592            }
15593        }
15594    })
15595    .next()
15596    .await
15597    .unwrap();
15598    cx.run_until_parked();
15599
15600    cx.update_editor(|editor, window, cx| {
15601        editor.context_menu_next(&Default::default(), window, cx);
15602    });
15603
15604    cx.update_editor(|editor, _, _| {
15605        let context_menu = editor.context_menu.borrow_mut();
15606        let context_menu = context_menu
15607            .as_ref()
15608            .expect("Should have the context menu deployed");
15609        match context_menu {
15610            CodeContextMenu::Completions(completions_menu) => {
15611                let completions = completions_menu.completions.borrow_mut();
15612                assert_eq!(
15613                    completions
15614                        .iter()
15615                        .map(|completion| &completion.label.text)
15616                        .collect::<Vec<_>>(),
15617                    vec!["method id() Now resolved!", "other"],
15618                    "Should update first completion label, but not second as the filter text did not match."
15619                );
15620            }
15621            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15622        }
15623    });
15624}
15625
15626#[gpui::test]
15627async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
15628    init_test(cx, |_| {});
15629    let mut cx = EditorLspTestContext::new_rust(
15630        lsp::ServerCapabilities {
15631            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
15632            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
15633            completion_provider: Some(lsp::CompletionOptions {
15634                resolve_provider: Some(true),
15635                ..Default::default()
15636            }),
15637            ..Default::default()
15638        },
15639        cx,
15640    )
15641    .await;
15642    cx.set_state(indoc! {"
15643        struct TestStruct {
15644            field: i32
15645        }
15646
15647        fn mainˇ() {
15648            let unused_var = 42;
15649            let test_struct = TestStruct { field: 42 };
15650        }
15651    "});
15652    let symbol_range = cx.lsp_range(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 mut hover_requests =
15663        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
15664            Ok(Some(lsp::Hover {
15665                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
15666                    kind: lsp::MarkupKind::Markdown,
15667                    value: "Function documentation".to_string(),
15668                }),
15669                range: Some(symbol_range),
15670            }))
15671        });
15672
15673    // Case 1: Test that code action menu hide hover popover
15674    cx.dispatch_action(Hover);
15675    hover_requests.next().await;
15676    cx.condition(|editor, _| editor.hover_state.visible()).await;
15677    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
15678        move |_, _, _| async move {
15679            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
15680                lsp::CodeAction {
15681                    title: "Remove unused variable".to_string(),
15682                    kind: Some(CodeActionKind::QUICKFIX),
15683                    edit: Some(lsp::WorkspaceEdit {
15684                        changes: Some(
15685                            [(
15686                                lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
15687                                vec![lsp::TextEdit {
15688                                    range: lsp::Range::new(
15689                                        lsp::Position::new(5, 4),
15690                                        lsp::Position::new(5, 27),
15691                                    ),
15692                                    new_text: "".to_string(),
15693                                }],
15694                            )]
15695                            .into_iter()
15696                            .collect(),
15697                        ),
15698                        ..Default::default()
15699                    }),
15700                    ..Default::default()
15701                },
15702            )]))
15703        },
15704    );
15705    cx.update_editor(|editor, window, cx| {
15706        editor.toggle_code_actions(
15707            &ToggleCodeActions {
15708                deployed_from: None,
15709                quick_launch: false,
15710            },
15711            window,
15712            cx,
15713        );
15714    });
15715    code_action_requests.next().await;
15716    cx.run_until_parked();
15717    cx.condition(|editor, _| editor.context_menu_visible())
15718        .await;
15719    cx.update_editor(|editor, _, _| {
15720        assert!(
15721            !editor.hover_state.visible(),
15722            "Hover popover should be hidden when code action menu is shown"
15723        );
15724        // Hide code actions
15725        editor.context_menu.take();
15726    });
15727
15728    // Case 2: Test that code completions hide hover popover
15729    cx.dispatch_action(Hover);
15730    hover_requests.next().await;
15731    cx.condition(|editor, _| editor.hover_state.visible()).await;
15732    let counter = Arc::new(AtomicUsize::new(0));
15733    let mut completion_requests =
15734        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15735            let counter = counter.clone();
15736            async move {
15737                counter.fetch_add(1, atomic::Ordering::Release);
15738                Ok(Some(lsp::CompletionResponse::Array(vec![
15739                    lsp::CompletionItem {
15740                        label: "main".into(),
15741                        kind: Some(lsp::CompletionItemKind::FUNCTION),
15742                        detail: Some("() -> ()".to_string()),
15743                        ..Default::default()
15744                    },
15745                    lsp::CompletionItem {
15746                        label: "TestStruct".into(),
15747                        kind: Some(lsp::CompletionItemKind::STRUCT),
15748                        detail: Some("struct TestStruct".to_string()),
15749                        ..Default::default()
15750                    },
15751                ])))
15752            }
15753        });
15754    cx.update_editor(|editor, window, cx| {
15755        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15756    });
15757    completion_requests.next().await;
15758    cx.condition(|editor, _| editor.context_menu_visible())
15759        .await;
15760    cx.update_editor(|editor, _, _| {
15761        assert!(
15762            !editor.hover_state.visible(),
15763            "Hover popover should be hidden when completion menu is shown"
15764        );
15765    });
15766}
15767
15768#[gpui::test]
15769async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
15770    init_test(cx, |_| {});
15771
15772    let mut cx = EditorLspTestContext::new_rust(
15773        lsp::ServerCapabilities {
15774            completion_provider: Some(lsp::CompletionOptions {
15775                trigger_characters: Some(vec![".".to_string()]),
15776                resolve_provider: Some(true),
15777                ..Default::default()
15778            }),
15779            ..Default::default()
15780        },
15781        cx,
15782    )
15783    .await;
15784
15785    cx.set_state("fn main() { let a = 2ˇ; }");
15786    cx.simulate_keystroke(".");
15787
15788    let unresolved_item_1 = lsp::CompletionItem {
15789        label: "id".to_string(),
15790        filter_text: Some("id".to_string()),
15791        detail: None,
15792        documentation: None,
15793        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15794            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15795            new_text: ".id".to_string(),
15796        })),
15797        ..lsp::CompletionItem::default()
15798    };
15799    let resolved_item_1 = lsp::CompletionItem {
15800        additional_text_edits: Some(vec![lsp::TextEdit {
15801            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
15802            new_text: "!!".to_string(),
15803        }]),
15804        ..unresolved_item_1.clone()
15805    };
15806    let unresolved_item_2 = lsp::CompletionItem {
15807        label: "other".to_string(),
15808        filter_text: Some("other".to_string()),
15809        detail: None,
15810        documentation: None,
15811        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15812            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15813            new_text: ".other".to_string(),
15814        })),
15815        ..lsp::CompletionItem::default()
15816    };
15817    let resolved_item_2 = lsp::CompletionItem {
15818        additional_text_edits: Some(vec![lsp::TextEdit {
15819            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
15820            new_text: "??".to_string(),
15821        }]),
15822        ..unresolved_item_2.clone()
15823    };
15824
15825    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
15826    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
15827    cx.lsp
15828        .server
15829        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
15830            let unresolved_item_1 = unresolved_item_1.clone();
15831            let resolved_item_1 = resolved_item_1.clone();
15832            let unresolved_item_2 = unresolved_item_2.clone();
15833            let resolved_item_2 = resolved_item_2.clone();
15834            let resolve_requests_1 = resolve_requests_1.clone();
15835            let resolve_requests_2 = resolve_requests_2.clone();
15836            move |unresolved_request, _| {
15837                let unresolved_item_1 = unresolved_item_1.clone();
15838                let resolved_item_1 = resolved_item_1.clone();
15839                let unresolved_item_2 = unresolved_item_2.clone();
15840                let resolved_item_2 = resolved_item_2.clone();
15841                let resolve_requests_1 = resolve_requests_1.clone();
15842                let resolve_requests_2 = resolve_requests_2.clone();
15843                async move {
15844                    if unresolved_request == unresolved_item_1 {
15845                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
15846                        Ok(resolved_item_1.clone())
15847                    } else if unresolved_request == unresolved_item_2 {
15848                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
15849                        Ok(resolved_item_2.clone())
15850                    } else {
15851                        panic!("Unexpected completion item {unresolved_request:?}")
15852                    }
15853                }
15854            }
15855        })
15856        .detach();
15857
15858    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15859        let unresolved_item_1 = unresolved_item_1.clone();
15860        let unresolved_item_2 = unresolved_item_2.clone();
15861        async move {
15862            Ok(Some(lsp::CompletionResponse::Array(vec![
15863                unresolved_item_1,
15864                unresolved_item_2,
15865            ])))
15866        }
15867    })
15868    .next()
15869    .await;
15870
15871    cx.condition(|editor, _| editor.context_menu_visible())
15872        .await;
15873    cx.update_editor(|editor, _, _| {
15874        let context_menu = editor.context_menu.borrow_mut();
15875        let context_menu = context_menu
15876            .as_ref()
15877            .expect("Should have the context menu deployed");
15878        match context_menu {
15879            CodeContextMenu::Completions(completions_menu) => {
15880                let completions = completions_menu.completions.borrow_mut();
15881                assert_eq!(
15882                    completions
15883                        .iter()
15884                        .map(|completion| &completion.label.text)
15885                        .collect::<Vec<_>>(),
15886                    vec!["id", "other"]
15887                )
15888            }
15889            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15890        }
15891    });
15892    cx.run_until_parked();
15893
15894    cx.update_editor(|editor, window, cx| {
15895        editor.context_menu_next(&ContextMenuNext, window, cx);
15896    });
15897    cx.run_until_parked();
15898    cx.update_editor(|editor, window, cx| {
15899        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
15900    });
15901    cx.run_until_parked();
15902    cx.update_editor(|editor, window, cx| {
15903        editor.context_menu_next(&ContextMenuNext, window, cx);
15904    });
15905    cx.run_until_parked();
15906    cx.update_editor(|editor, window, cx| {
15907        editor
15908            .compose_completion(&ComposeCompletion::default(), window, cx)
15909            .expect("No task returned")
15910    })
15911    .await
15912    .expect("Completion failed");
15913    cx.run_until_parked();
15914
15915    cx.update_editor(|editor, _, cx| {
15916        assert_eq!(
15917            resolve_requests_1.load(atomic::Ordering::Acquire),
15918            1,
15919            "Should always resolve once despite multiple selections"
15920        );
15921        assert_eq!(
15922            resolve_requests_2.load(atomic::Ordering::Acquire),
15923            1,
15924            "Should always resolve once after multiple selections and applying the completion"
15925        );
15926        assert_eq!(
15927            editor.text(cx),
15928            "fn main() { let a = ??.other; }",
15929            "Should use resolved data when applying the completion"
15930        );
15931    });
15932}
15933
15934#[gpui::test]
15935async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
15936    init_test(cx, |_| {});
15937
15938    let item_0 = lsp::CompletionItem {
15939        label: "abs".into(),
15940        insert_text: Some("abs".into()),
15941        data: Some(json!({ "very": "special"})),
15942        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
15943        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
15944            lsp::InsertReplaceEdit {
15945                new_text: "abs".to_string(),
15946                insert: lsp::Range::default(),
15947                replace: lsp::Range::default(),
15948            },
15949        )),
15950        ..lsp::CompletionItem::default()
15951    };
15952    let items = iter::once(item_0.clone())
15953        .chain((11..51).map(|i| lsp::CompletionItem {
15954            label: format!("item_{}", i),
15955            insert_text: Some(format!("item_{}", i)),
15956            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15957            ..lsp::CompletionItem::default()
15958        }))
15959        .collect::<Vec<_>>();
15960
15961    let default_commit_characters = vec!["?".to_string()];
15962    let default_data = json!({ "default": "data"});
15963    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
15964    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
15965    let default_edit_range = lsp::Range {
15966        start: lsp::Position {
15967            line: 0,
15968            character: 5,
15969        },
15970        end: lsp::Position {
15971            line: 0,
15972            character: 5,
15973        },
15974    };
15975
15976    let mut cx = EditorLspTestContext::new_rust(
15977        lsp::ServerCapabilities {
15978            completion_provider: Some(lsp::CompletionOptions {
15979                trigger_characters: Some(vec![".".to_string()]),
15980                resolve_provider: Some(true),
15981                ..Default::default()
15982            }),
15983            ..Default::default()
15984        },
15985        cx,
15986    )
15987    .await;
15988
15989    cx.set_state("fn main() { let a = 2ˇ; }");
15990    cx.simulate_keystroke(".");
15991
15992    let completion_data = default_data.clone();
15993    let completion_characters = default_commit_characters.clone();
15994    let completion_items = items.clone();
15995    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15996        let default_data = completion_data.clone();
15997        let default_commit_characters = completion_characters.clone();
15998        let items = completion_items.clone();
15999        async move {
16000            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16001                items,
16002                item_defaults: Some(lsp::CompletionListItemDefaults {
16003                    data: Some(default_data.clone()),
16004                    commit_characters: Some(default_commit_characters.clone()),
16005                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
16006                        default_edit_range,
16007                    )),
16008                    insert_text_format: Some(default_insert_text_format),
16009                    insert_text_mode: Some(default_insert_text_mode),
16010                }),
16011                ..lsp::CompletionList::default()
16012            })))
16013        }
16014    })
16015    .next()
16016    .await;
16017
16018    let resolved_items = Arc::new(Mutex::new(Vec::new()));
16019    cx.lsp
16020        .server
16021        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
16022            let closure_resolved_items = resolved_items.clone();
16023            move |item_to_resolve, _| {
16024                let closure_resolved_items = closure_resolved_items.clone();
16025                async move {
16026                    closure_resolved_items.lock().push(item_to_resolve.clone());
16027                    Ok(item_to_resolve)
16028                }
16029            }
16030        })
16031        .detach();
16032
16033    cx.condition(|editor, _| editor.context_menu_visible())
16034        .await;
16035    cx.run_until_parked();
16036    cx.update_editor(|editor, _, _| {
16037        let menu = editor.context_menu.borrow_mut();
16038        match menu.as_ref().expect("should have the completions menu") {
16039            CodeContextMenu::Completions(completions_menu) => {
16040                assert_eq!(
16041                    completions_menu
16042                        .entries
16043                        .borrow()
16044                        .iter()
16045                        .map(|mat| mat.string.clone())
16046                        .collect::<Vec<String>>(),
16047                    items
16048                        .iter()
16049                        .map(|completion| completion.label.clone())
16050                        .collect::<Vec<String>>()
16051                );
16052            }
16053            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
16054        }
16055    });
16056    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
16057    // with 4 from the end.
16058    assert_eq!(
16059        *resolved_items.lock(),
16060        [&items[0..16], &items[items.len() - 4..items.len()]]
16061            .concat()
16062            .iter()
16063            .cloned()
16064            .map(|mut item| {
16065                if item.data.is_none() {
16066                    item.data = Some(default_data.clone());
16067                }
16068                item
16069            })
16070            .collect::<Vec<lsp::CompletionItem>>(),
16071        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
16072    );
16073    resolved_items.lock().clear();
16074
16075    cx.update_editor(|editor, window, cx| {
16076        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
16077    });
16078    cx.run_until_parked();
16079    // Completions that have already been resolved are skipped.
16080    assert_eq!(
16081        *resolved_items.lock(),
16082        items[items.len() - 17..items.len() - 4]
16083            .iter()
16084            .cloned()
16085            .map(|mut item| {
16086                if item.data.is_none() {
16087                    item.data = Some(default_data.clone());
16088                }
16089                item
16090            })
16091            .collect::<Vec<lsp::CompletionItem>>()
16092    );
16093    resolved_items.lock().clear();
16094}
16095
16096#[gpui::test]
16097async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
16098    init_test(cx, |_| {});
16099
16100    let mut cx = EditorLspTestContext::new(
16101        Language::new(
16102            LanguageConfig {
16103                matcher: LanguageMatcher {
16104                    path_suffixes: vec!["jsx".into()],
16105                    ..Default::default()
16106                },
16107                overrides: [(
16108                    "element".into(),
16109                    LanguageConfigOverride {
16110                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
16111                        ..Default::default()
16112                    },
16113                )]
16114                .into_iter()
16115                .collect(),
16116                ..Default::default()
16117            },
16118            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
16119        )
16120        .with_override_query("(jsx_self_closing_element) @element")
16121        .unwrap(),
16122        lsp::ServerCapabilities {
16123            completion_provider: Some(lsp::CompletionOptions {
16124                trigger_characters: Some(vec![":".to_string()]),
16125                ..Default::default()
16126            }),
16127            ..Default::default()
16128        },
16129        cx,
16130    )
16131    .await;
16132
16133    cx.lsp
16134        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16135            Ok(Some(lsp::CompletionResponse::Array(vec![
16136                lsp::CompletionItem {
16137                    label: "bg-blue".into(),
16138                    ..Default::default()
16139                },
16140                lsp::CompletionItem {
16141                    label: "bg-red".into(),
16142                    ..Default::default()
16143                },
16144                lsp::CompletionItem {
16145                    label: "bg-yellow".into(),
16146                    ..Default::default()
16147                },
16148            ])))
16149        });
16150
16151    cx.set_state(r#"<p class="bgˇ" />"#);
16152
16153    // Trigger completion when typing a dash, because the dash is an extra
16154    // word character in the 'element' scope, which contains the cursor.
16155    cx.simulate_keystroke("-");
16156    cx.executor().run_until_parked();
16157    cx.update_editor(|editor, _, _| {
16158        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16159        {
16160            assert_eq!(
16161                completion_menu_entries(&menu),
16162                &["bg-blue", "bg-red", "bg-yellow"]
16163            );
16164        } else {
16165            panic!("expected completion menu to be open");
16166        }
16167    });
16168
16169    cx.simulate_keystroke("l");
16170    cx.executor().run_until_parked();
16171    cx.update_editor(|editor, _, _| {
16172        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16173        {
16174            assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
16175        } else {
16176            panic!("expected completion menu to be open");
16177        }
16178    });
16179
16180    // When filtering completions, consider the character after the '-' to
16181    // be the start of a subword.
16182    cx.set_state(r#"<p class="yelˇ" />"#);
16183    cx.simulate_keystroke("l");
16184    cx.executor().run_until_parked();
16185    cx.update_editor(|editor, _, _| {
16186        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16187        {
16188            assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
16189        } else {
16190            panic!("expected completion menu to be open");
16191        }
16192    });
16193}
16194
16195fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
16196    let entries = menu.entries.borrow();
16197    entries.iter().map(|mat| mat.string.clone()).collect()
16198}
16199
16200#[gpui::test]
16201async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
16202    init_test(cx, |settings| {
16203        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
16204            Formatter::Prettier,
16205        )))
16206    });
16207
16208    let fs = FakeFs::new(cx.executor());
16209    fs.insert_file(path!("/file.ts"), Default::default()).await;
16210
16211    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
16212    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16213
16214    language_registry.add(Arc::new(Language::new(
16215        LanguageConfig {
16216            name: "TypeScript".into(),
16217            matcher: LanguageMatcher {
16218                path_suffixes: vec!["ts".to_string()],
16219                ..Default::default()
16220            },
16221            ..Default::default()
16222        },
16223        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
16224    )));
16225    update_test_language_settings(cx, |settings| {
16226        settings.defaults.prettier = Some(PrettierSettings {
16227            allowed: true,
16228            ..PrettierSettings::default()
16229        });
16230    });
16231
16232    let test_plugin = "test_plugin";
16233    let _ = language_registry.register_fake_lsp(
16234        "TypeScript",
16235        FakeLspAdapter {
16236            prettier_plugins: vec![test_plugin],
16237            ..Default::default()
16238        },
16239    );
16240
16241    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
16242    let buffer = project
16243        .update(cx, |project, cx| {
16244            project.open_local_buffer(path!("/file.ts"), cx)
16245        })
16246        .await
16247        .unwrap();
16248
16249    let buffer_text = "one\ntwo\nthree\n";
16250    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16251    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16252    editor.update_in(cx, |editor, window, cx| {
16253        editor.set_text(buffer_text, window, cx)
16254    });
16255
16256    editor
16257        .update_in(cx, |editor, window, cx| {
16258            editor.perform_format(
16259                project.clone(),
16260                FormatTrigger::Manual,
16261                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
16262                window,
16263                cx,
16264            )
16265        })
16266        .unwrap()
16267        .await;
16268    assert_eq!(
16269        editor.update(cx, |editor, cx| editor.text(cx)),
16270        buffer_text.to_string() + prettier_format_suffix,
16271        "Test prettier formatting was not applied to the original buffer text",
16272    );
16273
16274    update_test_language_settings(cx, |settings| {
16275        settings.defaults.formatter = Some(SelectedFormatter::Auto)
16276    });
16277    let format = editor.update_in(cx, |editor, window, cx| {
16278        editor.perform_format(
16279            project.clone(),
16280            FormatTrigger::Manual,
16281            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
16282            window,
16283            cx,
16284        )
16285    });
16286    format.await.unwrap();
16287    assert_eq!(
16288        editor.update(cx, |editor, cx| editor.text(cx)),
16289        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
16290        "Autoformatting (via test prettier) was not applied to the original buffer text",
16291    );
16292}
16293
16294#[gpui::test]
16295async fn test_addition_reverts(cx: &mut TestAppContext) {
16296    init_test(cx, |_| {});
16297    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16298    let base_text = indoc! {r#"
16299        struct Row;
16300        struct Row1;
16301        struct Row2;
16302
16303        struct Row4;
16304        struct Row5;
16305        struct Row6;
16306
16307        struct Row8;
16308        struct Row9;
16309        struct Row10;"#};
16310
16311    // When addition hunks are not adjacent to carets, no hunk revert is performed
16312    assert_hunk_revert(
16313        indoc! {r#"struct Row;
16314                   struct Row1;
16315                   struct Row1.1;
16316                   struct Row1.2;
16317                   struct Row2;ˇ
16318
16319                   struct Row4;
16320                   struct Row5;
16321                   struct Row6;
16322
16323                   struct Row8;
16324                   ˇstruct Row9;
16325                   struct Row9.1;
16326                   struct Row9.2;
16327                   struct Row9.3;
16328                   struct Row10;"#},
16329        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
16330        indoc! {r#"struct Row;
16331                   struct Row1;
16332                   struct Row1.1;
16333                   struct Row1.2;
16334                   struct Row2;ˇ
16335
16336                   struct Row4;
16337                   struct Row5;
16338                   struct Row6;
16339
16340                   struct Row8;
16341                   ˇstruct Row9;
16342                   struct Row9.1;
16343                   struct Row9.2;
16344                   struct Row9.3;
16345                   struct Row10;"#},
16346        base_text,
16347        &mut cx,
16348    );
16349    // Same for selections
16350    assert_hunk_revert(
16351        indoc! {r#"struct Row;
16352                   struct Row1;
16353                   struct Row2;
16354                   struct Row2.1;
16355                   struct Row2.2;
16356                   «ˇ
16357                   struct Row4;
16358                   struct» Row5;
16359                   «struct Row6;
16360                   ˇ»
16361                   struct Row9.1;
16362                   struct Row9.2;
16363                   struct Row9.3;
16364                   struct Row8;
16365                   struct Row9;
16366                   struct Row10;"#},
16367        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
16368        indoc! {r#"struct Row;
16369                   struct Row1;
16370                   struct Row2;
16371                   struct Row2.1;
16372                   struct Row2.2;
16373                   «ˇ
16374                   struct Row4;
16375                   struct» Row5;
16376                   «struct Row6;
16377                   ˇ»
16378                   struct Row9.1;
16379                   struct Row9.2;
16380                   struct Row9.3;
16381                   struct Row8;
16382                   struct Row9;
16383                   struct Row10;"#},
16384        base_text,
16385        &mut cx,
16386    );
16387
16388    // When carets and selections intersect the addition hunks, those are reverted.
16389    // Adjacent carets got merged.
16390    assert_hunk_revert(
16391        indoc! {r#"struct Row;
16392                   ˇ// something on the top
16393                   struct Row1;
16394                   struct Row2;
16395                   struct Roˇw3.1;
16396                   struct Row2.2;
16397                   struct Row2.3;ˇ
16398
16399                   struct Row4;
16400                   struct ˇRow5.1;
16401                   struct Row5.2;
16402                   struct «Rowˇ»5.3;
16403                   struct Row5;
16404                   struct Row6;
16405                   ˇ
16406                   struct Row9.1;
16407                   struct «Rowˇ»9.2;
16408                   struct «ˇRow»9.3;
16409                   struct Row8;
16410                   struct Row9;
16411                   «ˇ// something on bottom»
16412                   struct Row10;"#},
16413        vec![
16414            DiffHunkStatusKind::Added,
16415            DiffHunkStatusKind::Added,
16416            DiffHunkStatusKind::Added,
16417            DiffHunkStatusKind::Added,
16418            DiffHunkStatusKind::Added,
16419        ],
16420        indoc! {r#"struct Row;
16421                   ˇstruct Row1;
16422                   struct Row2;
16423                   ˇ
16424                   struct Row4;
16425                   ˇstruct Row5;
16426                   struct Row6;
16427                   ˇ
16428                   ˇstruct Row8;
16429                   struct Row9;
16430                   ˇstruct Row10;"#},
16431        base_text,
16432        &mut cx,
16433    );
16434}
16435
16436#[gpui::test]
16437async fn test_modification_reverts(cx: &mut TestAppContext) {
16438    init_test(cx, |_| {});
16439    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16440    let base_text = indoc! {r#"
16441        struct Row;
16442        struct Row1;
16443        struct Row2;
16444
16445        struct Row4;
16446        struct Row5;
16447        struct Row6;
16448
16449        struct Row8;
16450        struct Row9;
16451        struct Row10;"#};
16452
16453    // Modification hunks behave the same as the addition ones.
16454    assert_hunk_revert(
16455        indoc! {r#"struct Row;
16456                   struct Row1;
16457                   struct Row33;
16458                   ˇ
16459                   struct Row4;
16460                   struct Row5;
16461                   struct Row6;
16462                   ˇ
16463                   struct Row99;
16464                   struct Row9;
16465                   struct Row10;"#},
16466        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
16467        indoc! {r#"struct Row;
16468                   struct Row1;
16469                   struct Row33;
16470                   ˇ
16471                   struct Row4;
16472                   struct Row5;
16473                   struct Row6;
16474                   ˇ
16475                   struct Row99;
16476                   struct Row9;
16477                   struct Row10;"#},
16478        base_text,
16479        &mut cx,
16480    );
16481    assert_hunk_revert(
16482        indoc! {r#"struct Row;
16483                   struct Row1;
16484                   struct Row33;
16485                   «ˇ
16486                   struct Row4;
16487                   struct» Row5;
16488                   «struct Row6;
16489                   ˇ»
16490                   struct Row99;
16491                   struct Row9;
16492                   struct Row10;"#},
16493        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
16494        indoc! {r#"struct Row;
16495                   struct Row1;
16496                   struct Row33;
16497                   «ˇ
16498                   struct Row4;
16499                   struct» Row5;
16500                   «struct Row6;
16501                   ˇ»
16502                   struct Row99;
16503                   struct Row9;
16504                   struct Row10;"#},
16505        base_text,
16506        &mut cx,
16507    );
16508
16509    assert_hunk_revert(
16510        indoc! {r#"ˇstruct Row1.1;
16511                   struct Row1;
16512                   «ˇstr»uct Row22;
16513
16514                   struct ˇRow44;
16515                   struct Row5;
16516                   struct «Rˇ»ow66;ˇ
16517
16518                   «struˇ»ct Row88;
16519                   struct Row9;
16520                   struct Row1011;ˇ"#},
16521        vec![
16522            DiffHunkStatusKind::Modified,
16523            DiffHunkStatusKind::Modified,
16524            DiffHunkStatusKind::Modified,
16525            DiffHunkStatusKind::Modified,
16526            DiffHunkStatusKind::Modified,
16527            DiffHunkStatusKind::Modified,
16528        ],
16529        indoc! {r#"struct Row;
16530                   ˇstruct Row1;
16531                   struct Row2;
16532                   ˇ
16533                   struct Row4;
16534                   ˇstruct Row5;
16535                   struct Row6;
16536                   ˇ
16537                   struct Row8;
16538                   ˇstruct Row9;
16539                   struct Row10;ˇ"#},
16540        base_text,
16541        &mut cx,
16542    );
16543}
16544
16545#[gpui::test]
16546async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
16547    init_test(cx, |_| {});
16548    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16549    let base_text = indoc! {r#"
16550        one
16551
16552        two
16553        three
16554        "#};
16555
16556    cx.set_head_text(base_text);
16557    cx.set_state("\nˇ\n");
16558    cx.executor().run_until_parked();
16559    cx.update_editor(|editor, _window, cx| {
16560        editor.expand_selected_diff_hunks(cx);
16561    });
16562    cx.executor().run_until_parked();
16563    cx.update_editor(|editor, window, cx| {
16564        editor.backspace(&Default::default(), window, cx);
16565    });
16566    cx.run_until_parked();
16567    cx.assert_state_with_diff(
16568        indoc! {r#"
16569
16570        - two
16571        - threeˇ
16572        +
16573        "#}
16574        .to_string(),
16575    );
16576}
16577
16578#[gpui::test]
16579async fn test_deletion_reverts(cx: &mut TestAppContext) {
16580    init_test(cx, |_| {});
16581    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16582    let base_text = indoc! {r#"struct Row;
16583struct Row1;
16584struct Row2;
16585
16586struct Row4;
16587struct Row5;
16588struct Row6;
16589
16590struct Row8;
16591struct Row9;
16592struct Row10;"#};
16593
16594    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
16595    assert_hunk_revert(
16596        indoc! {r#"struct Row;
16597                   struct Row2;
16598
16599                   ˇstruct Row4;
16600                   struct Row5;
16601                   struct Row6;
16602                   ˇ
16603                   struct Row8;
16604                   struct Row10;"#},
16605        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
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        base_text,
16616        &mut cx,
16617    );
16618    assert_hunk_revert(
16619        indoc! {r#"struct Row;
16620                   struct Row2;
16621
16622                   «ˇstruct Row4;
16623                   struct» Row5;
16624                   «struct Row6;
16625                   ˇ»
16626                   struct Row8;
16627                   struct Row10;"#},
16628        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
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        base_text,
16639        &mut cx,
16640    );
16641
16642    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
16643    assert_hunk_revert(
16644        indoc! {r#"struct Row;
16645                   ˇstruct Row2;
16646
16647                   struct Row4;
16648                   struct Row5;
16649                   struct Row6;
16650
16651                   struct Row8;ˇ
16652                   struct Row10;"#},
16653        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16654        indoc! {r#"struct Row;
16655                   struct Row1;
16656                   ˇstruct Row2;
16657
16658                   struct Row4;
16659                   struct Row5;
16660                   struct Row6;
16661
16662                   struct Row8;ˇ
16663                   struct Row9;
16664                   struct Row10;"#},
16665        base_text,
16666        &mut cx,
16667    );
16668    assert_hunk_revert(
16669        indoc! {r#"struct Row;
16670                   struct Row2«ˇ;
16671                   struct Row4;
16672                   struct» Row5;
16673                   «struct Row6;
16674
16675                   struct Row8;ˇ»
16676                   struct Row10;"#},
16677        vec![
16678            DiffHunkStatusKind::Deleted,
16679            DiffHunkStatusKind::Deleted,
16680            DiffHunkStatusKind::Deleted,
16681        ],
16682        indoc! {r#"struct Row;
16683                   struct Row1;
16684                   struct Row2«ˇ;
16685
16686                   struct Row4;
16687                   struct» Row5;
16688                   «struct Row6;
16689
16690                   struct Row8;ˇ»
16691                   struct Row9;
16692                   struct Row10;"#},
16693        base_text,
16694        &mut cx,
16695    );
16696}
16697
16698#[gpui::test]
16699async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
16700    init_test(cx, |_| {});
16701
16702    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
16703    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
16704    let base_text_3 =
16705        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
16706
16707    let text_1 = edit_first_char_of_every_line(base_text_1);
16708    let text_2 = edit_first_char_of_every_line(base_text_2);
16709    let text_3 = edit_first_char_of_every_line(base_text_3);
16710
16711    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
16712    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
16713    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
16714
16715    let multibuffer = cx.new(|cx| {
16716        let mut multibuffer = MultiBuffer::new(ReadWrite);
16717        multibuffer.push_excerpts(
16718            buffer_1.clone(),
16719            [
16720                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16721                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16722                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16723            ],
16724            cx,
16725        );
16726        multibuffer.push_excerpts(
16727            buffer_2.clone(),
16728            [
16729                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16730                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16731                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16732            ],
16733            cx,
16734        );
16735        multibuffer.push_excerpts(
16736            buffer_3.clone(),
16737            [
16738                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16739                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16740                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16741            ],
16742            cx,
16743        );
16744        multibuffer
16745    });
16746
16747    let fs = FakeFs::new(cx.executor());
16748    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
16749    let (editor, cx) = cx
16750        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
16751    editor.update_in(cx, |editor, _window, cx| {
16752        for (buffer, diff_base) in [
16753            (buffer_1.clone(), base_text_1),
16754            (buffer_2.clone(), base_text_2),
16755            (buffer_3.clone(), base_text_3),
16756        ] {
16757            let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
16758            editor
16759                .buffer
16760                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
16761        }
16762    });
16763    cx.executor().run_until_parked();
16764
16765    editor.update_in(cx, |editor, window, cx| {
16766        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}");
16767        editor.select_all(&SelectAll, window, cx);
16768        editor.git_restore(&Default::default(), window, cx);
16769    });
16770    cx.executor().run_until_parked();
16771
16772    // When all ranges are selected, all buffer hunks are reverted.
16773    editor.update(cx, |editor, cx| {
16774        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");
16775    });
16776    buffer_1.update(cx, |buffer, _| {
16777        assert_eq!(buffer.text(), base_text_1);
16778    });
16779    buffer_2.update(cx, |buffer, _| {
16780        assert_eq!(buffer.text(), base_text_2);
16781    });
16782    buffer_3.update(cx, |buffer, _| {
16783        assert_eq!(buffer.text(), base_text_3);
16784    });
16785
16786    editor.update_in(cx, |editor, window, cx| {
16787        editor.undo(&Default::default(), window, cx);
16788    });
16789
16790    editor.update_in(cx, |editor, window, cx| {
16791        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16792            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
16793        });
16794        editor.git_restore(&Default::default(), window, cx);
16795    });
16796
16797    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
16798    // but not affect buffer_2 and its related excerpts.
16799    editor.update(cx, |editor, cx| {
16800        assert_eq!(
16801            editor.text(cx),
16802            "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}"
16803        );
16804    });
16805    buffer_1.update(cx, |buffer, _| {
16806        assert_eq!(buffer.text(), base_text_1);
16807    });
16808    buffer_2.update(cx, |buffer, _| {
16809        assert_eq!(
16810            buffer.text(),
16811            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
16812        );
16813    });
16814    buffer_3.update(cx, |buffer, _| {
16815        assert_eq!(
16816            buffer.text(),
16817            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
16818        );
16819    });
16820
16821    fn edit_first_char_of_every_line(text: &str) -> String {
16822        text.split('\n')
16823            .map(|line| format!("X{}", &line[1..]))
16824            .collect::<Vec<_>>()
16825            .join("\n")
16826    }
16827}
16828
16829#[gpui::test]
16830async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
16831    init_test(cx, |_| {});
16832
16833    let cols = 4;
16834    let rows = 10;
16835    let sample_text_1 = sample_text(rows, cols, 'a');
16836    assert_eq!(
16837        sample_text_1,
16838        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
16839    );
16840    let sample_text_2 = sample_text(rows, cols, 'l');
16841    assert_eq!(
16842        sample_text_2,
16843        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
16844    );
16845    let sample_text_3 = sample_text(rows, cols, 'v');
16846    assert_eq!(
16847        sample_text_3,
16848        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
16849    );
16850
16851    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
16852    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
16853    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
16854
16855    let multi_buffer = cx.new(|cx| {
16856        let mut multibuffer = MultiBuffer::new(ReadWrite);
16857        multibuffer.push_excerpts(
16858            buffer_1.clone(),
16859            [
16860                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16861                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16862                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16863            ],
16864            cx,
16865        );
16866        multibuffer.push_excerpts(
16867            buffer_2.clone(),
16868            [
16869                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16870                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16871                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16872            ],
16873            cx,
16874        );
16875        multibuffer.push_excerpts(
16876            buffer_3.clone(),
16877            [
16878                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16879                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16880                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16881            ],
16882            cx,
16883        );
16884        multibuffer
16885    });
16886
16887    let fs = FakeFs::new(cx.executor());
16888    fs.insert_tree(
16889        "/a",
16890        json!({
16891            "main.rs": sample_text_1,
16892            "other.rs": sample_text_2,
16893            "lib.rs": sample_text_3,
16894        }),
16895    )
16896    .await;
16897    let project = Project::test(fs, ["/a".as_ref()], cx).await;
16898    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16899    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16900    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16901        Editor::new(
16902            EditorMode::full(),
16903            multi_buffer,
16904            Some(project.clone()),
16905            window,
16906            cx,
16907        )
16908    });
16909    let multibuffer_item_id = workspace
16910        .update(cx, |workspace, window, cx| {
16911            assert!(
16912                workspace.active_item(cx).is_none(),
16913                "active item should be None before the first item is added"
16914            );
16915            workspace.add_item_to_active_pane(
16916                Box::new(multi_buffer_editor.clone()),
16917                None,
16918                true,
16919                window,
16920                cx,
16921            );
16922            let active_item = workspace
16923                .active_item(cx)
16924                .expect("should have an active item after adding the multi buffer");
16925            assert!(
16926                !active_item.is_singleton(cx),
16927                "A multi buffer was expected to active after adding"
16928            );
16929            active_item.item_id()
16930        })
16931        .unwrap();
16932    cx.executor().run_until_parked();
16933
16934    multi_buffer_editor.update_in(cx, |editor, window, cx| {
16935        editor.change_selections(
16936            SelectionEffects::scroll(Autoscroll::Next),
16937            window,
16938            cx,
16939            |s| s.select_ranges(Some(1..2)),
16940        );
16941        editor.open_excerpts(&OpenExcerpts, window, cx);
16942    });
16943    cx.executor().run_until_parked();
16944    let first_item_id = workspace
16945        .update(cx, |workspace, window, cx| {
16946            let active_item = workspace
16947                .active_item(cx)
16948                .expect("should have an active item after navigating into the 1st buffer");
16949            let first_item_id = active_item.item_id();
16950            assert_ne!(
16951                first_item_id, multibuffer_item_id,
16952                "Should navigate into the 1st buffer and activate it"
16953            );
16954            assert!(
16955                active_item.is_singleton(cx),
16956                "New active item should be a singleton buffer"
16957            );
16958            assert_eq!(
16959                active_item
16960                    .act_as::<Editor>(cx)
16961                    .expect("should have navigated into an editor for the 1st buffer")
16962                    .read(cx)
16963                    .text(cx),
16964                sample_text_1
16965            );
16966
16967            workspace
16968                .go_back(workspace.active_pane().downgrade(), window, cx)
16969                .detach_and_log_err(cx);
16970
16971            first_item_id
16972        })
16973        .unwrap();
16974    cx.executor().run_until_parked();
16975    workspace
16976        .update(cx, |workspace, _, cx| {
16977            let active_item = workspace
16978                .active_item(cx)
16979                .expect("should have an active item after navigating back");
16980            assert_eq!(
16981                active_item.item_id(),
16982                multibuffer_item_id,
16983                "Should navigate back to the multi buffer"
16984            );
16985            assert!(!active_item.is_singleton(cx));
16986        })
16987        .unwrap();
16988
16989    multi_buffer_editor.update_in(cx, |editor, window, cx| {
16990        editor.change_selections(
16991            SelectionEffects::scroll(Autoscroll::Next),
16992            window,
16993            cx,
16994            |s| s.select_ranges(Some(39..40)),
16995        );
16996        editor.open_excerpts(&OpenExcerpts, window, cx);
16997    });
16998    cx.executor().run_until_parked();
16999    let second_item_id = workspace
17000        .update(cx, |workspace, window, cx| {
17001            let active_item = workspace
17002                .active_item(cx)
17003                .expect("should have an active item after navigating into the 2nd buffer");
17004            let second_item_id = active_item.item_id();
17005            assert_ne!(
17006                second_item_id, multibuffer_item_id,
17007                "Should navigate away from the multibuffer"
17008            );
17009            assert_ne!(
17010                second_item_id, first_item_id,
17011                "Should navigate into the 2nd buffer and activate it"
17012            );
17013            assert!(
17014                active_item.is_singleton(cx),
17015                "New active item should be a singleton buffer"
17016            );
17017            assert_eq!(
17018                active_item
17019                    .act_as::<Editor>(cx)
17020                    .expect("should have navigated into an editor")
17021                    .read(cx)
17022                    .text(cx),
17023                sample_text_2
17024            );
17025
17026            workspace
17027                .go_back(workspace.active_pane().downgrade(), window, cx)
17028                .detach_and_log_err(cx);
17029
17030            second_item_id
17031        })
17032        .unwrap();
17033    cx.executor().run_until_parked();
17034    workspace
17035        .update(cx, |workspace, _, cx| {
17036            let active_item = workspace
17037                .active_item(cx)
17038                .expect("should have an active item after navigating back from the 2nd buffer");
17039            assert_eq!(
17040                active_item.item_id(),
17041                multibuffer_item_id,
17042                "Should navigate back from the 2nd buffer to the multi buffer"
17043            );
17044            assert!(!active_item.is_singleton(cx));
17045        })
17046        .unwrap();
17047
17048    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17049        editor.change_selections(
17050            SelectionEffects::scroll(Autoscroll::Next),
17051            window,
17052            cx,
17053            |s| s.select_ranges(Some(70..70)),
17054        );
17055        editor.open_excerpts(&OpenExcerpts, window, cx);
17056    });
17057    cx.executor().run_until_parked();
17058    workspace
17059        .update(cx, |workspace, window, cx| {
17060            let active_item = workspace
17061                .active_item(cx)
17062                .expect("should have an active item after navigating into the 3rd buffer");
17063            let third_item_id = active_item.item_id();
17064            assert_ne!(
17065                third_item_id, multibuffer_item_id,
17066                "Should navigate into the 3rd buffer and activate it"
17067            );
17068            assert_ne!(third_item_id, first_item_id);
17069            assert_ne!(third_item_id, second_item_id);
17070            assert!(
17071                active_item.is_singleton(cx),
17072                "New active item should be a singleton buffer"
17073            );
17074            assert_eq!(
17075                active_item
17076                    .act_as::<Editor>(cx)
17077                    .expect("should have navigated into an editor")
17078                    .read(cx)
17079                    .text(cx),
17080                sample_text_3
17081            );
17082
17083            workspace
17084                .go_back(workspace.active_pane().downgrade(), window, cx)
17085                .detach_and_log_err(cx);
17086        })
17087        .unwrap();
17088    cx.executor().run_until_parked();
17089    workspace
17090        .update(cx, |workspace, _, cx| {
17091            let active_item = workspace
17092                .active_item(cx)
17093                .expect("should have an active item after navigating back from the 3rd buffer");
17094            assert_eq!(
17095                active_item.item_id(),
17096                multibuffer_item_id,
17097                "Should navigate back from the 3rd buffer to the multi buffer"
17098            );
17099            assert!(!active_item.is_singleton(cx));
17100        })
17101        .unwrap();
17102}
17103
17104#[gpui::test]
17105async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17106    init_test(cx, |_| {});
17107
17108    let mut cx = EditorTestContext::new(cx).await;
17109
17110    let diff_base = r#"
17111        use some::mod;
17112
17113        const A: u32 = 42;
17114
17115        fn main() {
17116            println!("hello");
17117
17118            println!("world");
17119        }
17120        "#
17121    .unindent();
17122
17123    cx.set_state(
17124        &r#"
17125        use some::modified;
17126
17127        ˇ
17128        fn main() {
17129            println!("hello there");
17130
17131            println!("around the");
17132            println!("world");
17133        }
17134        "#
17135        .unindent(),
17136    );
17137
17138    cx.set_head_text(&diff_base);
17139    executor.run_until_parked();
17140
17141    cx.update_editor(|editor, window, cx| {
17142        editor.go_to_next_hunk(&GoToHunk, window, cx);
17143        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17144    });
17145    executor.run_until_parked();
17146    cx.assert_state_with_diff(
17147        r#"
17148          use some::modified;
17149
17150
17151          fn main() {
17152        -     println!("hello");
17153        + ˇ    println!("hello there");
17154
17155              println!("around the");
17156              println!("world");
17157          }
17158        "#
17159        .unindent(),
17160    );
17161
17162    cx.update_editor(|editor, window, cx| {
17163        for _ in 0..2 {
17164            editor.go_to_next_hunk(&GoToHunk, window, cx);
17165            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17166        }
17167    });
17168    executor.run_until_parked();
17169    cx.assert_state_with_diff(
17170        r#"
17171        - use some::mod;
17172        + ˇuse some::modified;
17173
17174
17175          fn main() {
17176        -     println!("hello");
17177        +     println!("hello there");
17178
17179        +     println!("around the");
17180              println!("world");
17181          }
17182        "#
17183        .unindent(),
17184    );
17185
17186    cx.update_editor(|editor, window, cx| {
17187        editor.go_to_next_hunk(&GoToHunk, window, cx);
17188        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17189    });
17190    executor.run_until_parked();
17191    cx.assert_state_with_diff(
17192        r#"
17193        - use some::mod;
17194        + use some::modified;
17195
17196        - const A: u32 = 42;
17197          ˇ
17198          fn main() {
17199        -     println!("hello");
17200        +     println!("hello there");
17201
17202        +     println!("around the");
17203              println!("world");
17204          }
17205        "#
17206        .unindent(),
17207    );
17208
17209    cx.update_editor(|editor, window, cx| {
17210        editor.cancel(&Cancel, window, cx);
17211    });
17212
17213    cx.assert_state_with_diff(
17214        r#"
17215          use some::modified;
17216
17217          ˇ
17218          fn main() {
17219              println!("hello there");
17220
17221              println!("around the");
17222              println!("world");
17223          }
17224        "#
17225        .unindent(),
17226    );
17227}
17228
17229#[gpui::test]
17230async fn test_diff_base_change_with_expanded_diff_hunks(
17231    executor: BackgroundExecutor,
17232    cx: &mut TestAppContext,
17233) {
17234    init_test(cx, |_| {});
17235
17236    let mut cx = EditorTestContext::new(cx).await;
17237
17238    let diff_base = r#"
17239        use some::mod1;
17240        use some::mod2;
17241
17242        const A: u32 = 42;
17243        const B: u32 = 42;
17244        const C: u32 = 42;
17245
17246        fn main() {
17247            println!("hello");
17248
17249            println!("world");
17250        }
17251        "#
17252    .unindent();
17253
17254    cx.set_state(
17255        &r#"
17256        use some::mod2;
17257
17258        const A: u32 = 42;
17259        const C: u32 = 42;
17260
17261        fn main(ˇ) {
17262            //println!("hello");
17263
17264            println!("world");
17265            //
17266            //
17267        }
17268        "#
17269        .unindent(),
17270    );
17271
17272    cx.set_head_text(&diff_base);
17273    executor.run_until_parked();
17274
17275    cx.update_editor(|editor, window, cx| {
17276        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17277    });
17278    executor.run_until_parked();
17279    cx.assert_state_with_diff(
17280        r#"
17281        - use some::mod1;
17282          use some::mod2;
17283
17284          const A: u32 = 42;
17285        - const B: u32 = 42;
17286          const C: u32 = 42;
17287
17288          fn main(ˇ) {
17289        -     println!("hello");
17290        +     //println!("hello");
17291
17292              println!("world");
17293        +     //
17294        +     //
17295          }
17296        "#
17297        .unindent(),
17298    );
17299
17300    cx.set_head_text("new diff base!");
17301    executor.run_until_parked();
17302    cx.assert_state_with_diff(
17303        r#"
17304        - new diff base!
17305        + use some::mod2;
17306        +
17307        + const A: u32 = 42;
17308        + const C: u32 = 42;
17309        +
17310        + fn main(ˇ) {
17311        +     //println!("hello");
17312        +
17313        +     println!("world");
17314        +     //
17315        +     //
17316        + }
17317        "#
17318        .unindent(),
17319    );
17320}
17321
17322#[gpui::test]
17323async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
17324    init_test(cx, |_| {});
17325
17326    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
17327    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
17328    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
17329    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
17330    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
17331    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
17332
17333    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
17334    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
17335    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
17336
17337    let multi_buffer = cx.new(|cx| {
17338        let mut multibuffer = MultiBuffer::new(ReadWrite);
17339        multibuffer.push_excerpts(
17340            buffer_1.clone(),
17341            [
17342                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17343                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17344                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17345            ],
17346            cx,
17347        );
17348        multibuffer.push_excerpts(
17349            buffer_2.clone(),
17350            [
17351                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17352                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17353                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17354            ],
17355            cx,
17356        );
17357        multibuffer.push_excerpts(
17358            buffer_3.clone(),
17359            [
17360                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17361                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17362                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17363            ],
17364            cx,
17365        );
17366        multibuffer
17367    });
17368
17369    let editor =
17370        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
17371    editor
17372        .update(cx, |editor, _window, cx| {
17373            for (buffer, diff_base) in [
17374                (buffer_1.clone(), file_1_old),
17375                (buffer_2.clone(), file_2_old),
17376                (buffer_3.clone(), file_3_old),
17377            ] {
17378                let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
17379                editor
17380                    .buffer
17381                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
17382            }
17383        })
17384        .unwrap();
17385
17386    let mut cx = EditorTestContext::for_editor(editor, cx).await;
17387    cx.run_until_parked();
17388
17389    cx.assert_editor_state(
17390        &"
17391            ˇaaa
17392            ccc
17393            ddd
17394
17395            ggg
17396            hhh
17397
17398
17399            lll
17400            mmm
17401            NNN
17402
17403            qqq
17404            rrr
17405
17406            uuu
17407            111
17408            222
17409            333
17410
17411            666
17412            777
17413
17414            000
17415            !!!"
17416        .unindent(),
17417    );
17418
17419    cx.update_editor(|editor, window, cx| {
17420        editor.select_all(&SelectAll, window, cx);
17421        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17422    });
17423    cx.executor().run_until_parked();
17424
17425    cx.assert_state_with_diff(
17426        "
17427            «aaa
17428          - bbb
17429            ccc
17430            ddd
17431
17432            ggg
17433            hhh
17434
17435
17436            lll
17437            mmm
17438          - nnn
17439          + NNN
17440
17441            qqq
17442            rrr
17443
17444            uuu
17445            111
17446            222
17447            333
17448
17449          + 666
17450            777
17451
17452            000
17453            !!!ˇ»"
17454            .unindent(),
17455    );
17456}
17457
17458#[gpui::test]
17459async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
17460    init_test(cx, |_| {});
17461
17462    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
17463    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
17464
17465    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
17466    let multi_buffer = cx.new(|cx| {
17467        let mut multibuffer = MultiBuffer::new(ReadWrite);
17468        multibuffer.push_excerpts(
17469            buffer.clone(),
17470            [
17471                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
17472                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
17473                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
17474            ],
17475            cx,
17476        );
17477        multibuffer
17478    });
17479
17480    let editor =
17481        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
17482    editor
17483        .update(cx, |editor, _window, cx| {
17484            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
17485            editor
17486                .buffer
17487                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
17488        })
17489        .unwrap();
17490
17491    let mut cx = EditorTestContext::for_editor(editor, cx).await;
17492    cx.run_until_parked();
17493
17494    cx.update_editor(|editor, window, cx| {
17495        editor.expand_all_diff_hunks(&Default::default(), window, cx)
17496    });
17497    cx.executor().run_until_parked();
17498
17499    // When the start of a hunk coincides with the start of its excerpt,
17500    // the hunk is expanded. When the start of a a hunk is earlier than
17501    // the start of its excerpt, the hunk is not expanded.
17502    cx.assert_state_with_diff(
17503        "
17504            ˇaaa
17505          - bbb
17506          + BBB
17507
17508          - ddd
17509          - eee
17510          + DDD
17511          + EEE
17512            fff
17513
17514            iii
17515        "
17516        .unindent(),
17517    );
17518}
17519
17520#[gpui::test]
17521async fn test_edits_around_expanded_insertion_hunks(
17522    executor: BackgroundExecutor,
17523    cx: &mut TestAppContext,
17524) {
17525    init_test(cx, |_| {});
17526
17527    let mut cx = EditorTestContext::new(cx).await;
17528
17529    let diff_base = r#"
17530        use some::mod1;
17531        use some::mod2;
17532
17533        const A: u32 = 42;
17534
17535        fn main() {
17536            println!("hello");
17537
17538            println!("world");
17539        }
17540        "#
17541    .unindent();
17542    executor.run_until_parked();
17543    cx.set_state(
17544        &r#"
17545        use some::mod1;
17546        use some::mod2;
17547
17548        const A: u32 = 42;
17549        const B: u32 = 42;
17550        const C: u32 = 42;
17551        ˇ
17552
17553        fn main() {
17554            println!("hello");
17555
17556            println!("world");
17557        }
17558        "#
17559        .unindent(),
17560    );
17561
17562    cx.set_head_text(&diff_base);
17563    executor.run_until_parked();
17564
17565    cx.update_editor(|editor, window, cx| {
17566        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17567    });
17568    executor.run_until_parked();
17569
17570    cx.assert_state_with_diff(
17571        r#"
17572        use some::mod1;
17573        use some::mod2;
17574
17575        const A: u32 = 42;
17576      + const B: u32 = 42;
17577      + const C: u32 = 42;
17578      + ˇ
17579
17580        fn main() {
17581            println!("hello");
17582
17583            println!("world");
17584        }
17585      "#
17586        .unindent(),
17587    );
17588
17589    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
17590    executor.run_until_parked();
17591
17592    cx.assert_state_with_diff(
17593        r#"
17594        use some::mod1;
17595        use some::mod2;
17596
17597        const A: u32 = 42;
17598      + const B: u32 = 42;
17599      + const C: u32 = 42;
17600      + const D: u32 = 42;
17601      + ˇ
17602
17603        fn main() {
17604            println!("hello");
17605
17606            println!("world");
17607        }
17608      "#
17609        .unindent(),
17610    );
17611
17612    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
17613    executor.run_until_parked();
17614
17615    cx.assert_state_with_diff(
17616        r#"
17617        use some::mod1;
17618        use some::mod2;
17619
17620        const A: u32 = 42;
17621      + const B: u32 = 42;
17622      + const C: u32 = 42;
17623      + const D: u32 = 42;
17624      + const E: u32 = 42;
17625      + ˇ
17626
17627        fn main() {
17628            println!("hello");
17629
17630            println!("world");
17631        }
17632      "#
17633        .unindent(),
17634    );
17635
17636    cx.update_editor(|editor, window, cx| {
17637        editor.delete_line(&DeleteLine, window, cx);
17638    });
17639    executor.run_until_parked();
17640
17641    cx.assert_state_with_diff(
17642        r#"
17643        use some::mod1;
17644        use some::mod2;
17645
17646        const A: u32 = 42;
17647      + const B: u32 = 42;
17648      + const C: u32 = 42;
17649      + const D: u32 = 42;
17650      + const E: u32 = 42;
17651        ˇ
17652        fn main() {
17653            println!("hello");
17654
17655            println!("world");
17656        }
17657      "#
17658        .unindent(),
17659    );
17660
17661    cx.update_editor(|editor, window, cx| {
17662        editor.move_up(&MoveUp, window, cx);
17663        editor.delete_line(&DeleteLine, window, cx);
17664        editor.move_up(&MoveUp, window, cx);
17665        editor.delete_line(&DeleteLine, window, cx);
17666        editor.move_up(&MoveUp, window, cx);
17667        editor.delete_line(&DeleteLine, window, cx);
17668    });
17669    executor.run_until_parked();
17670    cx.assert_state_with_diff(
17671        r#"
17672        use some::mod1;
17673        use some::mod2;
17674
17675        const A: u32 = 42;
17676      + const B: u32 = 42;
17677        ˇ
17678        fn main() {
17679            println!("hello");
17680
17681            println!("world");
17682        }
17683      "#
17684        .unindent(),
17685    );
17686
17687    cx.update_editor(|editor, window, cx| {
17688        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
17689        editor.delete_line(&DeleteLine, window, cx);
17690    });
17691    executor.run_until_parked();
17692    cx.assert_state_with_diff(
17693        r#"
17694        ˇ
17695        fn main() {
17696            println!("hello");
17697
17698            println!("world");
17699        }
17700      "#
17701        .unindent(),
17702    );
17703}
17704
17705#[gpui::test]
17706async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
17707    init_test(cx, |_| {});
17708
17709    let mut cx = EditorTestContext::new(cx).await;
17710    cx.set_head_text(indoc! { "
17711        one
17712        two
17713        three
17714        four
17715        five
17716        "
17717    });
17718    cx.set_state(indoc! { "
17719        one
17720        ˇthree
17721        five
17722    "});
17723    cx.run_until_parked();
17724    cx.update_editor(|editor, window, cx| {
17725        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17726    });
17727    cx.assert_state_with_diff(
17728        indoc! { "
17729        one
17730      - two
17731        ˇthree
17732      - four
17733        five
17734    "}
17735        .to_string(),
17736    );
17737    cx.update_editor(|editor, window, cx| {
17738        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17739    });
17740
17741    cx.assert_state_with_diff(
17742        indoc! { "
17743        one
17744        ˇthree
17745        five
17746    "}
17747        .to_string(),
17748    );
17749
17750    cx.set_state(indoc! { "
17751        one
17752        ˇTWO
17753        three
17754        four
17755        five
17756    "});
17757    cx.run_until_parked();
17758    cx.update_editor(|editor, window, cx| {
17759        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17760    });
17761
17762    cx.assert_state_with_diff(
17763        indoc! { "
17764            one
17765          - two
17766          + ˇTWO
17767            three
17768            four
17769            five
17770        "}
17771        .to_string(),
17772    );
17773    cx.update_editor(|editor, window, cx| {
17774        editor.move_up(&Default::default(), window, cx);
17775        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17776    });
17777    cx.assert_state_with_diff(
17778        indoc! { "
17779            one
17780            ˇTWO
17781            three
17782            four
17783            five
17784        "}
17785        .to_string(),
17786    );
17787}
17788
17789#[gpui::test]
17790async fn test_edits_around_expanded_deletion_hunks(
17791    executor: BackgroundExecutor,
17792    cx: &mut TestAppContext,
17793) {
17794    init_test(cx, |_| {});
17795
17796    let mut cx = EditorTestContext::new(cx).await;
17797
17798    let diff_base = r#"
17799        use some::mod1;
17800        use some::mod2;
17801
17802        const A: u32 = 42;
17803        const B: u32 = 42;
17804        const C: u32 = 42;
17805
17806
17807        fn main() {
17808            println!("hello");
17809
17810            println!("world");
17811        }
17812    "#
17813    .unindent();
17814    executor.run_until_parked();
17815    cx.set_state(
17816        &r#"
17817        use some::mod1;
17818        use some::mod2;
17819
17820        ˇconst B: u32 = 42;
17821        const C: u32 = 42;
17822
17823
17824        fn main() {
17825            println!("hello");
17826
17827            println!("world");
17828        }
17829        "#
17830        .unindent(),
17831    );
17832
17833    cx.set_head_text(&diff_base);
17834    executor.run_until_parked();
17835
17836    cx.update_editor(|editor, window, cx| {
17837        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17838    });
17839    executor.run_until_parked();
17840
17841    cx.assert_state_with_diff(
17842        r#"
17843        use some::mod1;
17844        use some::mod2;
17845
17846      - const A: u32 = 42;
17847        ˇconst B: u32 = 42;
17848        const C: u32 = 42;
17849
17850
17851        fn main() {
17852            println!("hello");
17853
17854            println!("world");
17855        }
17856      "#
17857        .unindent(),
17858    );
17859
17860    cx.update_editor(|editor, window, cx| {
17861        editor.delete_line(&DeleteLine, window, cx);
17862    });
17863    executor.run_until_parked();
17864    cx.assert_state_with_diff(
17865        r#"
17866        use some::mod1;
17867        use some::mod2;
17868
17869      - const A: u32 = 42;
17870      - const B: u32 = 42;
17871        ˇconst C: u32 = 42;
17872
17873
17874        fn main() {
17875            println!("hello");
17876
17877            println!("world");
17878        }
17879      "#
17880        .unindent(),
17881    );
17882
17883    cx.update_editor(|editor, window, cx| {
17884        editor.delete_line(&DeleteLine, window, cx);
17885    });
17886    executor.run_until_parked();
17887    cx.assert_state_with_diff(
17888        r#"
17889        use some::mod1;
17890        use some::mod2;
17891
17892      - const A: u32 = 42;
17893      - const B: u32 = 42;
17894      - const C: u32 = 42;
17895        ˇ
17896
17897        fn main() {
17898            println!("hello");
17899
17900            println!("world");
17901        }
17902      "#
17903        .unindent(),
17904    );
17905
17906    cx.update_editor(|editor, window, cx| {
17907        editor.handle_input("replacement", window, cx);
17908    });
17909    executor.run_until_parked();
17910    cx.assert_state_with_diff(
17911        r#"
17912        use some::mod1;
17913        use some::mod2;
17914
17915      - const A: u32 = 42;
17916      - const B: u32 = 42;
17917      - const C: u32 = 42;
17918      -
17919      + replacementˇ
17920
17921        fn main() {
17922            println!("hello");
17923
17924            println!("world");
17925        }
17926      "#
17927        .unindent(),
17928    );
17929}
17930
17931#[gpui::test]
17932async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17933    init_test(cx, |_| {});
17934
17935    let mut cx = EditorTestContext::new(cx).await;
17936
17937    let base_text = r#"
17938        one
17939        two
17940        three
17941        four
17942        five
17943    "#
17944    .unindent();
17945    executor.run_until_parked();
17946    cx.set_state(
17947        &r#"
17948        one
17949        two
17950        fˇour
17951        five
17952        "#
17953        .unindent(),
17954    );
17955
17956    cx.set_head_text(&base_text);
17957    executor.run_until_parked();
17958
17959    cx.update_editor(|editor, window, cx| {
17960        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17961    });
17962    executor.run_until_parked();
17963
17964    cx.assert_state_with_diff(
17965        r#"
17966          one
17967          two
17968        - three
17969          fˇour
17970          five
17971        "#
17972        .unindent(),
17973    );
17974
17975    cx.update_editor(|editor, window, cx| {
17976        editor.backspace(&Backspace, window, cx);
17977        editor.backspace(&Backspace, window, cx);
17978    });
17979    executor.run_until_parked();
17980    cx.assert_state_with_diff(
17981        r#"
17982          one
17983          two
17984        - threeˇ
17985        - four
17986        + our
17987          five
17988        "#
17989        .unindent(),
17990    );
17991}
17992
17993#[gpui::test]
17994async fn test_edit_after_expanded_modification_hunk(
17995    executor: BackgroundExecutor,
17996    cx: &mut TestAppContext,
17997) {
17998    init_test(cx, |_| {});
17999
18000    let mut cx = EditorTestContext::new(cx).await;
18001
18002    let diff_base = r#"
18003        use some::mod1;
18004        use some::mod2;
18005
18006        const A: u32 = 42;
18007        const B: u32 = 42;
18008        const C: u32 = 42;
18009        const D: u32 = 42;
18010
18011
18012        fn main() {
18013            println!("hello");
18014
18015            println!("world");
18016        }"#
18017    .unindent();
18018
18019    cx.set_state(
18020        &r#"
18021        use some::mod1;
18022        use some::mod2;
18023
18024        const A: u32 = 42;
18025        const B: u32 = 42;
18026        const C: u32 = 43ˇ
18027        const D: u32 = 42;
18028
18029
18030        fn main() {
18031            println!("hello");
18032
18033            println!("world");
18034        }"#
18035        .unindent(),
18036    );
18037
18038    cx.set_head_text(&diff_base);
18039    executor.run_until_parked();
18040    cx.update_editor(|editor, window, cx| {
18041        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18042    });
18043    executor.run_until_parked();
18044
18045    cx.assert_state_with_diff(
18046        r#"
18047        use some::mod1;
18048        use some::mod2;
18049
18050        const A: u32 = 42;
18051        const B: u32 = 42;
18052      - const C: u32 = 42;
18053      + const C: u32 = 43ˇ
18054        const D: u32 = 42;
18055
18056
18057        fn main() {
18058            println!("hello");
18059
18060            println!("world");
18061        }"#
18062        .unindent(),
18063    );
18064
18065    cx.update_editor(|editor, window, cx| {
18066        editor.handle_input("\nnew_line\n", window, cx);
18067    });
18068    executor.run_until_parked();
18069
18070    cx.assert_state_with_diff(
18071        r#"
18072        use some::mod1;
18073        use some::mod2;
18074
18075        const A: u32 = 42;
18076        const B: u32 = 42;
18077      - const C: u32 = 42;
18078      + const C: u32 = 43
18079      + new_line
18080      + ˇ
18081        const D: u32 = 42;
18082
18083
18084        fn main() {
18085            println!("hello");
18086
18087            println!("world");
18088        }"#
18089        .unindent(),
18090    );
18091}
18092
18093#[gpui::test]
18094async fn test_stage_and_unstage_added_file_hunk(
18095    executor: BackgroundExecutor,
18096    cx: &mut TestAppContext,
18097) {
18098    init_test(cx, |_| {});
18099
18100    let mut cx = EditorTestContext::new(cx).await;
18101    cx.update_editor(|editor, _, cx| {
18102        editor.set_expand_all_diff_hunks(cx);
18103    });
18104
18105    let working_copy = r#"
18106            ˇfn main() {
18107                println!("hello, world!");
18108            }
18109        "#
18110    .unindent();
18111
18112    cx.set_state(&working_copy);
18113    executor.run_until_parked();
18114
18115    cx.assert_state_with_diff(
18116        r#"
18117            + ˇfn main() {
18118            +     println!("hello, world!");
18119            + }
18120        "#
18121        .unindent(),
18122    );
18123    cx.assert_index_text(None);
18124
18125    cx.update_editor(|editor, window, cx| {
18126        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18127    });
18128    executor.run_until_parked();
18129    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
18130    cx.assert_state_with_diff(
18131        r#"
18132            + ˇfn main() {
18133            +     println!("hello, world!");
18134            + }
18135        "#
18136        .unindent(),
18137    );
18138
18139    cx.update_editor(|editor, window, cx| {
18140        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18141    });
18142    executor.run_until_parked();
18143    cx.assert_index_text(None);
18144}
18145
18146async fn setup_indent_guides_editor(
18147    text: &str,
18148    cx: &mut TestAppContext,
18149) -> (BufferId, EditorTestContext) {
18150    init_test(cx, |_| {});
18151
18152    let mut cx = EditorTestContext::new(cx).await;
18153
18154    let buffer_id = cx.update_editor(|editor, window, cx| {
18155        editor.set_text(text, window, cx);
18156        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
18157
18158        buffer_ids[0]
18159    });
18160
18161    (buffer_id, cx)
18162}
18163
18164fn assert_indent_guides(
18165    range: Range<u32>,
18166    expected: Vec<IndentGuide>,
18167    active_indices: Option<Vec<usize>>,
18168    cx: &mut EditorTestContext,
18169) {
18170    let indent_guides = cx.update_editor(|editor, window, cx| {
18171        let snapshot = editor.snapshot(window, cx).display_snapshot;
18172        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
18173            editor,
18174            MultiBufferRow(range.start)..MultiBufferRow(range.end),
18175            true,
18176            &snapshot,
18177            cx,
18178        );
18179
18180        indent_guides.sort_by(|a, b| {
18181            a.depth.cmp(&b.depth).then(
18182                a.start_row
18183                    .cmp(&b.start_row)
18184                    .then(a.end_row.cmp(&b.end_row)),
18185            )
18186        });
18187        indent_guides
18188    });
18189
18190    if let Some(expected) = active_indices {
18191        let active_indices = cx.update_editor(|editor, window, cx| {
18192            let snapshot = editor.snapshot(window, cx).display_snapshot;
18193            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
18194        });
18195
18196        assert_eq!(
18197            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
18198            expected,
18199            "Active indent guide indices do not match"
18200        );
18201    }
18202
18203    assert_eq!(indent_guides, expected, "Indent guides do not match");
18204}
18205
18206fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
18207    IndentGuide {
18208        buffer_id,
18209        start_row: MultiBufferRow(start_row),
18210        end_row: MultiBufferRow(end_row),
18211        depth,
18212        tab_size: 4,
18213        settings: IndentGuideSettings {
18214            enabled: true,
18215            line_width: 1,
18216            active_line_width: 1,
18217            ..Default::default()
18218        },
18219    }
18220}
18221
18222#[gpui::test]
18223async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
18224    let (buffer_id, mut cx) = setup_indent_guides_editor(
18225        &"
18226        fn main() {
18227            let a = 1;
18228        }"
18229        .unindent(),
18230        cx,
18231    )
18232    .await;
18233
18234    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
18235}
18236
18237#[gpui::test]
18238async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
18239    let (buffer_id, mut cx) = setup_indent_guides_editor(
18240        &"
18241        fn main() {
18242            let a = 1;
18243            let b = 2;
18244        }"
18245        .unindent(),
18246        cx,
18247    )
18248    .await;
18249
18250    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
18251}
18252
18253#[gpui::test]
18254async fn test_indent_guide_nested(cx: &mut TestAppContext) {
18255    let (buffer_id, mut cx) = setup_indent_guides_editor(
18256        &"
18257        fn main() {
18258            let a = 1;
18259            if a == 3 {
18260                let b = 2;
18261            } else {
18262                let c = 3;
18263            }
18264        }"
18265        .unindent(),
18266        cx,
18267    )
18268    .await;
18269
18270    assert_indent_guides(
18271        0..8,
18272        vec![
18273            indent_guide(buffer_id, 1, 6, 0),
18274            indent_guide(buffer_id, 3, 3, 1),
18275            indent_guide(buffer_id, 5, 5, 1),
18276        ],
18277        None,
18278        &mut cx,
18279    );
18280}
18281
18282#[gpui::test]
18283async fn test_indent_guide_tab(cx: &mut TestAppContext) {
18284    let (buffer_id, mut cx) = setup_indent_guides_editor(
18285        &"
18286        fn main() {
18287            let a = 1;
18288                let b = 2;
18289            let c = 3;
18290        }"
18291        .unindent(),
18292        cx,
18293    )
18294    .await;
18295
18296    assert_indent_guides(
18297        0..5,
18298        vec![
18299            indent_guide(buffer_id, 1, 3, 0),
18300            indent_guide(buffer_id, 2, 2, 1),
18301        ],
18302        None,
18303        &mut cx,
18304    );
18305}
18306
18307#[gpui::test]
18308async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
18309    let (buffer_id, mut cx) = setup_indent_guides_editor(
18310        &"
18311        fn main() {
18312            let a = 1;
18313
18314            let c = 3;
18315        }"
18316        .unindent(),
18317        cx,
18318    )
18319    .await;
18320
18321    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
18322}
18323
18324#[gpui::test]
18325async fn test_indent_guide_complex(cx: &mut TestAppContext) {
18326    let (buffer_id, mut cx) = setup_indent_guides_editor(
18327        &"
18328        fn main() {
18329            let a = 1;
18330
18331            let c = 3;
18332
18333            if a == 3 {
18334                let b = 2;
18335            } else {
18336                let c = 3;
18337            }
18338        }"
18339        .unindent(),
18340        cx,
18341    )
18342    .await;
18343
18344    assert_indent_guides(
18345        0..11,
18346        vec![
18347            indent_guide(buffer_id, 1, 9, 0),
18348            indent_guide(buffer_id, 6, 6, 1),
18349            indent_guide(buffer_id, 8, 8, 1),
18350        ],
18351        None,
18352        &mut cx,
18353    );
18354}
18355
18356#[gpui::test]
18357async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
18358    let (buffer_id, mut cx) = setup_indent_guides_editor(
18359        &"
18360        fn main() {
18361            let a = 1;
18362
18363            let c = 3;
18364
18365            if a == 3 {
18366                let b = 2;
18367            } else {
18368                let c = 3;
18369            }
18370        }"
18371        .unindent(),
18372        cx,
18373    )
18374    .await;
18375
18376    assert_indent_guides(
18377        1..11,
18378        vec![
18379            indent_guide(buffer_id, 1, 9, 0),
18380            indent_guide(buffer_id, 6, 6, 1),
18381            indent_guide(buffer_id, 8, 8, 1),
18382        ],
18383        None,
18384        &mut cx,
18385    );
18386}
18387
18388#[gpui::test]
18389async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
18390    let (buffer_id, mut cx) = setup_indent_guides_editor(
18391        &"
18392        fn main() {
18393            let a = 1;
18394
18395            let c = 3;
18396
18397            if a == 3 {
18398                let b = 2;
18399            } else {
18400                let c = 3;
18401            }
18402        }"
18403        .unindent(),
18404        cx,
18405    )
18406    .await;
18407
18408    assert_indent_guides(
18409        1..10,
18410        vec![
18411            indent_guide(buffer_id, 1, 9, 0),
18412            indent_guide(buffer_id, 6, 6, 1),
18413            indent_guide(buffer_id, 8, 8, 1),
18414        ],
18415        None,
18416        &mut cx,
18417    );
18418}
18419
18420#[gpui::test]
18421async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
18422    let (buffer_id, mut cx) = setup_indent_guides_editor(
18423        &"
18424        fn main() {
18425            if a {
18426                b(
18427                    c,
18428                    d,
18429                )
18430            } else {
18431                e(
18432                    f
18433                )
18434            }
18435        }"
18436        .unindent(),
18437        cx,
18438    )
18439    .await;
18440
18441    assert_indent_guides(
18442        0..11,
18443        vec![
18444            indent_guide(buffer_id, 1, 10, 0),
18445            indent_guide(buffer_id, 2, 5, 1),
18446            indent_guide(buffer_id, 7, 9, 1),
18447            indent_guide(buffer_id, 3, 4, 2),
18448            indent_guide(buffer_id, 8, 8, 2),
18449        ],
18450        None,
18451        &mut cx,
18452    );
18453
18454    cx.update_editor(|editor, window, cx| {
18455        editor.fold_at(MultiBufferRow(2), window, cx);
18456        assert_eq!(
18457            editor.display_text(cx),
18458            "
18459            fn main() {
18460                if a {
18461                    b(⋯
18462                    )
18463                } else {
18464                    e(
18465                        f
18466                    )
18467                }
18468            }"
18469            .unindent()
18470        );
18471    });
18472
18473    assert_indent_guides(
18474        0..11,
18475        vec![
18476            indent_guide(buffer_id, 1, 10, 0),
18477            indent_guide(buffer_id, 2, 5, 1),
18478            indent_guide(buffer_id, 7, 9, 1),
18479            indent_guide(buffer_id, 8, 8, 2),
18480        ],
18481        None,
18482        &mut cx,
18483    );
18484}
18485
18486#[gpui::test]
18487async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
18488    let (buffer_id, mut cx) = setup_indent_guides_editor(
18489        &"
18490        block1
18491            block2
18492                block3
18493                    block4
18494            block2
18495        block1
18496        block1"
18497            .unindent(),
18498        cx,
18499    )
18500    .await;
18501
18502    assert_indent_guides(
18503        1..10,
18504        vec![
18505            indent_guide(buffer_id, 1, 4, 0),
18506            indent_guide(buffer_id, 2, 3, 1),
18507            indent_guide(buffer_id, 3, 3, 2),
18508        ],
18509        None,
18510        &mut cx,
18511    );
18512}
18513
18514#[gpui::test]
18515async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
18516    let (buffer_id, mut cx) = setup_indent_guides_editor(
18517        &"
18518        block1
18519            block2
18520                block3
18521
18522        block1
18523        block1"
18524            .unindent(),
18525        cx,
18526    )
18527    .await;
18528
18529    assert_indent_guides(
18530        0..6,
18531        vec![
18532            indent_guide(buffer_id, 1, 2, 0),
18533            indent_guide(buffer_id, 2, 2, 1),
18534        ],
18535        None,
18536        &mut cx,
18537    );
18538}
18539
18540#[gpui::test]
18541async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
18542    let (buffer_id, mut cx) = setup_indent_guides_editor(
18543        &"
18544        function component() {
18545        \treturn (
18546        \t\t\t
18547        \t\t<div>
18548        \t\t\t<abc></abc>
18549        \t\t</div>
18550        \t)
18551        }"
18552        .unindent(),
18553        cx,
18554    )
18555    .await;
18556
18557    assert_indent_guides(
18558        0..8,
18559        vec![
18560            indent_guide(buffer_id, 1, 6, 0),
18561            indent_guide(buffer_id, 2, 5, 1),
18562            indent_guide(buffer_id, 4, 4, 2),
18563        ],
18564        None,
18565        &mut cx,
18566    );
18567}
18568
18569#[gpui::test]
18570async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
18571    let (buffer_id, mut cx) = setup_indent_guides_editor(
18572        &"
18573        function component() {
18574        \treturn (
18575        \t
18576        \t\t<div>
18577        \t\t\t<abc></abc>
18578        \t\t</div>
18579        \t)
18580        }"
18581        .unindent(),
18582        cx,
18583    )
18584    .await;
18585
18586    assert_indent_guides(
18587        0..8,
18588        vec![
18589            indent_guide(buffer_id, 1, 6, 0),
18590            indent_guide(buffer_id, 2, 5, 1),
18591            indent_guide(buffer_id, 4, 4, 2),
18592        ],
18593        None,
18594        &mut cx,
18595    );
18596}
18597
18598#[gpui::test]
18599async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
18600    let (buffer_id, mut cx) = setup_indent_guides_editor(
18601        &"
18602        block1
18603
18604
18605
18606            block2
18607        "
18608        .unindent(),
18609        cx,
18610    )
18611    .await;
18612
18613    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
18614}
18615
18616#[gpui::test]
18617async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
18618    let (buffer_id, mut cx) = setup_indent_guides_editor(
18619        &"
18620        def a:
18621        \tb = 3
18622        \tif True:
18623        \t\tc = 4
18624        \t\td = 5
18625        \tprint(b)
18626        "
18627        .unindent(),
18628        cx,
18629    )
18630    .await;
18631
18632    assert_indent_guides(
18633        0..6,
18634        vec![
18635            indent_guide(buffer_id, 1, 5, 0),
18636            indent_guide(buffer_id, 3, 4, 1),
18637        ],
18638        None,
18639        &mut cx,
18640    );
18641}
18642
18643#[gpui::test]
18644async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
18645    let (buffer_id, mut cx) = setup_indent_guides_editor(
18646        &"
18647    fn main() {
18648        let a = 1;
18649    }"
18650        .unindent(),
18651        cx,
18652    )
18653    .await;
18654
18655    cx.update_editor(|editor, window, cx| {
18656        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18657            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18658        });
18659    });
18660
18661    assert_indent_guides(
18662        0..3,
18663        vec![indent_guide(buffer_id, 1, 1, 0)],
18664        Some(vec![0]),
18665        &mut cx,
18666    );
18667}
18668
18669#[gpui::test]
18670async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
18671    let (buffer_id, mut cx) = setup_indent_guides_editor(
18672        &"
18673    fn main() {
18674        if 1 == 2 {
18675            let a = 1;
18676        }
18677    }"
18678        .unindent(),
18679        cx,
18680    )
18681    .await;
18682
18683    cx.update_editor(|editor, window, cx| {
18684        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18685            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18686        });
18687    });
18688
18689    assert_indent_guides(
18690        0..4,
18691        vec![
18692            indent_guide(buffer_id, 1, 3, 0),
18693            indent_guide(buffer_id, 2, 2, 1),
18694        ],
18695        Some(vec![1]),
18696        &mut cx,
18697    );
18698
18699    cx.update_editor(|editor, window, cx| {
18700        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18701            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
18702        });
18703    });
18704
18705    assert_indent_guides(
18706        0..4,
18707        vec![
18708            indent_guide(buffer_id, 1, 3, 0),
18709            indent_guide(buffer_id, 2, 2, 1),
18710        ],
18711        Some(vec![1]),
18712        &mut cx,
18713    );
18714
18715    cx.update_editor(|editor, window, cx| {
18716        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18717            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
18718        });
18719    });
18720
18721    assert_indent_guides(
18722        0..4,
18723        vec![
18724            indent_guide(buffer_id, 1, 3, 0),
18725            indent_guide(buffer_id, 2, 2, 1),
18726        ],
18727        Some(vec![0]),
18728        &mut cx,
18729    );
18730}
18731
18732#[gpui::test]
18733async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
18734    let (buffer_id, mut cx) = setup_indent_guides_editor(
18735        &"
18736    fn main() {
18737        let a = 1;
18738
18739        let b = 2;
18740    }"
18741        .unindent(),
18742        cx,
18743    )
18744    .await;
18745
18746    cx.update_editor(|editor, window, cx| {
18747        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18748            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
18749        });
18750    });
18751
18752    assert_indent_guides(
18753        0..5,
18754        vec![indent_guide(buffer_id, 1, 3, 0)],
18755        Some(vec![0]),
18756        &mut cx,
18757    );
18758}
18759
18760#[gpui::test]
18761async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
18762    let (buffer_id, mut cx) = setup_indent_guides_editor(
18763        &"
18764    def m:
18765        a = 1
18766        pass"
18767            .unindent(),
18768        cx,
18769    )
18770    .await;
18771
18772    cx.update_editor(|editor, window, cx| {
18773        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18774            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18775        });
18776    });
18777
18778    assert_indent_guides(
18779        0..3,
18780        vec![indent_guide(buffer_id, 1, 2, 0)],
18781        Some(vec![0]),
18782        &mut cx,
18783    );
18784}
18785
18786#[gpui::test]
18787async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
18788    init_test(cx, |_| {});
18789    let mut cx = EditorTestContext::new(cx).await;
18790    let text = indoc! {
18791        "
18792        impl A {
18793            fn b() {
18794                0;
18795                3;
18796                5;
18797                6;
18798                7;
18799            }
18800        }
18801        "
18802    };
18803    let base_text = indoc! {
18804        "
18805        impl A {
18806            fn b() {
18807                0;
18808                1;
18809                2;
18810                3;
18811                4;
18812            }
18813            fn c() {
18814                5;
18815                6;
18816                7;
18817            }
18818        }
18819        "
18820    };
18821
18822    cx.update_editor(|editor, window, cx| {
18823        editor.set_text(text, window, cx);
18824
18825        editor.buffer().update(cx, |multibuffer, cx| {
18826            let buffer = multibuffer.as_singleton().unwrap();
18827            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
18828
18829            multibuffer.set_all_diff_hunks_expanded(cx);
18830            multibuffer.add_diff(diff, cx);
18831
18832            buffer.read(cx).remote_id()
18833        })
18834    });
18835    cx.run_until_parked();
18836
18837    cx.assert_state_with_diff(
18838        indoc! { "
18839          impl A {
18840              fn b() {
18841                  0;
18842        -         1;
18843        -         2;
18844                  3;
18845        -         4;
18846        -     }
18847        -     fn c() {
18848                  5;
18849                  6;
18850                  7;
18851              }
18852          }
18853          ˇ"
18854        }
18855        .to_string(),
18856    );
18857
18858    let mut actual_guides = cx.update_editor(|editor, window, cx| {
18859        editor
18860            .snapshot(window, cx)
18861            .buffer_snapshot
18862            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
18863            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
18864            .collect::<Vec<_>>()
18865    });
18866    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
18867    assert_eq!(
18868        actual_guides,
18869        vec![
18870            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
18871            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
18872            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
18873        ]
18874    );
18875}
18876
18877#[gpui::test]
18878async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18879    init_test(cx, |_| {});
18880    let mut cx = EditorTestContext::new(cx).await;
18881
18882    let diff_base = r#"
18883        a
18884        b
18885        c
18886        "#
18887    .unindent();
18888
18889    cx.set_state(
18890        &r#"
18891        ˇA
18892        b
18893        C
18894        "#
18895        .unindent(),
18896    );
18897    cx.set_head_text(&diff_base);
18898    cx.update_editor(|editor, window, cx| {
18899        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18900    });
18901    executor.run_until_parked();
18902
18903    let both_hunks_expanded = r#"
18904        - a
18905        + ˇA
18906          b
18907        - c
18908        + C
18909        "#
18910    .unindent();
18911
18912    cx.assert_state_with_diff(both_hunks_expanded.clone());
18913
18914    let hunk_ranges = cx.update_editor(|editor, window, cx| {
18915        let snapshot = editor.snapshot(window, cx);
18916        let hunks = editor
18917            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18918            .collect::<Vec<_>>();
18919        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
18920        let buffer_id = hunks[0].buffer_id;
18921        hunks
18922            .into_iter()
18923            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
18924            .collect::<Vec<_>>()
18925    });
18926    assert_eq!(hunk_ranges.len(), 2);
18927
18928    cx.update_editor(|editor, _, cx| {
18929        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18930    });
18931    executor.run_until_parked();
18932
18933    let second_hunk_expanded = r#"
18934          ˇA
18935          b
18936        - c
18937        + C
18938        "#
18939    .unindent();
18940
18941    cx.assert_state_with_diff(second_hunk_expanded);
18942
18943    cx.update_editor(|editor, _, cx| {
18944        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18945    });
18946    executor.run_until_parked();
18947
18948    cx.assert_state_with_diff(both_hunks_expanded.clone());
18949
18950    cx.update_editor(|editor, _, cx| {
18951        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
18952    });
18953    executor.run_until_parked();
18954
18955    let first_hunk_expanded = r#"
18956        - a
18957        + ˇA
18958          b
18959          C
18960        "#
18961    .unindent();
18962
18963    cx.assert_state_with_diff(first_hunk_expanded);
18964
18965    cx.update_editor(|editor, _, cx| {
18966        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
18967    });
18968    executor.run_until_parked();
18969
18970    cx.assert_state_with_diff(both_hunks_expanded);
18971
18972    cx.set_state(
18973        &r#"
18974        ˇA
18975        b
18976        "#
18977        .unindent(),
18978    );
18979    cx.run_until_parked();
18980
18981    // TODO this cursor position seems bad
18982    cx.assert_state_with_diff(
18983        r#"
18984        - ˇa
18985        + A
18986          b
18987        "#
18988        .unindent(),
18989    );
18990
18991    cx.update_editor(|editor, window, cx| {
18992        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18993    });
18994
18995    cx.assert_state_with_diff(
18996        r#"
18997            - ˇa
18998            + A
18999              b
19000            - c
19001            "#
19002        .unindent(),
19003    );
19004
19005    let hunk_ranges = cx.update_editor(|editor, window, cx| {
19006        let snapshot = editor.snapshot(window, cx);
19007        let hunks = editor
19008            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19009            .collect::<Vec<_>>();
19010        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
19011        let buffer_id = hunks[0].buffer_id;
19012        hunks
19013            .into_iter()
19014            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
19015            .collect::<Vec<_>>()
19016    });
19017    assert_eq!(hunk_ranges.len(), 2);
19018
19019    cx.update_editor(|editor, _, cx| {
19020        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
19021    });
19022    executor.run_until_parked();
19023
19024    cx.assert_state_with_diff(
19025        r#"
19026        - ˇa
19027        + A
19028          b
19029        "#
19030        .unindent(),
19031    );
19032}
19033
19034#[gpui::test]
19035async fn test_toggle_deletion_hunk_at_start_of_file(
19036    executor: BackgroundExecutor,
19037    cx: &mut TestAppContext,
19038) {
19039    init_test(cx, |_| {});
19040    let mut cx = EditorTestContext::new(cx).await;
19041
19042    let diff_base = r#"
19043        a
19044        b
19045        c
19046        "#
19047    .unindent();
19048
19049    cx.set_state(
19050        &r#"
19051        ˇb
19052        c
19053        "#
19054        .unindent(),
19055    );
19056    cx.set_head_text(&diff_base);
19057    cx.update_editor(|editor, window, cx| {
19058        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19059    });
19060    executor.run_until_parked();
19061
19062    let hunk_expanded = r#"
19063        - a
19064          ˇb
19065          c
19066        "#
19067    .unindent();
19068
19069    cx.assert_state_with_diff(hunk_expanded.clone());
19070
19071    let hunk_ranges = cx.update_editor(|editor, window, cx| {
19072        let snapshot = editor.snapshot(window, cx);
19073        let hunks = editor
19074            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19075            .collect::<Vec<_>>();
19076        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
19077        let buffer_id = hunks[0].buffer_id;
19078        hunks
19079            .into_iter()
19080            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
19081            .collect::<Vec<_>>()
19082    });
19083    assert_eq!(hunk_ranges.len(), 1);
19084
19085    cx.update_editor(|editor, _, cx| {
19086        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19087    });
19088    executor.run_until_parked();
19089
19090    let hunk_collapsed = r#"
19091          ˇb
19092          c
19093        "#
19094    .unindent();
19095
19096    cx.assert_state_with_diff(hunk_collapsed);
19097
19098    cx.update_editor(|editor, _, cx| {
19099        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19100    });
19101    executor.run_until_parked();
19102
19103    cx.assert_state_with_diff(hunk_expanded.clone());
19104}
19105
19106#[gpui::test]
19107async fn test_display_diff_hunks(cx: &mut TestAppContext) {
19108    init_test(cx, |_| {});
19109
19110    let fs = FakeFs::new(cx.executor());
19111    fs.insert_tree(
19112        path!("/test"),
19113        json!({
19114            ".git": {},
19115            "file-1": "ONE\n",
19116            "file-2": "TWO\n",
19117            "file-3": "THREE\n",
19118        }),
19119    )
19120    .await;
19121
19122    fs.set_head_for_repo(
19123        path!("/test/.git").as_ref(),
19124        &[
19125            ("file-1".into(), "one\n".into()),
19126            ("file-2".into(), "two\n".into()),
19127            ("file-3".into(), "three\n".into()),
19128        ],
19129        "deadbeef",
19130    );
19131
19132    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
19133    let mut buffers = vec![];
19134    for i in 1..=3 {
19135        let buffer = project
19136            .update(cx, |project, cx| {
19137                let path = format!(path!("/test/file-{}"), i);
19138                project.open_local_buffer(path, cx)
19139            })
19140            .await
19141            .unwrap();
19142        buffers.push(buffer);
19143    }
19144
19145    let multibuffer = cx.new(|cx| {
19146        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
19147        multibuffer.set_all_diff_hunks_expanded(cx);
19148        for buffer in &buffers {
19149            let snapshot = buffer.read(cx).snapshot();
19150            multibuffer.set_excerpts_for_path(
19151                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
19152                buffer.clone(),
19153                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
19154                DEFAULT_MULTIBUFFER_CONTEXT,
19155                cx,
19156            );
19157        }
19158        multibuffer
19159    });
19160
19161    let editor = cx.add_window(|window, cx| {
19162        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
19163    });
19164    cx.run_until_parked();
19165
19166    let snapshot = editor
19167        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19168        .unwrap();
19169    let hunks = snapshot
19170        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
19171        .map(|hunk| match hunk {
19172            DisplayDiffHunk::Unfolded {
19173                display_row_range, ..
19174            } => display_row_range,
19175            DisplayDiffHunk::Folded { .. } => unreachable!(),
19176        })
19177        .collect::<Vec<_>>();
19178    assert_eq!(
19179        hunks,
19180        [
19181            DisplayRow(2)..DisplayRow(4),
19182            DisplayRow(7)..DisplayRow(9),
19183            DisplayRow(12)..DisplayRow(14),
19184        ]
19185    );
19186}
19187
19188#[gpui::test]
19189async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
19190    init_test(cx, |_| {});
19191
19192    let mut cx = EditorTestContext::new(cx).await;
19193    cx.set_head_text(indoc! { "
19194        one
19195        two
19196        three
19197        four
19198        five
19199        "
19200    });
19201    cx.set_index_text(indoc! { "
19202        one
19203        two
19204        three
19205        four
19206        five
19207        "
19208    });
19209    cx.set_state(indoc! {"
19210        one
19211        TWO
19212        ˇTHREE
19213        FOUR
19214        five
19215    "});
19216    cx.run_until_parked();
19217    cx.update_editor(|editor, window, cx| {
19218        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19219    });
19220    cx.run_until_parked();
19221    cx.assert_index_text(Some(indoc! {"
19222        one
19223        TWO
19224        THREE
19225        FOUR
19226        five
19227    "}));
19228    cx.set_state(indoc! { "
19229        one
19230        TWO
19231        ˇTHREE-HUNDRED
19232        FOUR
19233        five
19234    "});
19235    cx.run_until_parked();
19236    cx.update_editor(|editor, window, cx| {
19237        let snapshot = editor.snapshot(window, cx);
19238        let hunks = editor
19239            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19240            .collect::<Vec<_>>();
19241        assert_eq!(hunks.len(), 1);
19242        assert_eq!(
19243            hunks[0].status(),
19244            DiffHunkStatus {
19245                kind: DiffHunkStatusKind::Modified,
19246                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
19247            }
19248        );
19249
19250        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19251    });
19252    cx.run_until_parked();
19253    cx.assert_index_text(Some(indoc! {"
19254        one
19255        TWO
19256        THREE-HUNDRED
19257        FOUR
19258        five
19259    "}));
19260}
19261
19262#[gpui::test]
19263fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
19264    init_test(cx, |_| {});
19265
19266    let editor = cx.add_window(|window, cx| {
19267        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
19268        build_editor(buffer, window, cx)
19269    });
19270
19271    let render_args = Arc::new(Mutex::new(None));
19272    let snapshot = editor
19273        .update(cx, |editor, window, cx| {
19274            let snapshot = editor.buffer().read(cx).snapshot(cx);
19275            let range =
19276                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
19277
19278            struct RenderArgs {
19279                row: MultiBufferRow,
19280                folded: bool,
19281                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
19282            }
19283
19284            let crease = Crease::inline(
19285                range,
19286                FoldPlaceholder::test(),
19287                {
19288                    let toggle_callback = render_args.clone();
19289                    move |row, folded, callback, _window, _cx| {
19290                        *toggle_callback.lock() = Some(RenderArgs {
19291                            row,
19292                            folded,
19293                            callback,
19294                        });
19295                        div()
19296                    }
19297                },
19298                |_row, _folded, _window, _cx| div(),
19299            );
19300
19301            editor.insert_creases(Some(crease), cx);
19302            let snapshot = editor.snapshot(window, cx);
19303            let _div = snapshot.render_crease_toggle(
19304                MultiBufferRow(1),
19305                false,
19306                cx.entity().clone(),
19307                window,
19308                cx,
19309            );
19310            snapshot
19311        })
19312        .unwrap();
19313
19314    let render_args = render_args.lock().take().unwrap();
19315    assert_eq!(render_args.row, MultiBufferRow(1));
19316    assert!(!render_args.folded);
19317    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
19318
19319    cx.update_window(*editor, |_, window, cx| {
19320        (render_args.callback)(true, window, cx)
19321    })
19322    .unwrap();
19323    let snapshot = editor
19324        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19325        .unwrap();
19326    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
19327
19328    cx.update_window(*editor, |_, window, cx| {
19329        (render_args.callback)(false, window, cx)
19330    })
19331    .unwrap();
19332    let snapshot = editor
19333        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19334        .unwrap();
19335    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
19336}
19337
19338#[gpui::test]
19339async fn test_input_text(cx: &mut TestAppContext) {
19340    init_test(cx, |_| {});
19341    let mut cx = EditorTestContext::new(cx).await;
19342
19343    cx.set_state(
19344        &r#"ˇone
19345        two
19346
19347        three
19348        fourˇ
19349        five
19350
19351        siˇx"#
19352            .unindent(),
19353    );
19354
19355    cx.dispatch_action(HandleInput(String::new()));
19356    cx.assert_editor_state(
19357        &r#"ˇone
19358        two
19359
19360        three
19361        fourˇ
19362        five
19363
19364        siˇx"#
19365            .unindent(),
19366    );
19367
19368    cx.dispatch_action(HandleInput("AAAA".to_string()));
19369    cx.assert_editor_state(
19370        &r#"AAAAˇone
19371        two
19372
19373        three
19374        fourAAAAˇ
19375        five
19376
19377        siAAAAˇx"#
19378            .unindent(),
19379    );
19380}
19381
19382#[gpui::test]
19383async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
19384    init_test(cx, |_| {});
19385
19386    let mut cx = EditorTestContext::new(cx).await;
19387    cx.set_state(
19388        r#"let foo = 1;
19389let foo = 2;
19390let foo = 3;
19391let fooˇ = 4;
19392let foo = 5;
19393let foo = 6;
19394let foo = 7;
19395let foo = 8;
19396let foo = 9;
19397let foo = 10;
19398let foo = 11;
19399let foo = 12;
19400let foo = 13;
19401let foo = 14;
19402let foo = 15;"#,
19403    );
19404
19405    cx.update_editor(|e, window, cx| {
19406        assert_eq!(
19407            e.next_scroll_position,
19408            NextScrollCursorCenterTopBottom::Center,
19409            "Default next scroll direction is center",
19410        );
19411
19412        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19413        assert_eq!(
19414            e.next_scroll_position,
19415            NextScrollCursorCenterTopBottom::Top,
19416            "After center, next scroll direction should be top",
19417        );
19418
19419        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19420        assert_eq!(
19421            e.next_scroll_position,
19422            NextScrollCursorCenterTopBottom::Bottom,
19423            "After top, next scroll direction should be bottom",
19424        );
19425
19426        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19427        assert_eq!(
19428            e.next_scroll_position,
19429            NextScrollCursorCenterTopBottom::Center,
19430            "After bottom, scrolling should start over",
19431        );
19432
19433        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19434        assert_eq!(
19435            e.next_scroll_position,
19436            NextScrollCursorCenterTopBottom::Top,
19437            "Scrolling continues if retriggered fast enough"
19438        );
19439    });
19440
19441    cx.executor()
19442        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
19443    cx.executor().run_until_parked();
19444    cx.update_editor(|e, _, _| {
19445        assert_eq!(
19446            e.next_scroll_position,
19447            NextScrollCursorCenterTopBottom::Center,
19448            "If scrolling is not triggered fast enough, it should reset"
19449        );
19450    });
19451}
19452
19453#[gpui::test]
19454async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
19455    init_test(cx, |_| {});
19456    let mut cx = EditorLspTestContext::new_rust(
19457        lsp::ServerCapabilities {
19458            definition_provider: Some(lsp::OneOf::Left(true)),
19459            references_provider: Some(lsp::OneOf::Left(true)),
19460            ..lsp::ServerCapabilities::default()
19461        },
19462        cx,
19463    )
19464    .await;
19465
19466    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
19467        let go_to_definition = cx
19468            .lsp
19469            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
19470                move |params, _| async move {
19471                    if empty_go_to_definition {
19472                        Ok(None)
19473                    } else {
19474                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
19475                            uri: params.text_document_position_params.text_document.uri,
19476                            range: lsp::Range::new(
19477                                lsp::Position::new(4, 3),
19478                                lsp::Position::new(4, 6),
19479                            ),
19480                        })))
19481                    }
19482                },
19483            );
19484        let references = cx
19485            .lsp
19486            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
19487                Ok(Some(vec![lsp::Location {
19488                    uri: params.text_document_position.text_document.uri,
19489                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
19490                }]))
19491            });
19492        (go_to_definition, references)
19493    };
19494
19495    cx.set_state(
19496        &r#"fn one() {
19497            let mut a = ˇtwo();
19498        }
19499
19500        fn two() {}"#
19501            .unindent(),
19502    );
19503    set_up_lsp_handlers(false, &mut cx);
19504    let navigated = cx
19505        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19506        .await
19507        .expect("Failed to navigate to definition");
19508    assert_eq!(
19509        navigated,
19510        Navigated::Yes,
19511        "Should have navigated to definition from the GetDefinition response"
19512    );
19513    cx.assert_editor_state(
19514        &r#"fn one() {
19515            let mut a = two();
19516        }
19517
19518        fn «twoˇ»() {}"#
19519            .unindent(),
19520    );
19521
19522    let editors = cx.update_workspace(|workspace, _, cx| {
19523        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19524    });
19525    cx.update_editor(|_, _, test_editor_cx| {
19526        assert_eq!(
19527            editors.len(),
19528            1,
19529            "Initially, only one, test, editor should be open in the workspace"
19530        );
19531        assert_eq!(
19532            test_editor_cx.entity(),
19533            editors.last().expect("Asserted len is 1").clone()
19534        );
19535    });
19536
19537    set_up_lsp_handlers(true, &mut cx);
19538    let navigated = cx
19539        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19540        .await
19541        .expect("Failed to navigate to lookup references");
19542    assert_eq!(
19543        navigated,
19544        Navigated::Yes,
19545        "Should have navigated to references as a fallback after empty GoToDefinition response"
19546    );
19547    // We should not change the selections in the existing file,
19548    // if opening another milti buffer with the references
19549    cx.assert_editor_state(
19550        &r#"fn one() {
19551            let mut a = two();
19552        }
19553
19554        fn «twoˇ»() {}"#
19555            .unindent(),
19556    );
19557    let editors = cx.update_workspace(|workspace, _, cx| {
19558        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19559    });
19560    cx.update_editor(|_, _, test_editor_cx| {
19561        assert_eq!(
19562            editors.len(),
19563            2,
19564            "After falling back to references search, we open a new editor with the results"
19565        );
19566        let references_fallback_text = editors
19567            .into_iter()
19568            .find(|new_editor| *new_editor != test_editor_cx.entity())
19569            .expect("Should have one non-test editor now")
19570            .read(test_editor_cx)
19571            .text(test_editor_cx);
19572        assert_eq!(
19573            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
19574            "Should use the range from the references response and not the GoToDefinition one"
19575        );
19576    });
19577}
19578
19579#[gpui::test]
19580async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
19581    init_test(cx, |_| {});
19582    cx.update(|cx| {
19583        let mut editor_settings = EditorSettings::get_global(cx).clone();
19584        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
19585        EditorSettings::override_global(editor_settings, cx);
19586    });
19587    let mut cx = EditorLspTestContext::new_rust(
19588        lsp::ServerCapabilities {
19589            definition_provider: Some(lsp::OneOf::Left(true)),
19590            references_provider: Some(lsp::OneOf::Left(true)),
19591            ..lsp::ServerCapabilities::default()
19592        },
19593        cx,
19594    )
19595    .await;
19596    let original_state = r#"fn one() {
19597        let mut a = ˇtwo();
19598    }
19599
19600    fn two() {}"#
19601        .unindent();
19602    cx.set_state(&original_state);
19603
19604    let mut go_to_definition = cx
19605        .lsp
19606        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
19607            move |_, _| async move { Ok(None) },
19608        );
19609    let _references = cx
19610        .lsp
19611        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
19612            panic!("Should not call for references with no go to definition fallback")
19613        });
19614
19615    let navigated = cx
19616        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19617        .await
19618        .expect("Failed to navigate to lookup references");
19619    go_to_definition
19620        .next()
19621        .await
19622        .expect("Should have called the go_to_definition handler");
19623
19624    assert_eq!(
19625        navigated,
19626        Navigated::No,
19627        "Should have navigated to references as a fallback after empty GoToDefinition response"
19628    );
19629    cx.assert_editor_state(&original_state);
19630    let editors = cx.update_workspace(|workspace, _, cx| {
19631        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19632    });
19633    cx.update_editor(|_, _, _| {
19634        assert_eq!(
19635            editors.len(),
19636            1,
19637            "After unsuccessful fallback, no other editor should have been opened"
19638        );
19639    });
19640}
19641
19642#[gpui::test]
19643async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
19644    init_test(cx, |_| {});
19645
19646    let language = Arc::new(Language::new(
19647        LanguageConfig::default(),
19648        Some(tree_sitter_rust::LANGUAGE.into()),
19649    ));
19650
19651    let text = r#"
19652        #[cfg(test)]
19653        mod tests() {
19654            #[test]
19655            fn runnable_1() {
19656                let a = 1;
19657            }
19658
19659            #[test]
19660            fn runnable_2() {
19661                let a = 1;
19662                let b = 2;
19663            }
19664        }
19665    "#
19666    .unindent();
19667
19668    let fs = FakeFs::new(cx.executor());
19669    fs.insert_file("/file.rs", Default::default()).await;
19670
19671    let project = Project::test(fs, ["/a".as_ref()], cx).await;
19672    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19673    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19674    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
19675    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
19676
19677    let editor = cx.new_window_entity(|window, cx| {
19678        Editor::new(
19679            EditorMode::full(),
19680            multi_buffer,
19681            Some(project.clone()),
19682            window,
19683            cx,
19684        )
19685    });
19686
19687    editor.update_in(cx, |editor, window, cx| {
19688        let snapshot = editor.buffer().read(cx).snapshot(cx);
19689        editor.tasks.insert(
19690            (buffer.read(cx).remote_id(), 3),
19691            RunnableTasks {
19692                templates: vec![],
19693                offset: snapshot.anchor_before(43),
19694                column: 0,
19695                extra_variables: HashMap::default(),
19696                context_range: BufferOffset(43)..BufferOffset(85),
19697            },
19698        );
19699        editor.tasks.insert(
19700            (buffer.read(cx).remote_id(), 8),
19701            RunnableTasks {
19702                templates: vec![],
19703                offset: snapshot.anchor_before(86),
19704                column: 0,
19705                extra_variables: HashMap::default(),
19706                context_range: BufferOffset(86)..BufferOffset(191),
19707            },
19708        );
19709
19710        // Test finding task when cursor is inside function body
19711        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19712            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
19713        });
19714        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
19715        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
19716
19717        // Test finding task when cursor is on function name
19718        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19719            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
19720        });
19721        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
19722        assert_eq!(row, 8, "Should find task when cursor is on function name");
19723    });
19724}
19725
19726#[gpui::test]
19727async fn test_folding_buffers(cx: &mut TestAppContext) {
19728    init_test(cx, |_| {});
19729
19730    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
19731    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
19732    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
19733
19734    let fs = FakeFs::new(cx.executor());
19735    fs.insert_tree(
19736        path!("/a"),
19737        json!({
19738            "first.rs": sample_text_1,
19739            "second.rs": sample_text_2,
19740            "third.rs": sample_text_3,
19741        }),
19742    )
19743    .await;
19744    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19745    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19746    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19747    let worktree = project.update(cx, |project, cx| {
19748        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19749        assert_eq!(worktrees.len(), 1);
19750        worktrees.pop().unwrap()
19751    });
19752    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19753
19754    let buffer_1 = project
19755        .update(cx, |project, cx| {
19756            project.open_buffer((worktree_id, "first.rs"), cx)
19757        })
19758        .await
19759        .unwrap();
19760    let buffer_2 = project
19761        .update(cx, |project, cx| {
19762            project.open_buffer((worktree_id, "second.rs"), cx)
19763        })
19764        .await
19765        .unwrap();
19766    let buffer_3 = project
19767        .update(cx, |project, cx| {
19768            project.open_buffer((worktree_id, "third.rs"), cx)
19769        })
19770        .await
19771        .unwrap();
19772
19773    let multi_buffer = cx.new(|cx| {
19774        let mut multi_buffer = MultiBuffer::new(ReadWrite);
19775        multi_buffer.push_excerpts(
19776            buffer_1.clone(),
19777            [
19778                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19779                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19780                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19781            ],
19782            cx,
19783        );
19784        multi_buffer.push_excerpts(
19785            buffer_2.clone(),
19786            [
19787                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19788                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19789                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19790            ],
19791            cx,
19792        );
19793        multi_buffer.push_excerpts(
19794            buffer_3.clone(),
19795            [
19796                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19797                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19798                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19799            ],
19800            cx,
19801        );
19802        multi_buffer
19803    });
19804    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19805        Editor::new(
19806            EditorMode::full(),
19807            multi_buffer.clone(),
19808            Some(project.clone()),
19809            window,
19810            cx,
19811        )
19812    });
19813
19814    assert_eq!(
19815        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19816        "\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",
19817    );
19818
19819    multi_buffer_editor.update(cx, |editor, cx| {
19820        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
19821    });
19822    assert_eq!(
19823        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19824        "\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",
19825        "After folding the first buffer, its text should not be displayed"
19826    );
19827
19828    multi_buffer_editor.update(cx, |editor, cx| {
19829        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
19830    });
19831    assert_eq!(
19832        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19833        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
19834        "After folding the second buffer, its text should not be displayed"
19835    );
19836
19837    multi_buffer_editor.update(cx, |editor, cx| {
19838        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
19839    });
19840    assert_eq!(
19841        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19842        "\n\n\n\n\n",
19843        "After folding the third buffer, its text should not be displayed"
19844    );
19845
19846    // Emulate selection inside the fold logic, that should work
19847    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19848        editor
19849            .snapshot(window, cx)
19850            .next_line_boundary(Point::new(0, 4));
19851    });
19852
19853    multi_buffer_editor.update(cx, |editor, cx| {
19854        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
19855    });
19856    assert_eq!(
19857        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19858        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
19859        "After unfolding the second buffer, its text should be displayed"
19860    );
19861
19862    // Typing inside of buffer 1 causes that buffer to be unfolded.
19863    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19864        assert_eq!(
19865            multi_buffer
19866                .read(cx)
19867                .snapshot(cx)
19868                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
19869                .collect::<String>(),
19870            "bbbb"
19871        );
19872        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
19873            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
19874        });
19875        editor.handle_input("B", window, cx);
19876    });
19877
19878    assert_eq!(
19879        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19880        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
19881        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
19882    );
19883
19884    multi_buffer_editor.update(cx, |editor, cx| {
19885        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
19886    });
19887    assert_eq!(
19888        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19889        "\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",
19890        "After unfolding the all buffers, all original text should be displayed"
19891    );
19892}
19893
19894#[gpui::test]
19895async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
19896    init_test(cx, |_| {});
19897
19898    let sample_text_1 = "1111\n2222\n3333".to_string();
19899    let sample_text_2 = "4444\n5555\n6666".to_string();
19900    let sample_text_3 = "7777\n8888\n9999".to_string();
19901
19902    let fs = FakeFs::new(cx.executor());
19903    fs.insert_tree(
19904        path!("/a"),
19905        json!({
19906            "first.rs": sample_text_1,
19907            "second.rs": sample_text_2,
19908            "third.rs": sample_text_3,
19909        }),
19910    )
19911    .await;
19912    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19913    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19914    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19915    let worktree = project.update(cx, |project, cx| {
19916        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19917        assert_eq!(worktrees.len(), 1);
19918        worktrees.pop().unwrap()
19919    });
19920    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19921
19922    let buffer_1 = project
19923        .update(cx, |project, cx| {
19924            project.open_buffer((worktree_id, "first.rs"), cx)
19925        })
19926        .await
19927        .unwrap();
19928    let buffer_2 = project
19929        .update(cx, |project, cx| {
19930            project.open_buffer((worktree_id, "second.rs"), cx)
19931        })
19932        .await
19933        .unwrap();
19934    let buffer_3 = project
19935        .update(cx, |project, cx| {
19936            project.open_buffer((worktree_id, "third.rs"), cx)
19937        })
19938        .await
19939        .unwrap();
19940
19941    let multi_buffer = cx.new(|cx| {
19942        let mut multi_buffer = MultiBuffer::new(ReadWrite);
19943        multi_buffer.push_excerpts(
19944            buffer_1.clone(),
19945            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19946            cx,
19947        );
19948        multi_buffer.push_excerpts(
19949            buffer_2.clone(),
19950            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19951            cx,
19952        );
19953        multi_buffer.push_excerpts(
19954            buffer_3.clone(),
19955            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19956            cx,
19957        );
19958        multi_buffer
19959    });
19960
19961    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19962        Editor::new(
19963            EditorMode::full(),
19964            multi_buffer,
19965            Some(project.clone()),
19966            window,
19967            cx,
19968        )
19969    });
19970
19971    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
19972    assert_eq!(
19973        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19974        full_text,
19975    );
19976
19977    multi_buffer_editor.update(cx, |editor, cx| {
19978        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
19979    });
19980    assert_eq!(
19981        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19982        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
19983        "After folding the first buffer, its text should not be displayed"
19984    );
19985
19986    multi_buffer_editor.update(cx, |editor, cx| {
19987        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
19988    });
19989
19990    assert_eq!(
19991        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19992        "\n\n\n\n\n\n7777\n8888\n9999",
19993        "After folding the second buffer, its text should not be displayed"
19994    );
19995
19996    multi_buffer_editor.update(cx, |editor, cx| {
19997        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
19998    });
19999    assert_eq!(
20000        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20001        "\n\n\n\n\n",
20002        "After folding the third buffer, its text should not be displayed"
20003    );
20004
20005    multi_buffer_editor.update(cx, |editor, cx| {
20006        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
20007    });
20008    assert_eq!(
20009        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20010        "\n\n\n\n4444\n5555\n6666\n\n",
20011        "After unfolding the second buffer, its text should be displayed"
20012    );
20013
20014    multi_buffer_editor.update(cx, |editor, cx| {
20015        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
20016    });
20017    assert_eq!(
20018        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20019        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
20020        "After unfolding the first buffer, its text should be displayed"
20021    );
20022
20023    multi_buffer_editor.update(cx, |editor, cx| {
20024        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
20025    });
20026    assert_eq!(
20027        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20028        full_text,
20029        "After unfolding all buffers, all original text should be displayed"
20030    );
20031}
20032
20033#[gpui::test]
20034async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
20035    init_test(cx, |_| {});
20036
20037    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
20038
20039    let fs = FakeFs::new(cx.executor());
20040    fs.insert_tree(
20041        path!("/a"),
20042        json!({
20043            "main.rs": sample_text,
20044        }),
20045    )
20046    .await;
20047    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20048    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20049    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20050    let worktree = project.update(cx, |project, cx| {
20051        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
20052        assert_eq!(worktrees.len(), 1);
20053        worktrees.pop().unwrap()
20054    });
20055    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
20056
20057    let buffer_1 = project
20058        .update(cx, |project, cx| {
20059            project.open_buffer((worktree_id, "main.rs"), cx)
20060        })
20061        .await
20062        .unwrap();
20063
20064    let multi_buffer = cx.new(|cx| {
20065        let mut multi_buffer = MultiBuffer::new(ReadWrite);
20066        multi_buffer.push_excerpts(
20067            buffer_1.clone(),
20068            [ExcerptRange::new(
20069                Point::new(0, 0)
20070                    ..Point::new(
20071                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
20072                        0,
20073                    ),
20074            )],
20075            cx,
20076        );
20077        multi_buffer
20078    });
20079    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20080        Editor::new(
20081            EditorMode::full(),
20082            multi_buffer,
20083            Some(project.clone()),
20084            window,
20085            cx,
20086        )
20087    });
20088
20089    let selection_range = Point::new(1, 0)..Point::new(2, 0);
20090    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20091        enum TestHighlight {}
20092        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
20093        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
20094        editor.highlight_text::<TestHighlight>(
20095            vec![highlight_range.clone()],
20096            HighlightStyle::color(Hsla::green()),
20097            cx,
20098        );
20099        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20100            s.select_ranges(Some(highlight_range))
20101        });
20102    });
20103
20104    let full_text = format!("\n\n{sample_text}");
20105    assert_eq!(
20106        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20107        full_text,
20108    );
20109}
20110
20111#[gpui::test]
20112async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
20113    init_test(cx, |_| {});
20114    cx.update(|cx| {
20115        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
20116            "keymaps/default-linux.json",
20117            cx,
20118        )
20119        .unwrap();
20120        cx.bind_keys(default_key_bindings);
20121    });
20122
20123    let (editor, cx) = cx.add_window_view(|window, cx| {
20124        let multi_buffer = MultiBuffer::build_multi(
20125            [
20126                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
20127                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
20128                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
20129                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
20130            ],
20131            cx,
20132        );
20133        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
20134
20135        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
20136        // fold all but the second buffer, so that we test navigating between two
20137        // adjacent folded buffers, as well as folded buffers at the start and
20138        // end the multibuffer
20139        editor.fold_buffer(buffer_ids[0], cx);
20140        editor.fold_buffer(buffer_ids[2], cx);
20141        editor.fold_buffer(buffer_ids[3], cx);
20142
20143        editor
20144    });
20145    cx.simulate_resize(size(px(1000.), px(1000.)));
20146
20147    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
20148    cx.assert_excerpts_with_selections(indoc! {"
20149        [EXCERPT]
20150        ˇ[FOLDED]
20151        [EXCERPT]
20152        a1
20153        b1
20154        [EXCERPT]
20155        [FOLDED]
20156        [EXCERPT]
20157        [FOLDED]
20158        "
20159    });
20160    cx.simulate_keystroke("down");
20161    cx.assert_excerpts_with_selections(indoc! {"
20162        [EXCERPT]
20163        [FOLDED]
20164        [EXCERPT]
20165        ˇa1
20166        b1
20167        [EXCERPT]
20168        [FOLDED]
20169        [EXCERPT]
20170        [FOLDED]
20171        "
20172    });
20173    cx.simulate_keystroke("down");
20174    cx.assert_excerpts_with_selections(indoc! {"
20175        [EXCERPT]
20176        [FOLDED]
20177        [EXCERPT]
20178        a1
20179        ˇb1
20180        [EXCERPT]
20181        [FOLDED]
20182        [EXCERPT]
20183        [FOLDED]
20184        "
20185    });
20186    cx.simulate_keystroke("down");
20187    cx.assert_excerpts_with_selections(indoc! {"
20188        [EXCERPT]
20189        [FOLDED]
20190        [EXCERPT]
20191        a1
20192        b1
20193        ˇ[EXCERPT]
20194        [FOLDED]
20195        [EXCERPT]
20196        [FOLDED]
20197        "
20198    });
20199    cx.simulate_keystroke("down");
20200    cx.assert_excerpts_with_selections(indoc! {"
20201        [EXCERPT]
20202        [FOLDED]
20203        [EXCERPT]
20204        a1
20205        b1
20206        [EXCERPT]
20207        ˇ[FOLDED]
20208        [EXCERPT]
20209        [FOLDED]
20210        "
20211    });
20212    for _ in 0..5 {
20213        cx.simulate_keystroke("down");
20214        cx.assert_excerpts_with_selections(indoc! {"
20215            [EXCERPT]
20216            [FOLDED]
20217            [EXCERPT]
20218            a1
20219            b1
20220            [EXCERPT]
20221            [FOLDED]
20222            [EXCERPT]
20223            ˇ[FOLDED]
20224            "
20225        });
20226    }
20227
20228    cx.simulate_keystroke("up");
20229    cx.assert_excerpts_with_selections(indoc! {"
20230        [EXCERPT]
20231        [FOLDED]
20232        [EXCERPT]
20233        a1
20234        b1
20235        [EXCERPT]
20236        ˇ[FOLDED]
20237        [EXCERPT]
20238        [FOLDED]
20239        "
20240    });
20241    cx.simulate_keystroke("up");
20242    cx.assert_excerpts_with_selections(indoc! {"
20243        [EXCERPT]
20244        [FOLDED]
20245        [EXCERPT]
20246        a1
20247        b1
20248        ˇ[EXCERPT]
20249        [FOLDED]
20250        [EXCERPT]
20251        [FOLDED]
20252        "
20253    });
20254    cx.simulate_keystroke("up");
20255    cx.assert_excerpts_with_selections(indoc! {"
20256        [EXCERPT]
20257        [FOLDED]
20258        [EXCERPT]
20259        a1
20260        ˇb1
20261        [EXCERPT]
20262        [FOLDED]
20263        [EXCERPT]
20264        [FOLDED]
20265        "
20266    });
20267    cx.simulate_keystroke("up");
20268    cx.assert_excerpts_with_selections(indoc! {"
20269        [EXCERPT]
20270        [FOLDED]
20271        [EXCERPT]
20272        ˇa1
20273        b1
20274        [EXCERPT]
20275        [FOLDED]
20276        [EXCERPT]
20277        [FOLDED]
20278        "
20279    });
20280    for _ in 0..5 {
20281        cx.simulate_keystroke("up");
20282        cx.assert_excerpts_with_selections(indoc! {"
20283            [EXCERPT]
20284            ˇ[FOLDED]
20285            [EXCERPT]
20286            a1
20287            b1
20288            [EXCERPT]
20289            [FOLDED]
20290            [EXCERPT]
20291            [FOLDED]
20292            "
20293        });
20294    }
20295}
20296
20297#[gpui::test]
20298async fn test_inline_completion_text(cx: &mut TestAppContext) {
20299    init_test(cx, |_| {});
20300
20301    // Simple insertion
20302    assert_highlighted_edits(
20303        "Hello, world!",
20304        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
20305        true,
20306        cx,
20307        |highlighted_edits, cx| {
20308            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
20309            assert_eq!(highlighted_edits.highlights.len(), 1);
20310            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
20311            assert_eq!(
20312                highlighted_edits.highlights[0].1.background_color,
20313                Some(cx.theme().status().created_background)
20314            );
20315        },
20316    )
20317    .await;
20318
20319    // Replacement
20320    assert_highlighted_edits(
20321        "This is a test.",
20322        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
20323        false,
20324        cx,
20325        |highlighted_edits, cx| {
20326            assert_eq!(highlighted_edits.text, "That is a test.");
20327            assert_eq!(highlighted_edits.highlights.len(), 1);
20328            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
20329            assert_eq!(
20330                highlighted_edits.highlights[0].1.background_color,
20331                Some(cx.theme().status().created_background)
20332            );
20333        },
20334    )
20335    .await;
20336
20337    // Multiple edits
20338    assert_highlighted_edits(
20339        "Hello, world!",
20340        vec![
20341            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
20342            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
20343        ],
20344        false,
20345        cx,
20346        |highlighted_edits, cx| {
20347            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
20348            assert_eq!(highlighted_edits.highlights.len(), 2);
20349            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
20350            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
20351            assert_eq!(
20352                highlighted_edits.highlights[0].1.background_color,
20353                Some(cx.theme().status().created_background)
20354            );
20355            assert_eq!(
20356                highlighted_edits.highlights[1].1.background_color,
20357                Some(cx.theme().status().created_background)
20358            );
20359        },
20360    )
20361    .await;
20362
20363    // Multiple lines with edits
20364    assert_highlighted_edits(
20365        "First line\nSecond line\nThird line\nFourth line",
20366        vec![
20367            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
20368            (
20369                Point::new(2, 0)..Point::new(2, 10),
20370                "New third line".to_string(),
20371            ),
20372            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
20373        ],
20374        false,
20375        cx,
20376        |highlighted_edits, cx| {
20377            assert_eq!(
20378                highlighted_edits.text,
20379                "Second modified\nNew third line\nFourth updated line"
20380            );
20381            assert_eq!(highlighted_edits.highlights.len(), 3);
20382            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
20383            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
20384            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
20385            for highlight in &highlighted_edits.highlights {
20386                assert_eq!(
20387                    highlight.1.background_color,
20388                    Some(cx.theme().status().created_background)
20389                );
20390            }
20391        },
20392    )
20393    .await;
20394}
20395
20396#[gpui::test]
20397async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
20398    init_test(cx, |_| {});
20399
20400    // Deletion
20401    assert_highlighted_edits(
20402        "Hello, world!",
20403        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
20404        true,
20405        cx,
20406        |highlighted_edits, cx| {
20407            assert_eq!(highlighted_edits.text, "Hello, world!");
20408            assert_eq!(highlighted_edits.highlights.len(), 1);
20409            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
20410            assert_eq!(
20411                highlighted_edits.highlights[0].1.background_color,
20412                Some(cx.theme().status().deleted_background)
20413            );
20414        },
20415    )
20416    .await;
20417
20418    // Insertion
20419    assert_highlighted_edits(
20420        "Hello, world!",
20421        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
20422        true,
20423        cx,
20424        |highlighted_edits, cx| {
20425            assert_eq!(highlighted_edits.highlights.len(), 1);
20426            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
20427            assert_eq!(
20428                highlighted_edits.highlights[0].1.background_color,
20429                Some(cx.theme().status().created_background)
20430            );
20431        },
20432    )
20433    .await;
20434}
20435
20436async fn assert_highlighted_edits(
20437    text: &str,
20438    edits: Vec<(Range<Point>, String)>,
20439    include_deletions: bool,
20440    cx: &mut TestAppContext,
20441    assertion_fn: impl Fn(HighlightedText, &App),
20442) {
20443    let window = cx.add_window(|window, cx| {
20444        let buffer = MultiBuffer::build_simple(text, cx);
20445        Editor::new(EditorMode::full(), buffer, None, window, cx)
20446    });
20447    let cx = &mut VisualTestContext::from_window(*window, cx);
20448
20449    let (buffer, snapshot) = window
20450        .update(cx, |editor, _window, cx| {
20451            (
20452                editor.buffer().clone(),
20453                editor.buffer().read(cx).snapshot(cx),
20454            )
20455        })
20456        .unwrap();
20457
20458    let edits = edits
20459        .into_iter()
20460        .map(|(range, edit)| {
20461            (
20462                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
20463                edit,
20464            )
20465        })
20466        .collect::<Vec<_>>();
20467
20468    let text_anchor_edits = edits
20469        .clone()
20470        .into_iter()
20471        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
20472        .collect::<Vec<_>>();
20473
20474    let edit_preview = window
20475        .update(cx, |_, _window, cx| {
20476            buffer
20477                .read(cx)
20478                .as_singleton()
20479                .unwrap()
20480                .read(cx)
20481                .preview_edits(text_anchor_edits.into(), cx)
20482        })
20483        .unwrap()
20484        .await;
20485
20486    cx.update(|_window, cx| {
20487        let highlighted_edits = inline_completion_edit_text(
20488            &snapshot.as_singleton().unwrap().2,
20489            &edits,
20490            &edit_preview,
20491            include_deletions,
20492            cx,
20493        );
20494        assertion_fn(highlighted_edits, cx)
20495    });
20496}
20497
20498#[track_caller]
20499fn assert_breakpoint(
20500    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
20501    path: &Arc<Path>,
20502    expected: Vec<(u32, Breakpoint)>,
20503) {
20504    if expected.len() == 0usize {
20505        assert!(!breakpoints.contains_key(path), "{}", path.display());
20506    } else {
20507        let mut breakpoint = breakpoints
20508            .get(path)
20509            .unwrap()
20510            .into_iter()
20511            .map(|breakpoint| {
20512                (
20513                    breakpoint.row,
20514                    Breakpoint {
20515                        message: breakpoint.message.clone(),
20516                        state: breakpoint.state,
20517                        condition: breakpoint.condition.clone(),
20518                        hit_condition: breakpoint.hit_condition.clone(),
20519                    },
20520                )
20521            })
20522            .collect::<Vec<_>>();
20523
20524        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
20525
20526        assert_eq!(expected, breakpoint);
20527    }
20528}
20529
20530fn add_log_breakpoint_at_cursor(
20531    editor: &mut Editor,
20532    log_message: &str,
20533    window: &mut Window,
20534    cx: &mut Context<Editor>,
20535) {
20536    let (anchor, bp) = editor
20537        .breakpoints_at_cursors(window, cx)
20538        .first()
20539        .and_then(|(anchor, bp)| {
20540            if let Some(bp) = bp {
20541                Some((*anchor, bp.clone()))
20542            } else {
20543                None
20544            }
20545        })
20546        .unwrap_or_else(|| {
20547            let cursor_position: Point = editor.selections.newest(cx).head();
20548
20549            let breakpoint_position = editor
20550                .snapshot(window, cx)
20551                .display_snapshot
20552                .buffer_snapshot
20553                .anchor_before(Point::new(cursor_position.row, 0));
20554
20555            (breakpoint_position, Breakpoint::new_log(&log_message))
20556        });
20557
20558    editor.edit_breakpoint_at_anchor(
20559        anchor,
20560        bp,
20561        BreakpointEditAction::EditLogMessage(log_message.into()),
20562        cx,
20563    );
20564}
20565
20566#[gpui::test]
20567async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
20568    init_test(cx, |_| {});
20569
20570    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20571    let fs = FakeFs::new(cx.executor());
20572    fs.insert_tree(
20573        path!("/a"),
20574        json!({
20575            "main.rs": sample_text,
20576        }),
20577    )
20578    .await;
20579    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20580    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20581    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20582
20583    let fs = FakeFs::new(cx.executor());
20584    fs.insert_tree(
20585        path!("/a"),
20586        json!({
20587            "main.rs": sample_text,
20588        }),
20589    )
20590    .await;
20591    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20592    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20593    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20594    let worktree_id = workspace
20595        .update(cx, |workspace, _window, cx| {
20596            workspace.project().update(cx, |project, cx| {
20597                project.worktrees(cx).next().unwrap().read(cx).id()
20598            })
20599        })
20600        .unwrap();
20601
20602    let buffer = project
20603        .update(cx, |project, cx| {
20604            project.open_buffer((worktree_id, "main.rs"), cx)
20605        })
20606        .await
20607        .unwrap();
20608
20609    let (editor, cx) = cx.add_window_view(|window, cx| {
20610        Editor::new(
20611            EditorMode::full(),
20612            MultiBuffer::build_from_buffer(buffer, cx),
20613            Some(project.clone()),
20614            window,
20615            cx,
20616        )
20617    });
20618
20619    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20620    let abs_path = project.read_with(cx, |project, cx| {
20621        project
20622            .absolute_path(&project_path, cx)
20623            .map(|path_buf| Arc::from(path_buf.to_owned()))
20624            .unwrap()
20625    });
20626
20627    // assert we can add breakpoint on the first line
20628    editor.update_in(cx, |editor, window, cx| {
20629        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20630        editor.move_to_end(&MoveToEnd, window, cx);
20631        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20632    });
20633
20634    let breakpoints = editor.update(cx, |editor, cx| {
20635        editor
20636            .breakpoint_store()
20637            .as_ref()
20638            .unwrap()
20639            .read(cx)
20640            .all_source_breakpoints(cx)
20641            .clone()
20642    });
20643
20644    assert_eq!(1, breakpoints.len());
20645    assert_breakpoint(
20646        &breakpoints,
20647        &abs_path,
20648        vec![
20649            (0, Breakpoint::new_standard()),
20650            (3, Breakpoint::new_standard()),
20651        ],
20652    );
20653
20654    editor.update_in(cx, |editor, window, cx| {
20655        editor.move_to_beginning(&MoveToBeginning, window, cx);
20656        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20657    });
20658
20659    let breakpoints = editor.update(cx, |editor, cx| {
20660        editor
20661            .breakpoint_store()
20662            .as_ref()
20663            .unwrap()
20664            .read(cx)
20665            .all_source_breakpoints(cx)
20666            .clone()
20667    });
20668
20669    assert_eq!(1, breakpoints.len());
20670    assert_breakpoint(
20671        &breakpoints,
20672        &abs_path,
20673        vec![(3, Breakpoint::new_standard())],
20674    );
20675
20676    editor.update_in(cx, |editor, window, cx| {
20677        editor.move_to_end(&MoveToEnd, window, cx);
20678        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20679    });
20680
20681    let breakpoints = editor.update(cx, |editor, cx| {
20682        editor
20683            .breakpoint_store()
20684            .as_ref()
20685            .unwrap()
20686            .read(cx)
20687            .all_source_breakpoints(cx)
20688            .clone()
20689    });
20690
20691    assert_eq!(0, breakpoints.len());
20692    assert_breakpoint(&breakpoints, &abs_path, vec![]);
20693}
20694
20695#[gpui::test]
20696async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
20697    init_test(cx, |_| {});
20698
20699    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20700
20701    let fs = FakeFs::new(cx.executor());
20702    fs.insert_tree(
20703        path!("/a"),
20704        json!({
20705            "main.rs": sample_text,
20706        }),
20707    )
20708    .await;
20709    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20710    let (workspace, cx) =
20711        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
20712
20713    let worktree_id = workspace.update(cx, |workspace, cx| {
20714        workspace.project().update(cx, |project, cx| {
20715            project.worktrees(cx).next().unwrap().read(cx).id()
20716        })
20717    });
20718
20719    let buffer = project
20720        .update(cx, |project, cx| {
20721            project.open_buffer((worktree_id, "main.rs"), cx)
20722        })
20723        .await
20724        .unwrap();
20725
20726    let (editor, cx) = cx.add_window_view(|window, cx| {
20727        Editor::new(
20728            EditorMode::full(),
20729            MultiBuffer::build_from_buffer(buffer, cx),
20730            Some(project.clone()),
20731            window,
20732            cx,
20733        )
20734    });
20735
20736    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20737    let abs_path = project.read_with(cx, |project, cx| {
20738        project
20739            .absolute_path(&project_path, cx)
20740            .map(|path_buf| Arc::from(path_buf.to_owned()))
20741            .unwrap()
20742    });
20743
20744    editor.update_in(cx, |editor, window, cx| {
20745        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
20746    });
20747
20748    let breakpoints = editor.update(cx, |editor, cx| {
20749        editor
20750            .breakpoint_store()
20751            .as_ref()
20752            .unwrap()
20753            .read(cx)
20754            .all_source_breakpoints(cx)
20755            .clone()
20756    });
20757
20758    assert_breakpoint(
20759        &breakpoints,
20760        &abs_path,
20761        vec![(0, Breakpoint::new_log("hello world"))],
20762    );
20763
20764    // Removing a log message from a log breakpoint should remove it
20765    editor.update_in(cx, |editor, window, cx| {
20766        add_log_breakpoint_at_cursor(editor, "", window, cx);
20767    });
20768
20769    let breakpoints = editor.update(cx, |editor, cx| {
20770        editor
20771            .breakpoint_store()
20772            .as_ref()
20773            .unwrap()
20774            .read(cx)
20775            .all_source_breakpoints(cx)
20776            .clone()
20777    });
20778
20779    assert_breakpoint(&breakpoints, &abs_path, vec![]);
20780
20781    editor.update_in(cx, |editor, window, cx| {
20782        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20783        editor.move_to_end(&MoveToEnd, window, cx);
20784        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20785        // Not adding a log message to a standard breakpoint shouldn't remove it
20786        add_log_breakpoint_at_cursor(editor, "", window, cx);
20787    });
20788
20789    let breakpoints = editor.update(cx, |editor, cx| {
20790        editor
20791            .breakpoint_store()
20792            .as_ref()
20793            .unwrap()
20794            .read(cx)
20795            .all_source_breakpoints(cx)
20796            .clone()
20797    });
20798
20799    assert_breakpoint(
20800        &breakpoints,
20801        &abs_path,
20802        vec![
20803            (0, Breakpoint::new_standard()),
20804            (3, Breakpoint::new_standard()),
20805        ],
20806    );
20807
20808    editor.update_in(cx, |editor, window, cx| {
20809        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
20810    });
20811
20812    let breakpoints = editor.update(cx, |editor, cx| {
20813        editor
20814            .breakpoint_store()
20815            .as_ref()
20816            .unwrap()
20817            .read(cx)
20818            .all_source_breakpoints(cx)
20819            .clone()
20820    });
20821
20822    assert_breakpoint(
20823        &breakpoints,
20824        &abs_path,
20825        vec![
20826            (0, Breakpoint::new_standard()),
20827            (3, Breakpoint::new_log("hello world")),
20828        ],
20829    );
20830
20831    editor.update_in(cx, |editor, window, cx| {
20832        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
20833    });
20834
20835    let breakpoints = editor.update(cx, |editor, cx| {
20836        editor
20837            .breakpoint_store()
20838            .as_ref()
20839            .unwrap()
20840            .read(cx)
20841            .all_source_breakpoints(cx)
20842            .clone()
20843    });
20844
20845    assert_breakpoint(
20846        &breakpoints,
20847        &abs_path,
20848        vec![
20849            (0, Breakpoint::new_standard()),
20850            (3, Breakpoint::new_log("hello Earth!!")),
20851        ],
20852    );
20853}
20854
20855/// This also tests that Editor::breakpoint_at_cursor_head is working properly
20856/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
20857/// or when breakpoints were placed out of order. This tests for a regression too
20858#[gpui::test]
20859async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
20860    init_test(cx, |_| {});
20861
20862    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20863    let fs = FakeFs::new(cx.executor());
20864    fs.insert_tree(
20865        path!("/a"),
20866        json!({
20867            "main.rs": sample_text,
20868        }),
20869    )
20870    .await;
20871    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20872    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20873    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20874
20875    let fs = FakeFs::new(cx.executor());
20876    fs.insert_tree(
20877        path!("/a"),
20878        json!({
20879            "main.rs": sample_text,
20880        }),
20881    )
20882    .await;
20883    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20884    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20885    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20886    let worktree_id = workspace
20887        .update(cx, |workspace, _window, cx| {
20888            workspace.project().update(cx, |project, cx| {
20889                project.worktrees(cx).next().unwrap().read(cx).id()
20890            })
20891        })
20892        .unwrap();
20893
20894    let buffer = project
20895        .update(cx, |project, cx| {
20896            project.open_buffer((worktree_id, "main.rs"), cx)
20897        })
20898        .await
20899        .unwrap();
20900
20901    let (editor, cx) = cx.add_window_view(|window, cx| {
20902        Editor::new(
20903            EditorMode::full(),
20904            MultiBuffer::build_from_buffer(buffer, cx),
20905            Some(project.clone()),
20906            window,
20907            cx,
20908        )
20909    });
20910
20911    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20912    let abs_path = project.read_with(cx, |project, cx| {
20913        project
20914            .absolute_path(&project_path, cx)
20915            .map(|path_buf| Arc::from(path_buf.to_owned()))
20916            .unwrap()
20917    });
20918
20919    // assert we can add breakpoint on the first line
20920    editor.update_in(cx, |editor, window, cx| {
20921        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20922        editor.move_to_end(&MoveToEnd, window, cx);
20923        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20924        editor.move_up(&MoveUp, window, cx);
20925        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20926    });
20927
20928    let breakpoints = editor.update(cx, |editor, cx| {
20929        editor
20930            .breakpoint_store()
20931            .as_ref()
20932            .unwrap()
20933            .read(cx)
20934            .all_source_breakpoints(cx)
20935            .clone()
20936    });
20937
20938    assert_eq!(1, breakpoints.len());
20939    assert_breakpoint(
20940        &breakpoints,
20941        &abs_path,
20942        vec![
20943            (0, Breakpoint::new_standard()),
20944            (2, Breakpoint::new_standard()),
20945            (3, Breakpoint::new_standard()),
20946        ],
20947    );
20948
20949    editor.update_in(cx, |editor, window, cx| {
20950        editor.move_to_beginning(&MoveToBeginning, window, cx);
20951        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20952        editor.move_to_end(&MoveToEnd, window, cx);
20953        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20954        // Disabling a breakpoint that doesn't exist should do nothing
20955        editor.move_up(&MoveUp, window, cx);
20956        editor.move_up(&MoveUp, window, cx);
20957        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20958    });
20959
20960    let breakpoints = editor.update(cx, |editor, cx| {
20961        editor
20962            .breakpoint_store()
20963            .as_ref()
20964            .unwrap()
20965            .read(cx)
20966            .all_source_breakpoints(cx)
20967            .clone()
20968    });
20969
20970    let disable_breakpoint = {
20971        let mut bp = Breakpoint::new_standard();
20972        bp.state = BreakpointState::Disabled;
20973        bp
20974    };
20975
20976    assert_eq!(1, breakpoints.len());
20977    assert_breakpoint(
20978        &breakpoints,
20979        &abs_path,
20980        vec![
20981            (0, disable_breakpoint.clone()),
20982            (2, Breakpoint::new_standard()),
20983            (3, disable_breakpoint.clone()),
20984        ],
20985    );
20986
20987    editor.update_in(cx, |editor, window, cx| {
20988        editor.move_to_beginning(&MoveToBeginning, window, cx);
20989        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
20990        editor.move_to_end(&MoveToEnd, window, cx);
20991        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
20992        editor.move_up(&MoveUp, window, cx);
20993        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20994    });
20995
20996    let breakpoints = editor.update(cx, |editor, cx| {
20997        editor
20998            .breakpoint_store()
20999            .as_ref()
21000            .unwrap()
21001            .read(cx)
21002            .all_source_breakpoints(cx)
21003            .clone()
21004    });
21005
21006    assert_eq!(1, breakpoints.len());
21007    assert_breakpoint(
21008        &breakpoints,
21009        &abs_path,
21010        vec![
21011            (0, Breakpoint::new_standard()),
21012            (2, disable_breakpoint),
21013            (3, Breakpoint::new_standard()),
21014        ],
21015    );
21016}
21017
21018#[gpui::test]
21019async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
21020    init_test(cx, |_| {});
21021    let capabilities = lsp::ServerCapabilities {
21022        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
21023            prepare_provider: Some(true),
21024            work_done_progress_options: Default::default(),
21025        })),
21026        ..Default::default()
21027    };
21028    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
21029
21030    cx.set_state(indoc! {"
21031        struct Fˇoo {}
21032    "});
21033
21034    cx.update_editor(|editor, _, cx| {
21035        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
21036        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
21037        editor.highlight_background::<DocumentHighlightRead>(
21038            &[highlight_range],
21039            |theme| theme.colors().editor_document_highlight_read_background,
21040            cx,
21041        );
21042    });
21043
21044    let mut prepare_rename_handler = cx
21045        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
21046            move |_, _, _| async move {
21047                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
21048                    start: lsp::Position {
21049                        line: 0,
21050                        character: 7,
21051                    },
21052                    end: lsp::Position {
21053                        line: 0,
21054                        character: 10,
21055                    },
21056                })))
21057            },
21058        );
21059    let prepare_rename_task = cx
21060        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
21061        .expect("Prepare rename was not started");
21062    prepare_rename_handler.next().await.unwrap();
21063    prepare_rename_task.await.expect("Prepare rename failed");
21064
21065    let mut rename_handler =
21066        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
21067            let edit = lsp::TextEdit {
21068                range: lsp::Range {
21069                    start: lsp::Position {
21070                        line: 0,
21071                        character: 7,
21072                    },
21073                    end: lsp::Position {
21074                        line: 0,
21075                        character: 10,
21076                    },
21077                },
21078                new_text: "FooRenamed".to_string(),
21079            };
21080            Ok(Some(lsp::WorkspaceEdit::new(
21081                // Specify the same edit twice
21082                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
21083            )))
21084        });
21085    let rename_task = cx
21086        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
21087        .expect("Confirm rename was not started");
21088    rename_handler.next().await.unwrap();
21089    rename_task.await.expect("Confirm rename failed");
21090    cx.run_until_parked();
21091
21092    // Despite two edits, only one is actually applied as those are identical
21093    cx.assert_editor_state(indoc! {"
21094        struct FooRenamedˇ {}
21095    "});
21096}
21097
21098#[gpui::test]
21099async fn test_rename_without_prepare(cx: &mut TestAppContext) {
21100    init_test(cx, |_| {});
21101    // These capabilities indicate that the server does not support prepare rename.
21102    let capabilities = lsp::ServerCapabilities {
21103        rename_provider: Some(lsp::OneOf::Left(true)),
21104        ..Default::default()
21105    };
21106    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
21107
21108    cx.set_state(indoc! {"
21109        struct Fˇoo {}
21110    "});
21111
21112    cx.update_editor(|editor, _window, cx| {
21113        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
21114        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
21115        editor.highlight_background::<DocumentHighlightRead>(
21116            &[highlight_range],
21117            |theme| theme.colors().editor_document_highlight_read_background,
21118            cx,
21119        );
21120    });
21121
21122    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
21123        .expect("Prepare rename was not started")
21124        .await
21125        .expect("Prepare rename failed");
21126
21127    let mut rename_handler =
21128        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
21129            let edit = lsp::TextEdit {
21130                range: lsp::Range {
21131                    start: lsp::Position {
21132                        line: 0,
21133                        character: 7,
21134                    },
21135                    end: lsp::Position {
21136                        line: 0,
21137                        character: 10,
21138                    },
21139                },
21140                new_text: "FooRenamed".to_string(),
21141            };
21142            Ok(Some(lsp::WorkspaceEdit::new(
21143                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
21144            )))
21145        });
21146    let rename_task = cx
21147        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
21148        .expect("Confirm rename was not started");
21149    rename_handler.next().await.unwrap();
21150    rename_task.await.expect("Confirm rename failed");
21151    cx.run_until_parked();
21152
21153    // Correct range is renamed, as `surrounding_word` is used to find it.
21154    cx.assert_editor_state(indoc! {"
21155        struct FooRenamedˇ {}
21156    "});
21157}
21158
21159#[gpui::test]
21160async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
21161    init_test(cx, |_| {});
21162    let mut cx = EditorTestContext::new(cx).await;
21163
21164    let language = Arc::new(
21165        Language::new(
21166            LanguageConfig::default(),
21167            Some(tree_sitter_html::LANGUAGE.into()),
21168        )
21169        .with_brackets_query(
21170            r#"
21171            ("<" @open "/>" @close)
21172            ("</" @open ">" @close)
21173            ("<" @open ">" @close)
21174            ("\"" @open "\"" @close)
21175            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
21176        "#,
21177        )
21178        .unwrap(),
21179    );
21180    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
21181
21182    cx.set_state(indoc! {"
21183        <span>ˇ</span>
21184    "});
21185    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21186    cx.assert_editor_state(indoc! {"
21187        <span>
21188        ˇ
21189        </span>
21190    "});
21191
21192    cx.set_state(indoc! {"
21193        <span><span></span>ˇ</span>
21194    "});
21195    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21196    cx.assert_editor_state(indoc! {"
21197        <span><span></span>
21198        ˇ</span>
21199    "});
21200
21201    cx.set_state(indoc! {"
21202        <span>ˇ
21203        </span>
21204    "});
21205    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21206    cx.assert_editor_state(indoc! {"
21207        <span>
21208        ˇ
21209        </span>
21210    "});
21211}
21212
21213#[gpui::test(iterations = 10)]
21214async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
21215    init_test(cx, |_| {});
21216
21217    let fs = FakeFs::new(cx.executor());
21218    fs.insert_tree(
21219        path!("/dir"),
21220        json!({
21221            "a.ts": "a",
21222        }),
21223    )
21224    .await;
21225
21226    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
21227    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21228    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21229
21230    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21231    language_registry.add(Arc::new(Language::new(
21232        LanguageConfig {
21233            name: "TypeScript".into(),
21234            matcher: LanguageMatcher {
21235                path_suffixes: vec!["ts".to_string()],
21236                ..Default::default()
21237            },
21238            ..Default::default()
21239        },
21240        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
21241    )));
21242    let mut fake_language_servers = language_registry.register_fake_lsp(
21243        "TypeScript",
21244        FakeLspAdapter {
21245            capabilities: lsp::ServerCapabilities {
21246                code_lens_provider: Some(lsp::CodeLensOptions {
21247                    resolve_provider: Some(true),
21248                }),
21249                execute_command_provider: Some(lsp::ExecuteCommandOptions {
21250                    commands: vec!["_the/command".to_string()],
21251                    ..lsp::ExecuteCommandOptions::default()
21252                }),
21253                ..lsp::ServerCapabilities::default()
21254            },
21255            ..FakeLspAdapter::default()
21256        },
21257    );
21258
21259    let (buffer, _handle) = project
21260        .update(cx, |p, cx| {
21261            p.open_local_buffer_with_lsp(path!("/dir/a.ts"), cx)
21262        })
21263        .await
21264        .unwrap();
21265    cx.executor().run_until_parked();
21266
21267    let fake_server = fake_language_servers.next().await.unwrap();
21268
21269    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
21270    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
21271    drop(buffer_snapshot);
21272    let actions = cx
21273        .update_window(*workspace, |_, window, cx| {
21274            project.code_actions(&buffer, anchor..anchor, window, cx)
21275        })
21276        .unwrap();
21277
21278    fake_server
21279        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
21280            Ok(Some(vec![
21281                lsp::CodeLens {
21282                    range: lsp::Range::default(),
21283                    command: Some(lsp::Command {
21284                        title: "Code lens command".to_owned(),
21285                        command: "_the/command".to_owned(),
21286                        arguments: None,
21287                    }),
21288                    data: None,
21289                },
21290                lsp::CodeLens {
21291                    range: lsp::Range::default(),
21292                    command: Some(lsp::Command {
21293                        title: "Command not in capabilities".to_owned(),
21294                        command: "not in capabilities".to_owned(),
21295                        arguments: None,
21296                    }),
21297                    data: None,
21298                },
21299                lsp::CodeLens {
21300                    range: lsp::Range {
21301                        start: lsp::Position {
21302                            line: 1,
21303                            character: 1,
21304                        },
21305                        end: lsp::Position {
21306                            line: 1,
21307                            character: 1,
21308                        },
21309                    },
21310                    command: Some(lsp::Command {
21311                        title: "Command not in range".to_owned(),
21312                        command: "_the/command".to_owned(),
21313                        arguments: None,
21314                    }),
21315                    data: None,
21316                },
21317            ]))
21318        })
21319        .next()
21320        .await;
21321
21322    let actions = actions.await.unwrap();
21323    assert_eq!(
21324        actions.len(),
21325        1,
21326        "Should have only one valid action for the 0..0 range"
21327    );
21328    let action = actions[0].clone();
21329    let apply = project.update(cx, |project, cx| {
21330        project.apply_code_action(buffer.clone(), action, true, cx)
21331    });
21332
21333    // Resolving the code action does not populate its edits. In absence of
21334    // edits, we must execute the given command.
21335    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
21336        |mut lens, _| async move {
21337            let lens_command = lens.command.as_mut().expect("should have a command");
21338            assert_eq!(lens_command.title, "Code lens command");
21339            lens_command.arguments = Some(vec![json!("the-argument")]);
21340            Ok(lens)
21341        },
21342    );
21343
21344    // While executing the command, the language server sends the editor
21345    // a `workspaceEdit` request.
21346    fake_server
21347        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
21348            let fake = fake_server.clone();
21349            move |params, _| {
21350                assert_eq!(params.command, "_the/command");
21351                let fake = fake.clone();
21352                async move {
21353                    fake.server
21354                        .request::<lsp::request::ApplyWorkspaceEdit>(
21355                            lsp::ApplyWorkspaceEditParams {
21356                                label: None,
21357                                edit: lsp::WorkspaceEdit {
21358                                    changes: Some(
21359                                        [(
21360                                            lsp::Url::from_file_path(path!("/dir/a.ts")).unwrap(),
21361                                            vec![lsp::TextEdit {
21362                                                range: lsp::Range::new(
21363                                                    lsp::Position::new(0, 0),
21364                                                    lsp::Position::new(0, 0),
21365                                                ),
21366                                                new_text: "X".into(),
21367                                            }],
21368                                        )]
21369                                        .into_iter()
21370                                        .collect(),
21371                                    ),
21372                                    ..Default::default()
21373                                },
21374                            },
21375                        )
21376                        .await
21377                        .into_response()
21378                        .unwrap();
21379                    Ok(Some(json!(null)))
21380                }
21381            }
21382        })
21383        .next()
21384        .await;
21385
21386    // Applying the code lens command returns a project transaction containing the edits
21387    // sent by the language server in its `workspaceEdit` request.
21388    let transaction = apply.await.unwrap();
21389    assert!(transaction.0.contains_key(&buffer));
21390    buffer.update(cx, |buffer, cx| {
21391        assert_eq!(buffer.text(), "Xa");
21392        buffer.undo(cx);
21393        assert_eq!(buffer.text(), "a");
21394    });
21395}
21396
21397#[gpui::test]
21398async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
21399    init_test(cx, |_| {});
21400
21401    let fs = FakeFs::new(cx.executor());
21402    let main_text = r#"fn main() {
21403println!("1");
21404println!("2");
21405println!("3");
21406println!("4");
21407println!("5");
21408}"#;
21409    let lib_text = "mod foo {}";
21410    fs.insert_tree(
21411        path!("/a"),
21412        json!({
21413            "lib.rs": lib_text,
21414            "main.rs": main_text,
21415        }),
21416    )
21417    .await;
21418
21419    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21420    let (workspace, cx) =
21421        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21422    let worktree_id = workspace.update(cx, |workspace, cx| {
21423        workspace.project().update(cx, |project, cx| {
21424            project.worktrees(cx).next().unwrap().read(cx).id()
21425        })
21426    });
21427
21428    let expected_ranges = vec![
21429        Point::new(0, 0)..Point::new(0, 0),
21430        Point::new(1, 0)..Point::new(1, 1),
21431        Point::new(2, 0)..Point::new(2, 2),
21432        Point::new(3, 0)..Point::new(3, 3),
21433    ];
21434
21435    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21436    let editor_1 = workspace
21437        .update_in(cx, |workspace, window, cx| {
21438            workspace.open_path(
21439                (worktree_id, "main.rs"),
21440                Some(pane_1.downgrade()),
21441                true,
21442                window,
21443                cx,
21444            )
21445        })
21446        .unwrap()
21447        .await
21448        .downcast::<Editor>()
21449        .unwrap();
21450    pane_1.update(cx, |pane, cx| {
21451        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21452        open_editor.update(cx, |editor, cx| {
21453            assert_eq!(
21454                editor.display_text(cx),
21455                main_text,
21456                "Original main.rs text on initial open",
21457            );
21458            assert_eq!(
21459                editor
21460                    .selections
21461                    .all::<Point>(cx)
21462                    .into_iter()
21463                    .map(|s| s.range())
21464                    .collect::<Vec<_>>(),
21465                vec![Point::zero()..Point::zero()],
21466                "Default selections on initial open",
21467            );
21468        })
21469    });
21470    editor_1.update_in(cx, |editor, window, cx| {
21471        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21472            s.select_ranges(expected_ranges.clone());
21473        });
21474    });
21475
21476    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
21477        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
21478    });
21479    let editor_2 = workspace
21480        .update_in(cx, |workspace, window, cx| {
21481            workspace.open_path(
21482                (worktree_id, "main.rs"),
21483                Some(pane_2.downgrade()),
21484                true,
21485                window,
21486                cx,
21487            )
21488        })
21489        .unwrap()
21490        .await
21491        .downcast::<Editor>()
21492        .unwrap();
21493    pane_2.update(cx, |pane, cx| {
21494        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21495        open_editor.update(cx, |editor, cx| {
21496            assert_eq!(
21497                editor.display_text(cx),
21498                main_text,
21499                "Original main.rs text on initial open in another panel",
21500            );
21501            assert_eq!(
21502                editor
21503                    .selections
21504                    .all::<Point>(cx)
21505                    .into_iter()
21506                    .map(|s| s.range())
21507                    .collect::<Vec<_>>(),
21508                vec![Point::zero()..Point::zero()],
21509                "Default selections on initial open in another panel",
21510            );
21511        })
21512    });
21513
21514    editor_2.update_in(cx, |editor, window, cx| {
21515        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
21516    });
21517
21518    let _other_editor_1 = workspace
21519        .update_in(cx, |workspace, window, cx| {
21520            workspace.open_path(
21521                (worktree_id, "lib.rs"),
21522                Some(pane_1.downgrade()),
21523                true,
21524                window,
21525                cx,
21526            )
21527        })
21528        .unwrap()
21529        .await
21530        .downcast::<Editor>()
21531        .unwrap();
21532    pane_1
21533        .update_in(cx, |pane, window, cx| {
21534            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
21535        })
21536        .await
21537        .unwrap();
21538    drop(editor_1);
21539    pane_1.update(cx, |pane, cx| {
21540        pane.active_item()
21541            .unwrap()
21542            .downcast::<Editor>()
21543            .unwrap()
21544            .update(cx, |editor, cx| {
21545                assert_eq!(
21546                    editor.display_text(cx),
21547                    lib_text,
21548                    "Other file should be open and active",
21549                );
21550            });
21551        assert_eq!(pane.items().count(), 1, "No other editors should be open");
21552    });
21553
21554    let _other_editor_2 = workspace
21555        .update_in(cx, |workspace, window, cx| {
21556            workspace.open_path(
21557                (worktree_id, "lib.rs"),
21558                Some(pane_2.downgrade()),
21559                true,
21560                window,
21561                cx,
21562            )
21563        })
21564        .unwrap()
21565        .await
21566        .downcast::<Editor>()
21567        .unwrap();
21568    pane_2
21569        .update_in(cx, |pane, window, cx| {
21570            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
21571        })
21572        .await
21573        .unwrap();
21574    drop(editor_2);
21575    pane_2.update(cx, |pane, cx| {
21576        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21577        open_editor.update(cx, |editor, cx| {
21578            assert_eq!(
21579                editor.display_text(cx),
21580                lib_text,
21581                "Other file should be open and active in another panel too",
21582            );
21583        });
21584        assert_eq!(
21585            pane.items().count(),
21586            1,
21587            "No other editors should be open in another pane",
21588        );
21589    });
21590
21591    let _editor_1_reopened = workspace
21592        .update_in(cx, |workspace, window, cx| {
21593            workspace.open_path(
21594                (worktree_id, "main.rs"),
21595                Some(pane_1.downgrade()),
21596                true,
21597                window,
21598                cx,
21599            )
21600        })
21601        .unwrap()
21602        .await
21603        .downcast::<Editor>()
21604        .unwrap();
21605    let _editor_2_reopened = workspace
21606        .update_in(cx, |workspace, window, cx| {
21607            workspace.open_path(
21608                (worktree_id, "main.rs"),
21609                Some(pane_2.downgrade()),
21610                true,
21611                window,
21612                cx,
21613            )
21614        })
21615        .unwrap()
21616        .await
21617        .downcast::<Editor>()
21618        .unwrap();
21619    pane_1.update(cx, |pane, cx| {
21620        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21621        open_editor.update(cx, |editor, cx| {
21622            assert_eq!(
21623                editor.display_text(cx),
21624                main_text,
21625                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
21626            );
21627            assert_eq!(
21628                editor
21629                    .selections
21630                    .all::<Point>(cx)
21631                    .into_iter()
21632                    .map(|s| s.range())
21633                    .collect::<Vec<_>>(),
21634                expected_ranges,
21635                "Previous editor in the 1st panel had selections and should get them restored on reopen",
21636            );
21637        })
21638    });
21639    pane_2.update(cx, |pane, cx| {
21640        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21641        open_editor.update(cx, |editor, cx| {
21642            assert_eq!(
21643                editor.display_text(cx),
21644                r#"fn main() {
21645⋯rintln!("1");
21646⋯intln!("2");
21647⋯ntln!("3");
21648println!("4");
21649println!("5");
21650}"#,
21651                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
21652            );
21653            assert_eq!(
21654                editor
21655                    .selections
21656                    .all::<Point>(cx)
21657                    .into_iter()
21658                    .map(|s| s.range())
21659                    .collect::<Vec<_>>(),
21660                vec![Point::zero()..Point::zero()],
21661                "Previous editor in the 2nd pane had no selections changed hence should restore none",
21662            );
21663        })
21664    });
21665}
21666
21667#[gpui::test]
21668async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
21669    init_test(cx, |_| {});
21670
21671    let fs = FakeFs::new(cx.executor());
21672    let main_text = r#"fn main() {
21673println!("1");
21674println!("2");
21675println!("3");
21676println!("4");
21677println!("5");
21678}"#;
21679    let lib_text = "mod foo {}";
21680    fs.insert_tree(
21681        path!("/a"),
21682        json!({
21683            "lib.rs": lib_text,
21684            "main.rs": main_text,
21685        }),
21686    )
21687    .await;
21688
21689    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21690    let (workspace, cx) =
21691        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21692    let worktree_id = workspace.update(cx, |workspace, cx| {
21693        workspace.project().update(cx, |project, cx| {
21694            project.worktrees(cx).next().unwrap().read(cx).id()
21695        })
21696    });
21697
21698    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21699    let editor = workspace
21700        .update_in(cx, |workspace, window, cx| {
21701            workspace.open_path(
21702                (worktree_id, "main.rs"),
21703                Some(pane.downgrade()),
21704                true,
21705                window,
21706                cx,
21707            )
21708        })
21709        .unwrap()
21710        .await
21711        .downcast::<Editor>()
21712        .unwrap();
21713    pane.update(cx, |pane, cx| {
21714        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21715        open_editor.update(cx, |editor, cx| {
21716            assert_eq!(
21717                editor.display_text(cx),
21718                main_text,
21719                "Original main.rs text on initial open",
21720            );
21721        })
21722    });
21723    editor.update_in(cx, |editor, window, cx| {
21724        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
21725    });
21726
21727    cx.update_global(|store: &mut SettingsStore, cx| {
21728        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
21729            s.restore_on_file_reopen = Some(false);
21730        });
21731    });
21732    editor.update_in(cx, |editor, window, cx| {
21733        editor.fold_ranges(
21734            vec![
21735                Point::new(1, 0)..Point::new(1, 1),
21736                Point::new(2, 0)..Point::new(2, 2),
21737                Point::new(3, 0)..Point::new(3, 3),
21738            ],
21739            false,
21740            window,
21741            cx,
21742        );
21743    });
21744    pane.update_in(cx, |pane, window, cx| {
21745        pane.close_all_items(&CloseAllItems::default(), window, cx)
21746    })
21747    .await
21748    .unwrap();
21749    pane.update(cx, |pane, _| {
21750        assert!(pane.active_item().is_none());
21751    });
21752    cx.update_global(|store: &mut SettingsStore, cx| {
21753        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
21754            s.restore_on_file_reopen = Some(true);
21755        });
21756    });
21757
21758    let _editor_reopened = workspace
21759        .update_in(cx, |workspace, window, cx| {
21760            workspace.open_path(
21761                (worktree_id, "main.rs"),
21762                Some(pane.downgrade()),
21763                true,
21764                window,
21765                cx,
21766            )
21767        })
21768        .unwrap()
21769        .await
21770        .downcast::<Editor>()
21771        .unwrap();
21772    pane.update(cx, |pane, cx| {
21773        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21774        open_editor.update(cx, |editor, cx| {
21775            assert_eq!(
21776                editor.display_text(cx),
21777                main_text,
21778                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
21779            );
21780        })
21781    });
21782}
21783
21784#[gpui::test]
21785async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
21786    struct EmptyModalView {
21787        focus_handle: gpui::FocusHandle,
21788    }
21789    impl EventEmitter<DismissEvent> for EmptyModalView {}
21790    impl Render for EmptyModalView {
21791        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
21792            div()
21793        }
21794    }
21795    impl Focusable for EmptyModalView {
21796        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
21797            self.focus_handle.clone()
21798        }
21799    }
21800    impl workspace::ModalView for EmptyModalView {}
21801    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
21802        EmptyModalView {
21803            focus_handle: cx.focus_handle(),
21804        }
21805    }
21806
21807    init_test(cx, |_| {});
21808
21809    let fs = FakeFs::new(cx.executor());
21810    let project = Project::test(fs, [], cx).await;
21811    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21812    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
21813    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21814    let editor = cx.new_window_entity(|window, cx| {
21815        Editor::new(
21816            EditorMode::full(),
21817            buffer,
21818            Some(project.clone()),
21819            window,
21820            cx,
21821        )
21822    });
21823    workspace
21824        .update(cx, |workspace, window, cx| {
21825            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
21826        })
21827        .unwrap();
21828    editor.update_in(cx, |editor, window, cx| {
21829        editor.open_context_menu(&OpenContextMenu, window, cx);
21830        assert!(editor.mouse_context_menu.is_some());
21831    });
21832    workspace
21833        .update(cx, |workspace, window, cx| {
21834            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
21835        })
21836        .unwrap();
21837    cx.read(|cx| {
21838        assert!(editor.read(cx).mouse_context_menu.is_none());
21839    });
21840}
21841
21842#[gpui::test]
21843async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
21844    init_test(cx, |_| {});
21845
21846    let fs = FakeFs::new(cx.executor());
21847    fs.insert_file(path!("/file.html"), Default::default())
21848        .await;
21849
21850    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
21851
21852    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21853    let html_language = Arc::new(Language::new(
21854        LanguageConfig {
21855            name: "HTML".into(),
21856            matcher: LanguageMatcher {
21857                path_suffixes: vec!["html".to_string()],
21858                ..LanguageMatcher::default()
21859            },
21860            brackets: BracketPairConfig {
21861                pairs: vec![BracketPair {
21862                    start: "<".into(),
21863                    end: ">".into(),
21864                    close: true,
21865                    ..Default::default()
21866                }],
21867                ..Default::default()
21868            },
21869            ..Default::default()
21870        },
21871        Some(tree_sitter_html::LANGUAGE.into()),
21872    ));
21873    language_registry.add(html_language);
21874    let mut fake_servers = language_registry.register_fake_lsp(
21875        "HTML",
21876        FakeLspAdapter {
21877            capabilities: lsp::ServerCapabilities {
21878                completion_provider: Some(lsp::CompletionOptions {
21879                    resolve_provider: Some(true),
21880                    ..Default::default()
21881                }),
21882                ..Default::default()
21883            },
21884            ..Default::default()
21885        },
21886    );
21887
21888    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21889    let cx = &mut VisualTestContext::from_window(*workspace, cx);
21890
21891    let worktree_id = workspace
21892        .update(cx, |workspace, _window, cx| {
21893            workspace.project().update(cx, |project, cx| {
21894                project.worktrees(cx).next().unwrap().read(cx).id()
21895            })
21896        })
21897        .unwrap();
21898    project
21899        .update(cx, |project, cx| {
21900            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
21901        })
21902        .await
21903        .unwrap();
21904    let editor = workspace
21905        .update(cx, |workspace, window, cx| {
21906            workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
21907        })
21908        .unwrap()
21909        .await
21910        .unwrap()
21911        .downcast::<Editor>()
21912        .unwrap();
21913
21914    let fake_server = fake_servers.next().await.unwrap();
21915    editor.update_in(cx, |editor, window, cx| {
21916        editor.set_text("<ad></ad>", window, cx);
21917        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21918            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
21919        });
21920        let Some((buffer, _)) = editor
21921            .buffer
21922            .read(cx)
21923            .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
21924        else {
21925            panic!("Failed to get buffer for selection position");
21926        };
21927        let buffer = buffer.read(cx);
21928        let buffer_id = buffer.remote_id();
21929        let opening_range =
21930            buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3));
21931        let closing_range =
21932            buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8));
21933        let mut linked_ranges = HashMap::default();
21934        linked_ranges.insert(
21935            buffer_id,
21936            vec![(opening_range.clone(), vec![closing_range.clone()])],
21937        );
21938        editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
21939    });
21940    let mut completion_handle =
21941        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
21942            Ok(Some(lsp::CompletionResponse::Array(vec![
21943                lsp::CompletionItem {
21944                    label: "head".to_string(),
21945                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21946                        lsp::InsertReplaceEdit {
21947                            new_text: "head".to_string(),
21948                            insert: lsp::Range::new(
21949                                lsp::Position::new(0, 1),
21950                                lsp::Position::new(0, 3),
21951                            ),
21952                            replace: lsp::Range::new(
21953                                lsp::Position::new(0, 1),
21954                                lsp::Position::new(0, 3),
21955                            ),
21956                        },
21957                    )),
21958                    ..Default::default()
21959                },
21960            ])))
21961        });
21962    editor.update_in(cx, |editor, window, cx| {
21963        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
21964    });
21965    cx.run_until_parked();
21966    completion_handle.next().await.unwrap();
21967    editor.update(cx, |editor, _| {
21968        assert!(
21969            editor.context_menu_visible(),
21970            "Completion menu should be visible"
21971        );
21972    });
21973    editor.update_in(cx, |editor, window, cx| {
21974        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
21975    });
21976    cx.executor().run_until_parked();
21977    editor.update(cx, |editor, cx| {
21978        assert_eq!(editor.text(cx), "<head></head>");
21979    });
21980}
21981
21982#[gpui::test]
21983async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
21984    init_test(cx, |_| {});
21985
21986    let fs = FakeFs::new(cx.executor());
21987    fs.insert_tree(
21988        path!("/root"),
21989        json!({
21990            "a": {
21991                "main.rs": "fn main() {}",
21992            },
21993            "foo": {
21994                "bar": {
21995                    "external_file.rs": "pub mod external {}",
21996                }
21997            }
21998        }),
21999    )
22000    .await;
22001
22002    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
22003    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22004    language_registry.add(rust_lang());
22005    let _fake_servers = language_registry.register_fake_lsp(
22006        "Rust",
22007        FakeLspAdapter {
22008            ..FakeLspAdapter::default()
22009        },
22010    );
22011    let (workspace, cx) =
22012        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22013    let worktree_id = workspace.update(cx, |workspace, cx| {
22014        workspace.project().update(cx, |project, cx| {
22015            project.worktrees(cx).next().unwrap().read(cx).id()
22016        })
22017    });
22018
22019    let assert_language_servers_count =
22020        |expected: usize, context: &str, cx: &mut VisualTestContext| {
22021            project.update(cx, |project, cx| {
22022                let current = project
22023                    .lsp_store()
22024                    .read(cx)
22025                    .as_local()
22026                    .unwrap()
22027                    .language_servers
22028                    .len();
22029                assert_eq!(expected, current, "{context}");
22030            });
22031        };
22032
22033    assert_language_servers_count(
22034        0,
22035        "No servers should be running before any file is open",
22036        cx,
22037    );
22038    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
22039    let main_editor = workspace
22040        .update_in(cx, |workspace, window, cx| {
22041            workspace.open_path(
22042                (worktree_id, "main.rs"),
22043                Some(pane.downgrade()),
22044                true,
22045                window,
22046                cx,
22047            )
22048        })
22049        .unwrap()
22050        .await
22051        .downcast::<Editor>()
22052        .unwrap();
22053    pane.update(cx, |pane, cx| {
22054        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22055        open_editor.update(cx, |editor, cx| {
22056            assert_eq!(
22057                editor.display_text(cx),
22058                "fn main() {}",
22059                "Original main.rs text on initial open",
22060            );
22061        });
22062        assert_eq!(open_editor, main_editor);
22063    });
22064    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
22065
22066    let external_editor = workspace
22067        .update_in(cx, |workspace, window, cx| {
22068            workspace.open_abs_path(
22069                PathBuf::from("/root/foo/bar/external_file.rs"),
22070                OpenOptions::default(),
22071                window,
22072                cx,
22073            )
22074        })
22075        .await
22076        .expect("opening external file")
22077        .downcast::<Editor>()
22078        .expect("downcasted external file's open element to editor");
22079    pane.update(cx, |pane, cx| {
22080        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22081        open_editor.update(cx, |editor, cx| {
22082            assert_eq!(
22083                editor.display_text(cx),
22084                "pub mod external {}",
22085                "External file is open now",
22086            );
22087        });
22088        assert_eq!(open_editor, external_editor);
22089    });
22090    assert_language_servers_count(
22091        1,
22092        "Second, external, *.rs file should join the existing server",
22093        cx,
22094    );
22095
22096    pane.update_in(cx, |pane, window, cx| {
22097        pane.close_active_item(&CloseActiveItem::default(), window, cx)
22098    })
22099    .await
22100    .unwrap();
22101    pane.update_in(cx, |pane, window, cx| {
22102        pane.navigate_backward(window, cx);
22103    });
22104    cx.run_until_parked();
22105    pane.update(cx, |pane, cx| {
22106        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22107        open_editor.update(cx, |editor, cx| {
22108            assert_eq!(
22109                editor.display_text(cx),
22110                "pub mod external {}",
22111                "External file is open now",
22112            );
22113        });
22114    });
22115    assert_language_servers_count(
22116        1,
22117        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
22118        cx,
22119    );
22120
22121    cx.update(|_, cx| {
22122        workspace::reload(&workspace::Reload::default(), cx);
22123    });
22124    assert_language_servers_count(
22125        1,
22126        "After reloading the worktree with local and external files opened, only one project should be started",
22127        cx,
22128    );
22129}
22130
22131#[gpui::test]
22132async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
22133    init_test(cx, |_| {});
22134
22135    let mut cx = EditorTestContext::new(cx).await;
22136    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22137    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22138
22139    // test cursor move to start of each line on tab
22140    // for `if`, `elif`, `else`, `while`, `with` and `for`
22141    cx.set_state(indoc! {"
22142        def main():
22143        ˇ    for item in items:
22144        ˇ        while item.active:
22145        ˇ            if item.value > 10:
22146        ˇ                continue
22147        ˇ            elif item.value < 0:
22148        ˇ                break
22149        ˇ            else:
22150        ˇ                with item.context() as ctx:
22151        ˇ                    yield count
22152        ˇ        else:
22153        ˇ            log('while else')
22154        ˇ    else:
22155        ˇ        log('for else')
22156    "});
22157    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22158    cx.assert_editor_state(indoc! {"
22159        def main():
22160            ˇfor item in items:
22161                ˇwhile item.active:
22162                    ˇif item.value > 10:
22163                        ˇcontinue
22164                    ˇelif item.value < 0:
22165                        ˇbreak
22166                    ˇelse:
22167                        ˇwith item.context() as ctx:
22168                            ˇyield count
22169                ˇelse:
22170                    ˇlog('while else')
22171            ˇelse:
22172                ˇlog('for else')
22173    "});
22174    // test relative indent is preserved when tab
22175    // for `if`, `elif`, `else`, `while`, `with` and `for`
22176    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22177    cx.assert_editor_state(indoc! {"
22178        def main():
22179                ˇfor item in items:
22180                    ˇwhile item.active:
22181                        ˇif item.value > 10:
22182                            ˇcontinue
22183                        ˇelif item.value < 0:
22184                            ˇbreak
22185                        ˇelse:
22186                            ˇwith item.context() as ctx:
22187                                ˇyield count
22188                    ˇelse:
22189                        ˇlog('while else')
22190                ˇelse:
22191                    ˇlog('for else')
22192    "});
22193
22194    // test cursor move to start of each line on tab
22195    // for `try`, `except`, `else`, `finally`, `match` and `def`
22196    cx.set_state(indoc! {"
22197        def main():
22198        ˇ    try:
22199        ˇ        fetch()
22200        ˇ    except ValueError:
22201        ˇ        handle_error()
22202        ˇ    else:
22203        ˇ        match value:
22204        ˇ            case _:
22205        ˇ    finally:
22206        ˇ        def status():
22207        ˇ            return 0
22208    "});
22209    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22210    cx.assert_editor_state(indoc! {"
22211        def main():
22212            ˇtry:
22213                ˇfetch()
22214            ˇexcept ValueError:
22215                ˇhandle_error()
22216            ˇelse:
22217                ˇmatch value:
22218                    ˇcase _:
22219            ˇfinally:
22220                ˇdef status():
22221                    ˇreturn 0
22222    "});
22223    // test relative indent is preserved when tab
22224    // for `try`, `except`, `else`, `finally`, `match` and `def`
22225    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22226    cx.assert_editor_state(indoc! {"
22227        def main():
22228                ˇtry:
22229                    ˇfetch()
22230                ˇexcept ValueError:
22231                    ˇhandle_error()
22232                ˇelse:
22233                    ˇmatch value:
22234                        ˇcase _:
22235                ˇfinally:
22236                    ˇdef status():
22237                        ˇreturn 0
22238    "});
22239}
22240
22241#[gpui::test]
22242async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
22243    init_test(cx, |_| {});
22244
22245    let mut cx = EditorTestContext::new(cx).await;
22246    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22247    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22248
22249    // test `else` auto outdents when typed inside `if` block
22250    cx.set_state(indoc! {"
22251        def main():
22252            if i == 2:
22253                return
22254                ˇ
22255    "});
22256    cx.update_editor(|editor, window, cx| {
22257        editor.handle_input("else:", window, cx);
22258    });
22259    cx.assert_editor_state(indoc! {"
22260        def main():
22261            if i == 2:
22262                return
22263            else:ˇ
22264    "});
22265
22266    // test `except` auto outdents when typed inside `try` block
22267    cx.set_state(indoc! {"
22268        def main():
22269            try:
22270                i = 2
22271                ˇ
22272    "});
22273    cx.update_editor(|editor, window, cx| {
22274        editor.handle_input("except:", window, cx);
22275    });
22276    cx.assert_editor_state(indoc! {"
22277        def main():
22278            try:
22279                i = 2
22280            except:ˇ
22281    "});
22282
22283    // test `else` auto outdents when typed inside `except` block
22284    cx.set_state(indoc! {"
22285        def main():
22286            try:
22287                i = 2
22288            except:
22289                j = 2
22290                ˇ
22291    "});
22292    cx.update_editor(|editor, window, cx| {
22293        editor.handle_input("else:", window, cx);
22294    });
22295    cx.assert_editor_state(indoc! {"
22296        def main():
22297            try:
22298                i = 2
22299            except:
22300                j = 2
22301            else:ˇ
22302    "});
22303
22304    // test `finally` auto outdents when typed inside `else` block
22305    cx.set_state(indoc! {"
22306        def main():
22307            try:
22308                i = 2
22309            except:
22310                j = 2
22311            else:
22312                k = 2
22313                ˇ
22314    "});
22315    cx.update_editor(|editor, window, cx| {
22316        editor.handle_input("finally:", window, cx);
22317    });
22318    cx.assert_editor_state(indoc! {"
22319        def main():
22320            try:
22321                i = 2
22322            except:
22323                j = 2
22324            else:
22325                k = 2
22326            finally:ˇ
22327    "});
22328
22329    // test `else` does not outdents when typed inside `except` block right after for block
22330    cx.set_state(indoc! {"
22331        def main():
22332            try:
22333                i = 2
22334            except:
22335                for i in range(n):
22336                    pass
22337                ˇ
22338    "});
22339    cx.update_editor(|editor, window, cx| {
22340        editor.handle_input("else:", window, cx);
22341    });
22342    cx.assert_editor_state(indoc! {"
22343        def main():
22344            try:
22345                i = 2
22346            except:
22347                for i in range(n):
22348                    pass
22349                else:ˇ
22350    "});
22351
22352    // test `finally` auto outdents when typed inside `else` block right after for block
22353    cx.set_state(indoc! {"
22354        def main():
22355            try:
22356                i = 2
22357            except:
22358                j = 2
22359            else:
22360                for i in range(n):
22361                    pass
22362                ˇ
22363    "});
22364    cx.update_editor(|editor, window, cx| {
22365        editor.handle_input("finally:", window, cx);
22366    });
22367    cx.assert_editor_state(indoc! {"
22368        def main():
22369            try:
22370                i = 2
22371            except:
22372                j = 2
22373            else:
22374                for i in range(n):
22375                    pass
22376            finally:ˇ
22377    "});
22378
22379    // test `except` outdents to inner "try" block
22380    cx.set_state(indoc! {"
22381        def main():
22382            try:
22383                i = 2
22384                if i == 2:
22385                    try:
22386                        i = 3
22387                        ˇ
22388    "});
22389    cx.update_editor(|editor, window, cx| {
22390        editor.handle_input("except:", window, cx);
22391    });
22392    cx.assert_editor_state(indoc! {"
22393        def main():
22394            try:
22395                i = 2
22396                if i == 2:
22397                    try:
22398                        i = 3
22399                    except:ˇ
22400    "});
22401
22402    // test `except` outdents to outer "try" block
22403    cx.set_state(indoc! {"
22404        def main():
22405            try:
22406                i = 2
22407                if i == 2:
22408                    try:
22409                        i = 3
22410                ˇ
22411    "});
22412    cx.update_editor(|editor, window, cx| {
22413        editor.handle_input("except:", window, cx);
22414    });
22415    cx.assert_editor_state(indoc! {"
22416        def main():
22417            try:
22418                i = 2
22419                if i == 2:
22420                    try:
22421                        i = 3
22422            except:ˇ
22423    "});
22424
22425    // test `else` stays at correct indent when typed after `for` block
22426    cx.set_state(indoc! {"
22427        def main():
22428            for i in range(10):
22429                if i == 3:
22430                    break
22431            ˇ
22432    "});
22433    cx.update_editor(|editor, window, cx| {
22434        editor.handle_input("else:", window, cx);
22435    });
22436    cx.assert_editor_state(indoc! {"
22437        def main():
22438            for i in range(10):
22439                if i == 3:
22440                    break
22441            else:ˇ
22442    "});
22443
22444    // test does not outdent on typing after line with square brackets
22445    cx.set_state(indoc! {"
22446        def f() -> list[str]:
22447            ˇ
22448    "});
22449    cx.update_editor(|editor, window, cx| {
22450        editor.handle_input("a", window, cx);
22451    });
22452    cx.assert_editor_state(indoc! {"
22453        def f() -> list[str]:
2245422455    "});
22456
22457    // test does not outdent on typing : after case keyword
22458    cx.set_state(indoc! {"
22459        match 1:
22460            caseˇ
22461    "});
22462    cx.update_editor(|editor, window, cx| {
22463        editor.handle_input(":", window, cx);
22464    });
22465    cx.assert_editor_state(indoc! {"
22466        match 1:
22467            case:ˇ
22468    "});
22469}
22470
22471#[gpui::test]
22472async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
22473    init_test(cx, |_| {});
22474    update_test_language_settings(cx, |settings| {
22475        settings.defaults.extend_comment_on_newline = Some(false);
22476    });
22477    let mut cx = EditorTestContext::new(cx).await;
22478    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22479    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22480
22481    // test correct indent after newline on comment
22482    cx.set_state(indoc! {"
22483        # COMMENT:ˇ
22484    "});
22485    cx.update_editor(|editor, window, cx| {
22486        editor.newline(&Newline, window, cx);
22487    });
22488    cx.assert_editor_state(indoc! {"
22489        # COMMENT:
22490        ˇ
22491    "});
22492
22493    // test correct indent after newline in brackets
22494    cx.set_state(indoc! {"
22495        {ˇ}
22496    "});
22497    cx.update_editor(|editor, window, cx| {
22498        editor.newline(&Newline, window, cx);
22499    });
22500    cx.run_until_parked();
22501    cx.assert_editor_state(indoc! {"
22502        {
22503            ˇ
22504        }
22505    "});
22506
22507    cx.set_state(indoc! {"
22508        (ˇ)
22509    "});
22510    cx.update_editor(|editor, window, cx| {
22511        editor.newline(&Newline, window, cx);
22512    });
22513    cx.run_until_parked();
22514    cx.assert_editor_state(indoc! {"
22515        (
22516            ˇ
22517        )
22518    "});
22519
22520    // do not indent after empty lists or dictionaries
22521    cx.set_state(indoc! {"
22522        a = []ˇ
22523    "});
22524    cx.update_editor(|editor, window, cx| {
22525        editor.newline(&Newline, window, cx);
22526    });
22527    cx.run_until_parked();
22528    cx.assert_editor_state(indoc! {"
22529        a = []
22530        ˇ
22531    "});
22532}
22533
22534fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
22535    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
22536    point..point
22537}
22538
22539#[track_caller]
22540fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
22541    let (text, ranges) = marked_text_ranges(marked_text, true);
22542    assert_eq!(editor.text(cx), text);
22543    assert_eq!(
22544        editor.selections.ranges(cx),
22545        ranges,
22546        "Assert selections are {}",
22547        marked_text
22548    );
22549}
22550
22551pub fn handle_signature_help_request(
22552    cx: &mut EditorLspTestContext,
22553    mocked_response: lsp::SignatureHelp,
22554) -> impl Future<Output = ()> + use<> {
22555    let mut request =
22556        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
22557            let mocked_response = mocked_response.clone();
22558            async move { Ok(Some(mocked_response)) }
22559        });
22560
22561    async move {
22562        request.next().await;
22563    }
22564}
22565
22566#[track_caller]
22567pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
22568    cx.update_editor(|editor, _, _| {
22569        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
22570            let entries = menu.entries.borrow();
22571            let entries = entries
22572                .iter()
22573                .map(|entry| entry.string.as_str())
22574                .collect::<Vec<_>>();
22575            assert_eq!(entries, expected);
22576        } else {
22577            panic!("Expected completions menu");
22578        }
22579    });
22580}
22581
22582/// Handle completion request passing a marked string specifying where the completion
22583/// should be triggered from using '|' character, what range should be replaced, and what completions
22584/// should be returned using '<' and '>' to delimit the range.
22585///
22586/// Also see `handle_completion_request_with_insert_and_replace`.
22587#[track_caller]
22588pub fn handle_completion_request(
22589    marked_string: &str,
22590    completions: Vec<&'static str>,
22591    is_incomplete: bool,
22592    counter: Arc<AtomicUsize>,
22593    cx: &mut EditorLspTestContext,
22594) -> impl Future<Output = ()> {
22595    let complete_from_marker: TextRangeMarker = '|'.into();
22596    let replace_range_marker: TextRangeMarker = ('<', '>').into();
22597    let (_, mut marked_ranges) = marked_text_ranges_by(
22598        marked_string,
22599        vec![complete_from_marker.clone(), replace_range_marker.clone()],
22600    );
22601
22602    let complete_from_position =
22603        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
22604    let replace_range =
22605        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
22606
22607    let mut request =
22608        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
22609            let completions = completions.clone();
22610            counter.fetch_add(1, atomic::Ordering::Release);
22611            async move {
22612                assert_eq!(params.text_document_position.text_document.uri, url.clone());
22613                assert_eq!(
22614                    params.text_document_position.position,
22615                    complete_from_position
22616                );
22617                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
22618                    is_incomplete: is_incomplete,
22619                    item_defaults: None,
22620                    items: completions
22621                        .iter()
22622                        .map(|completion_text| lsp::CompletionItem {
22623                            label: completion_text.to_string(),
22624                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
22625                                range: replace_range,
22626                                new_text: completion_text.to_string(),
22627                            })),
22628                            ..Default::default()
22629                        })
22630                        .collect(),
22631                })))
22632            }
22633        });
22634
22635    async move {
22636        request.next().await;
22637    }
22638}
22639
22640/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
22641/// given instead, which also contains an `insert` range.
22642///
22643/// This function uses markers to define ranges:
22644/// - `|` marks the cursor position
22645/// - `<>` marks the replace range
22646/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
22647pub fn handle_completion_request_with_insert_and_replace(
22648    cx: &mut EditorLspTestContext,
22649    marked_string: &str,
22650    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
22651    counter: Arc<AtomicUsize>,
22652) -> impl Future<Output = ()> {
22653    let complete_from_marker: TextRangeMarker = '|'.into();
22654    let replace_range_marker: TextRangeMarker = ('<', '>').into();
22655    let insert_range_marker: TextRangeMarker = ('{', '}').into();
22656
22657    let (_, mut marked_ranges) = marked_text_ranges_by(
22658        marked_string,
22659        vec![
22660            complete_from_marker.clone(),
22661            replace_range_marker.clone(),
22662            insert_range_marker.clone(),
22663        ],
22664    );
22665
22666    let complete_from_position =
22667        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
22668    let replace_range =
22669        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
22670
22671    let insert_range = match marked_ranges.remove(&insert_range_marker) {
22672        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
22673        _ => lsp::Range {
22674            start: replace_range.start,
22675            end: complete_from_position,
22676        },
22677    };
22678
22679    let mut request =
22680        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
22681            let completions = completions.clone();
22682            counter.fetch_add(1, atomic::Ordering::Release);
22683            async move {
22684                assert_eq!(params.text_document_position.text_document.uri, url.clone());
22685                assert_eq!(
22686                    params.text_document_position.position, complete_from_position,
22687                    "marker `|` position doesn't match",
22688                );
22689                Ok(Some(lsp::CompletionResponse::Array(
22690                    completions
22691                        .iter()
22692                        .map(|(label, new_text)| lsp::CompletionItem {
22693                            label: label.to_string(),
22694                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22695                                lsp::InsertReplaceEdit {
22696                                    insert: insert_range,
22697                                    replace: replace_range,
22698                                    new_text: new_text.to_string(),
22699                                },
22700                            )),
22701                            ..Default::default()
22702                        })
22703                        .collect(),
22704                )))
22705            }
22706        });
22707
22708    async move {
22709        request.next().await;
22710    }
22711}
22712
22713fn handle_resolve_completion_request(
22714    cx: &mut EditorLspTestContext,
22715    edits: Option<Vec<(&'static str, &'static str)>>,
22716) -> impl Future<Output = ()> {
22717    let edits = edits.map(|edits| {
22718        edits
22719            .iter()
22720            .map(|(marked_string, new_text)| {
22721                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
22722                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
22723                lsp::TextEdit::new(replace_range, new_text.to_string())
22724            })
22725            .collect::<Vec<_>>()
22726    });
22727
22728    let mut request =
22729        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
22730            let edits = edits.clone();
22731            async move {
22732                Ok(lsp::CompletionItem {
22733                    additional_text_edits: edits,
22734                    ..Default::default()
22735                })
22736            }
22737        });
22738
22739    async move {
22740        request.next().await;
22741    }
22742}
22743
22744pub(crate) fn update_test_language_settings(
22745    cx: &mut TestAppContext,
22746    f: impl Fn(&mut AllLanguageSettingsContent),
22747) {
22748    cx.update(|cx| {
22749        SettingsStore::update_global(cx, |store, cx| {
22750            store.update_user_settings::<AllLanguageSettings>(cx, f);
22751        });
22752    });
22753}
22754
22755pub(crate) fn update_test_project_settings(
22756    cx: &mut TestAppContext,
22757    f: impl Fn(&mut ProjectSettings),
22758) {
22759    cx.update(|cx| {
22760        SettingsStore::update_global(cx, |store, cx| {
22761            store.update_user_settings::<ProjectSettings>(cx, f);
22762        });
22763    });
22764}
22765
22766pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
22767    cx.update(|cx| {
22768        assets::Assets.load_test_fonts(cx);
22769        let store = SettingsStore::test(cx);
22770        cx.set_global(store);
22771        theme::init(theme::LoadThemes::JustBase, cx);
22772        release_channel::init(SemanticVersion::default(), cx);
22773        client::init_settings(cx);
22774        language::init(cx);
22775        Project::init_settings(cx);
22776        workspace::init_settings(cx);
22777        crate::init(cx);
22778    });
22779    zlog::init_test();
22780    update_test_language_settings(cx, f);
22781}
22782
22783#[track_caller]
22784fn assert_hunk_revert(
22785    not_reverted_text_with_selections: &str,
22786    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
22787    expected_reverted_text_with_selections: &str,
22788    base_text: &str,
22789    cx: &mut EditorLspTestContext,
22790) {
22791    cx.set_state(not_reverted_text_with_selections);
22792    cx.set_head_text(base_text);
22793    cx.executor().run_until_parked();
22794
22795    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
22796        let snapshot = editor.snapshot(window, cx);
22797        let reverted_hunk_statuses = snapshot
22798            .buffer_snapshot
22799            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
22800            .map(|hunk| hunk.status().kind)
22801            .collect::<Vec<_>>();
22802
22803        editor.git_restore(&Default::default(), window, cx);
22804        reverted_hunk_statuses
22805    });
22806    cx.executor().run_until_parked();
22807    cx.assert_editor_state(expected_reverted_text_with_selections);
22808    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
22809}
22810
22811#[gpui::test(iterations = 10)]
22812async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
22813    init_test(cx, |_| {});
22814
22815    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
22816    let counter = diagnostic_requests.clone();
22817
22818    let fs = FakeFs::new(cx.executor());
22819    fs.insert_tree(
22820        path!("/a"),
22821        json!({
22822            "first.rs": "fn main() { let a = 5; }",
22823            "second.rs": "// Test file",
22824        }),
22825    )
22826    .await;
22827
22828    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22829    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22830    let cx = &mut VisualTestContext::from_window(*workspace, cx);
22831
22832    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22833    language_registry.add(rust_lang());
22834    let mut fake_servers = language_registry.register_fake_lsp(
22835        "Rust",
22836        FakeLspAdapter {
22837            capabilities: lsp::ServerCapabilities {
22838                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
22839                    lsp::DiagnosticOptions {
22840                        identifier: None,
22841                        inter_file_dependencies: true,
22842                        workspace_diagnostics: true,
22843                        work_done_progress_options: Default::default(),
22844                    },
22845                )),
22846                ..Default::default()
22847            },
22848            ..Default::default()
22849        },
22850    );
22851
22852    let editor = workspace
22853        .update(cx, |workspace, window, cx| {
22854            workspace.open_abs_path(
22855                PathBuf::from(path!("/a/first.rs")),
22856                OpenOptions::default(),
22857                window,
22858                cx,
22859            )
22860        })
22861        .unwrap()
22862        .await
22863        .unwrap()
22864        .downcast::<Editor>()
22865        .unwrap();
22866    let fake_server = fake_servers.next().await.unwrap();
22867    let server_id = fake_server.server.server_id();
22868    let mut first_request = fake_server
22869        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
22870            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
22871            let result_id = Some(new_result_id.to_string());
22872            assert_eq!(
22873                params.text_document.uri,
22874                lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
22875            );
22876            async move {
22877                Ok(lsp::DocumentDiagnosticReportResult::Report(
22878                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
22879                        related_documents: None,
22880                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
22881                            items: Vec::new(),
22882                            result_id,
22883                        },
22884                    }),
22885                ))
22886            }
22887        });
22888
22889    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
22890        project.update(cx, |project, cx| {
22891            let buffer_id = editor
22892                .read(cx)
22893                .buffer()
22894                .read(cx)
22895                .as_singleton()
22896                .expect("created a singleton buffer")
22897                .read(cx)
22898                .remote_id();
22899            let buffer_result_id = project
22900                .lsp_store()
22901                .read(cx)
22902                .result_id(server_id, buffer_id, cx);
22903            assert_eq!(expected, buffer_result_id);
22904        });
22905    };
22906
22907    ensure_result_id(None, cx);
22908    cx.executor().advance_clock(Duration::from_millis(60));
22909    cx.executor().run_until_parked();
22910    assert_eq!(
22911        diagnostic_requests.load(atomic::Ordering::Acquire),
22912        1,
22913        "Opening file should trigger diagnostic request"
22914    );
22915    first_request
22916        .next()
22917        .await
22918        .expect("should have sent the first diagnostics pull request");
22919    ensure_result_id(Some("1".to_string()), cx);
22920
22921    // Editing should trigger diagnostics
22922    editor.update_in(cx, |editor, window, cx| {
22923        editor.handle_input("2", window, cx)
22924    });
22925    cx.executor().advance_clock(Duration::from_millis(60));
22926    cx.executor().run_until_parked();
22927    assert_eq!(
22928        diagnostic_requests.load(atomic::Ordering::Acquire),
22929        2,
22930        "Editing should trigger diagnostic request"
22931    );
22932    ensure_result_id(Some("2".to_string()), cx);
22933
22934    // Moving cursor should not trigger diagnostic request
22935    editor.update_in(cx, |editor, window, cx| {
22936        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22937            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
22938        });
22939    });
22940    cx.executor().advance_clock(Duration::from_millis(60));
22941    cx.executor().run_until_parked();
22942    assert_eq!(
22943        diagnostic_requests.load(atomic::Ordering::Acquire),
22944        2,
22945        "Cursor movement should not trigger diagnostic request"
22946    );
22947    ensure_result_id(Some("2".to_string()), cx);
22948    // Multiple rapid edits should be debounced
22949    for _ in 0..5 {
22950        editor.update_in(cx, |editor, window, cx| {
22951            editor.handle_input("x", window, cx)
22952        });
22953    }
22954    cx.executor().advance_clock(Duration::from_millis(60));
22955    cx.executor().run_until_parked();
22956
22957    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
22958    assert!(
22959        final_requests <= 4,
22960        "Multiple rapid edits should be debounced (got {final_requests} requests)",
22961    );
22962    ensure_result_id(Some(final_requests.to_string()), cx);
22963}
22964
22965#[gpui::test]
22966async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
22967    // Regression test for issue #11671
22968    // Previously, adding a cursor after moving multiple cursors would reset
22969    // the cursor count instead of adding to the existing cursors.
22970    init_test(cx, |_| {});
22971    let mut cx = EditorTestContext::new(cx).await;
22972
22973    // Create a simple buffer with cursor at start
22974    cx.set_state(indoc! {"
22975        ˇaaaa
22976        bbbb
22977        cccc
22978        dddd
22979        eeee
22980        ffff
22981        gggg
22982        hhhh"});
22983
22984    // Add 2 cursors below (so we have 3 total)
22985    cx.update_editor(|editor, window, cx| {
22986        editor.add_selection_below(&Default::default(), window, cx);
22987        editor.add_selection_below(&Default::default(), window, cx);
22988    });
22989
22990    // Verify we have 3 cursors
22991    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
22992    assert_eq!(
22993        initial_count, 3,
22994        "Should have 3 cursors after adding 2 below"
22995    );
22996
22997    // Move down one line
22998    cx.update_editor(|editor, window, cx| {
22999        editor.move_down(&MoveDown, window, cx);
23000    });
23001
23002    // Add another cursor below
23003    cx.update_editor(|editor, window, cx| {
23004        editor.add_selection_below(&Default::default(), window, cx);
23005    });
23006
23007    // Should now have 4 cursors (3 original + 1 new)
23008    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
23009    assert_eq!(
23010        final_count, 4,
23011        "Should have 4 cursors after moving and adding another"
23012    );
23013}
23014
23015#[gpui::test(iterations = 10)]
23016async fn test_document_colors(cx: &mut TestAppContext) {
23017    let expected_color = Rgba {
23018        r: 0.33,
23019        g: 0.33,
23020        b: 0.33,
23021        a: 0.33,
23022    };
23023
23024    init_test(cx, |_| {});
23025
23026    let fs = FakeFs::new(cx.executor());
23027    fs.insert_tree(
23028        path!("/a"),
23029        json!({
23030            "first.rs": "fn main() { let a = 5; }",
23031        }),
23032    )
23033    .await;
23034
23035    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23036    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23037    let cx = &mut VisualTestContext::from_window(*workspace, cx);
23038
23039    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23040    language_registry.add(rust_lang());
23041    let mut fake_servers = language_registry.register_fake_lsp(
23042        "Rust",
23043        FakeLspAdapter {
23044            capabilities: lsp::ServerCapabilities {
23045                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
23046                ..lsp::ServerCapabilities::default()
23047            },
23048            name: "rust-analyzer",
23049            ..FakeLspAdapter::default()
23050        },
23051    );
23052    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
23053        "Rust",
23054        FakeLspAdapter {
23055            capabilities: lsp::ServerCapabilities {
23056                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
23057                ..lsp::ServerCapabilities::default()
23058            },
23059            name: "not-rust-analyzer",
23060            ..FakeLspAdapter::default()
23061        },
23062    );
23063
23064    let editor = workspace
23065        .update(cx, |workspace, window, cx| {
23066            workspace.open_abs_path(
23067                PathBuf::from(path!("/a/first.rs")),
23068                OpenOptions::default(),
23069                window,
23070                cx,
23071            )
23072        })
23073        .unwrap()
23074        .await
23075        .unwrap()
23076        .downcast::<Editor>()
23077        .unwrap();
23078    let fake_language_server = fake_servers.next().await.unwrap();
23079    let fake_language_server_without_capabilities =
23080        fake_servers_without_capabilities.next().await.unwrap();
23081    let requests_made = Arc::new(AtomicUsize::new(0));
23082    let closure_requests_made = Arc::clone(&requests_made);
23083    let mut color_request_handle = fake_language_server
23084        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
23085            let requests_made = Arc::clone(&closure_requests_made);
23086            async move {
23087                assert_eq!(
23088                    params.text_document.uri,
23089                    lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
23090                );
23091                requests_made.fetch_add(1, atomic::Ordering::Release);
23092                Ok(vec![
23093                    lsp::ColorInformation {
23094                        range: lsp::Range {
23095                            start: lsp::Position {
23096                                line: 0,
23097                                character: 0,
23098                            },
23099                            end: lsp::Position {
23100                                line: 0,
23101                                character: 1,
23102                            },
23103                        },
23104                        color: lsp::Color {
23105                            red: 0.33,
23106                            green: 0.33,
23107                            blue: 0.33,
23108                            alpha: 0.33,
23109                        },
23110                    },
23111                    lsp::ColorInformation {
23112                        range: lsp::Range {
23113                            start: lsp::Position {
23114                                line: 0,
23115                                character: 0,
23116                            },
23117                            end: lsp::Position {
23118                                line: 0,
23119                                character: 1,
23120                            },
23121                        },
23122                        color: lsp::Color {
23123                            red: 0.33,
23124                            green: 0.33,
23125                            blue: 0.33,
23126                            alpha: 0.33,
23127                        },
23128                    },
23129                ])
23130            }
23131        });
23132
23133    let _handle = fake_language_server_without_capabilities
23134        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
23135            panic!("Should not be called");
23136        });
23137    cx.executor().advance_clock(Duration::from_millis(100));
23138    color_request_handle.next().await.unwrap();
23139    cx.run_until_parked();
23140    assert_eq!(
23141        1,
23142        requests_made.load(atomic::Ordering::Acquire),
23143        "Should query for colors once per editor open"
23144    );
23145    editor.update_in(cx, |editor, _, cx| {
23146        assert_eq!(
23147            vec![expected_color],
23148            extract_color_inlays(editor, cx),
23149            "Should have an initial inlay"
23150        );
23151    });
23152
23153    // opening another file in a split should not influence the LSP query counter
23154    workspace
23155        .update(cx, |workspace, window, cx| {
23156            assert_eq!(
23157                workspace.panes().len(),
23158                1,
23159                "Should have one pane with one editor"
23160            );
23161            workspace.move_item_to_pane_in_direction(
23162                &MoveItemToPaneInDirection {
23163                    direction: SplitDirection::Right,
23164                    focus: false,
23165                    clone: true,
23166                },
23167                window,
23168                cx,
23169            );
23170        })
23171        .unwrap();
23172    cx.run_until_parked();
23173    workspace
23174        .update(cx, |workspace, _, cx| {
23175            let panes = workspace.panes();
23176            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
23177            for pane in panes {
23178                let editor = pane
23179                    .read(cx)
23180                    .active_item()
23181                    .and_then(|item| item.downcast::<Editor>())
23182                    .expect("Should have opened an editor in each split");
23183                let editor_file = editor
23184                    .read(cx)
23185                    .buffer()
23186                    .read(cx)
23187                    .as_singleton()
23188                    .expect("test deals with singleton buffers")
23189                    .read(cx)
23190                    .file()
23191                    .expect("test buffese should have a file")
23192                    .path();
23193                assert_eq!(
23194                    editor_file.as_ref(),
23195                    Path::new("first.rs"),
23196                    "Both editors should be opened for the same file"
23197                )
23198            }
23199        })
23200        .unwrap();
23201
23202    cx.executor().advance_clock(Duration::from_millis(500));
23203    let save = editor.update_in(cx, |editor, window, cx| {
23204        editor.move_to_end(&MoveToEnd, window, cx);
23205        editor.handle_input("dirty", window, cx);
23206        editor.save(
23207            SaveOptions {
23208                format: true,
23209                autosave: true,
23210            },
23211            project.clone(),
23212            window,
23213            cx,
23214        )
23215    });
23216    save.await.unwrap();
23217
23218    color_request_handle.next().await.unwrap();
23219    cx.run_until_parked();
23220    assert_eq!(
23221        3,
23222        requests_made.load(atomic::Ordering::Acquire),
23223        "Should query for colors once per save and once per formatting after save"
23224    );
23225
23226    drop(editor);
23227    let close = workspace
23228        .update(cx, |workspace, window, cx| {
23229            workspace.active_pane().update(cx, |pane, cx| {
23230                pane.close_active_item(&CloseActiveItem::default(), window, cx)
23231            })
23232        })
23233        .unwrap();
23234    close.await.unwrap();
23235    let close = workspace
23236        .update(cx, |workspace, window, cx| {
23237            workspace.active_pane().update(cx, |pane, cx| {
23238                pane.close_active_item(&CloseActiveItem::default(), window, cx)
23239            })
23240        })
23241        .unwrap();
23242    close.await.unwrap();
23243    assert_eq!(
23244        3,
23245        requests_made.load(atomic::Ordering::Acquire),
23246        "After saving and closing all editors, no extra requests should be made"
23247    );
23248    workspace
23249        .update(cx, |workspace, _, cx| {
23250            assert!(
23251                workspace.active_item(cx).is_none(),
23252                "Should close all editors"
23253            )
23254        })
23255        .unwrap();
23256
23257    workspace
23258        .update(cx, |workspace, window, cx| {
23259            workspace.active_pane().update(cx, |pane, cx| {
23260                pane.navigate_backward(window, cx);
23261            })
23262        })
23263        .unwrap();
23264    cx.executor().advance_clock(Duration::from_millis(100));
23265    cx.run_until_parked();
23266    let editor = workspace
23267        .update(cx, |workspace, _, cx| {
23268            workspace
23269                .active_item(cx)
23270                .expect("Should have reopened the editor again after navigating back")
23271                .downcast::<Editor>()
23272                .expect("Should be an editor")
23273        })
23274        .unwrap();
23275    color_request_handle.next().await.unwrap();
23276    assert_eq!(
23277        3,
23278        requests_made.load(atomic::Ordering::Acquire),
23279        "Cache should be reused on buffer close and reopen"
23280    );
23281    editor.update(cx, |editor, cx| {
23282        assert_eq!(
23283            vec![expected_color],
23284            extract_color_inlays(editor, cx),
23285            "Should have an initial inlay"
23286        );
23287    });
23288}
23289
23290#[gpui::test]
23291async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
23292    init_test(cx, |_| {});
23293    let (editor, cx) = cx.add_window_view(Editor::single_line);
23294    editor.update_in(cx, |editor, window, cx| {
23295        editor.set_text("oops\n\nwow\n", window, cx)
23296    });
23297    cx.run_until_parked();
23298    editor.update(cx, |editor, cx| {
23299        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
23300    });
23301    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
23302    cx.run_until_parked();
23303    editor.update(cx, |editor, cx| {
23304        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
23305    });
23306}
23307
23308#[track_caller]
23309fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
23310    editor
23311        .all_inlays(cx)
23312        .into_iter()
23313        .filter_map(|inlay| inlay.get_color())
23314        .map(Rgba::from)
23315        .collect()
23316}