editor_tests.rs

    1use super::*;
    2use crate::{
    3    JoinLines,
    4    code_context_menus::CodeContextMenu,
    5    inline_completion_tests::FakeInlineCompletionProvider,
    6    linked_editing_ranges::LinkedEditingRanges,
    7    scroll::scroll_amount::ScrollAmount,
    8    test::{
    9        assert_text_with_selections, build_editor,
   10        editor_lsp_test_context::{EditorLspTestContext, git_commit_lang},
   11        editor_test_context::EditorTestContext,
   12        select_ranges,
   13    },
   14};
   15use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
   16use futures::StreamExt;
   17use gpui::{
   18    BackgroundExecutor, DismissEvent, Rgba, SemanticVersion, TestAppContext, UpdateGlobal,
   19    VisualTestContext, WindowBounds, WindowOptions, div,
   20};
   21use indoc::indoc;
   22use language::{
   23    BracketPairConfig,
   24    Capability::ReadWrite,
   25    DiagnosticSourceKind, FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageMatcher,
   26    LanguageName, Override, Point,
   27    language_settings::{
   28        AllLanguageSettings, AllLanguageSettingsContent, CompletionSettings, FormatterList,
   29        LanguageSettingsContent, LspInsertMode, PrettierSettings, SelectedFormatter,
   30    },
   31    tree_sitter_python,
   32};
   33use language_settings::{Formatter, IndentGuideSettings};
   34use lsp::CompletionParams;
   35use multi_buffer::{IndentGuide, PathKey};
   36use parking_lot::Mutex;
   37use pretty_assertions::{assert_eq, assert_ne};
   38use project::{
   39    FakeFs,
   40    debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
   41    project_settings::{LspSettings, ProjectSettings},
   42};
   43use serde_json::{self, json};
   44use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
   45use std::{
   46    iter,
   47    sync::atomic::{self, AtomicUsize},
   48};
   49use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
   50use text::ToPoint as _;
   51use unindent::Unindent;
   52use util::{
   53    assert_set_eq, path,
   54    test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text},
   55    uri,
   56};
   57use workspace::{
   58    CloseActiveItem, CloseAllItems, CloseOtherItems, MoveItemToPaneInDirection, NavigationEntry,
   59    OpenOptions, ViewId,
   60    item::{FollowEvent, FollowableItem, Item, ItemHandle, SaveOptions},
   61};
   62
   63#[gpui::test]
   64fn test_edit_events(cx: &mut TestAppContext) {
   65    init_test(cx, |_| {});
   66
   67    let buffer = cx.new(|cx| {
   68        let mut buffer = language::Buffer::local("123456", cx);
   69        buffer.set_group_interval(Duration::from_secs(1));
   70        buffer
   71    });
   72
   73    let events = Rc::new(RefCell::new(Vec::new()));
   74    let editor1 = cx.add_window({
   75        let events = events.clone();
   76        |window, cx| {
   77            let entity = cx.entity().clone();
   78            cx.subscribe_in(
   79                &entity,
   80                window,
   81                move |_, _, event: &EditorEvent, _, _| match event {
   82                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
   83                    EditorEvent::BufferEdited => {
   84                        events.borrow_mut().push(("editor1", "buffer edited"))
   85                    }
   86                    _ => {}
   87                },
   88            )
   89            .detach();
   90            Editor::for_buffer(buffer.clone(), None, window, cx)
   91        }
   92    });
   93
   94    let editor2 = cx.add_window({
   95        let events = events.clone();
   96        |window, cx| {
   97            cx.subscribe_in(
   98                &cx.entity().clone(),
   99                window,
  100                move |_, _, event: &EditorEvent, _, _| match event {
  101                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
  102                    EditorEvent::BufferEdited => {
  103                        events.borrow_mut().push(("editor2", "buffer edited"))
  104                    }
  105                    _ => {}
  106                },
  107            )
  108            .detach();
  109            Editor::for_buffer(buffer.clone(), None, window, cx)
  110        }
  111    });
  112
  113    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  114
  115    // Mutating editor 1 will emit an `Edited` event only for that editor.
  116    _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
  117    assert_eq!(
  118        mem::take(&mut *events.borrow_mut()),
  119        [
  120            ("editor1", "edited"),
  121            ("editor1", "buffer edited"),
  122            ("editor2", "buffer edited"),
  123        ]
  124    );
  125
  126    // Mutating editor 2 will emit an `Edited` event only for that editor.
  127    _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
  128    assert_eq!(
  129        mem::take(&mut *events.borrow_mut()),
  130        [
  131            ("editor2", "edited"),
  132            ("editor1", "buffer edited"),
  133            ("editor2", "buffer edited"),
  134        ]
  135    );
  136
  137    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  138    _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  139    assert_eq!(
  140        mem::take(&mut *events.borrow_mut()),
  141        [
  142            ("editor1", "edited"),
  143            ("editor1", "buffer edited"),
  144            ("editor2", "buffer edited"),
  145        ]
  146    );
  147
  148    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  149    _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  150    assert_eq!(
  151        mem::take(&mut *events.borrow_mut()),
  152        [
  153            ("editor1", "edited"),
  154            ("editor1", "buffer edited"),
  155            ("editor2", "buffer edited"),
  156        ]
  157    );
  158
  159    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  160    _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  161    assert_eq!(
  162        mem::take(&mut *events.borrow_mut()),
  163        [
  164            ("editor2", "edited"),
  165            ("editor1", "buffer edited"),
  166            ("editor2", "buffer edited"),
  167        ]
  168    );
  169
  170    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  171    _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  172    assert_eq!(
  173        mem::take(&mut *events.borrow_mut()),
  174        [
  175            ("editor2", "edited"),
  176            ("editor1", "buffer edited"),
  177            ("editor2", "buffer edited"),
  178        ]
  179    );
  180
  181    // No event is emitted when the mutation is a no-op.
  182    _ = editor2.update(cx, |editor, window, cx| {
  183        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  184            s.select_ranges([0..0])
  185        });
  186
  187        editor.backspace(&Backspace, window, cx);
  188    });
  189    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  190}
  191
  192#[gpui::test]
  193fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  194    init_test(cx, |_| {});
  195
  196    let mut now = Instant::now();
  197    let group_interval = Duration::from_millis(1);
  198    let buffer = cx.new(|cx| {
  199        let mut buf = language::Buffer::local("123456", cx);
  200        buf.set_group_interval(group_interval);
  201        buf
  202    });
  203    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  204    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
  205
  206    _ = editor.update(cx, |editor, window, cx| {
  207        editor.start_transaction_at(now, window, cx);
  208        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  209            s.select_ranges([2..4])
  210        });
  211
  212        editor.insert("cd", window, cx);
  213        editor.end_transaction_at(now, cx);
  214        assert_eq!(editor.text(cx), "12cd56");
  215        assert_eq!(editor.selections.ranges(cx), vec![4..4]);
  216
  217        editor.start_transaction_at(now, window, cx);
  218        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  219            s.select_ranges([4..5])
  220        });
  221        editor.insert("e", window, cx);
  222        editor.end_transaction_at(now, cx);
  223        assert_eq!(editor.text(cx), "12cde6");
  224        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  225
  226        now += group_interval + Duration::from_millis(1);
  227        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  228            s.select_ranges([2..2])
  229        });
  230
  231        // Simulate an edit in another editor
  232        buffer.update(cx, |buffer, cx| {
  233            buffer.start_transaction_at(now, cx);
  234            buffer.edit([(0..1, "a")], None, cx);
  235            buffer.edit([(1..1, "b")], None, cx);
  236            buffer.end_transaction_at(now, cx);
  237        });
  238
  239        assert_eq!(editor.text(cx), "ab2cde6");
  240        assert_eq!(editor.selections.ranges(cx), vec![3..3]);
  241
  242        // Last transaction happened past the group interval in a different editor.
  243        // Undo it individually and don't restore selections.
  244        editor.undo(&Undo, window, cx);
  245        assert_eq!(editor.text(cx), "12cde6");
  246        assert_eq!(editor.selections.ranges(cx), vec![2..2]);
  247
  248        // First two transactions happened within the group interval in this editor.
  249        // Undo them together and restore selections.
  250        editor.undo(&Undo, window, cx);
  251        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  252        assert_eq!(editor.text(cx), "123456");
  253        assert_eq!(editor.selections.ranges(cx), vec![0..0]);
  254
  255        // Redo the first two transactions together.
  256        editor.redo(&Redo, window, cx);
  257        assert_eq!(editor.text(cx), "12cde6");
  258        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  259
  260        // Redo the last transaction on its own.
  261        editor.redo(&Redo, window, cx);
  262        assert_eq!(editor.text(cx), "ab2cde6");
  263        assert_eq!(editor.selections.ranges(cx), vec![6..6]);
  264
  265        // Test empty transactions.
  266        editor.start_transaction_at(now, window, cx);
  267        editor.end_transaction_at(now, cx);
  268        editor.undo(&Undo, window, cx);
  269        assert_eq!(editor.text(cx), "12cde6");
  270    });
  271}
  272
  273#[gpui::test]
  274fn test_ime_composition(cx: &mut TestAppContext) {
  275    init_test(cx, |_| {});
  276
  277    let buffer = cx.new(|cx| {
  278        let mut buffer = language::Buffer::local("abcde", cx);
  279        // Ensure automatic grouping doesn't occur.
  280        buffer.set_group_interval(Duration::ZERO);
  281        buffer
  282    });
  283
  284    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  285    cx.add_window(|window, cx| {
  286        let mut editor = build_editor(buffer.clone(), window, cx);
  287
  288        // Start a new IME composition.
  289        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  290        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
  291        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  292        assert_eq!(editor.text(cx), "äbcde");
  293        assert_eq!(
  294            editor.marked_text_ranges(cx),
  295            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  296        );
  297
  298        // Finalize IME composition.
  299        editor.replace_text_in_range(None, "ā", window, cx);
  300        assert_eq!(editor.text(cx), "ābcde");
  301        assert_eq!(editor.marked_text_ranges(cx), None);
  302
  303        // IME composition edits are grouped and are undone/redone at once.
  304        editor.undo(&Default::default(), window, cx);
  305        assert_eq!(editor.text(cx), "abcde");
  306        assert_eq!(editor.marked_text_ranges(cx), None);
  307        editor.redo(&Default::default(), window, cx);
  308        assert_eq!(editor.text(cx), "ābcde");
  309        assert_eq!(editor.marked_text_ranges(cx), None);
  310
  311        // Start a new IME composition.
  312        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  313        assert_eq!(
  314            editor.marked_text_ranges(cx),
  315            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  316        );
  317
  318        // Undoing during an IME composition cancels it.
  319        editor.undo(&Default::default(), window, cx);
  320        assert_eq!(editor.text(cx), "ābcde");
  321        assert_eq!(editor.marked_text_ranges(cx), None);
  322
  323        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  324        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  325        assert_eq!(editor.text(cx), "ābcdè");
  326        assert_eq!(
  327            editor.marked_text_ranges(cx),
  328            Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
  329        );
  330
  331        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  332        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  333        assert_eq!(editor.text(cx), "ābcdę");
  334        assert_eq!(editor.marked_text_ranges(cx), None);
  335
  336        // Start a new IME composition with multiple cursors.
  337        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  338            s.select_ranges([
  339                OffsetUtf16(1)..OffsetUtf16(1),
  340                OffsetUtf16(3)..OffsetUtf16(3),
  341                OffsetUtf16(5)..OffsetUtf16(5),
  342            ])
  343        });
  344        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  345        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  346        assert_eq!(
  347            editor.marked_text_ranges(cx),
  348            Some(vec![
  349                OffsetUtf16(0)..OffsetUtf16(3),
  350                OffsetUtf16(4)..OffsetUtf16(7),
  351                OffsetUtf16(8)..OffsetUtf16(11)
  352            ])
  353        );
  354
  355        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  356        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  357        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  358        assert_eq!(
  359            editor.marked_text_ranges(cx),
  360            Some(vec![
  361                OffsetUtf16(1)..OffsetUtf16(2),
  362                OffsetUtf16(5)..OffsetUtf16(6),
  363                OffsetUtf16(9)..OffsetUtf16(10)
  364            ])
  365        );
  366
  367        // Finalize IME composition with multiple cursors.
  368        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  369        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  370        assert_eq!(editor.marked_text_ranges(cx), None);
  371
  372        editor
  373    });
  374}
  375
  376#[gpui::test]
  377fn test_selection_with_mouse(cx: &mut TestAppContext) {
  378    init_test(cx, |_| {});
  379
  380    let editor = cx.add_window(|window, cx| {
  381        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  382        build_editor(buffer, window, cx)
  383    });
  384
  385    _ = editor.update(cx, |editor, window, cx| {
  386        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  387    });
  388    assert_eq!(
  389        editor
  390            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  391            .unwrap(),
  392        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  393    );
  394
  395    _ = editor.update(cx, |editor, window, cx| {
  396        editor.update_selection(
  397            DisplayPoint::new(DisplayRow(3), 3),
  398            0,
  399            gpui::Point::<f32>::default(),
  400            window,
  401            cx,
  402        );
  403    });
  404
  405    assert_eq!(
  406        editor
  407            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  408            .unwrap(),
  409        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  410    );
  411
  412    _ = editor.update(cx, |editor, window, cx| {
  413        editor.update_selection(
  414            DisplayPoint::new(DisplayRow(1), 1),
  415            0,
  416            gpui::Point::<f32>::default(),
  417            window,
  418            cx,
  419        );
  420    });
  421
  422    assert_eq!(
  423        editor
  424            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  425            .unwrap(),
  426        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  427    );
  428
  429    _ = editor.update(cx, |editor, window, cx| {
  430        editor.end_selection(window, cx);
  431        editor.update_selection(
  432            DisplayPoint::new(DisplayRow(3), 3),
  433            0,
  434            gpui::Point::<f32>::default(),
  435            window,
  436            cx,
  437        );
  438    });
  439
  440    assert_eq!(
  441        editor
  442            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  443            .unwrap(),
  444        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  445    );
  446
  447    _ = editor.update(cx, |editor, window, cx| {
  448        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  449        editor.update_selection(
  450            DisplayPoint::new(DisplayRow(0), 0),
  451            0,
  452            gpui::Point::<f32>::default(),
  453            window,
  454            cx,
  455        );
  456    });
  457
  458    assert_eq!(
  459        editor
  460            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  461            .unwrap(),
  462        [
  463            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  464            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  465        ]
  466    );
  467
  468    _ = editor.update(cx, |editor, window, cx| {
  469        editor.end_selection(window, cx);
  470    });
  471
  472    assert_eq!(
  473        editor
  474            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  475            .unwrap(),
  476        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  477    );
  478}
  479
  480#[gpui::test]
  481fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  482    init_test(cx, |_| {});
  483
  484    let editor = cx.add_window(|window, cx| {
  485        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  486        build_editor(buffer, window, cx)
  487    });
  488
  489    _ = editor.update(cx, |editor, window, cx| {
  490        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  491    });
  492
  493    _ = editor.update(cx, |editor, window, cx| {
  494        editor.end_selection(window, cx);
  495    });
  496
  497    _ = editor.update(cx, |editor, window, cx| {
  498        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  499    });
  500
  501    _ = editor.update(cx, |editor, window, cx| {
  502        editor.end_selection(window, cx);
  503    });
  504
  505    assert_eq!(
  506        editor
  507            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  508            .unwrap(),
  509        [
  510            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  511            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  512        ]
  513    );
  514
  515    _ = editor.update(cx, |editor, window, cx| {
  516        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  517    });
  518
  519    _ = editor.update(cx, |editor, window, cx| {
  520        editor.end_selection(window, cx);
  521    });
  522
  523    assert_eq!(
  524        editor
  525            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  526            .unwrap(),
  527        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  528    );
  529}
  530
  531#[gpui::test]
  532fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  533    init_test(cx, |_| {});
  534
  535    let editor = cx.add_window(|window, cx| {
  536        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  537        build_editor(buffer, window, cx)
  538    });
  539
  540    _ = editor.update(cx, |editor, window, cx| {
  541        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  542        assert_eq!(
  543            editor.selections.display_ranges(cx),
  544            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  545        );
  546    });
  547
  548    _ = editor.update(cx, |editor, window, cx| {
  549        editor.update_selection(
  550            DisplayPoint::new(DisplayRow(3), 3),
  551            0,
  552            gpui::Point::<f32>::default(),
  553            window,
  554            cx,
  555        );
  556        assert_eq!(
  557            editor.selections.display_ranges(cx),
  558            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  559        );
  560    });
  561
  562    _ = editor.update(cx, |editor, window, cx| {
  563        editor.cancel(&Cancel, window, cx);
  564        editor.update_selection(
  565            DisplayPoint::new(DisplayRow(1), 1),
  566            0,
  567            gpui::Point::<f32>::default(),
  568            window,
  569            cx,
  570        );
  571        assert_eq!(
  572            editor.selections.display_ranges(cx),
  573            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  574        );
  575    });
  576}
  577
  578#[gpui::test]
  579fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  580    init_test(cx, |_| {});
  581
  582    let editor = cx.add_window(|window, cx| {
  583        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  584        build_editor(buffer, window, cx)
  585    });
  586
  587    _ = editor.update(cx, |editor, window, cx| {
  588        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  589        assert_eq!(
  590            editor.selections.display_ranges(cx),
  591            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  592        );
  593
  594        editor.move_down(&Default::default(), window, cx);
  595        assert_eq!(
  596            editor.selections.display_ranges(cx),
  597            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  598        );
  599
  600        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  601        assert_eq!(
  602            editor.selections.display_ranges(cx),
  603            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  604        );
  605
  606        editor.move_up(&Default::default(), window, cx);
  607        assert_eq!(
  608            editor.selections.display_ranges(cx),
  609            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  610        );
  611    });
  612}
  613
  614#[gpui::test]
  615fn test_clone(cx: &mut TestAppContext) {
  616    init_test(cx, |_| {});
  617
  618    let (text, selection_ranges) = marked_text_ranges(
  619        indoc! {"
  620            one
  621            two
  622            threeˇ
  623            four
  624            fiveˇ
  625        "},
  626        true,
  627    );
  628
  629    let editor = cx.add_window(|window, cx| {
  630        let buffer = MultiBuffer::build_simple(&text, cx);
  631        build_editor(buffer, window, cx)
  632    });
  633
  634    _ = editor.update(cx, |editor, window, cx| {
  635        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  636            s.select_ranges(selection_ranges.clone())
  637        });
  638        editor.fold_creases(
  639            vec![
  640                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  641                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  642            ],
  643            true,
  644            window,
  645            cx,
  646        );
  647    });
  648
  649    let cloned_editor = editor
  650        .update(cx, |editor, _, cx| {
  651            cx.open_window(Default::default(), |window, cx| {
  652                cx.new(|cx| editor.clone(window, cx))
  653            })
  654        })
  655        .unwrap()
  656        .unwrap();
  657
  658    let snapshot = editor
  659        .update(cx, |e, window, cx| e.snapshot(window, cx))
  660        .unwrap();
  661    let cloned_snapshot = cloned_editor
  662        .update(cx, |e, window, cx| e.snapshot(window, cx))
  663        .unwrap();
  664
  665    assert_eq!(
  666        cloned_editor
  667            .update(cx, |e, _, cx| e.display_text(cx))
  668            .unwrap(),
  669        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  670    );
  671    assert_eq!(
  672        cloned_snapshot
  673            .folds_in_range(0..text.len())
  674            .collect::<Vec<_>>(),
  675        snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
  676    );
  677    assert_set_eq!(
  678        cloned_editor
  679            .update(cx, |editor, _, cx| editor.selections.ranges::<Point>(cx))
  680            .unwrap(),
  681        editor
  682            .update(cx, |editor, _, cx| editor.selections.ranges(cx))
  683            .unwrap()
  684    );
  685    assert_set_eq!(
  686        cloned_editor
  687            .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
  688            .unwrap(),
  689        editor
  690            .update(cx, |e, _, cx| e.selections.display_ranges(cx))
  691            .unwrap()
  692    );
  693}
  694
  695#[gpui::test]
  696async fn test_navigation_history(cx: &mut TestAppContext) {
  697    init_test(cx, |_| {});
  698
  699    use workspace::item::Item;
  700
  701    let fs = FakeFs::new(cx.executor());
  702    let project = Project::test(fs, [], cx).await;
  703    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  704    let pane = workspace
  705        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  706        .unwrap();
  707
  708    _ = workspace.update(cx, |_v, window, cx| {
  709        cx.new(|cx| {
  710            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  711            let mut editor = build_editor(buffer.clone(), window, cx);
  712            let handle = cx.entity();
  713            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  714
  715            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  716                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  717            }
  718
  719            // Move the cursor a small distance.
  720            // Nothing is added to the navigation history.
  721            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  722                s.select_display_ranges([
  723                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  724                ])
  725            });
  726            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  727                s.select_display_ranges([
  728                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  729                ])
  730            });
  731            assert!(pop_history(&mut editor, cx).is_none());
  732
  733            // Move the cursor a large distance.
  734            // The history can jump back to the previous position.
  735            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  736                s.select_display_ranges([
  737                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  738                ])
  739            });
  740            let nav_entry = pop_history(&mut editor, cx).unwrap();
  741            editor.navigate(nav_entry.data.unwrap(), window, cx);
  742            assert_eq!(nav_entry.item.id(), cx.entity_id());
  743            assert_eq!(
  744                editor.selections.display_ranges(cx),
  745                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  746            );
  747            assert!(pop_history(&mut editor, cx).is_none());
  748
  749            // Move the cursor a small distance via the mouse.
  750            // Nothing is added to the navigation history.
  751            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  752            editor.end_selection(window, cx);
  753            assert_eq!(
  754                editor.selections.display_ranges(cx),
  755                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  756            );
  757            assert!(pop_history(&mut editor, cx).is_none());
  758
  759            // Move the cursor a large distance via the mouse.
  760            // The history can jump back to the previous position.
  761            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  762            editor.end_selection(window, cx);
  763            assert_eq!(
  764                editor.selections.display_ranges(cx),
  765                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  766            );
  767            let nav_entry = pop_history(&mut editor, cx).unwrap();
  768            editor.navigate(nav_entry.data.unwrap(), window, cx);
  769            assert_eq!(nav_entry.item.id(), cx.entity_id());
  770            assert_eq!(
  771                editor.selections.display_ranges(cx),
  772                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  773            );
  774            assert!(pop_history(&mut editor, cx).is_none());
  775
  776            // Set scroll position to check later
  777            editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), window, cx);
  778            let original_scroll_position = editor.scroll_manager.anchor();
  779
  780            // Jump to the end of the document and adjust scroll
  781            editor.move_to_end(&MoveToEnd, window, cx);
  782            editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), window, cx);
  783            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  784
  785            let nav_entry = pop_history(&mut editor, cx).unwrap();
  786            editor.navigate(nav_entry.data.unwrap(), window, cx);
  787            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  788
  789            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  790            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  791            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  792            let invalid_point = Point::new(9999, 0);
  793            editor.navigate(
  794                Box::new(NavigationData {
  795                    cursor_anchor: invalid_anchor,
  796                    cursor_position: invalid_point,
  797                    scroll_anchor: ScrollAnchor {
  798                        anchor: invalid_anchor,
  799                        offset: Default::default(),
  800                    },
  801                    scroll_top_row: invalid_point.row,
  802                }),
  803                window,
  804                cx,
  805            );
  806            assert_eq!(
  807                editor.selections.display_ranges(cx),
  808                &[editor.max_point(cx)..editor.max_point(cx)]
  809            );
  810            assert_eq!(
  811                editor.scroll_position(cx),
  812                gpui::Point::new(0., editor.max_point(cx).row().as_f32())
  813            );
  814
  815            editor
  816        })
  817    });
  818}
  819
  820#[gpui::test]
  821fn test_cancel(cx: &mut TestAppContext) {
  822    init_test(cx, |_| {});
  823
  824    let editor = cx.add_window(|window, cx| {
  825        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  826        build_editor(buffer, window, cx)
  827    });
  828
  829    _ = editor.update(cx, |editor, window, cx| {
  830        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
  831        editor.update_selection(
  832            DisplayPoint::new(DisplayRow(1), 1),
  833            0,
  834            gpui::Point::<f32>::default(),
  835            window,
  836            cx,
  837        );
  838        editor.end_selection(window, cx);
  839
  840        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
  841        editor.update_selection(
  842            DisplayPoint::new(DisplayRow(0), 3),
  843            0,
  844            gpui::Point::<f32>::default(),
  845            window,
  846            cx,
  847        );
  848        editor.end_selection(window, cx);
  849        assert_eq!(
  850            editor.selections.display_ranges(cx),
  851            [
  852                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
  853                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
  854            ]
  855        );
  856    });
  857
  858    _ = editor.update(cx, |editor, window, cx| {
  859        editor.cancel(&Cancel, window, cx);
  860        assert_eq!(
  861            editor.selections.display_ranges(cx),
  862            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
  863        );
  864    });
  865
  866    _ = editor.update(cx, |editor, window, cx| {
  867        editor.cancel(&Cancel, window, cx);
  868        assert_eq!(
  869            editor.selections.display_ranges(cx),
  870            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
  871        );
  872    });
  873}
  874
  875#[gpui::test]
  876fn test_fold_action(cx: &mut TestAppContext) {
  877    init_test(cx, |_| {});
  878
  879    let editor = cx.add_window(|window, cx| {
  880        let buffer = MultiBuffer::build_simple(
  881            &"
  882                impl Foo {
  883                    // Hello!
  884
  885                    fn a() {
  886                        1
  887                    }
  888
  889                    fn b() {
  890                        2
  891                    }
  892
  893                    fn c() {
  894                        3
  895                    }
  896                }
  897            "
  898            .unindent(),
  899            cx,
  900        );
  901        build_editor(buffer.clone(), window, cx)
  902    });
  903
  904    _ = editor.update(cx, |editor, window, cx| {
  905        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  906            s.select_display_ranges([
  907                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
  908            ]);
  909        });
  910        editor.fold(&Fold, window, cx);
  911        assert_eq!(
  912            editor.display_text(cx),
  913            "
  914                impl Foo {
  915                    // Hello!
  916
  917                    fn a() {
  918                        1
  919                    }
  920
  921                    fn b() {⋯
  922                    }
  923
  924                    fn c() {⋯
  925                    }
  926                }
  927            "
  928            .unindent(),
  929        );
  930
  931        editor.fold(&Fold, window, cx);
  932        assert_eq!(
  933            editor.display_text(cx),
  934            "
  935                impl Foo {⋯
  936                }
  937            "
  938            .unindent(),
  939        );
  940
  941        editor.unfold_lines(&UnfoldLines, window, cx);
  942        assert_eq!(
  943            editor.display_text(cx),
  944            "
  945                impl Foo {
  946                    // Hello!
  947
  948                    fn a() {
  949                        1
  950                    }
  951
  952                    fn b() {⋯
  953                    }
  954
  955                    fn c() {⋯
  956                    }
  957                }
  958            "
  959            .unindent(),
  960        );
  961
  962        editor.unfold_lines(&UnfoldLines, window, cx);
  963        assert_eq!(
  964            editor.display_text(cx),
  965            editor.buffer.read(cx).read(cx).text()
  966        );
  967    });
  968}
  969
  970#[gpui::test]
  971fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
  972    init_test(cx, |_| {});
  973
  974    let editor = cx.add_window(|window, cx| {
  975        let buffer = MultiBuffer::build_simple(
  976            &"
  977                class Foo:
  978                    # Hello!
  979
  980                    def a():
  981                        print(1)
  982
  983                    def b():
  984                        print(2)
  985
  986                    def c():
  987                        print(3)
  988            "
  989            .unindent(),
  990            cx,
  991        );
  992        build_editor(buffer.clone(), window, cx)
  993    });
  994
  995    _ = editor.update(cx, |editor, window, cx| {
  996        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  997            s.select_display_ranges([
  998                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
  999            ]);
 1000        });
 1001        editor.fold(&Fold, window, cx);
 1002        assert_eq!(
 1003            editor.display_text(cx),
 1004            "
 1005                class Foo:
 1006                    # Hello!
 1007
 1008                    def a():
 1009                        print(1)
 1010
 1011                    def b():⋯
 1012
 1013                    def c():⋯
 1014            "
 1015            .unindent(),
 1016        );
 1017
 1018        editor.fold(&Fold, window, cx);
 1019        assert_eq!(
 1020            editor.display_text(cx),
 1021            "
 1022                class Foo:⋯
 1023            "
 1024            .unindent(),
 1025        );
 1026
 1027        editor.unfold_lines(&UnfoldLines, window, cx);
 1028        assert_eq!(
 1029            editor.display_text(cx),
 1030            "
 1031                class Foo:
 1032                    # Hello!
 1033
 1034                    def a():
 1035                        print(1)
 1036
 1037                    def b():⋯
 1038
 1039                    def c():⋯
 1040            "
 1041            .unindent(),
 1042        );
 1043
 1044        editor.unfold_lines(&UnfoldLines, window, cx);
 1045        assert_eq!(
 1046            editor.display_text(cx),
 1047            editor.buffer.read(cx).read(cx).text()
 1048        );
 1049    });
 1050}
 1051
 1052#[gpui::test]
 1053fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1054    init_test(cx, |_| {});
 1055
 1056    let editor = cx.add_window(|window, cx| {
 1057        let buffer = MultiBuffer::build_simple(
 1058            &"
 1059                class Foo:
 1060                    # Hello!
 1061
 1062                    def a():
 1063                        print(1)
 1064
 1065                    def b():
 1066                        print(2)
 1067
 1068
 1069                    def c():
 1070                        print(3)
 1071
 1072
 1073            "
 1074            .unindent(),
 1075            cx,
 1076        );
 1077        build_editor(buffer.clone(), window, cx)
 1078    });
 1079
 1080    _ = editor.update(cx, |editor, window, cx| {
 1081        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1082            s.select_display_ranges([
 1083                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1084            ]);
 1085        });
 1086        editor.fold(&Fold, window, cx);
 1087        assert_eq!(
 1088            editor.display_text(cx),
 1089            "
 1090                class Foo:
 1091                    # Hello!
 1092
 1093                    def a():
 1094                        print(1)
 1095
 1096                    def b():⋯
 1097
 1098
 1099                    def c():⋯
 1100
 1101
 1102            "
 1103            .unindent(),
 1104        );
 1105
 1106        editor.fold(&Fold, window, cx);
 1107        assert_eq!(
 1108            editor.display_text(cx),
 1109            "
 1110                class Foo:⋯
 1111
 1112
 1113            "
 1114            .unindent(),
 1115        );
 1116
 1117        editor.unfold_lines(&UnfoldLines, window, cx);
 1118        assert_eq!(
 1119            editor.display_text(cx),
 1120            "
 1121                class Foo:
 1122                    # Hello!
 1123
 1124                    def a():
 1125                        print(1)
 1126
 1127                    def b():⋯
 1128
 1129
 1130                    def c():⋯
 1131
 1132
 1133            "
 1134            .unindent(),
 1135        );
 1136
 1137        editor.unfold_lines(&UnfoldLines, window, cx);
 1138        assert_eq!(
 1139            editor.display_text(cx),
 1140            editor.buffer.read(cx).read(cx).text()
 1141        );
 1142    });
 1143}
 1144
 1145#[gpui::test]
 1146fn test_fold_at_level(cx: &mut TestAppContext) {
 1147    init_test(cx, |_| {});
 1148
 1149    let editor = cx.add_window(|window, cx| {
 1150        let buffer = MultiBuffer::build_simple(
 1151            &"
 1152                class Foo:
 1153                    # Hello!
 1154
 1155                    def a():
 1156                        print(1)
 1157
 1158                    def b():
 1159                        print(2)
 1160
 1161
 1162                class Bar:
 1163                    # World!
 1164
 1165                    def a():
 1166                        print(1)
 1167
 1168                    def b():
 1169                        print(2)
 1170
 1171
 1172            "
 1173            .unindent(),
 1174            cx,
 1175        );
 1176        build_editor(buffer.clone(), window, cx)
 1177    });
 1178
 1179    _ = editor.update(cx, |editor, window, cx| {
 1180        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1181        assert_eq!(
 1182            editor.display_text(cx),
 1183            "
 1184                class Foo:
 1185                    # Hello!
 1186
 1187                    def a():⋯
 1188
 1189                    def b():⋯
 1190
 1191
 1192                class Bar:
 1193                    # World!
 1194
 1195                    def a():⋯
 1196
 1197                    def b():⋯
 1198
 1199
 1200            "
 1201            .unindent(),
 1202        );
 1203
 1204        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1205        assert_eq!(
 1206            editor.display_text(cx),
 1207            "
 1208                class Foo:⋯
 1209
 1210
 1211                class Bar:⋯
 1212
 1213
 1214            "
 1215            .unindent(),
 1216        );
 1217
 1218        editor.unfold_all(&UnfoldAll, window, cx);
 1219        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1220        assert_eq!(
 1221            editor.display_text(cx),
 1222            "
 1223                class Foo:
 1224                    # Hello!
 1225
 1226                    def a():
 1227                        print(1)
 1228
 1229                    def b():
 1230                        print(2)
 1231
 1232
 1233                class Bar:
 1234                    # World!
 1235
 1236                    def a():
 1237                        print(1)
 1238
 1239                    def b():
 1240                        print(2)
 1241
 1242
 1243            "
 1244            .unindent(),
 1245        );
 1246
 1247        assert_eq!(
 1248            editor.display_text(cx),
 1249            editor.buffer.read(cx).read(cx).text()
 1250        );
 1251    });
 1252}
 1253
 1254#[gpui::test]
 1255fn test_move_cursor(cx: &mut TestAppContext) {
 1256    init_test(cx, |_| {});
 1257
 1258    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1259    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1260
 1261    buffer.update(cx, |buffer, cx| {
 1262        buffer.edit(
 1263            vec![
 1264                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1265                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1266            ],
 1267            None,
 1268            cx,
 1269        );
 1270    });
 1271    _ = editor.update(cx, |editor, window, cx| {
 1272        assert_eq!(
 1273            editor.selections.display_ranges(cx),
 1274            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1275        );
 1276
 1277        editor.move_down(&MoveDown, window, cx);
 1278        assert_eq!(
 1279            editor.selections.display_ranges(cx),
 1280            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1281        );
 1282
 1283        editor.move_right(&MoveRight, window, cx);
 1284        assert_eq!(
 1285            editor.selections.display_ranges(cx),
 1286            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1287        );
 1288
 1289        editor.move_left(&MoveLeft, window, cx);
 1290        assert_eq!(
 1291            editor.selections.display_ranges(cx),
 1292            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1293        );
 1294
 1295        editor.move_up(&MoveUp, window, cx);
 1296        assert_eq!(
 1297            editor.selections.display_ranges(cx),
 1298            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1299        );
 1300
 1301        editor.move_to_end(&MoveToEnd, window, cx);
 1302        assert_eq!(
 1303            editor.selections.display_ranges(cx),
 1304            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1305        );
 1306
 1307        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1308        assert_eq!(
 1309            editor.selections.display_ranges(cx),
 1310            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1311        );
 1312
 1313        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1314            s.select_display_ranges([
 1315                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1316            ]);
 1317        });
 1318        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1319        assert_eq!(
 1320            editor.selections.display_ranges(cx),
 1321            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1322        );
 1323
 1324        editor.select_to_end(&SelectToEnd, window, cx);
 1325        assert_eq!(
 1326            editor.selections.display_ranges(cx),
 1327            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1328        );
 1329    });
 1330}
 1331
 1332#[gpui::test]
 1333fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1334    init_test(cx, |_| {});
 1335
 1336    let editor = cx.add_window(|window, cx| {
 1337        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1338        build_editor(buffer.clone(), window, cx)
 1339    });
 1340
 1341    assert_eq!('🟥'.len_utf8(), 4);
 1342    assert_eq!('α'.len_utf8(), 2);
 1343
 1344    _ = editor.update(cx, |editor, window, cx| {
 1345        editor.fold_creases(
 1346            vec![
 1347                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1348                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1349                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1350            ],
 1351            true,
 1352            window,
 1353            cx,
 1354        );
 1355        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1356
 1357        editor.move_right(&MoveRight, window, cx);
 1358        assert_eq!(
 1359            editor.selections.display_ranges(cx),
 1360            &[empty_range(0, "🟥".len())]
 1361        );
 1362        editor.move_right(&MoveRight, window, cx);
 1363        assert_eq!(
 1364            editor.selections.display_ranges(cx),
 1365            &[empty_range(0, "🟥🟧".len())]
 1366        );
 1367        editor.move_right(&MoveRight, window, cx);
 1368        assert_eq!(
 1369            editor.selections.display_ranges(cx),
 1370            &[empty_range(0, "🟥🟧⋯".len())]
 1371        );
 1372
 1373        editor.move_down(&MoveDown, window, cx);
 1374        assert_eq!(
 1375            editor.selections.display_ranges(cx),
 1376            &[empty_range(1, "ab⋯e".len())]
 1377        );
 1378        editor.move_left(&MoveLeft, window, cx);
 1379        assert_eq!(
 1380            editor.selections.display_ranges(cx),
 1381            &[empty_range(1, "ab⋯".len())]
 1382        );
 1383        editor.move_left(&MoveLeft, window, cx);
 1384        assert_eq!(
 1385            editor.selections.display_ranges(cx),
 1386            &[empty_range(1, "ab".len())]
 1387        );
 1388        editor.move_left(&MoveLeft, window, cx);
 1389        assert_eq!(
 1390            editor.selections.display_ranges(cx),
 1391            &[empty_range(1, "a".len())]
 1392        );
 1393
 1394        editor.move_down(&MoveDown, window, cx);
 1395        assert_eq!(
 1396            editor.selections.display_ranges(cx),
 1397            &[empty_range(2, "α".len())]
 1398        );
 1399        editor.move_right(&MoveRight, window, cx);
 1400        assert_eq!(
 1401            editor.selections.display_ranges(cx),
 1402            &[empty_range(2, "αβ".len())]
 1403        );
 1404        editor.move_right(&MoveRight, window, cx);
 1405        assert_eq!(
 1406            editor.selections.display_ranges(cx),
 1407            &[empty_range(2, "αβ⋯".len())]
 1408        );
 1409        editor.move_right(&MoveRight, window, cx);
 1410        assert_eq!(
 1411            editor.selections.display_ranges(cx),
 1412            &[empty_range(2, "αβ⋯ε".len())]
 1413        );
 1414
 1415        editor.move_up(&MoveUp, window, cx);
 1416        assert_eq!(
 1417            editor.selections.display_ranges(cx),
 1418            &[empty_range(1, "ab⋯e".len())]
 1419        );
 1420        editor.move_down(&MoveDown, window, cx);
 1421        assert_eq!(
 1422            editor.selections.display_ranges(cx),
 1423            &[empty_range(2, "αβ⋯ε".len())]
 1424        );
 1425        editor.move_up(&MoveUp, window, cx);
 1426        assert_eq!(
 1427            editor.selections.display_ranges(cx),
 1428            &[empty_range(1, "ab⋯e".len())]
 1429        );
 1430
 1431        editor.move_up(&MoveUp, window, cx);
 1432        assert_eq!(
 1433            editor.selections.display_ranges(cx),
 1434            &[empty_range(0, "🟥🟧".len())]
 1435        );
 1436        editor.move_left(&MoveLeft, window, cx);
 1437        assert_eq!(
 1438            editor.selections.display_ranges(cx),
 1439            &[empty_range(0, "🟥".len())]
 1440        );
 1441        editor.move_left(&MoveLeft, window, cx);
 1442        assert_eq!(
 1443            editor.selections.display_ranges(cx),
 1444            &[empty_range(0, "".len())]
 1445        );
 1446    });
 1447}
 1448
 1449#[gpui::test]
 1450fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1451    init_test(cx, |_| {});
 1452
 1453    let editor = cx.add_window(|window, cx| {
 1454        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1455        build_editor(buffer.clone(), window, cx)
 1456    });
 1457    _ = editor.update(cx, |editor, window, cx| {
 1458        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1459            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1460        });
 1461
 1462        // moving above start of document should move selection to start of document,
 1463        // but the next move down should still be at the original goal_x
 1464        editor.move_up(&MoveUp, window, cx);
 1465        assert_eq!(
 1466            editor.selections.display_ranges(cx),
 1467            &[empty_range(0, "".len())]
 1468        );
 1469
 1470        editor.move_down(&MoveDown, window, cx);
 1471        assert_eq!(
 1472            editor.selections.display_ranges(cx),
 1473            &[empty_range(1, "abcd".len())]
 1474        );
 1475
 1476        editor.move_down(&MoveDown, window, cx);
 1477        assert_eq!(
 1478            editor.selections.display_ranges(cx),
 1479            &[empty_range(2, "αβγ".len())]
 1480        );
 1481
 1482        editor.move_down(&MoveDown, window, cx);
 1483        assert_eq!(
 1484            editor.selections.display_ranges(cx),
 1485            &[empty_range(3, "abcd".len())]
 1486        );
 1487
 1488        editor.move_down(&MoveDown, window, cx);
 1489        assert_eq!(
 1490            editor.selections.display_ranges(cx),
 1491            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1492        );
 1493
 1494        // moving past end of document should not change goal_x
 1495        editor.move_down(&MoveDown, window, cx);
 1496        assert_eq!(
 1497            editor.selections.display_ranges(cx),
 1498            &[empty_range(5, "".len())]
 1499        );
 1500
 1501        editor.move_down(&MoveDown, window, cx);
 1502        assert_eq!(
 1503            editor.selections.display_ranges(cx),
 1504            &[empty_range(5, "".len())]
 1505        );
 1506
 1507        editor.move_up(&MoveUp, window, cx);
 1508        assert_eq!(
 1509            editor.selections.display_ranges(cx),
 1510            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1511        );
 1512
 1513        editor.move_up(&MoveUp, window, cx);
 1514        assert_eq!(
 1515            editor.selections.display_ranges(cx),
 1516            &[empty_range(3, "abcd".len())]
 1517        );
 1518
 1519        editor.move_up(&MoveUp, window, cx);
 1520        assert_eq!(
 1521            editor.selections.display_ranges(cx),
 1522            &[empty_range(2, "αβγ".len())]
 1523        );
 1524    });
 1525}
 1526
 1527#[gpui::test]
 1528fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1529    init_test(cx, |_| {});
 1530    let move_to_beg = MoveToBeginningOfLine {
 1531        stop_at_soft_wraps: true,
 1532        stop_at_indent: true,
 1533    };
 1534
 1535    let delete_to_beg = DeleteToBeginningOfLine {
 1536        stop_at_indent: false,
 1537    };
 1538
 1539    let move_to_end = MoveToEndOfLine {
 1540        stop_at_soft_wraps: true,
 1541    };
 1542
 1543    let editor = cx.add_window(|window, cx| {
 1544        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1545        build_editor(buffer, window, cx)
 1546    });
 1547    _ = editor.update(cx, |editor, window, cx| {
 1548        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1549            s.select_display_ranges([
 1550                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1551                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1552            ]);
 1553        });
 1554    });
 1555
 1556    _ = editor.update(cx, |editor, window, cx| {
 1557        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1558        assert_eq!(
 1559            editor.selections.display_ranges(cx),
 1560            &[
 1561                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1562                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1563            ]
 1564        );
 1565    });
 1566
 1567    _ = editor.update(cx, |editor, window, cx| {
 1568        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1569        assert_eq!(
 1570            editor.selections.display_ranges(cx),
 1571            &[
 1572                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1573                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1574            ]
 1575        );
 1576    });
 1577
 1578    _ = editor.update(cx, |editor, window, cx| {
 1579        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1580        assert_eq!(
 1581            editor.selections.display_ranges(cx),
 1582            &[
 1583                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1584                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1585            ]
 1586        );
 1587    });
 1588
 1589    _ = editor.update(cx, |editor, window, cx| {
 1590        editor.move_to_end_of_line(&move_to_end, window, cx);
 1591        assert_eq!(
 1592            editor.selections.display_ranges(cx),
 1593            &[
 1594                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1595                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1596            ]
 1597        );
 1598    });
 1599
 1600    // Moving to the end of line again is a no-op.
 1601    _ = editor.update(cx, |editor, window, cx| {
 1602        editor.move_to_end_of_line(&move_to_end, window, cx);
 1603        assert_eq!(
 1604            editor.selections.display_ranges(cx),
 1605            &[
 1606                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1607                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1608            ]
 1609        );
 1610    });
 1611
 1612    _ = editor.update(cx, |editor, window, cx| {
 1613        editor.move_left(&MoveLeft, window, cx);
 1614        editor.select_to_beginning_of_line(
 1615            &SelectToBeginningOfLine {
 1616                stop_at_soft_wraps: true,
 1617                stop_at_indent: true,
 1618            },
 1619            window,
 1620            cx,
 1621        );
 1622        assert_eq!(
 1623            editor.selections.display_ranges(cx),
 1624            &[
 1625                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1626                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1627            ]
 1628        );
 1629    });
 1630
 1631    _ = editor.update(cx, |editor, window, cx| {
 1632        editor.select_to_beginning_of_line(
 1633            &SelectToBeginningOfLine {
 1634                stop_at_soft_wraps: true,
 1635                stop_at_indent: true,
 1636            },
 1637            window,
 1638            cx,
 1639        );
 1640        assert_eq!(
 1641            editor.selections.display_ranges(cx),
 1642            &[
 1643                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1644                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1645            ]
 1646        );
 1647    });
 1648
 1649    _ = editor.update(cx, |editor, window, cx| {
 1650        editor.select_to_beginning_of_line(
 1651            &SelectToBeginningOfLine {
 1652                stop_at_soft_wraps: true,
 1653                stop_at_indent: true,
 1654            },
 1655            window,
 1656            cx,
 1657        );
 1658        assert_eq!(
 1659            editor.selections.display_ranges(cx),
 1660            &[
 1661                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1662                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1663            ]
 1664        );
 1665    });
 1666
 1667    _ = editor.update(cx, |editor, window, cx| {
 1668        editor.select_to_end_of_line(
 1669            &SelectToEndOfLine {
 1670                stop_at_soft_wraps: true,
 1671            },
 1672            window,
 1673            cx,
 1674        );
 1675        assert_eq!(
 1676            editor.selections.display_ranges(cx),
 1677            &[
 1678                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1679                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1680            ]
 1681        );
 1682    });
 1683
 1684    _ = editor.update(cx, |editor, window, cx| {
 1685        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1686        assert_eq!(editor.display_text(cx), "ab\n  de");
 1687        assert_eq!(
 1688            editor.selections.display_ranges(cx),
 1689            &[
 1690                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1691                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1692            ]
 1693        );
 1694    });
 1695
 1696    _ = editor.update(cx, |editor, window, cx| {
 1697        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1698        assert_eq!(editor.display_text(cx), "\n");
 1699        assert_eq!(
 1700            editor.selections.display_ranges(cx),
 1701            &[
 1702                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1703                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1704            ]
 1705        );
 1706    });
 1707}
 1708
 1709#[gpui::test]
 1710fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1711    init_test(cx, |_| {});
 1712    let move_to_beg = MoveToBeginningOfLine {
 1713        stop_at_soft_wraps: false,
 1714        stop_at_indent: false,
 1715    };
 1716
 1717    let move_to_end = MoveToEndOfLine {
 1718        stop_at_soft_wraps: false,
 1719    };
 1720
 1721    let editor = cx.add_window(|window, cx| {
 1722        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1723        build_editor(buffer, window, cx)
 1724    });
 1725
 1726    _ = editor.update(cx, |editor, window, cx| {
 1727        editor.set_wrap_width(Some(140.0.into()), cx);
 1728
 1729        // We expect the following lines after wrapping
 1730        // ```
 1731        // thequickbrownfox
 1732        // jumpedoverthelazydo
 1733        // gs
 1734        // ```
 1735        // The final `gs` was soft-wrapped onto a new line.
 1736        assert_eq!(
 1737            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1738            editor.display_text(cx),
 1739        );
 1740
 1741        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1742        // Start the cursor at the `k` on the first line
 1743        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1744            s.select_display_ranges([
 1745                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1746            ]);
 1747        });
 1748
 1749        // Moving to the beginning of the line should put us at the beginning of the line.
 1750        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1751        assert_eq!(
 1752            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1753            editor.selections.display_ranges(cx)
 1754        );
 1755
 1756        // Moving to the end of the line should put us at the end of the line.
 1757        editor.move_to_end_of_line(&move_to_end, window, cx);
 1758        assert_eq!(
 1759            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1760            editor.selections.display_ranges(cx)
 1761        );
 1762
 1763        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1764        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1765        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1766            s.select_display_ranges([
 1767                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1768            ]);
 1769        });
 1770
 1771        // Moving to the beginning of the line should put us at the start of the second line of
 1772        // display text, i.e., the `j`.
 1773        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1774        assert_eq!(
 1775            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1776            editor.selections.display_ranges(cx)
 1777        );
 1778
 1779        // Moving to the beginning of the line again should be a no-op.
 1780        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1781        assert_eq!(
 1782            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1783            editor.selections.display_ranges(cx)
 1784        );
 1785
 1786        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1787        // next display line.
 1788        editor.move_to_end_of_line(&move_to_end, window, cx);
 1789        assert_eq!(
 1790            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1791            editor.selections.display_ranges(cx)
 1792        );
 1793
 1794        // Moving to the end of the line again should be a no-op.
 1795        editor.move_to_end_of_line(&move_to_end, window, cx);
 1796        assert_eq!(
 1797            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1798            editor.selections.display_ranges(cx)
 1799        );
 1800    });
 1801}
 1802
 1803#[gpui::test]
 1804fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1805    init_test(cx, |_| {});
 1806
 1807    let move_to_beg = MoveToBeginningOfLine {
 1808        stop_at_soft_wraps: true,
 1809        stop_at_indent: true,
 1810    };
 1811
 1812    let select_to_beg = SelectToBeginningOfLine {
 1813        stop_at_soft_wraps: true,
 1814        stop_at_indent: true,
 1815    };
 1816
 1817    let delete_to_beg = DeleteToBeginningOfLine {
 1818        stop_at_indent: true,
 1819    };
 1820
 1821    let move_to_end = MoveToEndOfLine {
 1822        stop_at_soft_wraps: false,
 1823    };
 1824
 1825    let editor = cx.add_window(|window, cx| {
 1826        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1827        build_editor(buffer, window, cx)
 1828    });
 1829
 1830    _ = editor.update(cx, |editor, window, cx| {
 1831        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1832            s.select_display_ranges([
 1833                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1834                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1835            ]);
 1836        });
 1837
 1838        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 1839        // and the second cursor at the first non-whitespace character in the line.
 1840        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1841        assert_eq!(
 1842            editor.selections.display_ranges(cx),
 1843            &[
 1844                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1845                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1846            ]
 1847        );
 1848
 1849        // Moving to the beginning of the line again should be a no-op for the first cursor,
 1850        // and should move the second cursor to the beginning of the line.
 1851        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1852        assert_eq!(
 1853            editor.selections.display_ranges(cx),
 1854            &[
 1855                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1856                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1857            ]
 1858        );
 1859
 1860        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 1861        // and should move the second cursor back to the first non-whitespace character in the line.
 1862        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1863        assert_eq!(
 1864            editor.selections.display_ranges(cx),
 1865            &[
 1866                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1867                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1868            ]
 1869        );
 1870
 1871        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 1872        // and to the first non-whitespace character in the line for the second cursor.
 1873        editor.move_to_end_of_line(&move_to_end, window, cx);
 1874        editor.move_left(&MoveLeft, window, cx);
 1875        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1876        assert_eq!(
 1877            editor.selections.display_ranges(cx),
 1878            &[
 1879                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1880                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1881            ]
 1882        );
 1883
 1884        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 1885        // and should select to the beginning of the line for the second cursor.
 1886        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1887        assert_eq!(
 1888            editor.selections.display_ranges(cx),
 1889            &[
 1890                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1891                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1892            ]
 1893        );
 1894
 1895        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 1896        // and should delete to the first non-whitespace character in the line for the second cursor.
 1897        editor.move_to_end_of_line(&move_to_end, window, cx);
 1898        editor.move_left(&MoveLeft, window, cx);
 1899        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1900        assert_eq!(editor.text(cx), "c\n  f");
 1901    });
 1902}
 1903
 1904#[gpui::test]
 1905fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 1906    init_test(cx, |_| {});
 1907
 1908    let editor = cx.add_window(|window, cx| {
 1909        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 1910        build_editor(buffer, window, cx)
 1911    });
 1912    _ = editor.update(cx, |editor, window, cx| {
 1913        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1914            s.select_display_ranges([
 1915                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 1916                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 1917            ])
 1918        });
 1919        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1920        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 1921
 1922        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1923        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 1924
 1925        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1926        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1927
 1928        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1929        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1930
 1931        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1932        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 1933
 1934        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1935        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1936
 1937        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1938        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 1939
 1940        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1941        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1942
 1943        editor.move_right(&MoveRight, window, cx);
 1944        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1945        assert_selection_ranges(
 1946            "use std::«ˇs»tr::{foo, bar}\n«ˇ\n»  {baz.qux()}",
 1947            editor,
 1948            cx,
 1949        );
 1950
 1951        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1952        assert_selection_ranges(
 1953            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 1954            editor,
 1955            cx,
 1956        );
 1957
 1958        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 1959        assert_selection_ranges(
 1960            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 1961            editor,
 1962            cx,
 1963        );
 1964    });
 1965}
 1966
 1967#[gpui::test]
 1968fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 1969    init_test(cx, |_| {});
 1970
 1971    let editor = cx.add_window(|window, cx| {
 1972        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 1973        build_editor(buffer, window, cx)
 1974    });
 1975
 1976    _ = editor.update(cx, |editor, window, cx| {
 1977        editor.set_wrap_width(Some(140.0.into()), cx);
 1978        assert_eq!(
 1979            editor.display_text(cx),
 1980            "use one::{\n    two::three::\n    four::five\n};"
 1981        );
 1982
 1983        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1984            s.select_display_ranges([
 1985                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 1986            ]);
 1987        });
 1988
 1989        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1990        assert_eq!(
 1991            editor.selections.display_ranges(cx),
 1992            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 1993        );
 1994
 1995        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1996        assert_eq!(
 1997            editor.selections.display_ranges(cx),
 1998            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 1999        );
 2000
 2001        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2002        assert_eq!(
 2003            editor.selections.display_ranges(cx),
 2004            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2005        );
 2006
 2007        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2008        assert_eq!(
 2009            editor.selections.display_ranges(cx),
 2010            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2011        );
 2012
 2013        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2014        assert_eq!(
 2015            editor.selections.display_ranges(cx),
 2016            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2017        );
 2018
 2019        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2020        assert_eq!(
 2021            editor.selections.display_ranges(cx),
 2022            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2023        );
 2024    });
 2025}
 2026
 2027#[gpui::test]
 2028async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2029    init_test(cx, |_| {});
 2030    let mut cx = EditorTestContext::new(cx).await;
 2031
 2032    let line_height = cx.editor(|editor, window, _| {
 2033        editor
 2034            .style()
 2035            .unwrap()
 2036            .text
 2037            .line_height_in_pixels(window.rem_size())
 2038    });
 2039    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2040
 2041    cx.set_state(
 2042        &r#"ˇone
 2043        two
 2044
 2045        three
 2046        fourˇ
 2047        five
 2048
 2049        six"#
 2050            .unindent(),
 2051    );
 2052
 2053    cx.update_editor(|editor, window, cx| {
 2054        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2055    });
 2056    cx.assert_editor_state(
 2057        &r#"one
 2058        two
 2059        ˇ
 2060        three
 2061        four
 2062        five
 2063        ˇ
 2064        six"#
 2065            .unindent(),
 2066    );
 2067
 2068    cx.update_editor(|editor, window, cx| {
 2069        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2070    });
 2071    cx.assert_editor_state(
 2072        &r#"one
 2073        two
 2074
 2075        three
 2076        four
 2077        five
 2078        ˇ
 2079        sixˇ"#
 2080            .unindent(),
 2081    );
 2082
 2083    cx.update_editor(|editor, window, cx| {
 2084        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2085    });
 2086    cx.assert_editor_state(
 2087        &r#"one
 2088        two
 2089
 2090        three
 2091        four
 2092        five
 2093
 2094        sixˇ"#
 2095            .unindent(),
 2096    );
 2097
 2098    cx.update_editor(|editor, window, cx| {
 2099        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2100    });
 2101    cx.assert_editor_state(
 2102        &r#"one
 2103        two
 2104
 2105        three
 2106        four
 2107        five
 2108        ˇ
 2109        six"#
 2110            .unindent(),
 2111    );
 2112
 2113    cx.update_editor(|editor, window, cx| {
 2114        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2115    });
 2116    cx.assert_editor_state(
 2117        &r#"one
 2118        two
 2119        ˇ
 2120        three
 2121        four
 2122        five
 2123
 2124        six"#
 2125            .unindent(),
 2126    );
 2127
 2128    cx.update_editor(|editor, window, cx| {
 2129        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2130    });
 2131    cx.assert_editor_state(
 2132        &r#"ˇone
 2133        two
 2134
 2135        three
 2136        four
 2137        five
 2138
 2139        six"#
 2140            .unindent(),
 2141    );
 2142}
 2143
 2144#[gpui::test]
 2145async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2146    init_test(cx, |_| {});
 2147    let mut cx = EditorTestContext::new(cx).await;
 2148    let line_height = cx.editor(|editor, window, _| {
 2149        editor
 2150            .style()
 2151            .unwrap()
 2152            .text
 2153            .line_height_in_pixels(window.rem_size())
 2154    });
 2155    let window = cx.window;
 2156    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2157
 2158    cx.set_state(
 2159        r#"ˇone
 2160        two
 2161        three
 2162        four
 2163        five
 2164        six
 2165        seven
 2166        eight
 2167        nine
 2168        ten
 2169        "#,
 2170    );
 2171
 2172    cx.update_editor(|editor, window, cx| {
 2173        assert_eq!(
 2174            editor.snapshot(window, cx).scroll_position(),
 2175            gpui::Point::new(0., 0.)
 2176        );
 2177        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2178        assert_eq!(
 2179            editor.snapshot(window, cx).scroll_position(),
 2180            gpui::Point::new(0., 3.)
 2181        );
 2182        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2183        assert_eq!(
 2184            editor.snapshot(window, cx).scroll_position(),
 2185            gpui::Point::new(0., 6.)
 2186        );
 2187        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2188        assert_eq!(
 2189            editor.snapshot(window, cx).scroll_position(),
 2190            gpui::Point::new(0., 3.)
 2191        );
 2192
 2193        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2194        assert_eq!(
 2195            editor.snapshot(window, cx).scroll_position(),
 2196            gpui::Point::new(0., 1.)
 2197        );
 2198        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2199        assert_eq!(
 2200            editor.snapshot(window, cx).scroll_position(),
 2201            gpui::Point::new(0., 3.)
 2202        );
 2203    });
 2204}
 2205
 2206#[gpui::test]
 2207async fn test_autoscroll(cx: &mut TestAppContext) {
 2208    init_test(cx, |_| {});
 2209    let mut cx = EditorTestContext::new(cx).await;
 2210
 2211    let line_height = cx.update_editor(|editor, window, cx| {
 2212        editor.set_vertical_scroll_margin(2, cx);
 2213        editor
 2214            .style()
 2215            .unwrap()
 2216            .text
 2217            .line_height_in_pixels(window.rem_size())
 2218    });
 2219    let window = cx.window;
 2220    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2221
 2222    cx.set_state(
 2223        r#"ˇone
 2224            two
 2225            three
 2226            four
 2227            five
 2228            six
 2229            seven
 2230            eight
 2231            nine
 2232            ten
 2233        "#,
 2234    );
 2235    cx.update_editor(|editor, window, cx| {
 2236        assert_eq!(
 2237            editor.snapshot(window, cx).scroll_position(),
 2238            gpui::Point::new(0., 0.0)
 2239        );
 2240    });
 2241
 2242    // Add a cursor below the visible area. Since both cursors cannot fit
 2243    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2244    // allows the vertical scroll margin below that cursor.
 2245    cx.update_editor(|editor, window, cx| {
 2246        editor.change_selections(Default::default(), window, cx, |selections| {
 2247            selections.select_ranges([
 2248                Point::new(0, 0)..Point::new(0, 0),
 2249                Point::new(6, 0)..Point::new(6, 0),
 2250            ]);
 2251        })
 2252    });
 2253    cx.update_editor(|editor, window, cx| {
 2254        assert_eq!(
 2255            editor.snapshot(window, cx).scroll_position(),
 2256            gpui::Point::new(0., 3.0)
 2257        );
 2258    });
 2259
 2260    // Move down. The editor cursor scrolls down to track the newest cursor.
 2261    cx.update_editor(|editor, window, cx| {
 2262        editor.move_down(&Default::default(), window, cx);
 2263    });
 2264    cx.update_editor(|editor, window, cx| {
 2265        assert_eq!(
 2266            editor.snapshot(window, cx).scroll_position(),
 2267            gpui::Point::new(0., 4.0)
 2268        );
 2269    });
 2270
 2271    // Add a cursor above the visible area. Since both cursors fit on screen,
 2272    // the editor scrolls to show both.
 2273    cx.update_editor(|editor, window, cx| {
 2274        editor.change_selections(Default::default(), window, cx, |selections| {
 2275            selections.select_ranges([
 2276                Point::new(1, 0)..Point::new(1, 0),
 2277                Point::new(6, 0)..Point::new(6, 0),
 2278            ]);
 2279        })
 2280    });
 2281    cx.update_editor(|editor, window, cx| {
 2282        assert_eq!(
 2283            editor.snapshot(window, cx).scroll_position(),
 2284            gpui::Point::new(0., 1.0)
 2285        );
 2286    });
 2287}
 2288
 2289#[gpui::test]
 2290async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2291    init_test(cx, |_| {});
 2292    let mut cx = EditorTestContext::new(cx).await;
 2293
 2294    let line_height = cx.editor(|editor, window, _cx| {
 2295        editor
 2296            .style()
 2297            .unwrap()
 2298            .text
 2299            .line_height_in_pixels(window.rem_size())
 2300    });
 2301    let window = cx.window;
 2302    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2303    cx.set_state(
 2304        &r#"
 2305        ˇone
 2306        two
 2307        threeˇ
 2308        four
 2309        five
 2310        six
 2311        seven
 2312        eight
 2313        nine
 2314        ten
 2315        "#
 2316        .unindent(),
 2317    );
 2318
 2319    cx.update_editor(|editor, window, cx| {
 2320        editor.move_page_down(&MovePageDown::default(), window, cx)
 2321    });
 2322    cx.assert_editor_state(
 2323        &r#"
 2324        one
 2325        two
 2326        three
 2327        ˇfour
 2328        five
 2329        sixˇ
 2330        seven
 2331        eight
 2332        nine
 2333        ten
 2334        "#
 2335        .unindent(),
 2336    );
 2337
 2338    cx.update_editor(|editor, window, cx| {
 2339        editor.move_page_down(&MovePageDown::default(), window, cx)
 2340    });
 2341    cx.assert_editor_state(
 2342        &r#"
 2343        one
 2344        two
 2345        three
 2346        four
 2347        five
 2348        six
 2349        ˇseven
 2350        eight
 2351        nineˇ
 2352        ten
 2353        "#
 2354        .unindent(),
 2355    );
 2356
 2357    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2358    cx.assert_editor_state(
 2359        &r#"
 2360        one
 2361        two
 2362        three
 2363        ˇfour
 2364        five
 2365        sixˇ
 2366        seven
 2367        eight
 2368        nine
 2369        ten
 2370        "#
 2371        .unindent(),
 2372    );
 2373
 2374    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2375    cx.assert_editor_state(
 2376        &r#"
 2377        ˇone
 2378        two
 2379        threeˇ
 2380        four
 2381        five
 2382        six
 2383        seven
 2384        eight
 2385        nine
 2386        ten
 2387        "#
 2388        .unindent(),
 2389    );
 2390
 2391    // Test select collapsing
 2392    cx.update_editor(|editor, window, cx| {
 2393        editor.move_page_down(&MovePageDown::default(), window, cx);
 2394        editor.move_page_down(&MovePageDown::default(), window, cx);
 2395        editor.move_page_down(&MovePageDown::default(), window, cx);
 2396    });
 2397    cx.assert_editor_state(
 2398        &r#"
 2399        one
 2400        two
 2401        three
 2402        four
 2403        five
 2404        six
 2405        seven
 2406        eight
 2407        nine
 2408        ˇten
 2409        ˇ"#
 2410        .unindent(),
 2411    );
 2412}
 2413
 2414#[gpui::test]
 2415async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2416    init_test(cx, |_| {});
 2417    let mut cx = EditorTestContext::new(cx).await;
 2418    cx.set_state("one «two threeˇ» four");
 2419    cx.update_editor(|editor, window, cx| {
 2420        editor.delete_to_beginning_of_line(
 2421            &DeleteToBeginningOfLine {
 2422                stop_at_indent: false,
 2423            },
 2424            window,
 2425            cx,
 2426        );
 2427        assert_eq!(editor.text(cx), " four");
 2428    });
 2429}
 2430
 2431#[gpui::test]
 2432fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2433    init_test(cx, |_| {});
 2434
 2435    let editor = cx.add_window(|window, cx| {
 2436        let buffer = MultiBuffer::build_simple("one two three four", cx);
 2437        build_editor(buffer.clone(), window, cx)
 2438    });
 2439
 2440    _ = editor.update(cx, |editor, window, cx| {
 2441        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2442            s.select_display_ranges([
 2443                // an empty selection - the preceding word fragment is deleted
 2444                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2445                // characters selected - they are deleted
 2446                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
 2447            ])
 2448        });
 2449        editor.delete_to_previous_word_start(
 2450            &DeleteToPreviousWordStart {
 2451                ignore_newlines: false,
 2452            },
 2453            window,
 2454            cx,
 2455        );
 2456        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e two te four");
 2457    });
 2458
 2459    _ = editor.update(cx, |editor, window, cx| {
 2460        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2461            s.select_display_ranges([
 2462                // an empty selection - the following word fragment is deleted
 2463                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 2464                // characters selected - they are deleted
 2465                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
 2466            ])
 2467        });
 2468        editor.delete_to_next_word_end(
 2469            &DeleteToNextWordEnd {
 2470                ignore_newlines: false,
 2471            },
 2472            window,
 2473            cx,
 2474        );
 2475        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e t te our");
 2476    });
 2477}
 2478
 2479#[gpui::test]
 2480fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2481    init_test(cx, |_| {});
 2482
 2483    let editor = cx.add_window(|window, cx| {
 2484        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 2485        build_editor(buffer.clone(), window, cx)
 2486    });
 2487    let del_to_prev_word_start = DeleteToPreviousWordStart {
 2488        ignore_newlines: false,
 2489    };
 2490    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 2491        ignore_newlines: true,
 2492    };
 2493
 2494    _ = editor.update(cx, |editor, window, cx| {
 2495        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2496            s.select_display_ranges([
 2497                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 2498            ])
 2499        });
 2500        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2501        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 2502        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2503        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 2504        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2505        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 2506        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2507        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 2508        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2509        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 2510        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2511        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2512    });
 2513}
 2514
 2515#[gpui::test]
 2516fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 2517    init_test(cx, |_| {});
 2518
 2519    let editor = cx.add_window(|window, cx| {
 2520        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 2521        build_editor(buffer.clone(), window, cx)
 2522    });
 2523    let del_to_next_word_end = DeleteToNextWordEnd {
 2524        ignore_newlines: false,
 2525    };
 2526    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 2527        ignore_newlines: true,
 2528    };
 2529
 2530    _ = editor.update(cx, |editor, window, cx| {
 2531        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2532            s.select_display_ranges([
 2533                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 2534            ])
 2535        });
 2536        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2537        assert_eq!(
 2538            editor.buffer.read(cx).read(cx).text(),
 2539            "one\n   two\nthree\n   four"
 2540        );
 2541        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2542        assert_eq!(
 2543            editor.buffer.read(cx).read(cx).text(),
 2544            "\n   two\nthree\n   four"
 2545        );
 2546        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2547        assert_eq!(
 2548            editor.buffer.read(cx).read(cx).text(),
 2549            "two\nthree\n   four"
 2550        );
 2551        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2552        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 2553        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2554        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 2555        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2556        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2557    });
 2558}
 2559
 2560#[gpui::test]
 2561fn test_newline(cx: &mut TestAppContext) {
 2562    init_test(cx, |_| {});
 2563
 2564    let editor = cx.add_window(|window, cx| {
 2565        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 2566        build_editor(buffer.clone(), window, cx)
 2567    });
 2568
 2569    _ = editor.update(cx, |editor, window, cx| {
 2570        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2571            s.select_display_ranges([
 2572                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2573                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2574                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 2575            ])
 2576        });
 2577
 2578        editor.newline(&Newline, window, cx);
 2579        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 2580    });
 2581}
 2582
 2583#[gpui::test]
 2584fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 2585    init_test(cx, |_| {});
 2586
 2587    let editor = cx.add_window(|window, cx| {
 2588        let buffer = MultiBuffer::build_simple(
 2589            "
 2590                a
 2591                b(
 2592                    X
 2593                )
 2594                c(
 2595                    X
 2596                )
 2597            "
 2598            .unindent()
 2599            .as_str(),
 2600            cx,
 2601        );
 2602        let mut editor = build_editor(buffer.clone(), window, cx);
 2603        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2604            s.select_ranges([
 2605                Point::new(2, 4)..Point::new(2, 5),
 2606                Point::new(5, 4)..Point::new(5, 5),
 2607            ])
 2608        });
 2609        editor
 2610    });
 2611
 2612    _ = editor.update(cx, |editor, window, cx| {
 2613        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 2614        editor.buffer.update(cx, |buffer, cx| {
 2615            buffer.edit(
 2616                [
 2617                    (Point::new(1, 2)..Point::new(3, 0), ""),
 2618                    (Point::new(4, 2)..Point::new(6, 0), ""),
 2619                ],
 2620                None,
 2621                cx,
 2622            );
 2623            assert_eq!(
 2624                buffer.read(cx).text(),
 2625                "
 2626                    a
 2627                    b()
 2628                    c()
 2629                "
 2630                .unindent()
 2631            );
 2632        });
 2633        assert_eq!(
 2634            editor.selections.ranges(cx),
 2635            &[
 2636                Point::new(1, 2)..Point::new(1, 2),
 2637                Point::new(2, 2)..Point::new(2, 2),
 2638            ],
 2639        );
 2640
 2641        editor.newline(&Newline, window, cx);
 2642        assert_eq!(
 2643            editor.text(cx),
 2644            "
 2645                a
 2646                b(
 2647                )
 2648                c(
 2649                )
 2650            "
 2651            .unindent()
 2652        );
 2653
 2654        // The selections are moved after the inserted newlines
 2655        assert_eq!(
 2656            editor.selections.ranges(cx),
 2657            &[
 2658                Point::new(2, 0)..Point::new(2, 0),
 2659                Point::new(4, 0)..Point::new(4, 0),
 2660            ],
 2661        );
 2662    });
 2663}
 2664
 2665#[gpui::test]
 2666async fn test_newline_above(cx: &mut TestAppContext) {
 2667    init_test(cx, |settings| {
 2668        settings.defaults.tab_size = NonZeroU32::new(4)
 2669    });
 2670
 2671    let language = Arc::new(
 2672        Language::new(
 2673            LanguageConfig::default(),
 2674            Some(tree_sitter_rust::LANGUAGE.into()),
 2675        )
 2676        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2677        .unwrap(),
 2678    );
 2679
 2680    let mut cx = EditorTestContext::new(cx).await;
 2681    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2682    cx.set_state(indoc! {"
 2683        const a: ˇA = (
 2684 2685                «const_functionˇ»(ˇ),
 2686                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2687 2688        ˇ);ˇ
 2689    "});
 2690
 2691    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 2692    cx.assert_editor_state(indoc! {"
 2693        ˇ
 2694        const a: A = (
 2695            ˇ
 2696            (
 2697                ˇ
 2698                ˇ
 2699                const_function(),
 2700                ˇ
 2701                ˇ
 2702                ˇ
 2703                ˇ
 2704                something_else,
 2705                ˇ
 2706            )
 2707            ˇ
 2708            ˇ
 2709        );
 2710    "});
 2711}
 2712
 2713#[gpui::test]
 2714async fn test_newline_below(cx: &mut TestAppContext) {
 2715    init_test(cx, |settings| {
 2716        settings.defaults.tab_size = NonZeroU32::new(4)
 2717    });
 2718
 2719    let language = Arc::new(
 2720        Language::new(
 2721            LanguageConfig::default(),
 2722            Some(tree_sitter_rust::LANGUAGE.into()),
 2723        )
 2724        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2725        .unwrap(),
 2726    );
 2727
 2728    let mut cx = EditorTestContext::new(cx).await;
 2729    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2730    cx.set_state(indoc! {"
 2731        const a: ˇA = (
 2732 2733                «const_functionˇ»(ˇ),
 2734                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2735 2736        ˇ);ˇ
 2737    "});
 2738
 2739    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 2740    cx.assert_editor_state(indoc! {"
 2741        const a: A = (
 2742            ˇ
 2743            (
 2744                ˇ
 2745                const_function(),
 2746                ˇ
 2747                ˇ
 2748                something_else,
 2749                ˇ
 2750                ˇ
 2751                ˇ
 2752                ˇ
 2753            )
 2754            ˇ
 2755        );
 2756        ˇ
 2757        ˇ
 2758    "});
 2759}
 2760
 2761#[gpui::test]
 2762async fn test_newline_comments(cx: &mut TestAppContext) {
 2763    init_test(cx, |settings| {
 2764        settings.defaults.tab_size = NonZeroU32::new(4)
 2765    });
 2766
 2767    let language = Arc::new(Language::new(
 2768        LanguageConfig {
 2769            line_comments: vec!["// ".into()],
 2770            ..LanguageConfig::default()
 2771        },
 2772        None,
 2773    ));
 2774    {
 2775        let mut cx = EditorTestContext::new(cx).await;
 2776        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2777        cx.set_state(indoc! {"
 2778        // Fooˇ
 2779    "});
 2780
 2781        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2782        cx.assert_editor_state(indoc! {"
 2783        // Foo
 2784        // ˇ
 2785    "});
 2786        // Ensure that we add comment prefix when existing line contains space
 2787        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2788        cx.assert_editor_state(
 2789            indoc! {"
 2790        // Foo
 2791        //s
 2792        // ˇ
 2793    "}
 2794            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2795            .as_str(),
 2796        );
 2797        // Ensure that we add comment prefix when existing line does not contain space
 2798        cx.set_state(indoc! {"
 2799        // Foo
 2800        //ˇ
 2801    "});
 2802        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2803        cx.assert_editor_state(indoc! {"
 2804        // Foo
 2805        //
 2806        // ˇ
 2807    "});
 2808        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 2809        cx.set_state(indoc! {"
 2810        ˇ// Foo
 2811    "});
 2812        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2813        cx.assert_editor_state(indoc! {"
 2814
 2815        ˇ// Foo
 2816    "});
 2817    }
 2818    // Ensure that comment continuations can be disabled.
 2819    update_test_language_settings(cx, |settings| {
 2820        settings.defaults.extend_comment_on_newline = Some(false);
 2821    });
 2822    let mut cx = EditorTestContext::new(cx).await;
 2823    cx.set_state(indoc! {"
 2824        // Fooˇ
 2825    "});
 2826    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2827    cx.assert_editor_state(indoc! {"
 2828        // Foo
 2829        ˇ
 2830    "});
 2831}
 2832
 2833#[gpui::test]
 2834async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 2835    init_test(cx, |settings| {
 2836        settings.defaults.tab_size = NonZeroU32::new(4)
 2837    });
 2838
 2839    let language = Arc::new(Language::new(
 2840        LanguageConfig {
 2841            line_comments: vec!["// ".into(), "/// ".into()],
 2842            ..LanguageConfig::default()
 2843        },
 2844        None,
 2845    ));
 2846    {
 2847        let mut cx = EditorTestContext::new(cx).await;
 2848        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2849        cx.set_state(indoc! {"
 2850        //ˇ
 2851    "});
 2852        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2853        cx.assert_editor_state(indoc! {"
 2854        //
 2855        // ˇ
 2856    "});
 2857
 2858        cx.set_state(indoc! {"
 2859        ///ˇ
 2860    "});
 2861        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2862        cx.assert_editor_state(indoc! {"
 2863        ///
 2864        /// ˇ
 2865    "});
 2866    }
 2867}
 2868
 2869#[gpui::test]
 2870async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 2871    init_test(cx, |settings| {
 2872        settings.defaults.tab_size = NonZeroU32::new(4)
 2873    });
 2874
 2875    let language = Arc::new(
 2876        Language::new(
 2877            LanguageConfig {
 2878                documentation_comment: Some(language::BlockCommentConfig {
 2879                    start: "/**".into(),
 2880                    end: "*/".into(),
 2881                    prefix: "* ".into(),
 2882                    tab_size: 1,
 2883                }),
 2884
 2885                ..LanguageConfig::default()
 2886            },
 2887            Some(tree_sitter_rust::LANGUAGE.into()),
 2888        )
 2889        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 2890        .unwrap(),
 2891    );
 2892
 2893    {
 2894        let mut cx = EditorTestContext::new(cx).await;
 2895        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2896        cx.set_state(indoc! {"
 2897        /**ˇ
 2898    "});
 2899
 2900        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2901        cx.assert_editor_state(indoc! {"
 2902        /**
 2903         * ˇ
 2904    "});
 2905        // Ensure that if cursor is before the comment start,
 2906        // we do not actually insert a comment prefix.
 2907        cx.set_state(indoc! {"
 2908        ˇ/**
 2909    "});
 2910        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2911        cx.assert_editor_state(indoc! {"
 2912
 2913        ˇ/**
 2914    "});
 2915        // Ensure that if cursor is between it doesn't add comment prefix.
 2916        cx.set_state(indoc! {"
 2917        /*ˇ*
 2918    "});
 2919        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2920        cx.assert_editor_state(indoc! {"
 2921        /*
 2922        ˇ*
 2923    "});
 2924        // Ensure that if suffix exists on same line after cursor it adds new line.
 2925        cx.set_state(indoc! {"
 2926        /**ˇ*/
 2927    "});
 2928        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2929        cx.assert_editor_state(indoc! {"
 2930        /**
 2931         * ˇ
 2932         */
 2933    "});
 2934        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2935        cx.set_state(indoc! {"
 2936        /**ˇ */
 2937    "});
 2938        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2939        cx.assert_editor_state(indoc! {"
 2940        /**
 2941         * ˇ
 2942         */
 2943    "});
 2944        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2945        cx.set_state(indoc! {"
 2946        /** ˇ*/
 2947    "});
 2948        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2949        cx.assert_editor_state(
 2950            indoc! {"
 2951        /**s
 2952         * ˇ
 2953         */
 2954    "}
 2955            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2956            .as_str(),
 2957        );
 2958        // Ensure that delimiter space is preserved when newline on already
 2959        // spaced delimiter.
 2960        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2961        cx.assert_editor_state(
 2962            indoc! {"
 2963        /**s
 2964         *s
 2965         * ˇ
 2966         */
 2967    "}
 2968            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2969            .as_str(),
 2970        );
 2971        // Ensure that delimiter space is preserved when space is not
 2972        // on existing delimiter.
 2973        cx.set_state(indoc! {"
 2974        /**
 2975 2976         */
 2977    "});
 2978        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2979        cx.assert_editor_state(indoc! {"
 2980        /**
 2981         *
 2982         * ˇ
 2983         */
 2984    "});
 2985        // Ensure that if suffix exists on same line after cursor it
 2986        // doesn't add extra new line if prefix is not on same line.
 2987        cx.set_state(indoc! {"
 2988        /**
 2989        ˇ*/
 2990    "});
 2991        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2992        cx.assert_editor_state(indoc! {"
 2993        /**
 2994
 2995        ˇ*/
 2996    "});
 2997        // Ensure that it detects suffix after existing prefix.
 2998        cx.set_state(indoc! {"
 2999        /**ˇ/
 3000    "});
 3001        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3002        cx.assert_editor_state(indoc! {"
 3003        /**
 3004        ˇ/
 3005    "});
 3006        // Ensure that if suffix exists on same line before
 3007        // cursor it does not add comment prefix.
 3008        cx.set_state(indoc! {"
 3009        /** */ˇ
 3010    "});
 3011        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3012        cx.assert_editor_state(indoc! {"
 3013        /** */
 3014        ˇ
 3015    "});
 3016        // Ensure that if suffix exists on same line before
 3017        // cursor it does not add comment prefix.
 3018        cx.set_state(indoc! {"
 3019        /**
 3020         *
 3021         */ˇ
 3022    "});
 3023        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3024        cx.assert_editor_state(indoc! {"
 3025        /**
 3026         *
 3027         */
 3028         ˇ
 3029    "});
 3030
 3031        // Ensure that inline comment followed by code
 3032        // doesn't add comment prefix on newline
 3033        cx.set_state(indoc! {"
 3034        /** */ textˇ
 3035    "});
 3036        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3037        cx.assert_editor_state(indoc! {"
 3038        /** */ text
 3039        ˇ
 3040    "});
 3041
 3042        // Ensure that text after comment end tag
 3043        // doesn't add comment prefix on newline
 3044        cx.set_state(indoc! {"
 3045        /**
 3046         *
 3047         */ˇtext
 3048    "});
 3049        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3050        cx.assert_editor_state(indoc! {"
 3051        /**
 3052         *
 3053         */
 3054         ˇtext
 3055    "});
 3056
 3057        // Ensure if not comment block it doesn't
 3058        // add comment prefix on newline
 3059        cx.set_state(indoc! {"
 3060        * textˇ
 3061    "});
 3062        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3063        cx.assert_editor_state(indoc! {"
 3064        * text
 3065        ˇ
 3066    "});
 3067    }
 3068    // Ensure that comment continuations can be disabled.
 3069    update_test_language_settings(cx, |settings| {
 3070        settings.defaults.extend_comment_on_newline = Some(false);
 3071    });
 3072    let mut cx = EditorTestContext::new(cx).await;
 3073    cx.set_state(indoc! {"
 3074        /**ˇ
 3075    "});
 3076    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3077    cx.assert_editor_state(indoc! {"
 3078        /**
 3079        ˇ
 3080    "});
 3081}
 3082
 3083#[gpui::test]
 3084async fn test_newline_comments_with_block_comment(cx: &mut TestAppContext) {
 3085    init_test(cx, |settings| {
 3086        settings.defaults.tab_size = NonZeroU32::new(4)
 3087    });
 3088
 3089    let lua_language = Arc::new(Language::new(
 3090        LanguageConfig {
 3091            line_comments: vec!["--".into()],
 3092            block_comment: Some(language::BlockCommentConfig {
 3093                start: "--[[".into(),
 3094                prefix: "".into(),
 3095                end: "]]".into(),
 3096                tab_size: 0,
 3097            }),
 3098            ..LanguageConfig::default()
 3099        },
 3100        None,
 3101    ));
 3102
 3103    let mut cx = EditorTestContext::new(cx).await;
 3104    cx.update_buffer(|buffer, cx| buffer.set_language(Some(lua_language), cx));
 3105
 3106    // Line with line comment should extend
 3107    cx.set_state(indoc! {"
 3108        --ˇ
 3109    "});
 3110    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3111    cx.assert_editor_state(indoc! {"
 3112        --
 3113        --ˇ
 3114    "});
 3115
 3116    // Line with block comment that matches line comment should not extend
 3117    cx.set_state(indoc! {"
 3118        --[[ˇ
 3119    "});
 3120    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3121    cx.assert_editor_state(indoc! {"
 3122        --[[
 3123        ˇ
 3124    "});
 3125}
 3126
 3127#[gpui::test]
 3128fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3129    init_test(cx, |_| {});
 3130
 3131    let editor = cx.add_window(|window, cx| {
 3132        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3133        let mut editor = build_editor(buffer.clone(), window, cx);
 3134        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3135            s.select_ranges([3..4, 11..12, 19..20])
 3136        });
 3137        editor
 3138    });
 3139
 3140    _ = editor.update(cx, |editor, window, cx| {
 3141        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3142        editor.buffer.update(cx, |buffer, cx| {
 3143            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 3144            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3145        });
 3146        assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
 3147
 3148        editor.insert("Z", window, cx);
 3149        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3150
 3151        // The selections are moved after the inserted characters
 3152        assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
 3153    });
 3154}
 3155
 3156#[gpui::test]
 3157async fn test_tab(cx: &mut TestAppContext) {
 3158    init_test(cx, |settings| {
 3159        settings.defaults.tab_size = NonZeroU32::new(3)
 3160    });
 3161
 3162    let mut cx = EditorTestContext::new(cx).await;
 3163    cx.set_state(indoc! {"
 3164        ˇabˇc
 3165        ˇ🏀ˇ🏀ˇefg
 3166 3167    "});
 3168    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3169    cx.assert_editor_state(indoc! {"
 3170           ˇab ˇc
 3171           ˇ🏀  ˇ🏀  ˇefg
 3172        d  ˇ
 3173    "});
 3174
 3175    cx.set_state(indoc! {"
 3176        a
 3177        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3178    "});
 3179    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3180    cx.assert_editor_state(indoc! {"
 3181        a
 3182           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3183    "});
 3184}
 3185
 3186#[gpui::test]
 3187async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3188    init_test(cx, |_| {});
 3189
 3190    let mut cx = EditorTestContext::new(cx).await;
 3191    let language = Arc::new(
 3192        Language::new(
 3193            LanguageConfig::default(),
 3194            Some(tree_sitter_rust::LANGUAGE.into()),
 3195        )
 3196        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3197        .unwrap(),
 3198    );
 3199    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3200
 3201    // test when all cursors are not at suggested indent
 3202    // then simply move to their suggested indent location
 3203    cx.set_state(indoc! {"
 3204        const a: B = (
 3205            c(
 3206        ˇ
 3207        ˇ    )
 3208        );
 3209    "});
 3210    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3211    cx.assert_editor_state(indoc! {"
 3212        const a: B = (
 3213            c(
 3214                ˇ
 3215            ˇ)
 3216        );
 3217    "});
 3218
 3219    // test cursor already at suggested indent not moving when
 3220    // other cursors are yet to reach their suggested indents
 3221    cx.set_state(indoc! {"
 3222        ˇ
 3223        const a: B = (
 3224            c(
 3225                d(
 3226        ˇ
 3227                )
 3228        ˇ
 3229        ˇ    )
 3230        );
 3231    "});
 3232    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3233    cx.assert_editor_state(indoc! {"
 3234        ˇ
 3235        const a: B = (
 3236            c(
 3237                d(
 3238                    ˇ
 3239                )
 3240                ˇ
 3241            ˇ)
 3242        );
 3243    "});
 3244    // test when all cursors are at suggested indent then tab is inserted
 3245    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3246    cx.assert_editor_state(indoc! {"
 3247            ˇ
 3248        const a: B = (
 3249            c(
 3250                d(
 3251                        ˇ
 3252                )
 3253                    ˇ
 3254                ˇ)
 3255        );
 3256    "});
 3257
 3258    // test when current indent is less than suggested indent,
 3259    // we adjust line to match suggested indent and move cursor to it
 3260    //
 3261    // when no other cursor is at word boundary, all of them should move
 3262    cx.set_state(indoc! {"
 3263        const a: B = (
 3264            c(
 3265                d(
 3266        ˇ
 3267        ˇ   )
 3268        ˇ   )
 3269        );
 3270    "});
 3271    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3272    cx.assert_editor_state(indoc! {"
 3273        const a: B = (
 3274            c(
 3275                d(
 3276                    ˇ
 3277                ˇ)
 3278            ˇ)
 3279        );
 3280    "});
 3281
 3282    // test when current indent is less than suggested indent,
 3283    // we adjust line to match suggested indent and move cursor to it
 3284    //
 3285    // when some other cursor is at word boundary, it should not move
 3286    cx.set_state(indoc! {"
 3287        const a: B = (
 3288            c(
 3289                d(
 3290        ˇ
 3291        ˇ   )
 3292           ˇ)
 3293        );
 3294    "});
 3295    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3296    cx.assert_editor_state(indoc! {"
 3297        const a: B = (
 3298            c(
 3299                d(
 3300                    ˇ
 3301                ˇ)
 3302            ˇ)
 3303        );
 3304    "});
 3305
 3306    // test when current indent is more than suggested indent,
 3307    // we just move cursor to current indent instead of suggested indent
 3308    //
 3309    // when no other cursor is at word boundary, all of them should move
 3310    cx.set_state(indoc! {"
 3311        const a: B = (
 3312            c(
 3313                d(
 3314        ˇ
 3315        ˇ                )
 3316        ˇ   )
 3317        );
 3318    "});
 3319    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3320    cx.assert_editor_state(indoc! {"
 3321        const a: B = (
 3322            c(
 3323                d(
 3324                    ˇ
 3325                        ˇ)
 3326            ˇ)
 3327        );
 3328    "});
 3329    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3330    cx.assert_editor_state(indoc! {"
 3331        const a: B = (
 3332            c(
 3333                d(
 3334                        ˇ
 3335                            ˇ)
 3336                ˇ)
 3337        );
 3338    "});
 3339
 3340    // test when current indent is more than suggested indent,
 3341    // we just move cursor to current indent instead of suggested indent
 3342    //
 3343    // when some other cursor is at word boundary, it doesn't move
 3344    cx.set_state(indoc! {"
 3345        const a: B = (
 3346            c(
 3347                d(
 3348        ˇ
 3349        ˇ                )
 3350            ˇ)
 3351        );
 3352    "});
 3353    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3354    cx.assert_editor_state(indoc! {"
 3355        const a: B = (
 3356            c(
 3357                d(
 3358                    ˇ
 3359                        ˇ)
 3360            ˇ)
 3361        );
 3362    "});
 3363
 3364    // handle auto-indent when there are multiple cursors on the same line
 3365    cx.set_state(indoc! {"
 3366        const a: B = (
 3367            c(
 3368        ˇ    ˇ
 3369        ˇ    )
 3370        );
 3371    "});
 3372    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3373    cx.assert_editor_state(indoc! {"
 3374        const a: B = (
 3375            c(
 3376                ˇ
 3377            ˇ)
 3378        );
 3379    "});
 3380}
 3381
 3382#[gpui::test]
 3383async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 3384    init_test(cx, |settings| {
 3385        settings.defaults.tab_size = NonZeroU32::new(3)
 3386    });
 3387
 3388    let mut cx = EditorTestContext::new(cx).await;
 3389    cx.set_state(indoc! {"
 3390         ˇ
 3391        \t ˇ
 3392        \t  ˇ
 3393        \t   ˇ
 3394         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 3395    "});
 3396
 3397    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3398    cx.assert_editor_state(indoc! {"
 3399           ˇ
 3400        \t   ˇ
 3401        \t   ˇ
 3402        \t      ˇ
 3403         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 3404    "});
 3405}
 3406
 3407#[gpui::test]
 3408async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 3409    init_test(cx, |settings| {
 3410        settings.defaults.tab_size = NonZeroU32::new(4)
 3411    });
 3412
 3413    let language = Arc::new(
 3414        Language::new(
 3415            LanguageConfig::default(),
 3416            Some(tree_sitter_rust::LANGUAGE.into()),
 3417        )
 3418        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 3419        .unwrap(),
 3420    );
 3421
 3422    let mut cx = EditorTestContext::new(cx).await;
 3423    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3424    cx.set_state(indoc! {"
 3425        fn a() {
 3426            if b {
 3427        \t ˇc
 3428            }
 3429        }
 3430    "});
 3431
 3432    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3433    cx.assert_editor_state(indoc! {"
 3434        fn a() {
 3435            if b {
 3436                ˇc
 3437            }
 3438        }
 3439    "});
 3440}
 3441
 3442#[gpui::test]
 3443async fn test_indent_outdent(cx: &mut TestAppContext) {
 3444    init_test(cx, |settings| {
 3445        settings.defaults.tab_size = NonZeroU32::new(4);
 3446    });
 3447
 3448    let mut cx = EditorTestContext::new(cx).await;
 3449
 3450    cx.set_state(indoc! {"
 3451          «oneˇ» «twoˇ»
 3452        three
 3453         four
 3454    "});
 3455    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3456    cx.assert_editor_state(indoc! {"
 3457            «oneˇ» «twoˇ»
 3458        three
 3459         four
 3460    "});
 3461
 3462    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3463    cx.assert_editor_state(indoc! {"
 3464        «oneˇ» «twoˇ»
 3465        three
 3466         four
 3467    "});
 3468
 3469    // select across line ending
 3470    cx.set_state(indoc! {"
 3471        one two
 3472        t«hree
 3473        ˇ» four
 3474    "});
 3475    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3476    cx.assert_editor_state(indoc! {"
 3477        one two
 3478            t«hree
 3479        ˇ» four
 3480    "});
 3481
 3482    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3483    cx.assert_editor_state(indoc! {"
 3484        one two
 3485        t«hree
 3486        ˇ» four
 3487    "});
 3488
 3489    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3490    cx.set_state(indoc! {"
 3491        one two
 3492        ˇthree
 3493            four
 3494    "});
 3495    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3496    cx.assert_editor_state(indoc! {"
 3497        one two
 3498            ˇthree
 3499            four
 3500    "});
 3501
 3502    cx.set_state(indoc! {"
 3503        one two
 3504        ˇ    three
 3505            four
 3506    "});
 3507    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3508    cx.assert_editor_state(indoc! {"
 3509        one two
 3510        ˇthree
 3511            four
 3512    "});
 3513}
 3514
 3515#[gpui::test]
 3516async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3517    // This is a regression test for issue #33761
 3518    init_test(cx, |_| {});
 3519
 3520    let mut cx = EditorTestContext::new(cx).await;
 3521    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3522    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3523
 3524    cx.set_state(
 3525        r#"ˇ#     ingress:
 3526ˇ#         api:
 3527ˇ#             enabled: false
 3528ˇ#             pathType: Prefix
 3529ˇ#           console:
 3530ˇ#               enabled: false
 3531ˇ#               pathType: Prefix
 3532"#,
 3533    );
 3534
 3535    // Press tab to indent all lines
 3536    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3537
 3538    cx.assert_editor_state(
 3539        r#"    ˇ#     ingress:
 3540    ˇ#         api:
 3541    ˇ#             enabled: false
 3542    ˇ#             pathType: Prefix
 3543    ˇ#           console:
 3544    ˇ#               enabled: false
 3545    ˇ#               pathType: Prefix
 3546"#,
 3547    );
 3548}
 3549
 3550#[gpui::test]
 3551async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3552    // This is a test to make sure our fix for issue #33761 didn't break anything
 3553    init_test(cx, |_| {});
 3554
 3555    let mut cx = EditorTestContext::new(cx).await;
 3556    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3557    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3558
 3559    cx.set_state(
 3560        r#"ˇingress:
 3561ˇ  api:
 3562ˇ    enabled: false
 3563ˇ    pathType: Prefix
 3564"#,
 3565    );
 3566
 3567    // Press tab to indent all lines
 3568    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3569
 3570    cx.assert_editor_state(
 3571        r#"ˇingress:
 3572    ˇapi:
 3573        ˇenabled: false
 3574        ˇpathType: Prefix
 3575"#,
 3576    );
 3577}
 3578
 3579#[gpui::test]
 3580async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 3581    init_test(cx, |settings| {
 3582        settings.defaults.hard_tabs = Some(true);
 3583    });
 3584
 3585    let mut cx = EditorTestContext::new(cx).await;
 3586
 3587    // select two ranges on one line
 3588    cx.set_state(indoc! {"
 3589        «oneˇ» «twoˇ»
 3590        three
 3591        four
 3592    "});
 3593    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3594    cx.assert_editor_state(indoc! {"
 3595        \t«oneˇ» «twoˇ»
 3596        three
 3597        four
 3598    "});
 3599    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3600    cx.assert_editor_state(indoc! {"
 3601        \t\t«oneˇ» «twoˇ»
 3602        three
 3603        four
 3604    "});
 3605    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3606    cx.assert_editor_state(indoc! {"
 3607        \t«oneˇ» «twoˇ»
 3608        three
 3609        four
 3610    "});
 3611    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3612    cx.assert_editor_state(indoc! {"
 3613        «oneˇ» «twoˇ»
 3614        three
 3615        four
 3616    "});
 3617
 3618    // select across a line ending
 3619    cx.set_state(indoc! {"
 3620        one two
 3621        t«hree
 3622        ˇ»four
 3623    "});
 3624    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3625    cx.assert_editor_state(indoc! {"
 3626        one two
 3627        \tt«hree
 3628        ˇ»four
 3629    "});
 3630    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3631    cx.assert_editor_state(indoc! {"
 3632        one two
 3633        \t\tt«hree
 3634        ˇ»four
 3635    "});
 3636    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3637    cx.assert_editor_state(indoc! {"
 3638        one two
 3639        \tt«hree
 3640        ˇ»four
 3641    "});
 3642    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3643    cx.assert_editor_state(indoc! {"
 3644        one two
 3645        t«hree
 3646        ˇ»four
 3647    "});
 3648
 3649    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3650    cx.set_state(indoc! {"
 3651        one two
 3652        ˇthree
 3653        four
 3654    "});
 3655    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3656    cx.assert_editor_state(indoc! {"
 3657        one two
 3658        ˇthree
 3659        four
 3660    "});
 3661    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3662    cx.assert_editor_state(indoc! {"
 3663        one two
 3664        \tˇthree
 3665        four
 3666    "});
 3667    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3668    cx.assert_editor_state(indoc! {"
 3669        one two
 3670        ˇthree
 3671        four
 3672    "});
 3673}
 3674
 3675#[gpui::test]
 3676fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 3677    init_test(cx, |settings| {
 3678        settings.languages.0.extend([
 3679            (
 3680                "TOML".into(),
 3681                LanguageSettingsContent {
 3682                    tab_size: NonZeroU32::new(2),
 3683                    ..Default::default()
 3684                },
 3685            ),
 3686            (
 3687                "Rust".into(),
 3688                LanguageSettingsContent {
 3689                    tab_size: NonZeroU32::new(4),
 3690                    ..Default::default()
 3691                },
 3692            ),
 3693        ]);
 3694    });
 3695
 3696    let toml_language = Arc::new(Language::new(
 3697        LanguageConfig {
 3698            name: "TOML".into(),
 3699            ..Default::default()
 3700        },
 3701        None,
 3702    ));
 3703    let rust_language = Arc::new(Language::new(
 3704        LanguageConfig {
 3705            name: "Rust".into(),
 3706            ..Default::default()
 3707        },
 3708        None,
 3709    ));
 3710
 3711    let toml_buffer =
 3712        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 3713    let rust_buffer =
 3714        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 3715    let multibuffer = cx.new(|cx| {
 3716        let mut multibuffer = MultiBuffer::new(ReadWrite);
 3717        multibuffer.push_excerpts(
 3718            toml_buffer.clone(),
 3719            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 3720            cx,
 3721        );
 3722        multibuffer.push_excerpts(
 3723            rust_buffer.clone(),
 3724            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 3725            cx,
 3726        );
 3727        multibuffer
 3728    });
 3729
 3730    cx.add_window(|window, cx| {
 3731        let mut editor = build_editor(multibuffer, window, cx);
 3732
 3733        assert_eq!(
 3734            editor.text(cx),
 3735            indoc! {"
 3736                a = 1
 3737                b = 2
 3738
 3739                const c: usize = 3;
 3740            "}
 3741        );
 3742
 3743        select_ranges(
 3744            &mut editor,
 3745            indoc! {"
 3746                «aˇ» = 1
 3747                b = 2
 3748
 3749                «const c:ˇ» usize = 3;
 3750            "},
 3751            window,
 3752            cx,
 3753        );
 3754
 3755        editor.tab(&Tab, window, cx);
 3756        assert_text_with_selections(
 3757            &mut editor,
 3758            indoc! {"
 3759                  «aˇ» = 1
 3760                b = 2
 3761
 3762                    «const c:ˇ» usize = 3;
 3763            "},
 3764            cx,
 3765        );
 3766        editor.backtab(&Backtab, window, cx);
 3767        assert_text_with_selections(
 3768            &mut editor,
 3769            indoc! {"
 3770                «aˇ» = 1
 3771                b = 2
 3772
 3773                «const c:ˇ» usize = 3;
 3774            "},
 3775            cx,
 3776        );
 3777
 3778        editor
 3779    });
 3780}
 3781
 3782#[gpui::test]
 3783async fn test_backspace(cx: &mut TestAppContext) {
 3784    init_test(cx, |_| {});
 3785
 3786    let mut cx = EditorTestContext::new(cx).await;
 3787
 3788    // Basic backspace
 3789    cx.set_state(indoc! {"
 3790        onˇe two three
 3791        fou«rˇ» five six
 3792        seven «ˇeight nine
 3793        »ten
 3794    "});
 3795    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3796    cx.assert_editor_state(indoc! {"
 3797        oˇe two three
 3798        fouˇ five six
 3799        seven ˇten
 3800    "});
 3801
 3802    // Test backspace inside and around indents
 3803    cx.set_state(indoc! {"
 3804        zero
 3805            ˇone
 3806                ˇtwo
 3807            ˇ ˇ ˇ  three
 3808        ˇ  ˇ  four
 3809    "});
 3810    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3811    cx.assert_editor_state(indoc! {"
 3812        zero
 3813        ˇone
 3814            ˇtwo
 3815        ˇ  threeˇ  four
 3816    "});
 3817}
 3818
 3819#[gpui::test]
 3820async fn test_delete(cx: &mut TestAppContext) {
 3821    init_test(cx, |_| {});
 3822
 3823    let mut cx = EditorTestContext::new(cx).await;
 3824    cx.set_state(indoc! {"
 3825        onˇe two three
 3826        fou«rˇ» five six
 3827        seven «ˇeight nine
 3828        »ten
 3829    "});
 3830    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 3831    cx.assert_editor_state(indoc! {"
 3832        onˇ two three
 3833        fouˇ five six
 3834        seven ˇten
 3835    "});
 3836}
 3837
 3838#[gpui::test]
 3839fn test_delete_line(cx: &mut TestAppContext) {
 3840    init_test(cx, |_| {});
 3841
 3842    let editor = cx.add_window(|window, cx| {
 3843        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3844        build_editor(buffer, window, cx)
 3845    });
 3846    _ = editor.update(cx, |editor, window, cx| {
 3847        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3848            s.select_display_ranges([
 3849                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 3850                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 3851                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 3852            ])
 3853        });
 3854        editor.delete_line(&DeleteLine, window, cx);
 3855        assert_eq!(editor.display_text(cx), "ghi");
 3856        assert_eq!(
 3857            editor.selections.display_ranges(cx),
 3858            vec![
 3859                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 3860                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
 3861            ]
 3862        );
 3863    });
 3864
 3865    let editor = cx.add_window(|window, cx| {
 3866        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3867        build_editor(buffer, window, cx)
 3868    });
 3869    _ = editor.update(cx, |editor, window, cx| {
 3870        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3871            s.select_display_ranges([
 3872                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 3873            ])
 3874        });
 3875        editor.delete_line(&DeleteLine, window, cx);
 3876        assert_eq!(editor.display_text(cx), "ghi\n");
 3877        assert_eq!(
 3878            editor.selections.display_ranges(cx),
 3879            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 3880        );
 3881    });
 3882}
 3883
 3884#[gpui::test]
 3885fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 3886    init_test(cx, |_| {});
 3887
 3888    cx.add_window(|window, cx| {
 3889        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3890        let mut editor = build_editor(buffer.clone(), window, cx);
 3891        let buffer = buffer.read(cx).as_singleton().unwrap();
 3892
 3893        assert_eq!(
 3894            editor.selections.ranges::<Point>(cx),
 3895            &[Point::new(0, 0)..Point::new(0, 0)]
 3896        );
 3897
 3898        // When on single line, replace newline at end by space
 3899        editor.join_lines(&JoinLines, window, cx);
 3900        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3901        assert_eq!(
 3902            editor.selections.ranges::<Point>(cx),
 3903            &[Point::new(0, 3)..Point::new(0, 3)]
 3904        );
 3905
 3906        // When multiple lines are selected, remove newlines that are spanned by the selection
 3907        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3908            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 3909        });
 3910        editor.join_lines(&JoinLines, window, cx);
 3911        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 3912        assert_eq!(
 3913            editor.selections.ranges::<Point>(cx),
 3914            &[Point::new(0, 11)..Point::new(0, 11)]
 3915        );
 3916
 3917        // Undo should be transactional
 3918        editor.undo(&Undo, window, cx);
 3919        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3920        assert_eq!(
 3921            editor.selections.ranges::<Point>(cx),
 3922            &[Point::new(0, 5)..Point::new(2, 2)]
 3923        );
 3924
 3925        // When joining an empty line don't insert a space
 3926        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3927            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 3928        });
 3929        editor.join_lines(&JoinLines, window, cx);
 3930        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 3931        assert_eq!(
 3932            editor.selections.ranges::<Point>(cx),
 3933            [Point::new(2, 3)..Point::new(2, 3)]
 3934        );
 3935
 3936        // We can remove trailing newlines
 3937        editor.join_lines(&JoinLines, window, cx);
 3938        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3939        assert_eq!(
 3940            editor.selections.ranges::<Point>(cx),
 3941            [Point::new(2, 3)..Point::new(2, 3)]
 3942        );
 3943
 3944        // We don't blow up on the last line
 3945        editor.join_lines(&JoinLines, window, cx);
 3946        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3947        assert_eq!(
 3948            editor.selections.ranges::<Point>(cx),
 3949            [Point::new(2, 3)..Point::new(2, 3)]
 3950        );
 3951
 3952        // reset to test indentation
 3953        editor.buffer.update(cx, |buffer, cx| {
 3954            buffer.edit(
 3955                [
 3956                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 3957                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 3958                ],
 3959                None,
 3960                cx,
 3961            )
 3962        });
 3963
 3964        // We remove any leading spaces
 3965        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 3966        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3967            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 3968        });
 3969        editor.join_lines(&JoinLines, window, cx);
 3970        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 3971
 3972        // We don't insert a space for a line containing only spaces
 3973        editor.join_lines(&JoinLines, window, cx);
 3974        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 3975
 3976        // We ignore any leading tabs
 3977        editor.join_lines(&JoinLines, window, cx);
 3978        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 3979
 3980        editor
 3981    });
 3982}
 3983
 3984#[gpui::test]
 3985fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 3986    init_test(cx, |_| {});
 3987
 3988    cx.add_window(|window, cx| {
 3989        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3990        let mut editor = build_editor(buffer.clone(), window, cx);
 3991        let buffer = buffer.read(cx).as_singleton().unwrap();
 3992
 3993        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3994            s.select_ranges([
 3995                Point::new(0, 2)..Point::new(1, 1),
 3996                Point::new(1, 2)..Point::new(1, 2),
 3997                Point::new(3, 1)..Point::new(3, 2),
 3998            ])
 3999        });
 4000
 4001        editor.join_lines(&JoinLines, window, cx);
 4002        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 4003
 4004        assert_eq!(
 4005            editor.selections.ranges::<Point>(cx),
 4006            [
 4007                Point::new(0, 7)..Point::new(0, 7),
 4008                Point::new(1, 3)..Point::new(1, 3)
 4009            ]
 4010        );
 4011        editor
 4012    });
 4013}
 4014
 4015#[gpui::test]
 4016async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 4017    init_test(cx, |_| {});
 4018
 4019    let mut cx = EditorTestContext::new(cx).await;
 4020
 4021    let diff_base = r#"
 4022        Line 0
 4023        Line 1
 4024        Line 2
 4025        Line 3
 4026        "#
 4027    .unindent();
 4028
 4029    cx.set_state(
 4030        &r#"
 4031        ˇLine 0
 4032        Line 1
 4033        Line 2
 4034        Line 3
 4035        "#
 4036        .unindent(),
 4037    );
 4038
 4039    cx.set_head_text(&diff_base);
 4040    executor.run_until_parked();
 4041
 4042    // Join lines
 4043    cx.update_editor(|editor, window, cx| {
 4044        editor.join_lines(&JoinLines, window, cx);
 4045    });
 4046    executor.run_until_parked();
 4047
 4048    cx.assert_editor_state(
 4049        &r#"
 4050        Line 0ˇ Line 1
 4051        Line 2
 4052        Line 3
 4053        "#
 4054        .unindent(),
 4055    );
 4056    // Join again
 4057    cx.update_editor(|editor, window, cx| {
 4058        editor.join_lines(&JoinLines, window, cx);
 4059    });
 4060    executor.run_until_parked();
 4061
 4062    cx.assert_editor_state(
 4063        &r#"
 4064        Line 0 Line 1ˇ Line 2
 4065        Line 3
 4066        "#
 4067        .unindent(),
 4068    );
 4069}
 4070
 4071#[gpui::test]
 4072async fn test_custom_newlines_cause_no_false_positive_diffs(
 4073    executor: BackgroundExecutor,
 4074    cx: &mut TestAppContext,
 4075) {
 4076    init_test(cx, |_| {});
 4077    let mut cx = EditorTestContext::new(cx).await;
 4078    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 4079    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 4080    executor.run_until_parked();
 4081
 4082    cx.update_editor(|editor, window, cx| {
 4083        let snapshot = editor.snapshot(window, cx);
 4084        assert_eq!(
 4085            snapshot
 4086                .buffer_snapshot
 4087                .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
 4088                .collect::<Vec<_>>(),
 4089            Vec::new(),
 4090            "Should not have any diffs for files with custom newlines"
 4091        );
 4092    });
 4093}
 4094
 4095#[gpui::test]
 4096async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 4097    init_test(cx, |_| {});
 4098
 4099    let mut cx = EditorTestContext::new(cx).await;
 4100
 4101    // Test sort_lines_case_insensitive()
 4102    cx.set_state(indoc! {"
 4103        «z
 4104        y
 4105        x
 4106        Z
 4107        Y
 4108        Xˇ»
 4109    "});
 4110    cx.update_editor(|e, window, cx| {
 4111        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4112    });
 4113    cx.assert_editor_state(indoc! {"
 4114        «x
 4115        X
 4116        y
 4117        Y
 4118        z
 4119        Zˇ»
 4120    "});
 4121
 4122    // Test sort_lines_by_length()
 4123    //
 4124    // Demonstrates:
 4125    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 4126    // - sort is stable
 4127    cx.set_state(indoc! {"
 4128        «123
 4129        æ
 4130        12
 4131 4132        1
 4133        æˇ»
 4134    "});
 4135    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 4136    cx.assert_editor_state(indoc! {"
 4137        «æ
 4138 4139        1
 4140        æ
 4141        12
 4142        123ˇ»
 4143    "});
 4144
 4145    // Test reverse_lines()
 4146    cx.set_state(indoc! {"
 4147        «5
 4148        4
 4149        3
 4150        2
 4151        1ˇ»
 4152    "});
 4153    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4154    cx.assert_editor_state(indoc! {"
 4155        «1
 4156        2
 4157        3
 4158        4
 4159        5ˇ»
 4160    "});
 4161
 4162    // Skip testing shuffle_line()
 4163
 4164    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4165    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4166
 4167    // Don't manipulate when cursor is on single line, but expand the selection
 4168    cx.set_state(indoc! {"
 4169        ddˇdd
 4170        ccc
 4171        bb
 4172        a
 4173    "});
 4174    cx.update_editor(|e, window, cx| {
 4175        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4176    });
 4177    cx.assert_editor_state(indoc! {"
 4178        «ddddˇ»
 4179        ccc
 4180        bb
 4181        a
 4182    "});
 4183
 4184    // Basic manipulate case
 4185    // Start selection moves to column 0
 4186    // End of selection shrinks to fit shorter line
 4187    cx.set_state(indoc! {"
 4188        dd«d
 4189        ccc
 4190        bb
 4191        aaaaaˇ»
 4192    "});
 4193    cx.update_editor(|e, window, cx| {
 4194        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4195    });
 4196    cx.assert_editor_state(indoc! {"
 4197        «aaaaa
 4198        bb
 4199        ccc
 4200        dddˇ»
 4201    "});
 4202
 4203    // Manipulate case with newlines
 4204    cx.set_state(indoc! {"
 4205        dd«d
 4206        ccc
 4207
 4208        bb
 4209        aaaaa
 4210
 4211        ˇ»
 4212    "});
 4213    cx.update_editor(|e, window, cx| {
 4214        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4215    });
 4216    cx.assert_editor_state(indoc! {"
 4217        «
 4218
 4219        aaaaa
 4220        bb
 4221        ccc
 4222        dddˇ»
 4223
 4224    "});
 4225
 4226    // Adding new line
 4227    cx.set_state(indoc! {"
 4228        aa«a
 4229        bbˇ»b
 4230    "});
 4231    cx.update_editor(|e, window, cx| {
 4232        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4233    });
 4234    cx.assert_editor_state(indoc! {"
 4235        «aaa
 4236        bbb
 4237        added_lineˇ»
 4238    "});
 4239
 4240    // Removing line
 4241    cx.set_state(indoc! {"
 4242        aa«a
 4243        bbbˇ»
 4244    "});
 4245    cx.update_editor(|e, window, cx| {
 4246        e.manipulate_immutable_lines(window, cx, |lines| {
 4247            lines.pop();
 4248        })
 4249    });
 4250    cx.assert_editor_state(indoc! {"
 4251        «aaaˇ»
 4252    "});
 4253
 4254    // Removing all lines
 4255    cx.set_state(indoc! {"
 4256        aa«a
 4257        bbbˇ»
 4258    "});
 4259    cx.update_editor(|e, window, cx| {
 4260        e.manipulate_immutable_lines(window, cx, |lines| {
 4261            lines.drain(..);
 4262        })
 4263    });
 4264    cx.assert_editor_state(indoc! {"
 4265        ˇ
 4266    "});
 4267}
 4268
 4269#[gpui::test]
 4270async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4271    init_test(cx, |_| {});
 4272
 4273    let mut cx = EditorTestContext::new(cx).await;
 4274
 4275    // Consider continuous selection as single selection
 4276    cx.set_state(indoc! {"
 4277        Aaa«aa
 4278        cˇ»c«c
 4279        bb
 4280        aaaˇ»aa
 4281    "});
 4282    cx.update_editor(|e, window, cx| {
 4283        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4284    });
 4285    cx.assert_editor_state(indoc! {"
 4286        «Aaaaa
 4287        ccc
 4288        bb
 4289        aaaaaˇ»
 4290    "});
 4291
 4292    cx.set_state(indoc! {"
 4293        Aaa«aa
 4294        cˇ»c«c
 4295        bb
 4296        aaaˇ»aa
 4297    "});
 4298    cx.update_editor(|e, window, cx| {
 4299        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4300    });
 4301    cx.assert_editor_state(indoc! {"
 4302        «Aaaaa
 4303        ccc
 4304        bbˇ»
 4305    "});
 4306
 4307    // Consider non continuous selection as distinct dedup operations
 4308    cx.set_state(indoc! {"
 4309        «aaaaa
 4310        bb
 4311        aaaaa
 4312        aaaaaˇ»
 4313
 4314        aaa«aaˇ»
 4315    "});
 4316    cx.update_editor(|e, window, cx| {
 4317        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4318    });
 4319    cx.assert_editor_state(indoc! {"
 4320        «aaaaa
 4321        bbˇ»
 4322
 4323        «aaaaaˇ»
 4324    "});
 4325}
 4326
 4327#[gpui::test]
 4328async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4329    init_test(cx, |_| {});
 4330
 4331    let mut cx = EditorTestContext::new(cx).await;
 4332
 4333    cx.set_state(indoc! {"
 4334        «Aaa
 4335        aAa
 4336        Aaaˇ»
 4337    "});
 4338    cx.update_editor(|e, window, cx| {
 4339        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4340    });
 4341    cx.assert_editor_state(indoc! {"
 4342        «Aaa
 4343        aAaˇ»
 4344    "});
 4345
 4346    cx.set_state(indoc! {"
 4347        «Aaa
 4348        aAa
 4349        aaAˇ»
 4350    "});
 4351    cx.update_editor(|e, window, cx| {
 4352        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4353    });
 4354    cx.assert_editor_state(indoc! {"
 4355        «Aaaˇ»
 4356    "});
 4357}
 4358
 4359#[gpui::test]
 4360async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 4361    init_test(cx, |_| {});
 4362
 4363    let mut cx = EditorTestContext::new(cx).await;
 4364
 4365    // Manipulate with multiple selections on a single line
 4366    cx.set_state(indoc! {"
 4367        dd«dd
 4368        cˇ»c«c
 4369        bb
 4370        aaaˇ»aa
 4371    "});
 4372    cx.update_editor(|e, window, cx| {
 4373        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4374    });
 4375    cx.assert_editor_state(indoc! {"
 4376        «aaaaa
 4377        bb
 4378        ccc
 4379        ddddˇ»
 4380    "});
 4381
 4382    // Manipulate with multiple disjoin selections
 4383    cx.set_state(indoc! {"
 4384 4385        4
 4386        3
 4387        2
 4388        1ˇ»
 4389
 4390        dd«dd
 4391        ccc
 4392        bb
 4393        aaaˇ»aa
 4394    "});
 4395    cx.update_editor(|e, window, cx| {
 4396        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4397    });
 4398    cx.assert_editor_state(indoc! {"
 4399        «1
 4400        2
 4401        3
 4402        4
 4403        5ˇ»
 4404
 4405        «aaaaa
 4406        bb
 4407        ccc
 4408        ddddˇ»
 4409    "});
 4410
 4411    // Adding lines on each selection
 4412    cx.set_state(indoc! {"
 4413 4414        1ˇ»
 4415
 4416        bb«bb
 4417        aaaˇ»aa
 4418    "});
 4419    cx.update_editor(|e, window, cx| {
 4420        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 4421    });
 4422    cx.assert_editor_state(indoc! {"
 4423        «2
 4424        1
 4425        added lineˇ»
 4426
 4427        «bbbb
 4428        aaaaa
 4429        added lineˇ»
 4430    "});
 4431
 4432    // Removing lines on each selection
 4433    cx.set_state(indoc! {"
 4434 4435        1ˇ»
 4436
 4437        bb«bb
 4438        aaaˇ»aa
 4439    "});
 4440    cx.update_editor(|e, window, cx| {
 4441        e.manipulate_immutable_lines(window, cx, |lines| {
 4442            lines.pop();
 4443        })
 4444    });
 4445    cx.assert_editor_state(indoc! {"
 4446        «2ˇ»
 4447
 4448        «bbbbˇ»
 4449    "});
 4450}
 4451
 4452#[gpui::test]
 4453async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 4454    init_test(cx, |settings| {
 4455        settings.defaults.tab_size = NonZeroU32::new(3)
 4456    });
 4457
 4458    let mut cx = EditorTestContext::new(cx).await;
 4459
 4460    // MULTI SELECTION
 4461    // Ln.1 "«" tests empty lines
 4462    // Ln.9 tests just leading whitespace
 4463    cx.set_state(indoc! {"
 4464        «
 4465        abc                 // No indentationˇ»
 4466        «\tabc              // 1 tabˇ»
 4467        \t\tabc «      ˇ»   // 2 tabs
 4468        \t ab«c             // Tab followed by space
 4469         \tabc              // Space followed by tab (3 spaces should be the result)
 4470        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4471           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 4472        \t
 4473        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4474    "});
 4475    cx.update_editor(|e, window, cx| {
 4476        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4477    });
 4478    cx.assert_editor_state(
 4479        indoc! {"
 4480            «
 4481            abc                 // No indentation
 4482               abc              // 1 tab
 4483                  abc          // 2 tabs
 4484                abc             // Tab followed by space
 4485               abc              // Space followed by tab (3 spaces should be the result)
 4486                           abc   // Mixed indentation (tab conversion depends on the column)
 4487               abc         // Already space indented
 4488               ·
 4489               abc\tdef          // Only the leading tab is manipulatedˇ»
 4490        "}
 4491        .replace("·", "")
 4492        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4493    );
 4494
 4495    // Test on just a few lines, the others should remain unchanged
 4496    // Only lines (3, 5, 10, 11) should change
 4497    cx.set_state(
 4498        indoc! {"
 4499            ·
 4500            abc                 // No indentation
 4501            \tabcˇ               // 1 tab
 4502            \t\tabc             // 2 tabs
 4503            \t abcˇ              // Tab followed by space
 4504             \tabc              // Space followed by tab (3 spaces should be the result)
 4505            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4506               abc              // Already space indented
 4507            «\t
 4508            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4509        "}
 4510        .replace("·", "")
 4511        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4512    );
 4513    cx.update_editor(|e, window, cx| {
 4514        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4515    });
 4516    cx.assert_editor_state(
 4517        indoc! {"
 4518            ·
 4519            abc                 // No indentation
 4520            «   abc               // 1 tabˇ»
 4521            \t\tabc             // 2 tabs
 4522            «    abc              // Tab followed by spaceˇ»
 4523             \tabc              // Space followed by tab (3 spaces should be the result)
 4524            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4525               abc              // Already space indented
 4526            «   ·
 4527               abc\tdef          // Only the leading tab is manipulatedˇ»
 4528        "}
 4529        .replace("·", "")
 4530        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4531    );
 4532
 4533    // SINGLE SELECTION
 4534    // Ln.1 "«" tests empty lines
 4535    // Ln.9 tests just leading whitespace
 4536    cx.set_state(indoc! {"
 4537        «
 4538        abc                 // No indentation
 4539        \tabc               // 1 tab
 4540        \t\tabc             // 2 tabs
 4541        \t abc              // Tab followed by space
 4542         \tabc              // Space followed by tab (3 spaces should be the result)
 4543        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4544           abc              // Already space indented
 4545        \t
 4546        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4547    "});
 4548    cx.update_editor(|e, window, cx| {
 4549        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4550    });
 4551    cx.assert_editor_state(
 4552        indoc! {"
 4553            «
 4554            abc                 // No indentation
 4555               abc               // 1 tab
 4556                  abc             // 2 tabs
 4557                abc              // Tab followed by space
 4558               abc              // Space followed by tab (3 spaces should be the result)
 4559                           abc   // Mixed indentation (tab conversion depends on the column)
 4560               abc              // Already space indented
 4561               ·
 4562               abc\tdef          // Only the leading tab is manipulatedˇ»
 4563        "}
 4564        .replace("·", "")
 4565        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4566    );
 4567}
 4568
 4569#[gpui::test]
 4570async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 4571    init_test(cx, |settings| {
 4572        settings.defaults.tab_size = NonZeroU32::new(3)
 4573    });
 4574
 4575    let mut cx = EditorTestContext::new(cx).await;
 4576
 4577    // MULTI SELECTION
 4578    // Ln.1 "«" tests empty lines
 4579    // Ln.11 tests just leading whitespace
 4580    cx.set_state(indoc! {"
 4581        «
 4582        abˇ»ˇc                 // No indentation
 4583         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 4584          abc  «             // 2 spaces (< 3 so dont convert)
 4585           abc              // 3 spaces (convert)
 4586             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 4587        «\tˇ»\t«\tˇ»abc           // Already tab indented
 4588        «\t abc              // Tab followed by space
 4589         \tabc              // Space followed by tab (should be consumed due to tab)
 4590        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4591           \tˇ»  «\t
 4592           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 4593    "});
 4594    cx.update_editor(|e, window, cx| {
 4595        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4596    });
 4597    cx.assert_editor_state(indoc! {"
 4598        «
 4599        abc                 // No indentation
 4600         abc                // 1 space (< 3 so dont convert)
 4601          abc               // 2 spaces (< 3 so dont convert)
 4602        \tabc              // 3 spaces (convert)
 4603        \t  abc            // 5 spaces (1 tab + 2 spaces)
 4604        \t\t\tabc           // Already tab indented
 4605        \t abc              // Tab followed by space
 4606        \tabc              // Space followed by tab (should be consumed due to tab)
 4607        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4608        \t\t\t
 4609        \tabc   \t         // Only the leading spaces should be convertedˇ»
 4610    "});
 4611
 4612    // Test on just a few lines, the other should remain unchanged
 4613    // Only lines (4, 8, 11, 12) should change
 4614    cx.set_state(
 4615        indoc! {"
 4616            ·
 4617            abc                 // No indentation
 4618             abc                // 1 space (< 3 so dont convert)
 4619              abc               // 2 spaces (< 3 so dont convert)
 4620            «   abc              // 3 spaces (convert)ˇ»
 4621                 abc            // 5 spaces (1 tab + 2 spaces)
 4622            \t\t\tabc           // Already tab indented
 4623            \t abc              // Tab followed by space
 4624             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 4625               \t\t  \tabc      // Mixed indentation
 4626            \t \t  \t   \tabc   // Mixed indentation
 4627               \t  \tˇ
 4628            «   abc   \t         // Only the leading spaces should be convertedˇ»
 4629        "}
 4630        .replace("·", "")
 4631        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4632    );
 4633    cx.update_editor(|e, window, cx| {
 4634        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4635    });
 4636    cx.assert_editor_state(
 4637        indoc! {"
 4638            ·
 4639            abc                 // No indentation
 4640             abc                // 1 space (< 3 so dont convert)
 4641              abc               // 2 spaces (< 3 so dont convert)
 4642            «\tabc              // 3 spaces (convert)ˇ»
 4643                 abc            // 5 spaces (1 tab + 2 spaces)
 4644            \t\t\tabc           // Already tab indented
 4645            \t abc              // Tab followed by space
 4646            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 4647               \t\t  \tabc      // Mixed indentation
 4648            \t \t  \t   \tabc   // Mixed indentation
 4649            «\t\t\t
 4650            \tabc   \t         // Only the leading spaces should be convertedˇ»
 4651        "}
 4652        .replace("·", "")
 4653        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4654    );
 4655
 4656    // SINGLE SELECTION
 4657    // Ln.1 "«" tests empty lines
 4658    // Ln.11 tests just leading whitespace
 4659    cx.set_state(indoc! {"
 4660        «
 4661        abc                 // No indentation
 4662         abc                // 1 space (< 3 so dont convert)
 4663          abc               // 2 spaces (< 3 so dont convert)
 4664           abc              // 3 spaces (convert)
 4665             abc            // 5 spaces (1 tab + 2 spaces)
 4666        \t\t\tabc           // Already tab indented
 4667        \t abc              // Tab followed by space
 4668         \tabc              // Space followed by tab (should be consumed due to tab)
 4669        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4670           \t  \t
 4671           abc   \t         // Only the leading spaces should be convertedˇ»
 4672    "});
 4673    cx.update_editor(|e, window, cx| {
 4674        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4675    });
 4676    cx.assert_editor_state(indoc! {"
 4677        «
 4678        abc                 // No indentation
 4679         abc                // 1 space (< 3 so dont convert)
 4680          abc               // 2 spaces (< 3 so dont convert)
 4681        \tabc              // 3 spaces (convert)
 4682        \t  abc            // 5 spaces (1 tab + 2 spaces)
 4683        \t\t\tabc           // Already tab indented
 4684        \t abc              // Tab followed by space
 4685        \tabc              // Space followed by tab (should be consumed due to tab)
 4686        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4687        \t\t\t
 4688        \tabc   \t         // Only the leading spaces should be convertedˇ»
 4689    "});
 4690}
 4691
 4692#[gpui::test]
 4693async fn test_toggle_case(cx: &mut TestAppContext) {
 4694    init_test(cx, |_| {});
 4695
 4696    let mut cx = EditorTestContext::new(cx).await;
 4697
 4698    // If all lower case -> upper case
 4699    cx.set_state(indoc! {"
 4700        «hello worldˇ»
 4701    "});
 4702    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4703    cx.assert_editor_state(indoc! {"
 4704        «HELLO WORLDˇ»
 4705    "});
 4706
 4707    // If all upper case -> lower case
 4708    cx.set_state(indoc! {"
 4709        «HELLO WORLDˇ»
 4710    "});
 4711    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4712    cx.assert_editor_state(indoc! {"
 4713        «hello worldˇ»
 4714    "});
 4715
 4716    // If any upper case characters are identified -> lower case
 4717    // This matches JetBrains IDEs
 4718    cx.set_state(indoc! {"
 4719        «hEllo worldˇ»
 4720    "});
 4721    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4722    cx.assert_editor_state(indoc! {"
 4723        «hello worldˇ»
 4724    "});
 4725}
 4726
 4727#[gpui::test]
 4728async fn test_manipulate_text(cx: &mut TestAppContext) {
 4729    init_test(cx, |_| {});
 4730
 4731    let mut cx = EditorTestContext::new(cx).await;
 4732
 4733    // Test convert_to_upper_case()
 4734    cx.set_state(indoc! {"
 4735        «hello worldˇ»
 4736    "});
 4737    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4738    cx.assert_editor_state(indoc! {"
 4739        «HELLO WORLDˇ»
 4740    "});
 4741
 4742    // Test convert_to_lower_case()
 4743    cx.set_state(indoc! {"
 4744        «HELLO WORLDˇ»
 4745    "});
 4746    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 4747    cx.assert_editor_state(indoc! {"
 4748        «hello worldˇ»
 4749    "});
 4750
 4751    // Test multiple line, single selection case
 4752    cx.set_state(indoc! {"
 4753        «The quick brown
 4754        fox jumps over
 4755        the lazy dogˇ»
 4756    "});
 4757    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 4758    cx.assert_editor_state(indoc! {"
 4759        «The Quick Brown
 4760        Fox Jumps Over
 4761        The Lazy Dogˇ»
 4762    "});
 4763
 4764    // Test multiple line, single selection case
 4765    cx.set_state(indoc! {"
 4766        «The quick brown
 4767        fox jumps over
 4768        the lazy dogˇ»
 4769    "});
 4770    cx.update_editor(|e, window, cx| {
 4771        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 4772    });
 4773    cx.assert_editor_state(indoc! {"
 4774        «TheQuickBrown
 4775        FoxJumpsOver
 4776        TheLazyDogˇ»
 4777    "});
 4778
 4779    // From here on out, test more complex cases of manipulate_text()
 4780
 4781    // Test no selection case - should affect words cursors are in
 4782    // Cursor at beginning, middle, and end of word
 4783    cx.set_state(indoc! {"
 4784        ˇhello big beauˇtiful worldˇ
 4785    "});
 4786    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4787    cx.assert_editor_state(indoc! {"
 4788        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 4789    "});
 4790
 4791    // Test multiple selections on a single line and across multiple lines
 4792    cx.set_state(indoc! {"
 4793        «Theˇ» quick «brown
 4794        foxˇ» jumps «overˇ»
 4795        the «lazyˇ» dog
 4796    "});
 4797    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4798    cx.assert_editor_state(indoc! {"
 4799        «THEˇ» quick «BROWN
 4800        FOXˇ» jumps «OVERˇ»
 4801        the «LAZYˇ» dog
 4802    "});
 4803
 4804    // Test case where text length grows
 4805    cx.set_state(indoc! {"
 4806        «tschüߡ»
 4807    "});
 4808    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4809    cx.assert_editor_state(indoc! {"
 4810        «TSCHÜSSˇ»
 4811    "});
 4812
 4813    // Test to make sure we don't crash when text shrinks
 4814    cx.set_state(indoc! {"
 4815        aaa_bbbˇ
 4816    "});
 4817    cx.update_editor(|e, window, cx| {
 4818        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4819    });
 4820    cx.assert_editor_state(indoc! {"
 4821        «aaaBbbˇ»
 4822    "});
 4823
 4824    // Test to make sure we all aware of the fact that each word can grow and shrink
 4825    // Final selections should be aware of this fact
 4826    cx.set_state(indoc! {"
 4827        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 4828    "});
 4829    cx.update_editor(|e, window, cx| {
 4830        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4831    });
 4832    cx.assert_editor_state(indoc! {"
 4833        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 4834    "});
 4835
 4836    cx.set_state(indoc! {"
 4837        «hElLo, WoRld!ˇ»
 4838    "});
 4839    cx.update_editor(|e, window, cx| {
 4840        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 4841    });
 4842    cx.assert_editor_state(indoc! {"
 4843        «HeLlO, wOrLD!ˇ»
 4844    "});
 4845}
 4846
 4847#[gpui::test]
 4848fn test_duplicate_line(cx: &mut TestAppContext) {
 4849    init_test(cx, |_| {});
 4850
 4851    let editor = cx.add_window(|window, cx| {
 4852        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4853        build_editor(buffer, window, cx)
 4854    });
 4855    _ = editor.update(cx, |editor, window, cx| {
 4856        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4857            s.select_display_ranges([
 4858                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4859                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4860                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4861                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4862            ])
 4863        });
 4864        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4865        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4866        assert_eq!(
 4867            editor.selections.display_ranges(cx),
 4868            vec![
 4869                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4870                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 4871                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4872                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4873            ]
 4874        );
 4875    });
 4876
 4877    let editor = cx.add_window(|window, cx| {
 4878        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4879        build_editor(buffer, window, cx)
 4880    });
 4881    _ = editor.update(cx, |editor, window, cx| {
 4882        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4883            s.select_display_ranges([
 4884                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4885                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4886            ])
 4887        });
 4888        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4889        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4890        assert_eq!(
 4891            editor.selections.display_ranges(cx),
 4892            vec![
 4893                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 4894                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 4895            ]
 4896        );
 4897    });
 4898
 4899    // With `move_upwards` the selections stay in place, except for
 4900    // the lines inserted above them
 4901    let editor = cx.add_window(|window, cx| {
 4902        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4903        build_editor(buffer, window, cx)
 4904    });
 4905    _ = editor.update(cx, |editor, window, cx| {
 4906        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4907            s.select_display_ranges([
 4908                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4909                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4910                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4911                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4912            ])
 4913        });
 4914        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4915        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4916        assert_eq!(
 4917            editor.selections.display_ranges(cx),
 4918            vec![
 4919                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4920                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4921                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 4922                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4923            ]
 4924        );
 4925    });
 4926
 4927    let editor = cx.add_window(|window, cx| {
 4928        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4929        build_editor(buffer, window, cx)
 4930    });
 4931    _ = editor.update(cx, |editor, window, cx| {
 4932        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4933            s.select_display_ranges([
 4934                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4935                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4936            ])
 4937        });
 4938        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4939        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4940        assert_eq!(
 4941            editor.selections.display_ranges(cx),
 4942            vec![
 4943                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4944                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4945            ]
 4946        );
 4947    });
 4948
 4949    let editor = cx.add_window(|window, cx| {
 4950        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4951        build_editor(buffer, window, cx)
 4952    });
 4953    _ = editor.update(cx, |editor, window, cx| {
 4954        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4955            s.select_display_ranges([
 4956                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4957                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4958            ])
 4959        });
 4960        editor.duplicate_selection(&DuplicateSelection, window, cx);
 4961        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 4962        assert_eq!(
 4963            editor.selections.display_ranges(cx),
 4964            vec![
 4965                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4966                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 4967            ]
 4968        );
 4969    });
 4970}
 4971
 4972#[gpui::test]
 4973fn test_move_line_up_down(cx: &mut TestAppContext) {
 4974    init_test(cx, |_| {});
 4975
 4976    let editor = cx.add_window(|window, cx| {
 4977        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 4978        build_editor(buffer, window, cx)
 4979    });
 4980    _ = editor.update(cx, |editor, window, cx| {
 4981        editor.fold_creases(
 4982            vec![
 4983                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 4984                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 4985                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 4986            ],
 4987            true,
 4988            window,
 4989            cx,
 4990        );
 4991        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4992            s.select_display_ranges([
 4993                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4994                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4995                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4996                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 4997            ])
 4998        });
 4999        assert_eq!(
 5000            editor.display_text(cx),
 5001            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 5002        );
 5003
 5004        editor.move_line_up(&MoveLineUp, window, cx);
 5005        assert_eq!(
 5006            editor.display_text(cx),
 5007            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 5008        );
 5009        assert_eq!(
 5010            editor.selections.display_ranges(cx),
 5011            vec![
 5012                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5013                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5014                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5015                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5016            ]
 5017        );
 5018    });
 5019
 5020    _ = editor.update(cx, |editor, window, cx| {
 5021        editor.move_line_down(&MoveLineDown, window, cx);
 5022        assert_eq!(
 5023            editor.display_text(cx),
 5024            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 5025        );
 5026        assert_eq!(
 5027            editor.selections.display_ranges(cx),
 5028            vec![
 5029                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5030                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5031                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5032                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5033            ]
 5034        );
 5035    });
 5036
 5037    _ = editor.update(cx, |editor, window, cx| {
 5038        editor.move_line_down(&MoveLineDown, window, cx);
 5039        assert_eq!(
 5040            editor.display_text(cx),
 5041            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 5042        );
 5043        assert_eq!(
 5044            editor.selections.display_ranges(cx),
 5045            vec![
 5046                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5047                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5048                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5049                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5050            ]
 5051        );
 5052    });
 5053
 5054    _ = editor.update(cx, |editor, window, cx| {
 5055        editor.move_line_up(&MoveLineUp, window, cx);
 5056        assert_eq!(
 5057            editor.display_text(cx),
 5058            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 5059        );
 5060        assert_eq!(
 5061            editor.selections.display_ranges(cx),
 5062            vec![
 5063                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5064                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5065                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5066                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5067            ]
 5068        );
 5069    });
 5070}
 5071
 5072#[gpui::test]
 5073fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 5074    init_test(cx, |_| {});
 5075
 5076    let editor = cx.add_window(|window, cx| {
 5077        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5078        build_editor(buffer, window, cx)
 5079    });
 5080    _ = editor.update(cx, |editor, window, cx| {
 5081        let snapshot = editor.buffer.read(cx).snapshot(cx);
 5082        editor.insert_blocks(
 5083            [BlockProperties {
 5084                style: BlockStyle::Fixed,
 5085                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 5086                height: Some(1),
 5087                render: Arc::new(|_| div().into_any()),
 5088                priority: 0,
 5089            }],
 5090            Some(Autoscroll::fit()),
 5091            cx,
 5092        );
 5093        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5094            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 5095        });
 5096        editor.move_line_down(&MoveLineDown, window, cx);
 5097    });
 5098}
 5099
 5100#[gpui::test]
 5101async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 5102    init_test(cx, |_| {});
 5103
 5104    let mut cx = EditorTestContext::new(cx).await;
 5105    cx.set_state(
 5106        &"
 5107            ˇzero
 5108            one
 5109            two
 5110            three
 5111            four
 5112            five
 5113        "
 5114        .unindent(),
 5115    );
 5116
 5117    // Create a four-line block that replaces three lines of text.
 5118    cx.update_editor(|editor, window, cx| {
 5119        let snapshot = editor.snapshot(window, cx);
 5120        let snapshot = &snapshot.buffer_snapshot;
 5121        let placement = BlockPlacement::Replace(
 5122            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 5123        );
 5124        editor.insert_blocks(
 5125            [BlockProperties {
 5126                placement,
 5127                height: Some(4),
 5128                style: BlockStyle::Sticky,
 5129                render: Arc::new(|_| gpui::div().into_any_element()),
 5130                priority: 0,
 5131            }],
 5132            None,
 5133            cx,
 5134        );
 5135    });
 5136
 5137    // Move down so that the cursor touches the block.
 5138    cx.update_editor(|editor, window, cx| {
 5139        editor.move_down(&Default::default(), window, cx);
 5140    });
 5141    cx.assert_editor_state(
 5142        &"
 5143            zero
 5144            «one
 5145            two
 5146            threeˇ»
 5147            four
 5148            five
 5149        "
 5150        .unindent(),
 5151    );
 5152
 5153    // Move down past the block.
 5154    cx.update_editor(|editor, window, cx| {
 5155        editor.move_down(&Default::default(), window, cx);
 5156    });
 5157    cx.assert_editor_state(
 5158        &"
 5159            zero
 5160            one
 5161            two
 5162            three
 5163            ˇfour
 5164            five
 5165        "
 5166        .unindent(),
 5167    );
 5168}
 5169
 5170#[gpui::test]
 5171fn test_transpose(cx: &mut TestAppContext) {
 5172    init_test(cx, |_| {});
 5173
 5174    _ = cx.add_window(|window, cx| {
 5175        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 5176        editor.set_style(EditorStyle::default(), window, cx);
 5177        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5178            s.select_ranges([1..1])
 5179        });
 5180        editor.transpose(&Default::default(), window, cx);
 5181        assert_eq!(editor.text(cx), "bac");
 5182        assert_eq!(editor.selections.ranges(cx), [2..2]);
 5183
 5184        editor.transpose(&Default::default(), window, cx);
 5185        assert_eq!(editor.text(cx), "bca");
 5186        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5187
 5188        editor.transpose(&Default::default(), window, cx);
 5189        assert_eq!(editor.text(cx), "bac");
 5190        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5191
 5192        editor
 5193    });
 5194
 5195    _ = cx.add_window(|window, cx| {
 5196        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5197        editor.set_style(EditorStyle::default(), window, cx);
 5198        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5199            s.select_ranges([3..3])
 5200        });
 5201        editor.transpose(&Default::default(), window, cx);
 5202        assert_eq!(editor.text(cx), "acb\nde");
 5203        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5204
 5205        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5206            s.select_ranges([4..4])
 5207        });
 5208        editor.transpose(&Default::default(), window, cx);
 5209        assert_eq!(editor.text(cx), "acbd\ne");
 5210        assert_eq!(editor.selections.ranges(cx), [5..5]);
 5211
 5212        editor.transpose(&Default::default(), window, cx);
 5213        assert_eq!(editor.text(cx), "acbde\n");
 5214        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5215
 5216        editor.transpose(&Default::default(), window, cx);
 5217        assert_eq!(editor.text(cx), "acbd\ne");
 5218        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5219
 5220        editor
 5221    });
 5222
 5223    _ = cx.add_window(|window, cx| {
 5224        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5225        editor.set_style(EditorStyle::default(), window, cx);
 5226        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5227            s.select_ranges([1..1, 2..2, 4..4])
 5228        });
 5229        editor.transpose(&Default::default(), window, cx);
 5230        assert_eq!(editor.text(cx), "bacd\ne");
 5231        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 5232
 5233        editor.transpose(&Default::default(), window, cx);
 5234        assert_eq!(editor.text(cx), "bcade\n");
 5235        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 5236
 5237        editor.transpose(&Default::default(), window, cx);
 5238        assert_eq!(editor.text(cx), "bcda\ne");
 5239        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5240
 5241        editor.transpose(&Default::default(), window, cx);
 5242        assert_eq!(editor.text(cx), "bcade\n");
 5243        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5244
 5245        editor.transpose(&Default::default(), window, cx);
 5246        assert_eq!(editor.text(cx), "bcaed\n");
 5247        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 5248
 5249        editor
 5250    });
 5251
 5252    _ = cx.add_window(|window, cx| {
 5253        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 5254        editor.set_style(EditorStyle::default(), window, cx);
 5255        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5256            s.select_ranges([4..4])
 5257        });
 5258        editor.transpose(&Default::default(), window, cx);
 5259        assert_eq!(editor.text(cx), "🏀🍐✋");
 5260        assert_eq!(editor.selections.ranges(cx), [8..8]);
 5261
 5262        editor.transpose(&Default::default(), window, cx);
 5263        assert_eq!(editor.text(cx), "🏀✋🍐");
 5264        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5265
 5266        editor.transpose(&Default::default(), window, cx);
 5267        assert_eq!(editor.text(cx), "🏀🍐✋");
 5268        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5269
 5270        editor
 5271    });
 5272}
 5273
 5274#[gpui::test]
 5275async fn test_rewrap(cx: &mut TestAppContext) {
 5276    init_test(cx, |settings| {
 5277        settings.languages.0.extend([
 5278            (
 5279                "Markdown".into(),
 5280                LanguageSettingsContent {
 5281                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5282                    preferred_line_length: Some(40),
 5283                    ..Default::default()
 5284                },
 5285            ),
 5286            (
 5287                "Plain Text".into(),
 5288                LanguageSettingsContent {
 5289                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5290                    preferred_line_length: Some(40),
 5291                    ..Default::default()
 5292                },
 5293            ),
 5294            (
 5295                "C++".into(),
 5296                LanguageSettingsContent {
 5297                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5298                    preferred_line_length: Some(40),
 5299                    ..Default::default()
 5300                },
 5301            ),
 5302            (
 5303                "Python".into(),
 5304                LanguageSettingsContent {
 5305                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5306                    preferred_line_length: Some(40),
 5307                    ..Default::default()
 5308                },
 5309            ),
 5310            (
 5311                "Rust".into(),
 5312                LanguageSettingsContent {
 5313                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5314                    preferred_line_length: Some(40),
 5315                    ..Default::default()
 5316                },
 5317            ),
 5318        ])
 5319    });
 5320
 5321    let mut cx = EditorTestContext::new(cx).await;
 5322
 5323    let cpp_language = Arc::new(Language::new(
 5324        LanguageConfig {
 5325            name: "C++".into(),
 5326            line_comments: vec!["// ".into()],
 5327            ..LanguageConfig::default()
 5328        },
 5329        None,
 5330    ));
 5331    let python_language = Arc::new(Language::new(
 5332        LanguageConfig {
 5333            name: "Python".into(),
 5334            line_comments: vec!["# ".into()],
 5335            ..LanguageConfig::default()
 5336        },
 5337        None,
 5338    ));
 5339    let markdown_language = Arc::new(Language::new(
 5340        LanguageConfig {
 5341            name: "Markdown".into(),
 5342            rewrap_prefixes: vec![
 5343                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 5344                regex::Regex::new("[-*+]\\s+").unwrap(),
 5345            ],
 5346            ..LanguageConfig::default()
 5347        },
 5348        None,
 5349    ));
 5350    let rust_language = Arc::new(Language::new(
 5351        LanguageConfig {
 5352            name: "Rust".into(),
 5353            line_comments: vec!["// ".into(), "/// ".into()],
 5354            ..LanguageConfig::default()
 5355        },
 5356        Some(tree_sitter_rust::LANGUAGE.into()),
 5357    ));
 5358
 5359    let plaintext_language = Arc::new(Language::new(
 5360        LanguageConfig {
 5361            name: "Plain Text".into(),
 5362            ..LanguageConfig::default()
 5363        },
 5364        None,
 5365    ));
 5366
 5367    // Test basic rewrapping of a long line with a cursor
 5368    assert_rewrap(
 5369        indoc! {"
 5370            // ˇThis is a long comment that needs to be wrapped.
 5371        "},
 5372        indoc! {"
 5373            // ˇThis is a long comment that needs to
 5374            // be wrapped.
 5375        "},
 5376        cpp_language.clone(),
 5377        &mut cx,
 5378    );
 5379
 5380    // Test rewrapping a full selection
 5381    assert_rewrap(
 5382        indoc! {"
 5383            «// This selected long comment needs to be wrapped.ˇ»"
 5384        },
 5385        indoc! {"
 5386            «// This selected long comment needs to
 5387            // be wrapped.ˇ»"
 5388        },
 5389        cpp_language.clone(),
 5390        &mut cx,
 5391    );
 5392
 5393    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 5394    assert_rewrap(
 5395        indoc! {"
 5396            // ˇThis is the first line.
 5397            // Thisˇ is the second line.
 5398            // This is the thirdˇ line, all part of one paragraph.
 5399         "},
 5400        indoc! {"
 5401            // ˇThis is the first line. Thisˇ is the
 5402            // second line. This is the thirdˇ line,
 5403            // all part of one paragraph.
 5404         "},
 5405        cpp_language.clone(),
 5406        &mut cx,
 5407    );
 5408
 5409    // Test multiple cursors in different paragraphs trigger separate rewraps
 5410    assert_rewrap(
 5411        indoc! {"
 5412            // ˇThis is the first paragraph, first line.
 5413            // ˇThis is the first paragraph, second line.
 5414
 5415            // ˇThis is the second paragraph, first line.
 5416            // ˇThis is the second paragraph, second line.
 5417        "},
 5418        indoc! {"
 5419            // ˇThis is the first paragraph, first
 5420            // line. ˇThis is the first paragraph,
 5421            // second line.
 5422
 5423            // ˇThis is the second paragraph, first
 5424            // line. ˇThis is the second paragraph,
 5425            // second line.
 5426        "},
 5427        cpp_language.clone(),
 5428        &mut cx,
 5429    );
 5430
 5431    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 5432    assert_rewrap(
 5433        indoc! {"
 5434            «// A regular long long comment to be wrapped.
 5435            /// A documentation long comment to be wrapped.ˇ»
 5436          "},
 5437        indoc! {"
 5438            «// A regular long long comment to be
 5439            // wrapped.
 5440            /// A documentation long comment to be
 5441            /// wrapped.ˇ»
 5442          "},
 5443        rust_language.clone(),
 5444        &mut cx,
 5445    );
 5446
 5447    // Test that change in indentation level trigger seperate rewraps
 5448    assert_rewrap(
 5449        indoc! {"
 5450            fn foo() {
 5451                «// This is a long comment at the base indent.
 5452                    // This is a long comment at the next indent.ˇ»
 5453            }
 5454        "},
 5455        indoc! {"
 5456            fn foo() {
 5457                «// This is a long comment at the
 5458                // base indent.
 5459                    // This is a long comment at the
 5460                    // next indent.ˇ»
 5461            }
 5462        "},
 5463        rust_language.clone(),
 5464        &mut cx,
 5465    );
 5466
 5467    // Test that different comment prefix characters (e.g., '#') are handled correctly
 5468    assert_rewrap(
 5469        indoc! {"
 5470            # ˇThis is a long comment using a pound sign.
 5471        "},
 5472        indoc! {"
 5473            # ˇThis is a long comment using a pound
 5474            # sign.
 5475        "},
 5476        python_language.clone(),
 5477        &mut cx,
 5478    );
 5479
 5480    // Test rewrapping only affects comments, not code even when selected
 5481    assert_rewrap(
 5482        indoc! {"
 5483            «/// This doc comment is long and should be wrapped.
 5484            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 5485        "},
 5486        indoc! {"
 5487            «/// This doc comment is long and should
 5488            /// be wrapped.
 5489            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 5490        "},
 5491        rust_language.clone(),
 5492        &mut cx,
 5493    );
 5494
 5495    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 5496    assert_rewrap(
 5497        indoc! {"
 5498            # Header
 5499
 5500            A long long long line of markdown text to wrap.ˇ
 5501         "},
 5502        indoc! {"
 5503            # Header
 5504
 5505            A long long long line of markdown text
 5506            to wrap.ˇ
 5507         "},
 5508        markdown_language.clone(),
 5509        &mut cx,
 5510    );
 5511
 5512    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 5513    assert_rewrap(
 5514        indoc! {"
 5515            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 5516            2. This is a numbered list item that is very long and needs to be wrapped properly.
 5517            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 5518        "},
 5519        indoc! {"
 5520            «1. This is a numbered list item that is
 5521               very long and needs to be wrapped
 5522               properly.
 5523            2. This is a numbered list item that is
 5524               very long and needs to be wrapped
 5525               properly.
 5526            - This is an unordered list item that is
 5527              also very long and should not merge
 5528              with the numbered item.ˇ»
 5529        "},
 5530        markdown_language.clone(),
 5531        &mut cx,
 5532    );
 5533
 5534    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 5535    assert_rewrap(
 5536        indoc! {"
 5537            «1. This is a numbered list item that is
 5538            very long and needs to be wrapped
 5539            properly.
 5540            2. This is a numbered list item that is
 5541            very long and needs to be wrapped
 5542            properly.
 5543            - This is an unordered list item that is
 5544            also very long and should not merge with
 5545            the numbered item.ˇ»
 5546        "},
 5547        indoc! {"
 5548            «1. This is a numbered list item that is
 5549               very long and needs to be wrapped
 5550               properly.
 5551            2. This is a numbered list item that is
 5552               very long and needs to be wrapped
 5553               properly.
 5554            - This is an unordered list item that is
 5555              also very long and should not merge
 5556              with the numbered item.ˇ»
 5557        "},
 5558        markdown_language.clone(),
 5559        &mut cx,
 5560    );
 5561
 5562    // Test that rewrapping maintain indents even when they already exists.
 5563    assert_rewrap(
 5564        indoc! {"
 5565            «1. This is a numbered list
 5566               item that is very long and needs to be wrapped properly.
 5567            2. This is a numbered list
 5568               item that is very long and needs to be wrapped properly.
 5569            - This is an unordered list item that is also very long and
 5570              should not merge with the numbered item.ˇ»
 5571        "},
 5572        indoc! {"
 5573            «1. This is a numbered list item that is
 5574               very long and needs to be wrapped
 5575               properly.
 5576            2. This is a numbered list item that is
 5577               very long and needs to be wrapped
 5578               properly.
 5579            - This is an unordered list item that is
 5580              also very long and should not merge
 5581              with the numbered item.ˇ»
 5582        "},
 5583        markdown_language.clone(),
 5584        &mut cx,
 5585    );
 5586
 5587    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 5588    assert_rewrap(
 5589        indoc! {"
 5590            ˇThis is a very long line of plain text that will be wrapped.
 5591        "},
 5592        indoc! {"
 5593            ˇThis is a very long line of plain text
 5594            that will be wrapped.
 5595        "},
 5596        plaintext_language.clone(),
 5597        &mut cx,
 5598    );
 5599
 5600    // Test that non-commented code acts as a paragraph boundary within a selection
 5601    assert_rewrap(
 5602        indoc! {"
 5603               «// This is the first long comment block to be wrapped.
 5604               fn my_func(a: u32);
 5605               // This is the second long comment block to be wrapped.ˇ»
 5606           "},
 5607        indoc! {"
 5608               «// This is the first long comment block
 5609               // to be wrapped.
 5610               fn my_func(a: u32);
 5611               // This is the second long comment block
 5612               // to be wrapped.ˇ»
 5613           "},
 5614        rust_language.clone(),
 5615        &mut cx,
 5616    );
 5617
 5618    // Test rewrapping multiple selections, including ones with blank lines or tabs
 5619    assert_rewrap(
 5620        indoc! {"
 5621            «ˇThis is a very long line that will be wrapped.
 5622
 5623            This is another paragraph in the same selection.»
 5624
 5625            «\tThis is a very long indented line that will be wrapped.ˇ»
 5626         "},
 5627        indoc! {"
 5628            «ˇThis is a very long line that will be
 5629            wrapped.
 5630
 5631            This is another paragraph in the same
 5632            selection.»
 5633
 5634            «\tThis is a very long indented line
 5635            \tthat will be wrapped.ˇ»
 5636         "},
 5637        plaintext_language.clone(),
 5638        &mut cx,
 5639    );
 5640
 5641    // Test that an empty comment line acts as a paragraph boundary
 5642    assert_rewrap(
 5643        indoc! {"
 5644            // ˇThis is a long comment that will be wrapped.
 5645            //
 5646            // And this is another long comment that will also be wrapped.ˇ
 5647         "},
 5648        indoc! {"
 5649            // ˇThis is a long comment that will be
 5650            // wrapped.
 5651            //
 5652            // And this is another long comment that
 5653            // will also be wrapped.ˇ
 5654         "},
 5655        cpp_language,
 5656        &mut cx,
 5657    );
 5658
 5659    #[track_caller]
 5660    fn assert_rewrap(
 5661        unwrapped_text: &str,
 5662        wrapped_text: &str,
 5663        language: Arc<Language>,
 5664        cx: &mut EditorTestContext,
 5665    ) {
 5666        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 5667        cx.set_state(unwrapped_text);
 5668        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 5669        cx.assert_editor_state(wrapped_text);
 5670    }
 5671}
 5672
 5673#[gpui::test]
 5674async fn test_hard_wrap(cx: &mut TestAppContext) {
 5675    init_test(cx, |_| {});
 5676    let mut cx = EditorTestContext::new(cx).await;
 5677
 5678    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 5679    cx.update_editor(|editor, _, cx| {
 5680        editor.set_hard_wrap(Some(14), cx);
 5681    });
 5682
 5683    cx.set_state(indoc!(
 5684        "
 5685        one two three ˇ
 5686        "
 5687    ));
 5688    cx.simulate_input("four");
 5689    cx.run_until_parked();
 5690
 5691    cx.assert_editor_state(indoc!(
 5692        "
 5693        one two three
 5694        fourˇ
 5695        "
 5696    ));
 5697
 5698    cx.update_editor(|editor, window, cx| {
 5699        editor.newline(&Default::default(), window, cx);
 5700    });
 5701    cx.run_until_parked();
 5702    cx.assert_editor_state(indoc!(
 5703        "
 5704        one two three
 5705        four
 5706        ˇ
 5707        "
 5708    ));
 5709
 5710    cx.simulate_input("five");
 5711    cx.run_until_parked();
 5712    cx.assert_editor_state(indoc!(
 5713        "
 5714        one two three
 5715        four
 5716        fiveˇ
 5717        "
 5718    ));
 5719
 5720    cx.update_editor(|editor, window, cx| {
 5721        editor.newline(&Default::default(), window, cx);
 5722    });
 5723    cx.run_until_parked();
 5724    cx.simulate_input("# ");
 5725    cx.run_until_parked();
 5726    cx.assert_editor_state(indoc!(
 5727        "
 5728        one two three
 5729        four
 5730        five
 5731        # ˇ
 5732        "
 5733    ));
 5734
 5735    cx.update_editor(|editor, window, cx| {
 5736        editor.newline(&Default::default(), window, cx);
 5737    });
 5738    cx.run_until_parked();
 5739    cx.assert_editor_state(indoc!(
 5740        "
 5741        one two three
 5742        four
 5743        five
 5744        #\x20
 5745 5746        "
 5747    ));
 5748
 5749    cx.simulate_input(" 6");
 5750    cx.run_until_parked();
 5751    cx.assert_editor_state(indoc!(
 5752        "
 5753        one two three
 5754        four
 5755        five
 5756        #
 5757        # 6ˇ
 5758        "
 5759    ));
 5760}
 5761
 5762#[gpui::test]
 5763async fn test_clipboard(cx: &mut TestAppContext) {
 5764    init_test(cx, |_| {});
 5765
 5766    let mut cx = EditorTestContext::new(cx).await;
 5767
 5768    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 5769    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5770    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 5771
 5772    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 5773    cx.set_state("two ˇfour ˇsix ˇ");
 5774    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5775    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 5776
 5777    // Paste again but with only two cursors. Since the number of cursors doesn't
 5778    // match the number of slices in the clipboard, the entire clipboard text
 5779    // is pasted at each cursor.
 5780    cx.set_state("ˇtwo one✅ four three six five ˇ");
 5781    cx.update_editor(|e, window, cx| {
 5782        e.handle_input("( ", window, cx);
 5783        e.paste(&Paste, window, cx);
 5784        e.handle_input(") ", window, cx);
 5785    });
 5786    cx.assert_editor_state(
 5787        &([
 5788            "( one✅ ",
 5789            "three ",
 5790            "five ) ˇtwo one✅ four three six five ( one✅ ",
 5791            "three ",
 5792            "five ) ˇ",
 5793        ]
 5794        .join("\n")),
 5795    );
 5796
 5797    // Cut with three selections, one of which is full-line.
 5798    cx.set_state(indoc! {"
 5799        1«2ˇ»3
 5800        4ˇ567
 5801        «8ˇ»9"});
 5802    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5803    cx.assert_editor_state(indoc! {"
 5804        1ˇ3
 5805        ˇ9"});
 5806
 5807    // Paste with three selections, noticing how the copied selection that was full-line
 5808    // gets inserted before the second cursor.
 5809    cx.set_state(indoc! {"
 5810        1ˇ3
 5811 5812        «oˇ»ne"});
 5813    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5814    cx.assert_editor_state(indoc! {"
 5815        12ˇ3
 5816        4567
 5817 5818        8ˇne"});
 5819
 5820    // Copy with a single cursor only, which writes the whole line into the clipboard.
 5821    cx.set_state(indoc! {"
 5822        The quick brown
 5823        fox juˇmps over
 5824        the lazy dog"});
 5825    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5826    assert_eq!(
 5827        cx.read_from_clipboard()
 5828            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5829        Some("fox jumps over\n".to_string())
 5830    );
 5831
 5832    // Paste with three selections, noticing how the copied full-line selection is inserted
 5833    // before the empty selections but replaces the selection that is non-empty.
 5834    cx.set_state(indoc! {"
 5835        Tˇhe quick brown
 5836        «foˇ»x jumps over
 5837        tˇhe lazy dog"});
 5838    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5839    cx.assert_editor_state(indoc! {"
 5840        fox jumps over
 5841        Tˇhe quick brown
 5842        fox jumps over
 5843        ˇx jumps over
 5844        fox jumps over
 5845        tˇhe lazy dog"});
 5846}
 5847
 5848#[gpui::test]
 5849async fn test_copy_trim(cx: &mut TestAppContext) {
 5850    init_test(cx, |_| {});
 5851
 5852    let mut cx = EditorTestContext::new(cx).await;
 5853    cx.set_state(
 5854        r#"            «for selection in selections.iter() {
 5855            let mut start = selection.start;
 5856            let mut end = selection.end;
 5857            let is_entire_line = selection.is_empty();
 5858            if is_entire_line {
 5859                start = Point::new(start.row, 0);ˇ»
 5860                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5861            }
 5862        "#,
 5863    );
 5864    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5865    assert_eq!(
 5866        cx.read_from_clipboard()
 5867            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5868        Some(
 5869            "for selection in selections.iter() {
 5870            let mut start = selection.start;
 5871            let mut end = selection.end;
 5872            let is_entire_line = selection.is_empty();
 5873            if is_entire_line {
 5874                start = Point::new(start.row, 0);"
 5875                .to_string()
 5876        ),
 5877        "Regular copying preserves all indentation selected",
 5878    );
 5879    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5880    assert_eq!(
 5881        cx.read_from_clipboard()
 5882            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5883        Some(
 5884            "for selection in selections.iter() {
 5885let mut start = selection.start;
 5886let mut end = selection.end;
 5887let is_entire_line = selection.is_empty();
 5888if is_entire_line {
 5889    start = Point::new(start.row, 0);"
 5890                .to_string()
 5891        ),
 5892        "Copying with stripping should strip all leading whitespaces"
 5893    );
 5894
 5895    cx.set_state(
 5896        r#"       «     for selection in selections.iter() {
 5897            let mut start = selection.start;
 5898            let mut end = selection.end;
 5899            let is_entire_line = selection.is_empty();
 5900            if is_entire_line {
 5901                start = Point::new(start.row, 0);ˇ»
 5902                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5903            }
 5904        "#,
 5905    );
 5906    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5907    assert_eq!(
 5908        cx.read_from_clipboard()
 5909            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5910        Some(
 5911            "     for selection in selections.iter() {
 5912            let mut start = selection.start;
 5913            let mut end = selection.end;
 5914            let is_entire_line = selection.is_empty();
 5915            if is_entire_line {
 5916                start = Point::new(start.row, 0);"
 5917                .to_string()
 5918        ),
 5919        "Regular copying preserves all indentation selected",
 5920    );
 5921    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5922    assert_eq!(
 5923        cx.read_from_clipboard()
 5924            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5925        Some(
 5926            "for selection in selections.iter() {
 5927let mut start = selection.start;
 5928let mut end = selection.end;
 5929let is_entire_line = selection.is_empty();
 5930if is_entire_line {
 5931    start = Point::new(start.row, 0);"
 5932                .to_string()
 5933        ),
 5934        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 5935    );
 5936
 5937    cx.set_state(
 5938        r#"       «ˇ     for selection in selections.iter() {
 5939            let mut start = selection.start;
 5940            let mut end = selection.end;
 5941            let is_entire_line = selection.is_empty();
 5942            if is_entire_line {
 5943                start = Point::new(start.row, 0);»
 5944                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5945            }
 5946        "#,
 5947    );
 5948    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5949    assert_eq!(
 5950        cx.read_from_clipboard()
 5951            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5952        Some(
 5953            "     for selection in selections.iter() {
 5954            let mut start = selection.start;
 5955            let mut end = selection.end;
 5956            let is_entire_line = selection.is_empty();
 5957            if is_entire_line {
 5958                start = Point::new(start.row, 0);"
 5959                .to_string()
 5960        ),
 5961        "Regular copying for reverse selection works the same",
 5962    );
 5963    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5964    assert_eq!(
 5965        cx.read_from_clipboard()
 5966            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5967        Some(
 5968            "for selection in selections.iter() {
 5969let mut start = selection.start;
 5970let mut end = selection.end;
 5971let is_entire_line = selection.is_empty();
 5972if is_entire_line {
 5973    start = Point::new(start.row, 0);"
 5974                .to_string()
 5975        ),
 5976        "Copying with stripping for reverse selection works the same"
 5977    );
 5978
 5979    cx.set_state(
 5980        r#"            for selection «in selections.iter() {
 5981            let mut start = selection.start;
 5982            let mut end = selection.end;
 5983            let is_entire_line = selection.is_empty();
 5984            if is_entire_line {
 5985                start = Point::new(start.row, 0);ˇ»
 5986                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5987            }
 5988        "#,
 5989    );
 5990    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5991    assert_eq!(
 5992        cx.read_from_clipboard()
 5993            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5994        Some(
 5995            "in selections.iter() {
 5996            let mut start = selection.start;
 5997            let mut end = selection.end;
 5998            let is_entire_line = selection.is_empty();
 5999            if is_entire_line {
 6000                start = Point::new(start.row, 0);"
 6001                .to_string()
 6002        ),
 6003        "When selecting past the indent, the copying works as usual",
 6004    );
 6005    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6006    assert_eq!(
 6007        cx.read_from_clipboard()
 6008            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6009        Some(
 6010            "in selections.iter() {
 6011            let mut start = selection.start;
 6012            let mut end = selection.end;
 6013            let is_entire_line = selection.is_empty();
 6014            if is_entire_line {
 6015                start = Point::new(start.row, 0);"
 6016                .to_string()
 6017        ),
 6018        "When selecting past the indent, nothing is trimmed"
 6019    );
 6020
 6021    cx.set_state(
 6022        r#"            «for selection in selections.iter() {
 6023            let mut start = selection.start;
 6024
 6025            let mut end = selection.end;
 6026            let is_entire_line = selection.is_empty();
 6027            if is_entire_line {
 6028                start = Point::new(start.row, 0);
 6029ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6030            }
 6031        "#,
 6032    );
 6033    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6034    assert_eq!(
 6035        cx.read_from_clipboard()
 6036            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6037        Some(
 6038            "for selection in selections.iter() {
 6039let mut start = selection.start;
 6040
 6041let mut end = selection.end;
 6042let is_entire_line = selection.is_empty();
 6043if is_entire_line {
 6044    start = Point::new(start.row, 0);
 6045"
 6046            .to_string()
 6047        ),
 6048        "Copying with stripping should ignore empty lines"
 6049    );
 6050}
 6051
 6052#[gpui::test]
 6053async fn test_paste_multiline(cx: &mut TestAppContext) {
 6054    init_test(cx, |_| {});
 6055
 6056    let mut cx = EditorTestContext::new(cx).await;
 6057    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 6058
 6059    // Cut an indented block, without the leading whitespace.
 6060    cx.set_state(indoc! {"
 6061        const a: B = (
 6062            c(),
 6063            «d(
 6064                e,
 6065                f
 6066            )ˇ»
 6067        );
 6068    "});
 6069    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6070    cx.assert_editor_state(indoc! {"
 6071        const a: B = (
 6072            c(),
 6073            ˇ
 6074        );
 6075    "});
 6076
 6077    // Paste it at the same position.
 6078    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6079    cx.assert_editor_state(indoc! {"
 6080        const a: B = (
 6081            c(),
 6082            d(
 6083                e,
 6084                f
 6085 6086        );
 6087    "});
 6088
 6089    // Paste it at a line with a lower indent level.
 6090    cx.set_state(indoc! {"
 6091        ˇ
 6092        const a: B = (
 6093            c(),
 6094        );
 6095    "});
 6096    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6097    cx.assert_editor_state(indoc! {"
 6098        d(
 6099            e,
 6100            f
 6101 6102        const a: B = (
 6103            c(),
 6104        );
 6105    "});
 6106
 6107    // Cut an indented block, with the leading whitespace.
 6108    cx.set_state(indoc! {"
 6109        const a: B = (
 6110            c(),
 6111        «    d(
 6112                e,
 6113                f
 6114            )
 6115        ˇ»);
 6116    "});
 6117    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6118    cx.assert_editor_state(indoc! {"
 6119        const a: B = (
 6120            c(),
 6121        ˇ);
 6122    "});
 6123
 6124    // Paste it at the same position.
 6125    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6126    cx.assert_editor_state(indoc! {"
 6127        const a: B = (
 6128            c(),
 6129            d(
 6130                e,
 6131                f
 6132            )
 6133        ˇ);
 6134    "});
 6135
 6136    // Paste it at a line with a higher indent level.
 6137    cx.set_state(indoc! {"
 6138        const a: B = (
 6139            c(),
 6140            d(
 6141                e,
 6142 6143            )
 6144        );
 6145    "});
 6146    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6147    cx.assert_editor_state(indoc! {"
 6148        const a: B = (
 6149            c(),
 6150            d(
 6151                e,
 6152                f    d(
 6153                    e,
 6154                    f
 6155                )
 6156        ˇ
 6157            )
 6158        );
 6159    "});
 6160
 6161    // Copy an indented block, starting mid-line
 6162    cx.set_state(indoc! {"
 6163        const a: B = (
 6164            c(),
 6165            somethin«g(
 6166                e,
 6167                f
 6168            )ˇ»
 6169        );
 6170    "});
 6171    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6172
 6173    // Paste it on a line with a lower indent level
 6174    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 6175    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6176    cx.assert_editor_state(indoc! {"
 6177        const a: B = (
 6178            c(),
 6179            something(
 6180                e,
 6181                f
 6182            )
 6183        );
 6184        g(
 6185            e,
 6186            f
 6187"});
 6188}
 6189
 6190#[gpui::test]
 6191async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 6192    init_test(cx, |_| {});
 6193
 6194    cx.write_to_clipboard(ClipboardItem::new_string(
 6195        "    d(\n        e\n    );\n".into(),
 6196    ));
 6197
 6198    let mut cx = EditorTestContext::new(cx).await;
 6199    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 6200
 6201    cx.set_state(indoc! {"
 6202        fn a() {
 6203            b();
 6204            if c() {
 6205                ˇ
 6206            }
 6207        }
 6208    "});
 6209
 6210    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6211    cx.assert_editor_state(indoc! {"
 6212        fn a() {
 6213            b();
 6214            if c() {
 6215                d(
 6216                    e
 6217                );
 6218        ˇ
 6219            }
 6220        }
 6221    "});
 6222
 6223    cx.set_state(indoc! {"
 6224        fn a() {
 6225            b();
 6226            ˇ
 6227        }
 6228    "});
 6229
 6230    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6231    cx.assert_editor_state(indoc! {"
 6232        fn a() {
 6233            b();
 6234            d(
 6235                e
 6236            );
 6237        ˇ
 6238        }
 6239    "});
 6240}
 6241
 6242#[gpui::test]
 6243fn test_select_all(cx: &mut TestAppContext) {
 6244    init_test(cx, |_| {});
 6245
 6246    let editor = cx.add_window(|window, cx| {
 6247        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 6248        build_editor(buffer, window, cx)
 6249    });
 6250    _ = editor.update(cx, |editor, window, cx| {
 6251        editor.select_all(&SelectAll, window, cx);
 6252        assert_eq!(
 6253            editor.selections.display_ranges(cx),
 6254            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 6255        );
 6256    });
 6257}
 6258
 6259#[gpui::test]
 6260fn test_select_line(cx: &mut TestAppContext) {
 6261    init_test(cx, |_| {});
 6262
 6263    let editor = cx.add_window(|window, cx| {
 6264        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 6265        build_editor(buffer, window, cx)
 6266    });
 6267    _ = editor.update(cx, |editor, window, cx| {
 6268        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6269            s.select_display_ranges([
 6270                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6271                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6272                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6273                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 6274            ])
 6275        });
 6276        editor.select_line(&SelectLine, window, cx);
 6277        assert_eq!(
 6278            editor.selections.display_ranges(cx),
 6279            vec![
 6280                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 6281                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 6282            ]
 6283        );
 6284    });
 6285
 6286    _ = editor.update(cx, |editor, window, cx| {
 6287        editor.select_line(&SelectLine, window, cx);
 6288        assert_eq!(
 6289            editor.selections.display_ranges(cx),
 6290            vec![
 6291                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 6292                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 6293            ]
 6294        );
 6295    });
 6296
 6297    _ = editor.update(cx, |editor, window, cx| {
 6298        editor.select_line(&SelectLine, window, cx);
 6299        assert_eq!(
 6300            editor.selections.display_ranges(cx),
 6301            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 6302        );
 6303    });
 6304}
 6305
 6306#[gpui::test]
 6307async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 6308    init_test(cx, |_| {});
 6309    let mut cx = EditorTestContext::new(cx).await;
 6310
 6311    #[track_caller]
 6312    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 6313        cx.set_state(initial_state);
 6314        cx.update_editor(|e, window, cx| {
 6315            e.split_selection_into_lines(&SplitSelectionIntoLines, window, cx)
 6316        });
 6317        cx.assert_editor_state(expected_state);
 6318    }
 6319
 6320    // Selection starts and ends at the middle of lines, left-to-right
 6321    test(
 6322        &mut cx,
 6323        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 6324        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 6325    );
 6326    // Same thing, right-to-left
 6327    test(
 6328        &mut cx,
 6329        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 6330        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 6331    );
 6332
 6333    // Whole buffer, left-to-right, last line *doesn't* end with newline
 6334    test(
 6335        &mut cx,
 6336        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 6337        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 6338    );
 6339    // Same thing, right-to-left
 6340    test(
 6341        &mut cx,
 6342        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 6343        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 6344    );
 6345
 6346    // Whole buffer, left-to-right, last line ends with newline
 6347    test(
 6348        &mut cx,
 6349        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 6350        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 6351    );
 6352    // Same thing, right-to-left
 6353    test(
 6354        &mut cx,
 6355        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 6356        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 6357    );
 6358
 6359    // Starts at the end of a line, ends at the start of another
 6360    test(
 6361        &mut cx,
 6362        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 6363        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 6364    );
 6365}
 6366
 6367#[gpui::test]
 6368async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 6369    init_test(cx, |_| {});
 6370
 6371    let editor = cx.add_window(|window, cx| {
 6372        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 6373        build_editor(buffer, window, cx)
 6374    });
 6375
 6376    // setup
 6377    _ = editor.update(cx, |editor, window, cx| {
 6378        editor.fold_creases(
 6379            vec![
 6380                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 6381                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 6382                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 6383            ],
 6384            true,
 6385            window,
 6386            cx,
 6387        );
 6388        assert_eq!(
 6389            editor.display_text(cx),
 6390            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 6391        );
 6392    });
 6393
 6394    _ = editor.update(cx, |editor, window, cx| {
 6395        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6396            s.select_display_ranges([
 6397                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6398                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6399                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6400                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 6401            ])
 6402        });
 6403        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 6404        assert_eq!(
 6405            editor.display_text(cx),
 6406            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 6407        );
 6408    });
 6409    EditorTestContext::for_editor(editor, cx)
 6410        .await
 6411        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 6412
 6413    _ = editor.update(cx, |editor, window, cx| {
 6414        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6415            s.select_display_ranges([
 6416                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 6417            ])
 6418        });
 6419        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 6420        assert_eq!(
 6421            editor.display_text(cx),
 6422            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 6423        );
 6424        assert_eq!(
 6425            editor.selections.display_ranges(cx),
 6426            [
 6427                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 6428                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 6429                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 6430                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 6431                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 6432                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 6433                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 6434            ]
 6435        );
 6436    });
 6437    EditorTestContext::for_editor(editor, cx)
 6438        .await
 6439        .assert_editor_state(
 6440            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 6441        );
 6442}
 6443
 6444#[gpui::test]
 6445async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 6446    init_test(cx, |_| {});
 6447
 6448    let mut cx = EditorTestContext::new(cx).await;
 6449
 6450    cx.set_state(indoc!(
 6451        r#"abc
 6452           defˇghi
 6453
 6454           jk
 6455           nlmo
 6456           "#
 6457    ));
 6458
 6459    cx.update_editor(|editor, window, cx| {
 6460        editor.add_selection_above(&Default::default(), window, cx);
 6461    });
 6462
 6463    cx.assert_editor_state(indoc!(
 6464        r#"abcˇ
 6465           defˇghi
 6466
 6467           jk
 6468           nlmo
 6469           "#
 6470    ));
 6471
 6472    cx.update_editor(|editor, window, cx| {
 6473        editor.add_selection_above(&Default::default(), window, cx);
 6474    });
 6475
 6476    cx.assert_editor_state(indoc!(
 6477        r#"abcˇ
 6478            defˇghi
 6479
 6480            jk
 6481            nlmo
 6482            "#
 6483    ));
 6484
 6485    cx.update_editor(|editor, window, cx| {
 6486        editor.add_selection_below(&Default::default(), window, cx);
 6487    });
 6488
 6489    cx.assert_editor_state(indoc!(
 6490        r#"abc
 6491           defˇghi
 6492
 6493           jk
 6494           nlmo
 6495           "#
 6496    ));
 6497
 6498    cx.update_editor(|editor, window, cx| {
 6499        editor.undo_selection(&Default::default(), window, cx);
 6500    });
 6501
 6502    cx.assert_editor_state(indoc!(
 6503        r#"abcˇ
 6504           defˇghi
 6505
 6506           jk
 6507           nlmo
 6508           "#
 6509    ));
 6510
 6511    cx.update_editor(|editor, window, cx| {
 6512        editor.redo_selection(&Default::default(), window, cx);
 6513    });
 6514
 6515    cx.assert_editor_state(indoc!(
 6516        r#"abc
 6517           defˇghi
 6518
 6519           jk
 6520           nlmo
 6521           "#
 6522    ));
 6523
 6524    cx.update_editor(|editor, window, cx| {
 6525        editor.add_selection_below(&Default::default(), window, cx);
 6526    });
 6527
 6528    cx.assert_editor_state(indoc!(
 6529        r#"abc
 6530           defˇghi
 6531           ˇ
 6532           jk
 6533           nlmo
 6534           "#
 6535    ));
 6536
 6537    cx.update_editor(|editor, window, cx| {
 6538        editor.add_selection_below(&Default::default(), window, cx);
 6539    });
 6540
 6541    cx.assert_editor_state(indoc!(
 6542        r#"abc
 6543           defˇghi
 6544           ˇ
 6545           jkˇ
 6546           nlmo
 6547           "#
 6548    ));
 6549
 6550    cx.update_editor(|editor, window, cx| {
 6551        editor.add_selection_below(&Default::default(), window, cx);
 6552    });
 6553
 6554    cx.assert_editor_state(indoc!(
 6555        r#"abc
 6556           defˇghi
 6557           ˇ
 6558           jkˇ
 6559           nlmˇo
 6560           "#
 6561    ));
 6562
 6563    cx.update_editor(|editor, window, cx| {
 6564        editor.add_selection_below(&Default::default(), window, cx);
 6565    });
 6566
 6567    cx.assert_editor_state(indoc!(
 6568        r#"abc
 6569           defˇghi
 6570           ˇ
 6571           jkˇ
 6572           nlmˇo
 6573           ˇ"#
 6574    ));
 6575
 6576    // change selections
 6577    cx.set_state(indoc!(
 6578        r#"abc
 6579           def«ˇg»hi
 6580
 6581           jk
 6582           nlmo
 6583           "#
 6584    ));
 6585
 6586    cx.update_editor(|editor, window, cx| {
 6587        editor.add_selection_below(&Default::default(), window, cx);
 6588    });
 6589
 6590    cx.assert_editor_state(indoc!(
 6591        r#"abc
 6592           def«ˇg»hi
 6593
 6594           jk
 6595           nlm«ˇo»
 6596           "#
 6597    ));
 6598
 6599    cx.update_editor(|editor, window, cx| {
 6600        editor.add_selection_below(&Default::default(), window, cx);
 6601    });
 6602
 6603    cx.assert_editor_state(indoc!(
 6604        r#"abc
 6605           def«ˇg»hi
 6606
 6607           jk
 6608           nlm«ˇo»
 6609           "#
 6610    ));
 6611
 6612    cx.update_editor(|editor, window, cx| {
 6613        editor.add_selection_above(&Default::default(), window, cx);
 6614    });
 6615
 6616    cx.assert_editor_state(indoc!(
 6617        r#"abc
 6618           def«ˇg»hi
 6619
 6620           jk
 6621           nlmo
 6622           "#
 6623    ));
 6624
 6625    cx.update_editor(|editor, window, cx| {
 6626        editor.add_selection_above(&Default::default(), window, cx);
 6627    });
 6628
 6629    cx.assert_editor_state(indoc!(
 6630        r#"abc
 6631           def«ˇg»hi
 6632
 6633           jk
 6634           nlmo
 6635           "#
 6636    ));
 6637
 6638    // Change selections again
 6639    cx.set_state(indoc!(
 6640        r#"a«bc
 6641           defgˇ»hi
 6642
 6643           jk
 6644           nlmo
 6645           "#
 6646    ));
 6647
 6648    cx.update_editor(|editor, window, cx| {
 6649        editor.add_selection_below(&Default::default(), window, cx);
 6650    });
 6651
 6652    cx.assert_editor_state(indoc!(
 6653        r#"a«bcˇ»
 6654           d«efgˇ»hi
 6655
 6656           j«kˇ»
 6657           nlmo
 6658           "#
 6659    ));
 6660
 6661    cx.update_editor(|editor, window, cx| {
 6662        editor.add_selection_below(&Default::default(), window, cx);
 6663    });
 6664    cx.assert_editor_state(indoc!(
 6665        r#"a«bcˇ»
 6666           d«efgˇ»hi
 6667
 6668           j«kˇ»
 6669           n«lmoˇ»
 6670           "#
 6671    ));
 6672    cx.update_editor(|editor, window, cx| {
 6673        editor.add_selection_above(&Default::default(), window, cx);
 6674    });
 6675
 6676    cx.assert_editor_state(indoc!(
 6677        r#"a«bcˇ»
 6678           d«efgˇ»hi
 6679
 6680           j«kˇ»
 6681           nlmo
 6682           "#
 6683    ));
 6684
 6685    // Change selections again
 6686    cx.set_state(indoc!(
 6687        r#"abc
 6688           d«ˇefghi
 6689
 6690           jk
 6691           nlm»o
 6692           "#
 6693    ));
 6694
 6695    cx.update_editor(|editor, window, cx| {
 6696        editor.add_selection_above(&Default::default(), window, cx);
 6697    });
 6698
 6699    cx.assert_editor_state(indoc!(
 6700        r#"a«ˇbc»
 6701           d«ˇef»ghi
 6702
 6703           j«ˇk»
 6704           n«ˇlm»o
 6705           "#
 6706    ));
 6707
 6708    cx.update_editor(|editor, window, cx| {
 6709        editor.add_selection_below(&Default::default(), window, cx);
 6710    });
 6711
 6712    cx.assert_editor_state(indoc!(
 6713        r#"abc
 6714           d«ˇef»ghi
 6715
 6716           j«ˇk»
 6717           n«ˇlm»o
 6718           "#
 6719    ));
 6720}
 6721
 6722#[gpui::test]
 6723async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 6724    init_test(cx, |_| {});
 6725    let mut cx = EditorTestContext::new(cx).await;
 6726
 6727    cx.set_state(indoc!(
 6728        r#"line onˇe
 6729           liˇne two
 6730           line three
 6731           line four"#
 6732    ));
 6733
 6734    cx.update_editor(|editor, window, cx| {
 6735        editor.add_selection_below(&Default::default(), window, cx);
 6736    });
 6737
 6738    // test multiple cursors expand in the same direction
 6739    cx.assert_editor_state(indoc!(
 6740        r#"line onˇe
 6741           liˇne twˇo
 6742           liˇne three
 6743           line four"#
 6744    ));
 6745
 6746    cx.update_editor(|editor, window, cx| {
 6747        editor.add_selection_below(&Default::default(), window, cx);
 6748    });
 6749
 6750    cx.update_editor(|editor, window, cx| {
 6751        editor.add_selection_below(&Default::default(), window, cx);
 6752    });
 6753
 6754    // test multiple cursors expand below overflow
 6755    cx.assert_editor_state(indoc!(
 6756        r#"line onˇe
 6757           liˇne twˇo
 6758           liˇne thˇree
 6759           liˇne foˇur"#
 6760    ));
 6761
 6762    cx.update_editor(|editor, window, cx| {
 6763        editor.add_selection_above(&Default::default(), window, cx);
 6764    });
 6765
 6766    // test multiple cursors retrieves back correctly
 6767    cx.assert_editor_state(indoc!(
 6768        r#"line onˇe
 6769           liˇne twˇo
 6770           liˇne thˇree
 6771           line four"#
 6772    ));
 6773
 6774    cx.update_editor(|editor, window, cx| {
 6775        editor.add_selection_above(&Default::default(), window, cx);
 6776    });
 6777
 6778    cx.update_editor(|editor, window, cx| {
 6779        editor.add_selection_above(&Default::default(), window, cx);
 6780    });
 6781
 6782    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 6783    cx.assert_editor_state(indoc!(
 6784        r#"liˇne onˇe
 6785           liˇne two
 6786           line three
 6787           line four"#
 6788    ));
 6789
 6790    cx.update_editor(|editor, window, cx| {
 6791        editor.undo_selection(&Default::default(), window, cx);
 6792    });
 6793
 6794    // test undo
 6795    cx.assert_editor_state(indoc!(
 6796        r#"line onˇe
 6797           liˇne twˇo
 6798           line three
 6799           line four"#
 6800    ));
 6801
 6802    cx.update_editor(|editor, window, cx| {
 6803        editor.redo_selection(&Default::default(), window, cx);
 6804    });
 6805
 6806    // test redo
 6807    cx.assert_editor_state(indoc!(
 6808        r#"liˇne onˇe
 6809           liˇne two
 6810           line three
 6811           line four"#
 6812    ));
 6813
 6814    cx.set_state(indoc!(
 6815        r#"abcd
 6816           ef«ghˇ»
 6817           ijkl
 6818           «mˇ»nop"#
 6819    ));
 6820
 6821    cx.update_editor(|editor, window, cx| {
 6822        editor.add_selection_above(&Default::default(), window, cx);
 6823    });
 6824
 6825    // test multiple selections expand in the same direction
 6826    cx.assert_editor_state(indoc!(
 6827        r#"ab«cdˇ»
 6828           ef«ghˇ»
 6829           «iˇ»jkl
 6830           «mˇ»nop"#
 6831    ));
 6832
 6833    cx.update_editor(|editor, window, cx| {
 6834        editor.add_selection_above(&Default::default(), window, cx);
 6835    });
 6836
 6837    // test multiple selection upward overflow
 6838    cx.assert_editor_state(indoc!(
 6839        r#"ab«cdˇ»
 6840           «eˇ»f«ghˇ»
 6841           «iˇ»jkl
 6842           «mˇ»nop"#
 6843    ));
 6844
 6845    cx.update_editor(|editor, window, cx| {
 6846        editor.add_selection_below(&Default::default(), window, cx);
 6847    });
 6848
 6849    // test multiple selection retrieves back correctly
 6850    cx.assert_editor_state(indoc!(
 6851        r#"abcd
 6852           ef«ghˇ»
 6853           «iˇ»jkl
 6854           «mˇ»nop"#
 6855    ));
 6856
 6857    cx.update_editor(|editor, window, cx| {
 6858        editor.add_selection_below(&Default::default(), window, cx);
 6859    });
 6860
 6861    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 6862    cx.assert_editor_state(indoc!(
 6863        r#"abcd
 6864           ef«ghˇ»
 6865           ij«klˇ»
 6866           «mˇ»nop"#
 6867    ));
 6868
 6869    cx.update_editor(|editor, window, cx| {
 6870        editor.undo_selection(&Default::default(), window, cx);
 6871    });
 6872
 6873    // test undo
 6874    cx.assert_editor_state(indoc!(
 6875        r#"abcd
 6876           ef«ghˇ»
 6877           «iˇ»jkl
 6878           «mˇ»nop"#
 6879    ));
 6880
 6881    cx.update_editor(|editor, window, cx| {
 6882        editor.redo_selection(&Default::default(), window, cx);
 6883    });
 6884
 6885    // test redo
 6886    cx.assert_editor_state(indoc!(
 6887        r#"abcd
 6888           ef«ghˇ»
 6889           ij«klˇ»
 6890           «mˇ»nop"#
 6891    ));
 6892}
 6893
 6894#[gpui::test]
 6895async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 6896    init_test(cx, |_| {});
 6897    let mut cx = EditorTestContext::new(cx).await;
 6898
 6899    cx.set_state(indoc!(
 6900        r#"line onˇe
 6901           liˇne two
 6902           line three
 6903           line four"#
 6904    ));
 6905
 6906    cx.update_editor(|editor, window, cx| {
 6907        editor.add_selection_below(&Default::default(), window, cx);
 6908        editor.add_selection_below(&Default::default(), window, cx);
 6909        editor.add_selection_below(&Default::default(), window, cx);
 6910    });
 6911
 6912    // initial state with two multi cursor groups
 6913    cx.assert_editor_state(indoc!(
 6914        r#"line onˇe
 6915           liˇne twˇo
 6916           liˇne thˇree
 6917           liˇne foˇur"#
 6918    ));
 6919
 6920    // add single cursor in middle - simulate opt click
 6921    cx.update_editor(|editor, window, cx| {
 6922        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 6923        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 6924        editor.end_selection(window, cx);
 6925    });
 6926
 6927    cx.assert_editor_state(indoc!(
 6928        r#"line onˇe
 6929           liˇne twˇo
 6930           liˇneˇ thˇree
 6931           liˇne foˇur"#
 6932    ));
 6933
 6934    cx.update_editor(|editor, window, cx| {
 6935        editor.add_selection_above(&Default::default(), window, cx);
 6936    });
 6937
 6938    // test new added selection expands above and existing selection shrinks
 6939    cx.assert_editor_state(indoc!(
 6940        r#"line onˇe
 6941           liˇneˇ twˇo
 6942           liˇneˇ thˇree
 6943           line four"#
 6944    ));
 6945
 6946    cx.update_editor(|editor, window, cx| {
 6947        editor.add_selection_above(&Default::default(), window, cx);
 6948    });
 6949
 6950    // test new added selection expands above and existing selection shrinks
 6951    cx.assert_editor_state(indoc!(
 6952        r#"lineˇ onˇe
 6953           liˇneˇ twˇo
 6954           lineˇ three
 6955           line four"#
 6956    ));
 6957
 6958    // intial state with two selection groups
 6959    cx.set_state(indoc!(
 6960        r#"abcd
 6961           ef«ghˇ»
 6962           ijkl
 6963           «mˇ»nop"#
 6964    ));
 6965
 6966    cx.update_editor(|editor, window, cx| {
 6967        editor.add_selection_above(&Default::default(), window, cx);
 6968        editor.add_selection_above(&Default::default(), window, cx);
 6969    });
 6970
 6971    cx.assert_editor_state(indoc!(
 6972        r#"ab«cdˇ»
 6973           «eˇ»f«ghˇ»
 6974           «iˇ»jkl
 6975           «mˇ»nop"#
 6976    ));
 6977
 6978    // add single selection in middle - simulate opt drag
 6979    cx.update_editor(|editor, window, cx| {
 6980        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 6981        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 6982        editor.update_selection(
 6983            DisplayPoint::new(DisplayRow(2), 4),
 6984            0,
 6985            gpui::Point::<f32>::default(),
 6986            window,
 6987            cx,
 6988        );
 6989        editor.end_selection(window, cx);
 6990    });
 6991
 6992    cx.assert_editor_state(indoc!(
 6993        r#"ab«cdˇ»
 6994           «eˇ»f«ghˇ»
 6995           «iˇ»jk«lˇ»
 6996           «mˇ»nop"#
 6997    ));
 6998
 6999    cx.update_editor(|editor, window, cx| {
 7000        editor.add_selection_below(&Default::default(), window, cx);
 7001    });
 7002
 7003    // test new added selection expands below, others shrinks from above
 7004    cx.assert_editor_state(indoc!(
 7005        r#"abcd
 7006           ef«ghˇ»
 7007           «iˇ»jk«lˇ»
 7008           «mˇ»no«pˇ»"#
 7009    ));
 7010}
 7011
 7012#[gpui::test]
 7013async fn test_select_next(cx: &mut TestAppContext) {
 7014    init_test(cx, |_| {});
 7015
 7016    let mut cx = EditorTestContext::new(cx).await;
 7017    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7018
 7019    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7020        .unwrap();
 7021    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7022
 7023    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7024        .unwrap();
 7025    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 7026
 7027    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7028    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7029
 7030    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7031    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 7032
 7033    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7034        .unwrap();
 7035    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7036
 7037    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7038        .unwrap();
 7039    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7040
 7041    // Test selection direction should be preserved
 7042    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 7043
 7044    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7045        .unwrap();
 7046    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 7047}
 7048
 7049#[gpui::test]
 7050async fn test_select_all_matches(cx: &mut TestAppContext) {
 7051    init_test(cx, |_| {});
 7052
 7053    let mut cx = EditorTestContext::new(cx).await;
 7054
 7055    // Test caret-only selections
 7056    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7057    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7058        .unwrap();
 7059    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7060
 7061    // Test left-to-right selections
 7062    cx.set_state("abc\n«abcˇ»\nabc");
 7063    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7064        .unwrap();
 7065    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 7066
 7067    // Test right-to-left selections
 7068    cx.set_state("abc\n«ˇabc»\nabc");
 7069    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7070        .unwrap();
 7071    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 7072
 7073    // Test selecting whitespace with caret selection
 7074    cx.set_state("abc\nˇ   abc\nabc");
 7075    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7076        .unwrap();
 7077    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 7078
 7079    // Test selecting whitespace with left-to-right selection
 7080    cx.set_state("abc\n«ˇ  »abc\nabc");
 7081    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7082        .unwrap();
 7083    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 7084
 7085    // Test no matches with right-to-left selection
 7086    cx.set_state("abc\n«  ˇ»abc\nabc");
 7087    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7088        .unwrap();
 7089    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 7090
 7091    // Test with a single word and clip_at_line_ends=true (#29823)
 7092    cx.set_state("aˇbc");
 7093    cx.update_editor(|e, window, cx| {
 7094        e.set_clip_at_line_ends(true, cx);
 7095        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 7096        e.set_clip_at_line_ends(false, cx);
 7097    });
 7098    cx.assert_editor_state("«abcˇ»");
 7099}
 7100
 7101#[gpui::test]
 7102async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 7103    init_test(cx, |_| {});
 7104
 7105    let mut cx = EditorTestContext::new(cx).await;
 7106
 7107    let large_body_1 = "\nd".repeat(200);
 7108    let large_body_2 = "\ne".repeat(200);
 7109
 7110    cx.set_state(&format!(
 7111        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 7112    ));
 7113    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 7114        let scroll_position = editor.scroll_position(cx);
 7115        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 7116        scroll_position
 7117    });
 7118
 7119    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7120        .unwrap();
 7121    cx.assert_editor_state(&format!(
 7122        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 7123    ));
 7124    let scroll_position_after_selection =
 7125        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 7126    assert_eq!(
 7127        initial_scroll_position, scroll_position_after_selection,
 7128        "Scroll position should not change after selecting all matches"
 7129    );
 7130}
 7131
 7132#[gpui::test]
 7133async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 7134    init_test(cx, |_| {});
 7135
 7136    let mut cx = EditorLspTestContext::new_rust(
 7137        lsp::ServerCapabilities {
 7138            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 7139            ..Default::default()
 7140        },
 7141        cx,
 7142    )
 7143    .await;
 7144
 7145    cx.set_state(indoc! {"
 7146        line 1
 7147        line 2
 7148        linˇe 3
 7149        line 4
 7150        line 5
 7151    "});
 7152
 7153    // Make an edit
 7154    cx.update_editor(|editor, window, cx| {
 7155        editor.handle_input("X", window, cx);
 7156    });
 7157
 7158    // Move cursor to a different position
 7159    cx.update_editor(|editor, window, cx| {
 7160        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7161            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 7162        });
 7163    });
 7164
 7165    cx.assert_editor_state(indoc! {"
 7166        line 1
 7167        line 2
 7168        linXe 3
 7169        line 4
 7170        liˇne 5
 7171    "});
 7172
 7173    cx.lsp
 7174        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 7175            Ok(Some(vec![lsp::TextEdit::new(
 7176                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 7177                "PREFIX ".to_string(),
 7178            )]))
 7179        });
 7180
 7181    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 7182        .unwrap()
 7183        .await
 7184        .unwrap();
 7185
 7186    cx.assert_editor_state(indoc! {"
 7187        PREFIX line 1
 7188        line 2
 7189        linXe 3
 7190        line 4
 7191        liˇne 5
 7192    "});
 7193
 7194    // Undo formatting
 7195    cx.update_editor(|editor, window, cx| {
 7196        editor.undo(&Default::default(), window, cx);
 7197    });
 7198
 7199    // Verify cursor moved back to position after edit
 7200    cx.assert_editor_state(indoc! {"
 7201        line 1
 7202        line 2
 7203        linXˇe 3
 7204        line 4
 7205        line 5
 7206    "});
 7207}
 7208
 7209#[gpui::test]
 7210async fn test_undo_inline_completion_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 7211    init_test(cx, |_| {});
 7212
 7213    let mut cx = EditorTestContext::new(cx).await;
 7214
 7215    let provider = cx.new(|_| FakeInlineCompletionProvider::default());
 7216    cx.update_editor(|editor, window, cx| {
 7217        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 7218    });
 7219
 7220    cx.set_state(indoc! {"
 7221        line 1
 7222        line 2
 7223        linˇe 3
 7224        line 4
 7225        line 5
 7226        line 6
 7227        line 7
 7228        line 8
 7229        line 9
 7230        line 10
 7231    "});
 7232
 7233    let snapshot = cx.buffer_snapshot();
 7234    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 7235
 7236    cx.update(|_, cx| {
 7237        provider.update(cx, |provider, _| {
 7238            provider.set_inline_completion(Some(inline_completion::InlineCompletion {
 7239                id: None,
 7240                edits: vec![(edit_position..edit_position, "X".into())],
 7241                edit_preview: None,
 7242            }))
 7243        })
 7244    });
 7245
 7246    cx.update_editor(|editor, window, cx| editor.update_visible_inline_completion(window, cx));
 7247    cx.update_editor(|editor, window, cx| {
 7248        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 7249    });
 7250
 7251    cx.assert_editor_state(indoc! {"
 7252        line 1
 7253        line 2
 7254        lineXˇ 3
 7255        line 4
 7256        line 5
 7257        line 6
 7258        line 7
 7259        line 8
 7260        line 9
 7261        line 10
 7262    "});
 7263
 7264    cx.update_editor(|editor, window, cx| {
 7265        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7266            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 7267        });
 7268    });
 7269
 7270    cx.assert_editor_state(indoc! {"
 7271        line 1
 7272        line 2
 7273        lineX 3
 7274        line 4
 7275        line 5
 7276        line 6
 7277        line 7
 7278        line 8
 7279        line 9
 7280        liˇne 10
 7281    "});
 7282
 7283    cx.update_editor(|editor, window, cx| {
 7284        editor.undo(&Default::default(), window, cx);
 7285    });
 7286
 7287    cx.assert_editor_state(indoc! {"
 7288        line 1
 7289        line 2
 7290        lineˇ 3
 7291        line 4
 7292        line 5
 7293        line 6
 7294        line 7
 7295        line 8
 7296        line 9
 7297        line 10
 7298    "});
 7299}
 7300
 7301#[gpui::test]
 7302async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 7303    init_test(cx, |_| {});
 7304
 7305    let mut cx = EditorTestContext::new(cx).await;
 7306    cx.set_state(
 7307        r#"let foo = 2;
 7308lˇet foo = 2;
 7309let fooˇ = 2;
 7310let foo = 2;
 7311let foo = ˇ2;"#,
 7312    );
 7313
 7314    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7315        .unwrap();
 7316    cx.assert_editor_state(
 7317        r#"let foo = 2;
 7318«letˇ» foo = 2;
 7319let «fooˇ» = 2;
 7320let foo = 2;
 7321let foo = «2ˇ»;"#,
 7322    );
 7323
 7324    // noop for multiple selections with different contents
 7325    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7326        .unwrap();
 7327    cx.assert_editor_state(
 7328        r#"let foo = 2;
 7329«letˇ» foo = 2;
 7330let «fooˇ» = 2;
 7331let foo = 2;
 7332let foo = «2ˇ»;"#,
 7333    );
 7334
 7335    // Test last selection direction should be preserved
 7336    cx.set_state(
 7337        r#"let foo = 2;
 7338let foo = 2;
 7339let «fooˇ» = 2;
 7340let «ˇfoo» = 2;
 7341let foo = 2;"#,
 7342    );
 7343
 7344    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7345        .unwrap();
 7346    cx.assert_editor_state(
 7347        r#"let foo = 2;
 7348let foo = 2;
 7349let «fooˇ» = 2;
 7350let «ˇfoo» = 2;
 7351let «ˇfoo» = 2;"#,
 7352    );
 7353}
 7354
 7355#[gpui::test]
 7356async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 7357    init_test(cx, |_| {});
 7358
 7359    let mut cx =
 7360        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 7361
 7362    cx.assert_editor_state(indoc! {"
 7363        ˇbbb
 7364        ccc
 7365
 7366        bbb
 7367        ccc
 7368        "});
 7369    cx.dispatch_action(SelectPrevious::default());
 7370    cx.assert_editor_state(indoc! {"
 7371                «bbbˇ»
 7372                ccc
 7373
 7374                bbb
 7375                ccc
 7376                "});
 7377    cx.dispatch_action(SelectPrevious::default());
 7378    cx.assert_editor_state(indoc! {"
 7379                «bbbˇ»
 7380                ccc
 7381
 7382                «bbbˇ»
 7383                ccc
 7384                "});
 7385}
 7386
 7387#[gpui::test]
 7388async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 7389    init_test(cx, |_| {});
 7390
 7391    let mut cx = EditorTestContext::new(cx).await;
 7392    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7393
 7394    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7395        .unwrap();
 7396    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7397
 7398    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7399        .unwrap();
 7400    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 7401
 7402    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7403    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7404
 7405    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7406    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 7407
 7408    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7409        .unwrap();
 7410    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 7411
 7412    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7413        .unwrap();
 7414    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7415}
 7416
 7417#[gpui::test]
 7418async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 7419    init_test(cx, |_| {});
 7420
 7421    let mut cx = EditorTestContext::new(cx).await;
 7422    cx.set_state("");
 7423
 7424    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7425        .unwrap();
 7426    cx.assert_editor_state("«aˇ»");
 7427    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7428        .unwrap();
 7429    cx.assert_editor_state("«aˇ»");
 7430}
 7431
 7432#[gpui::test]
 7433async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 7434    init_test(cx, |_| {});
 7435
 7436    let mut cx = EditorTestContext::new(cx).await;
 7437    cx.set_state(
 7438        r#"let foo = 2;
 7439lˇet foo = 2;
 7440let fooˇ = 2;
 7441let foo = 2;
 7442let foo = ˇ2;"#,
 7443    );
 7444
 7445    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7446        .unwrap();
 7447    cx.assert_editor_state(
 7448        r#"let foo = 2;
 7449«letˇ» foo = 2;
 7450let «fooˇ» = 2;
 7451let foo = 2;
 7452let foo = «2ˇ»;"#,
 7453    );
 7454
 7455    // noop for multiple selections with different contents
 7456    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7457        .unwrap();
 7458    cx.assert_editor_state(
 7459        r#"let foo = 2;
 7460«letˇ» foo = 2;
 7461let «fooˇ» = 2;
 7462let foo = 2;
 7463let foo = «2ˇ»;"#,
 7464    );
 7465}
 7466
 7467#[gpui::test]
 7468async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 7469    init_test(cx, |_| {});
 7470
 7471    let mut cx = EditorTestContext::new(cx).await;
 7472    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 7473
 7474    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7475        .unwrap();
 7476    // selection direction is preserved
 7477    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 7478
 7479    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7480        .unwrap();
 7481    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7482
 7483    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7484    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 7485
 7486    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7487    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7488
 7489    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7490        .unwrap();
 7491    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 7492
 7493    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7494        .unwrap();
 7495    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 7496}
 7497
 7498#[gpui::test]
 7499async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 7500    init_test(cx, |_| {});
 7501
 7502    let language = Arc::new(Language::new(
 7503        LanguageConfig::default(),
 7504        Some(tree_sitter_rust::LANGUAGE.into()),
 7505    ));
 7506
 7507    let text = r#"
 7508        use mod1::mod2::{mod3, mod4};
 7509
 7510        fn fn_1(param1: bool, param2: &str) {
 7511            let var1 = "text";
 7512        }
 7513    "#
 7514    .unindent();
 7515
 7516    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7517    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7518    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7519
 7520    editor
 7521        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7522        .await;
 7523
 7524    editor.update_in(cx, |editor, window, cx| {
 7525        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7526            s.select_display_ranges([
 7527                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 7528                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 7529                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 7530            ]);
 7531        });
 7532        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7533    });
 7534    editor.update(cx, |editor, cx| {
 7535        assert_text_with_selections(
 7536            editor,
 7537            indoc! {r#"
 7538                use mod1::mod2::{mod3, «mod4ˇ»};
 7539
 7540                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7541                    let var1 = "«ˇtext»";
 7542                }
 7543            "#},
 7544            cx,
 7545        );
 7546    });
 7547
 7548    editor.update_in(cx, |editor, window, cx| {
 7549        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7550    });
 7551    editor.update(cx, |editor, cx| {
 7552        assert_text_with_selections(
 7553            editor,
 7554            indoc! {r#"
 7555                use mod1::mod2::«{mod3, mod4}ˇ»;
 7556
 7557                «ˇfn fn_1(param1: bool, param2: &str) {
 7558                    let var1 = "text";
 7559 7560            "#},
 7561            cx,
 7562        );
 7563    });
 7564
 7565    editor.update_in(cx, |editor, window, cx| {
 7566        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7567    });
 7568    assert_eq!(
 7569        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7570        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7571    );
 7572
 7573    // Trying to expand the selected syntax node one more time has no effect.
 7574    editor.update_in(cx, |editor, window, cx| {
 7575        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7576    });
 7577    assert_eq!(
 7578        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7579        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7580    );
 7581
 7582    editor.update_in(cx, |editor, window, cx| {
 7583        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7584    });
 7585    editor.update(cx, |editor, cx| {
 7586        assert_text_with_selections(
 7587            editor,
 7588            indoc! {r#"
 7589                use mod1::mod2::«{mod3, mod4}ˇ»;
 7590
 7591                «ˇfn fn_1(param1: bool, param2: &str) {
 7592                    let var1 = "text";
 7593 7594            "#},
 7595            cx,
 7596        );
 7597    });
 7598
 7599    editor.update_in(cx, |editor, window, cx| {
 7600        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7601    });
 7602    editor.update(cx, |editor, cx| {
 7603        assert_text_with_selections(
 7604            editor,
 7605            indoc! {r#"
 7606                use mod1::mod2::{mod3, «mod4ˇ»};
 7607
 7608                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7609                    let var1 = "«ˇtext»";
 7610                }
 7611            "#},
 7612            cx,
 7613        );
 7614    });
 7615
 7616    editor.update_in(cx, |editor, window, cx| {
 7617        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7618    });
 7619    editor.update(cx, |editor, cx| {
 7620        assert_text_with_selections(
 7621            editor,
 7622            indoc! {r#"
 7623                use mod1::mod2::{mod3, mo«ˇ»d4};
 7624
 7625                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7626                    let var1 = "te«ˇ»xt";
 7627                }
 7628            "#},
 7629            cx,
 7630        );
 7631    });
 7632
 7633    // Trying to shrink the selected syntax node one more time has no effect.
 7634    editor.update_in(cx, |editor, window, cx| {
 7635        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7636    });
 7637    editor.update_in(cx, |editor, _, cx| {
 7638        assert_text_with_selections(
 7639            editor,
 7640            indoc! {r#"
 7641                use mod1::mod2::{mod3, mo«ˇ»d4};
 7642
 7643                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7644                    let var1 = "te«ˇ»xt";
 7645                }
 7646            "#},
 7647            cx,
 7648        );
 7649    });
 7650
 7651    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 7652    // a fold.
 7653    editor.update_in(cx, |editor, window, cx| {
 7654        editor.fold_creases(
 7655            vec![
 7656                Crease::simple(
 7657                    Point::new(0, 21)..Point::new(0, 24),
 7658                    FoldPlaceholder::test(),
 7659                ),
 7660                Crease::simple(
 7661                    Point::new(3, 20)..Point::new(3, 22),
 7662                    FoldPlaceholder::test(),
 7663                ),
 7664            ],
 7665            true,
 7666            window,
 7667            cx,
 7668        );
 7669        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7670    });
 7671    editor.update(cx, |editor, cx| {
 7672        assert_text_with_selections(
 7673            editor,
 7674            indoc! {r#"
 7675                use mod1::mod2::«{mod3, mod4}ˇ»;
 7676
 7677                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7678                    let var1 = "«ˇtext»";
 7679                }
 7680            "#},
 7681            cx,
 7682        );
 7683    });
 7684}
 7685
 7686#[gpui::test]
 7687async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 7688    init_test(cx, |_| {});
 7689
 7690    let language = Arc::new(Language::new(
 7691        LanguageConfig::default(),
 7692        Some(tree_sitter_rust::LANGUAGE.into()),
 7693    ));
 7694
 7695    let text = "let a = 2;";
 7696
 7697    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7698    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7699    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7700
 7701    editor
 7702        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7703        .await;
 7704
 7705    // Test case 1: Cursor at end of word
 7706    editor.update_in(cx, |editor, window, cx| {
 7707        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7708            s.select_display_ranges([
 7709                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 7710            ]);
 7711        });
 7712    });
 7713    editor.update(cx, |editor, cx| {
 7714        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 7715    });
 7716    editor.update_in(cx, |editor, window, cx| {
 7717        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7718    });
 7719    editor.update(cx, |editor, cx| {
 7720        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 7721    });
 7722    editor.update_in(cx, |editor, window, cx| {
 7723        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7724    });
 7725    editor.update(cx, |editor, cx| {
 7726        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7727    });
 7728
 7729    // Test case 2: Cursor at end of statement
 7730    editor.update_in(cx, |editor, window, cx| {
 7731        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7732            s.select_display_ranges([
 7733                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 7734            ]);
 7735        });
 7736    });
 7737    editor.update(cx, |editor, cx| {
 7738        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 7739    });
 7740    editor.update_in(cx, |editor, window, cx| {
 7741        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7742    });
 7743    editor.update(cx, |editor, cx| {
 7744        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7745    });
 7746}
 7747
 7748#[gpui::test]
 7749async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 7750    init_test(cx, |_| {});
 7751
 7752    let language = Arc::new(Language::new(
 7753        LanguageConfig::default(),
 7754        Some(tree_sitter_rust::LANGUAGE.into()),
 7755    ));
 7756
 7757    let text = r#"
 7758        use mod1::mod2::{mod3, mod4};
 7759
 7760        fn fn_1(param1: bool, param2: &str) {
 7761            let var1 = "hello world";
 7762        }
 7763    "#
 7764    .unindent();
 7765
 7766    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7767    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7768    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7769
 7770    editor
 7771        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7772        .await;
 7773
 7774    // Test 1: Cursor on a letter of a string word
 7775    editor.update_in(cx, |editor, window, cx| {
 7776        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7777            s.select_display_ranges([
 7778                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 7779            ]);
 7780        });
 7781    });
 7782    editor.update_in(cx, |editor, window, cx| {
 7783        assert_text_with_selections(
 7784            editor,
 7785            indoc! {r#"
 7786                use mod1::mod2::{mod3, mod4};
 7787
 7788                fn fn_1(param1: bool, param2: &str) {
 7789                    let var1 = "hˇello world";
 7790                }
 7791            "#},
 7792            cx,
 7793        );
 7794        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7795        assert_text_with_selections(
 7796            editor,
 7797            indoc! {r#"
 7798                use mod1::mod2::{mod3, mod4};
 7799
 7800                fn fn_1(param1: bool, param2: &str) {
 7801                    let var1 = "«ˇhello» world";
 7802                }
 7803            "#},
 7804            cx,
 7805        );
 7806    });
 7807
 7808    // Test 2: Partial selection within a word
 7809    editor.update_in(cx, |editor, window, cx| {
 7810        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7811            s.select_display_ranges([
 7812                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 7813            ]);
 7814        });
 7815    });
 7816    editor.update_in(cx, |editor, window, cx| {
 7817        assert_text_with_selections(
 7818            editor,
 7819            indoc! {r#"
 7820                use mod1::mod2::{mod3, mod4};
 7821
 7822                fn fn_1(param1: bool, param2: &str) {
 7823                    let var1 = "h«elˇ»lo world";
 7824                }
 7825            "#},
 7826            cx,
 7827        );
 7828        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7829        assert_text_with_selections(
 7830            editor,
 7831            indoc! {r#"
 7832                use mod1::mod2::{mod3, mod4};
 7833
 7834                fn fn_1(param1: bool, param2: &str) {
 7835                    let var1 = "«ˇhello» world";
 7836                }
 7837            "#},
 7838            cx,
 7839        );
 7840    });
 7841
 7842    // Test 3: Complete word already selected
 7843    editor.update_in(cx, |editor, window, cx| {
 7844        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7845            s.select_display_ranges([
 7846                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 7847            ]);
 7848        });
 7849    });
 7850    editor.update_in(cx, |editor, window, cx| {
 7851        assert_text_with_selections(
 7852            editor,
 7853            indoc! {r#"
 7854                use mod1::mod2::{mod3, mod4};
 7855
 7856                fn fn_1(param1: bool, param2: &str) {
 7857                    let var1 = "«helloˇ» world";
 7858                }
 7859            "#},
 7860            cx,
 7861        );
 7862        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7863        assert_text_with_selections(
 7864            editor,
 7865            indoc! {r#"
 7866                use mod1::mod2::{mod3, mod4};
 7867
 7868                fn fn_1(param1: bool, param2: &str) {
 7869                    let var1 = "«hello worldˇ»";
 7870                }
 7871            "#},
 7872            cx,
 7873        );
 7874    });
 7875
 7876    // Test 4: Selection spanning across words
 7877    editor.update_in(cx, |editor, window, cx| {
 7878        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7879            s.select_display_ranges([
 7880                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 7881            ]);
 7882        });
 7883    });
 7884    editor.update_in(cx, |editor, window, cx| {
 7885        assert_text_with_selections(
 7886            editor,
 7887            indoc! {r#"
 7888                use mod1::mod2::{mod3, mod4};
 7889
 7890                fn fn_1(param1: bool, param2: &str) {
 7891                    let var1 = "hel«lo woˇ»rld";
 7892                }
 7893            "#},
 7894            cx,
 7895        );
 7896        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7897        assert_text_with_selections(
 7898            editor,
 7899            indoc! {r#"
 7900                use mod1::mod2::{mod3, mod4};
 7901
 7902                fn fn_1(param1: bool, param2: &str) {
 7903                    let var1 = "«ˇhello world»";
 7904                }
 7905            "#},
 7906            cx,
 7907        );
 7908    });
 7909
 7910    // Test 5: Expansion beyond string
 7911    editor.update_in(cx, |editor, window, cx| {
 7912        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7913        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7914        assert_text_with_selections(
 7915            editor,
 7916            indoc! {r#"
 7917                use mod1::mod2::{mod3, mod4};
 7918
 7919                fn fn_1(param1: bool, param2: &str) {
 7920                    «ˇlet var1 = "hello world";»
 7921                }
 7922            "#},
 7923            cx,
 7924        );
 7925    });
 7926}
 7927
 7928#[gpui::test]
 7929async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 7930    init_test(cx, |_| {});
 7931
 7932    let base_text = r#"
 7933        impl A {
 7934            // this is an uncommitted comment
 7935
 7936            fn b() {
 7937                c();
 7938            }
 7939
 7940            // this is another uncommitted comment
 7941
 7942            fn d() {
 7943                // e
 7944                // f
 7945            }
 7946        }
 7947
 7948        fn g() {
 7949            // h
 7950        }
 7951    "#
 7952    .unindent();
 7953
 7954    let text = r#"
 7955        ˇimpl A {
 7956
 7957            fn b() {
 7958                c();
 7959            }
 7960
 7961            fn d() {
 7962                // e
 7963                // f
 7964            }
 7965        }
 7966
 7967        fn g() {
 7968            // h
 7969        }
 7970    "#
 7971    .unindent();
 7972
 7973    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 7974    cx.set_state(&text);
 7975    cx.set_head_text(&base_text);
 7976    cx.update_editor(|editor, window, cx| {
 7977        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 7978    });
 7979
 7980    cx.assert_state_with_diff(
 7981        "
 7982        ˇimpl A {
 7983      -     // this is an uncommitted comment
 7984
 7985            fn b() {
 7986                c();
 7987            }
 7988
 7989      -     // this is another uncommitted comment
 7990      -
 7991            fn d() {
 7992                // e
 7993                // f
 7994            }
 7995        }
 7996
 7997        fn g() {
 7998            // h
 7999        }
 8000    "
 8001        .unindent(),
 8002    );
 8003
 8004    let expected_display_text = "
 8005        impl A {
 8006            // this is an uncommitted comment
 8007
 8008            fn b() {
 8009 8010            }
 8011
 8012            // this is another uncommitted comment
 8013
 8014            fn d() {
 8015 8016            }
 8017        }
 8018
 8019        fn g() {
 8020 8021        }
 8022        "
 8023    .unindent();
 8024
 8025    cx.update_editor(|editor, window, cx| {
 8026        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 8027        assert_eq!(editor.display_text(cx), expected_display_text);
 8028    });
 8029}
 8030
 8031#[gpui::test]
 8032async fn test_autoindent(cx: &mut TestAppContext) {
 8033    init_test(cx, |_| {});
 8034
 8035    let language = Arc::new(
 8036        Language::new(
 8037            LanguageConfig {
 8038                brackets: BracketPairConfig {
 8039                    pairs: vec![
 8040                        BracketPair {
 8041                            start: "{".to_string(),
 8042                            end: "}".to_string(),
 8043                            close: false,
 8044                            surround: false,
 8045                            newline: true,
 8046                        },
 8047                        BracketPair {
 8048                            start: "(".to_string(),
 8049                            end: ")".to_string(),
 8050                            close: false,
 8051                            surround: false,
 8052                            newline: true,
 8053                        },
 8054                    ],
 8055                    ..Default::default()
 8056                },
 8057                ..Default::default()
 8058            },
 8059            Some(tree_sitter_rust::LANGUAGE.into()),
 8060        )
 8061        .with_indents_query(
 8062            r#"
 8063                (_ "(" ")" @end) @indent
 8064                (_ "{" "}" @end) @indent
 8065            "#,
 8066        )
 8067        .unwrap(),
 8068    );
 8069
 8070    let text = "fn a() {}";
 8071
 8072    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8073    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8074    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8075    editor
 8076        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8077        .await;
 8078
 8079    editor.update_in(cx, |editor, window, cx| {
 8080        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8081            s.select_ranges([5..5, 8..8, 9..9])
 8082        });
 8083        editor.newline(&Newline, window, cx);
 8084        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 8085        assert_eq!(
 8086            editor.selections.ranges(cx),
 8087            &[
 8088                Point::new(1, 4)..Point::new(1, 4),
 8089                Point::new(3, 4)..Point::new(3, 4),
 8090                Point::new(5, 0)..Point::new(5, 0)
 8091            ]
 8092        );
 8093    });
 8094}
 8095
 8096#[gpui::test]
 8097async fn test_autoindent_selections(cx: &mut TestAppContext) {
 8098    init_test(cx, |_| {});
 8099
 8100    {
 8101        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 8102        cx.set_state(indoc! {"
 8103            impl A {
 8104
 8105                fn b() {}
 8106
 8107            «fn c() {
 8108
 8109            }ˇ»
 8110            }
 8111        "});
 8112
 8113        cx.update_editor(|editor, window, cx| {
 8114            editor.autoindent(&Default::default(), window, cx);
 8115        });
 8116
 8117        cx.assert_editor_state(indoc! {"
 8118            impl A {
 8119
 8120                fn b() {}
 8121
 8122                «fn c() {
 8123
 8124                }ˇ»
 8125            }
 8126        "});
 8127    }
 8128
 8129    {
 8130        let mut cx = EditorTestContext::new_multibuffer(
 8131            cx,
 8132            [indoc! { "
 8133                impl A {
 8134                «
 8135                // a
 8136                fn b(){}
 8137                »
 8138                «
 8139                    }
 8140                    fn c(){}
 8141                »
 8142            "}],
 8143        );
 8144
 8145        let buffer = cx.update_editor(|editor, _, cx| {
 8146            let buffer = editor.buffer().update(cx, |buffer, _| {
 8147                buffer.all_buffers().iter().next().unwrap().clone()
 8148            });
 8149            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 8150            buffer
 8151        });
 8152
 8153        cx.run_until_parked();
 8154        cx.update_editor(|editor, window, cx| {
 8155            editor.select_all(&Default::default(), window, cx);
 8156            editor.autoindent(&Default::default(), window, cx)
 8157        });
 8158        cx.run_until_parked();
 8159
 8160        cx.update(|_, cx| {
 8161            assert_eq!(
 8162                buffer.read(cx).text(),
 8163                indoc! { "
 8164                    impl A {
 8165
 8166                        // a
 8167                        fn b(){}
 8168
 8169
 8170                    }
 8171                    fn c(){}
 8172
 8173                " }
 8174            )
 8175        });
 8176    }
 8177}
 8178
 8179#[gpui::test]
 8180async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 8181    init_test(cx, |_| {});
 8182
 8183    let mut cx = EditorTestContext::new(cx).await;
 8184
 8185    let language = Arc::new(Language::new(
 8186        LanguageConfig {
 8187            brackets: BracketPairConfig {
 8188                pairs: vec![
 8189                    BracketPair {
 8190                        start: "{".to_string(),
 8191                        end: "}".to_string(),
 8192                        close: true,
 8193                        surround: true,
 8194                        newline: true,
 8195                    },
 8196                    BracketPair {
 8197                        start: "(".to_string(),
 8198                        end: ")".to_string(),
 8199                        close: true,
 8200                        surround: true,
 8201                        newline: true,
 8202                    },
 8203                    BracketPair {
 8204                        start: "/*".to_string(),
 8205                        end: " */".to_string(),
 8206                        close: true,
 8207                        surround: true,
 8208                        newline: true,
 8209                    },
 8210                    BracketPair {
 8211                        start: "[".to_string(),
 8212                        end: "]".to_string(),
 8213                        close: false,
 8214                        surround: false,
 8215                        newline: true,
 8216                    },
 8217                    BracketPair {
 8218                        start: "\"".to_string(),
 8219                        end: "\"".to_string(),
 8220                        close: true,
 8221                        surround: true,
 8222                        newline: false,
 8223                    },
 8224                    BracketPair {
 8225                        start: "<".to_string(),
 8226                        end: ">".to_string(),
 8227                        close: false,
 8228                        surround: true,
 8229                        newline: true,
 8230                    },
 8231                ],
 8232                ..Default::default()
 8233            },
 8234            autoclose_before: "})]".to_string(),
 8235            ..Default::default()
 8236        },
 8237        Some(tree_sitter_rust::LANGUAGE.into()),
 8238    ));
 8239
 8240    cx.language_registry().add(language.clone());
 8241    cx.update_buffer(|buffer, cx| {
 8242        buffer.set_language(Some(language), cx);
 8243    });
 8244
 8245    cx.set_state(
 8246        &r#"
 8247            🏀ˇ
 8248            εˇ
 8249            ❤️ˇ
 8250        "#
 8251        .unindent(),
 8252    );
 8253
 8254    // autoclose multiple nested brackets at multiple cursors
 8255    cx.update_editor(|editor, window, cx| {
 8256        editor.handle_input("{", window, cx);
 8257        editor.handle_input("{", window, cx);
 8258        editor.handle_input("{", window, cx);
 8259    });
 8260    cx.assert_editor_state(
 8261        &"
 8262            🏀{{{ˇ}}}
 8263            ε{{{ˇ}}}
 8264            ❤️{{{ˇ}}}
 8265        "
 8266        .unindent(),
 8267    );
 8268
 8269    // insert a different closing bracket
 8270    cx.update_editor(|editor, window, cx| {
 8271        editor.handle_input(")", window, cx);
 8272    });
 8273    cx.assert_editor_state(
 8274        &"
 8275            🏀{{{)ˇ}}}
 8276            ε{{{)ˇ}}}
 8277            ❤️{{{)ˇ}}}
 8278        "
 8279        .unindent(),
 8280    );
 8281
 8282    // skip over the auto-closed brackets when typing a closing bracket
 8283    cx.update_editor(|editor, window, cx| {
 8284        editor.move_right(&MoveRight, window, cx);
 8285        editor.handle_input("}", window, cx);
 8286        editor.handle_input("}", window, cx);
 8287        editor.handle_input("}", window, cx);
 8288    });
 8289    cx.assert_editor_state(
 8290        &"
 8291            🏀{{{)}}}}ˇ
 8292            ε{{{)}}}}ˇ
 8293            ❤️{{{)}}}}ˇ
 8294        "
 8295        .unindent(),
 8296    );
 8297
 8298    // autoclose multi-character pairs
 8299    cx.set_state(
 8300        &"
 8301            ˇ
 8302            ˇ
 8303        "
 8304        .unindent(),
 8305    );
 8306    cx.update_editor(|editor, window, cx| {
 8307        editor.handle_input("/", window, cx);
 8308        editor.handle_input("*", window, cx);
 8309    });
 8310    cx.assert_editor_state(
 8311        &"
 8312            /*ˇ */
 8313            /*ˇ */
 8314        "
 8315        .unindent(),
 8316    );
 8317
 8318    // one cursor autocloses a multi-character pair, one cursor
 8319    // does not autoclose.
 8320    cx.set_state(
 8321        &"
 8322 8323            ˇ
 8324        "
 8325        .unindent(),
 8326    );
 8327    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 8328    cx.assert_editor_state(
 8329        &"
 8330            /*ˇ */
 8331 8332        "
 8333        .unindent(),
 8334    );
 8335
 8336    // Don't autoclose if the next character isn't whitespace and isn't
 8337    // listed in the language's "autoclose_before" section.
 8338    cx.set_state("ˇa b");
 8339    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8340    cx.assert_editor_state("{ˇa b");
 8341
 8342    // Don't autoclose if `close` is false for the bracket pair
 8343    cx.set_state("ˇ");
 8344    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 8345    cx.assert_editor_state("");
 8346
 8347    // Surround with brackets if text is selected
 8348    cx.set_state("«aˇ» b");
 8349    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8350    cx.assert_editor_state("{«aˇ»} b");
 8351
 8352    // Autoclose when not immediately after a word character
 8353    cx.set_state("a ˇ");
 8354    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8355    cx.assert_editor_state("a \"ˇ\"");
 8356
 8357    // Autoclose pair where the start and end characters are the same
 8358    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8359    cx.assert_editor_state("a \"\"ˇ");
 8360
 8361    // Don't autoclose when immediately after a word character
 8362    cx.set_state("");
 8363    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8364    cx.assert_editor_state("a\"ˇ");
 8365
 8366    // Do autoclose when after a non-word character
 8367    cx.set_state("");
 8368    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8369    cx.assert_editor_state("{\"ˇ\"");
 8370
 8371    // Non identical pairs autoclose regardless of preceding character
 8372    cx.set_state("");
 8373    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8374    cx.assert_editor_state("a{ˇ}");
 8375
 8376    // Don't autoclose pair if autoclose is disabled
 8377    cx.set_state("ˇ");
 8378    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 8379    cx.assert_editor_state("");
 8380
 8381    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 8382    cx.set_state("«aˇ» b");
 8383    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 8384    cx.assert_editor_state("<«aˇ»> b");
 8385}
 8386
 8387#[gpui::test]
 8388async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 8389    init_test(cx, |settings| {
 8390        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 8391    });
 8392
 8393    let mut cx = EditorTestContext::new(cx).await;
 8394
 8395    let language = Arc::new(Language::new(
 8396        LanguageConfig {
 8397            brackets: BracketPairConfig {
 8398                pairs: vec![
 8399                    BracketPair {
 8400                        start: "{".to_string(),
 8401                        end: "}".to_string(),
 8402                        close: true,
 8403                        surround: true,
 8404                        newline: true,
 8405                    },
 8406                    BracketPair {
 8407                        start: "(".to_string(),
 8408                        end: ")".to_string(),
 8409                        close: true,
 8410                        surround: true,
 8411                        newline: true,
 8412                    },
 8413                    BracketPair {
 8414                        start: "[".to_string(),
 8415                        end: "]".to_string(),
 8416                        close: false,
 8417                        surround: false,
 8418                        newline: true,
 8419                    },
 8420                ],
 8421                ..Default::default()
 8422            },
 8423            autoclose_before: "})]".to_string(),
 8424            ..Default::default()
 8425        },
 8426        Some(tree_sitter_rust::LANGUAGE.into()),
 8427    ));
 8428
 8429    cx.language_registry().add(language.clone());
 8430    cx.update_buffer(|buffer, cx| {
 8431        buffer.set_language(Some(language), cx);
 8432    });
 8433
 8434    cx.set_state(
 8435        &"
 8436            ˇ
 8437            ˇ
 8438            ˇ
 8439        "
 8440        .unindent(),
 8441    );
 8442
 8443    // ensure only matching closing brackets are skipped over
 8444    cx.update_editor(|editor, window, cx| {
 8445        editor.handle_input("}", window, cx);
 8446        editor.move_left(&MoveLeft, window, cx);
 8447        editor.handle_input(")", window, cx);
 8448        editor.move_left(&MoveLeft, window, cx);
 8449    });
 8450    cx.assert_editor_state(
 8451        &"
 8452            ˇ)}
 8453            ˇ)}
 8454            ˇ)}
 8455        "
 8456        .unindent(),
 8457    );
 8458
 8459    // skip-over closing brackets at multiple cursors
 8460    cx.update_editor(|editor, window, cx| {
 8461        editor.handle_input(")", window, cx);
 8462        editor.handle_input("}", window, cx);
 8463    });
 8464    cx.assert_editor_state(
 8465        &"
 8466            )}ˇ
 8467            )}ˇ
 8468            )}ˇ
 8469        "
 8470        .unindent(),
 8471    );
 8472
 8473    // ignore non-close brackets
 8474    cx.update_editor(|editor, window, cx| {
 8475        editor.handle_input("]", window, cx);
 8476        editor.move_left(&MoveLeft, window, cx);
 8477        editor.handle_input("]", window, cx);
 8478    });
 8479    cx.assert_editor_state(
 8480        &"
 8481            )}]ˇ]
 8482            )}]ˇ]
 8483            )}]ˇ]
 8484        "
 8485        .unindent(),
 8486    );
 8487}
 8488
 8489#[gpui::test]
 8490async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
 8491    init_test(cx, |_| {});
 8492
 8493    let mut cx = EditorTestContext::new(cx).await;
 8494
 8495    let html_language = Arc::new(
 8496        Language::new(
 8497            LanguageConfig {
 8498                name: "HTML".into(),
 8499                brackets: BracketPairConfig {
 8500                    pairs: vec![
 8501                        BracketPair {
 8502                            start: "<".into(),
 8503                            end: ">".into(),
 8504                            close: true,
 8505                            ..Default::default()
 8506                        },
 8507                        BracketPair {
 8508                            start: "{".into(),
 8509                            end: "}".into(),
 8510                            close: true,
 8511                            ..Default::default()
 8512                        },
 8513                        BracketPair {
 8514                            start: "(".into(),
 8515                            end: ")".into(),
 8516                            close: true,
 8517                            ..Default::default()
 8518                        },
 8519                    ],
 8520                    ..Default::default()
 8521                },
 8522                autoclose_before: "})]>".into(),
 8523                ..Default::default()
 8524            },
 8525            Some(tree_sitter_html::LANGUAGE.into()),
 8526        )
 8527        .with_injection_query(
 8528            r#"
 8529            (script_element
 8530                (raw_text) @injection.content
 8531                (#set! injection.language "javascript"))
 8532            "#,
 8533        )
 8534        .unwrap(),
 8535    );
 8536
 8537    let javascript_language = Arc::new(Language::new(
 8538        LanguageConfig {
 8539            name: "JavaScript".into(),
 8540            brackets: BracketPairConfig {
 8541                pairs: vec![
 8542                    BracketPair {
 8543                        start: "/*".into(),
 8544                        end: " */".into(),
 8545                        close: true,
 8546                        ..Default::default()
 8547                    },
 8548                    BracketPair {
 8549                        start: "{".into(),
 8550                        end: "}".into(),
 8551                        close: true,
 8552                        ..Default::default()
 8553                    },
 8554                    BracketPair {
 8555                        start: "(".into(),
 8556                        end: ")".into(),
 8557                        close: true,
 8558                        ..Default::default()
 8559                    },
 8560                ],
 8561                ..Default::default()
 8562            },
 8563            autoclose_before: "})]>".into(),
 8564            ..Default::default()
 8565        },
 8566        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 8567    ));
 8568
 8569    cx.language_registry().add(html_language.clone());
 8570    cx.language_registry().add(javascript_language.clone());
 8571
 8572    cx.update_buffer(|buffer, cx| {
 8573        buffer.set_language(Some(html_language), cx);
 8574    });
 8575
 8576    cx.set_state(
 8577        &r#"
 8578            <body>ˇ
 8579                <script>
 8580                    var x = 1;ˇ
 8581                </script>
 8582            </body>ˇ
 8583        "#
 8584        .unindent(),
 8585    );
 8586
 8587    // Precondition: different languages are active at different locations.
 8588    cx.update_editor(|editor, window, cx| {
 8589        let snapshot = editor.snapshot(window, cx);
 8590        let cursors = editor.selections.ranges::<usize>(cx);
 8591        let languages = cursors
 8592            .iter()
 8593            .map(|c| snapshot.language_at(c.start).unwrap().name())
 8594            .collect::<Vec<_>>();
 8595        assert_eq!(
 8596            languages,
 8597            &["HTML".into(), "JavaScript".into(), "HTML".into()]
 8598        );
 8599    });
 8600
 8601    // Angle brackets autoclose in HTML, but not JavaScript.
 8602    cx.update_editor(|editor, window, cx| {
 8603        editor.handle_input("<", window, cx);
 8604        editor.handle_input("a", window, cx);
 8605    });
 8606    cx.assert_editor_state(
 8607        &r#"
 8608            <body><aˇ>
 8609                <script>
 8610                    var x = 1;<aˇ
 8611                </script>
 8612            </body><aˇ>
 8613        "#
 8614        .unindent(),
 8615    );
 8616
 8617    // Curly braces and parens autoclose in both HTML and JavaScript.
 8618    cx.update_editor(|editor, window, cx| {
 8619        editor.handle_input(" b=", window, cx);
 8620        editor.handle_input("{", window, cx);
 8621        editor.handle_input("c", window, cx);
 8622        editor.handle_input("(", window, cx);
 8623    });
 8624    cx.assert_editor_state(
 8625        &r#"
 8626            <body><a b={c(ˇ)}>
 8627                <script>
 8628                    var x = 1;<a b={c(ˇ)}
 8629                </script>
 8630            </body><a b={c(ˇ)}>
 8631        "#
 8632        .unindent(),
 8633    );
 8634
 8635    // Brackets that were already autoclosed are skipped.
 8636    cx.update_editor(|editor, window, cx| {
 8637        editor.handle_input(")", window, cx);
 8638        editor.handle_input("d", window, cx);
 8639        editor.handle_input("}", window, cx);
 8640    });
 8641    cx.assert_editor_state(
 8642        &r#"
 8643            <body><a b={c()d}ˇ>
 8644                <script>
 8645                    var x = 1;<a b={c()d}ˇ
 8646                </script>
 8647            </body><a b={c()d}ˇ>
 8648        "#
 8649        .unindent(),
 8650    );
 8651    cx.update_editor(|editor, window, cx| {
 8652        editor.handle_input(">", window, cx);
 8653    });
 8654    cx.assert_editor_state(
 8655        &r#"
 8656            <body><a b={c()d}>ˇ
 8657                <script>
 8658                    var x = 1;<a b={c()d}>ˇ
 8659                </script>
 8660            </body><a b={c()d}>ˇ
 8661        "#
 8662        .unindent(),
 8663    );
 8664
 8665    // Reset
 8666    cx.set_state(
 8667        &r#"
 8668            <body>ˇ
 8669                <script>
 8670                    var x = 1;ˇ
 8671                </script>
 8672            </body>ˇ
 8673        "#
 8674        .unindent(),
 8675    );
 8676
 8677    cx.update_editor(|editor, window, cx| {
 8678        editor.handle_input("<", window, cx);
 8679    });
 8680    cx.assert_editor_state(
 8681        &r#"
 8682            <body><ˇ>
 8683                <script>
 8684                    var x = 1;<ˇ
 8685                </script>
 8686            </body><ˇ>
 8687        "#
 8688        .unindent(),
 8689    );
 8690
 8691    // When backspacing, the closing angle brackets are removed.
 8692    cx.update_editor(|editor, window, cx| {
 8693        editor.backspace(&Backspace, window, cx);
 8694    });
 8695    cx.assert_editor_state(
 8696        &r#"
 8697            <body>ˇ
 8698                <script>
 8699                    var x = 1;ˇ
 8700                </script>
 8701            </body>ˇ
 8702        "#
 8703        .unindent(),
 8704    );
 8705
 8706    // Block comments autoclose in JavaScript, but not HTML.
 8707    cx.update_editor(|editor, window, cx| {
 8708        editor.handle_input("/", window, cx);
 8709        editor.handle_input("*", window, cx);
 8710    });
 8711    cx.assert_editor_state(
 8712        &r#"
 8713            <body>/*ˇ
 8714                <script>
 8715                    var x = 1;/*ˇ */
 8716                </script>
 8717            </body>/*ˇ
 8718        "#
 8719        .unindent(),
 8720    );
 8721}
 8722
 8723#[gpui::test]
 8724async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
 8725    init_test(cx, |_| {});
 8726
 8727    let mut cx = EditorTestContext::new(cx).await;
 8728
 8729    let rust_language = Arc::new(
 8730        Language::new(
 8731            LanguageConfig {
 8732                name: "Rust".into(),
 8733                brackets: serde_json::from_value(json!([
 8734                    { "start": "{", "end": "}", "close": true, "newline": true },
 8735                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
 8736                ]))
 8737                .unwrap(),
 8738                autoclose_before: "})]>".into(),
 8739                ..Default::default()
 8740            },
 8741            Some(tree_sitter_rust::LANGUAGE.into()),
 8742        )
 8743        .with_override_query("(string_literal) @string")
 8744        .unwrap(),
 8745    );
 8746
 8747    cx.language_registry().add(rust_language.clone());
 8748    cx.update_buffer(|buffer, cx| {
 8749        buffer.set_language(Some(rust_language), cx);
 8750    });
 8751
 8752    cx.set_state(
 8753        &r#"
 8754            let x = ˇ
 8755        "#
 8756        .unindent(),
 8757    );
 8758
 8759    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
 8760    cx.update_editor(|editor, window, cx| {
 8761        editor.handle_input("\"", window, cx);
 8762    });
 8763    cx.assert_editor_state(
 8764        &r#"
 8765            let x = "ˇ"
 8766        "#
 8767        .unindent(),
 8768    );
 8769
 8770    // Inserting another quotation mark. The cursor moves across the existing
 8771    // automatically-inserted quotation mark.
 8772    cx.update_editor(|editor, window, cx| {
 8773        editor.handle_input("\"", window, cx);
 8774    });
 8775    cx.assert_editor_state(
 8776        &r#"
 8777            let x = ""ˇ
 8778        "#
 8779        .unindent(),
 8780    );
 8781
 8782    // Reset
 8783    cx.set_state(
 8784        &r#"
 8785            let x = ˇ
 8786        "#
 8787        .unindent(),
 8788    );
 8789
 8790    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
 8791    cx.update_editor(|editor, window, cx| {
 8792        editor.handle_input("\"", window, cx);
 8793        editor.handle_input(" ", window, cx);
 8794        editor.move_left(&Default::default(), window, cx);
 8795        editor.handle_input("\\", window, cx);
 8796        editor.handle_input("\"", window, cx);
 8797    });
 8798    cx.assert_editor_state(
 8799        &r#"
 8800            let x = "\"ˇ "
 8801        "#
 8802        .unindent(),
 8803    );
 8804
 8805    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
 8806    // mark. Nothing is inserted.
 8807    cx.update_editor(|editor, window, cx| {
 8808        editor.move_right(&Default::default(), window, cx);
 8809        editor.handle_input("\"", window, cx);
 8810    });
 8811    cx.assert_editor_state(
 8812        &r#"
 8813            let x = "\" "ˇ
 8814        "#
 8815        .unindent(),
 8816    );
 8817}
 8818
 8819#[gpui::test]
 8820async fn test_surround_with_pair(cx: &mut TestAppContext) {
 8821    init_test(cx, |_| {});
 8822
 8823    let language = Arc::new(Language::new(
 8824        LanguageConfig {
 8825            brackets: BracketPairConfig {
 8826                pairs: vec![
 8827                    BracketPair {
 8828                        start: "{".to_string(),
 8829                        end: "}".to_string(),
 8830                        close: true,
 8831                        surround: true,
 8832                        newline: true,
 8833                    },
 8834                    BracketPair {
 8835                        start: "/* ".to_string(),
 8836                        end: "*/".to_string(),
 8837                        close: true,
 8838                        surround: true,
 8839                        ..Default::default()
 8840                    },
 8841                ],
 8842                ..Default::default()
 8843            },
 8844            ..Default::default()
 8845        },
 8846        Some(tree_sitter_rust::LANGUAGE.into()),
 8847    ));
 8848
 8849    let text = r#"
 8850        a
 8851        b
 8852        c
 8853    "#
 8854    .unindent();
 8855
 8856    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8857    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8858    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8859    editor
 8860        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8861        .await;
 8862
 8863    editor.update_in(cx, |editor, window, cx| {
 8864        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8865            s.select_display_ranges([
 8866                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8867                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8868                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
 8869            ])
 8870        });
 8871
 8872        editor.handle_input("{", window, cx);
 8873        editor.handle_input("{", window, cx);
 8874        editor.handle_input("{", window, cx);
 8875        assert_eq!(
 8876            editor.text(cx),
 8877            "
 8878                {{{a}}}
 8879                {{{b}}}
 8880                {{{c}}}
 8881            "
 8882            .unindent()
 8883        );
 8884        assert_eq!(
 8885            editor.selections.display_ranges(cx),
 8886            [
 8887                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
 8888                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
 8889                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
 8890            ]
 8891        );
 8892
 8893        editor.undo(&Undo, window, cx);
 8894        editor.undo(&Undo, window, cx);
 8895        editor.undo(&Undo, window, cx);
 8896        assert_eq!(
 8897            editor.text(cx),
 8898            "
 8899                a
 8900                b
 8901                c
 8902            "
 8903            .unindent()
 8904        );
 8905        assert_eq!(
 8906            editor.selections.display_ranges(cx),
 8907            [
 8908                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8909                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8910                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 8911            ]
 8912        );
 8913
 8914        // Ensure inserting the first character of a multi-byte bracket pair
 8915        // doesn't surround the selections with the bracket.
 8916        editor.handle_input("/", window, cx);
 8917        assert_eq!(
 8918            editor.text(cx),
 8919            "
 8920                /
 8921                /
 8922                /
 8923            "
 8924            .unindent()
 8925        );
 8926        assert_eq!(
 8927            editor.selections.display_ranges(cx),
 8928            [
 8929                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 8930                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 8931                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 8932            ]
 8933        );
 8934
 8935        editor.undo(&Undo, window, cx);
 8936        assert_eq!(
 8937            editor.text(cx),
 8938            "
 8939                a
 8940                b
 8941                c
 8942            "
 8943            .unindent()
 8944        );
 8945        assert_eq!(
 8946            editor.selections.display_ranges(cx),
 8947            [
 8948                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8949                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8950                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 8951            ]
 8952        );
 8953
 8954        // Ensure inserting the last character of a multi-byte bracket pair
 8955        // doesn't surround the selections with the bracket.
 8956        editor.handle_input("*", window, cx);
 8957        assert_eq!(
 8958            editor.text(cx),
 8959            "
 8960                *
 8961                *
 8962                *
 8963            "
 8964            .unindent()
 8965        );
 8966        assert_eq!(
 8967            editor.selections.display_ranges(cx),
 8968            [
 8969                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 8970                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 8971                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 8972            ]
 8973        );
 8974    });
 8975}
 8976
 8977#[gpui::test]
 8978async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
 8979    init_test(cx, |_| {});
 8980
 8981    let language = Arc::new(Language::new(
 8982        LanguageConfig {
 8983            brackets: BracketPairConfig {
 8984                pairs: vec![BracketPair {
 8985                    start: "{".to_string(),
 8986                    end: "}".to_string(),
 8987                    close: true,
 8988                    surround: true,
 8989                    newline: true,
 8990                }],
 8991                ..Default::default()
 8992            },
 8993            autoclose_before: "}".to_string(),
 8994            ..Default::default()
 8995        },
 8996        Some(tree_sitter_rust::LANGUAGE.into()),
 8997    ));
 8998
 8999    let text = r#"
 9000        a
 9001        b
 9002        c
 9003    "#
 9004    .unindent();
 9005
 9006    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9007    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9008    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9009    editor
 9010        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9011        .await;
 9012
 9013    editor.update_in(cx, |editor, window, cx| {
 9014        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9015            s.select_ranges([
 9016                Point::new(0, 1)..Point::new(0, 1),
 9017                Point::new(1, 1)..Point::new(1, 1),
 9018                Point::new(2, 1)..Point::new(2, 1),
 9019            ])
 9020        });
 9021
 9022        editor.handle_input("{", window, cx);
 9023        editor.handle_input("{", window, cx);
 9024        editor.handle_input("_", window, cx);
 9025        assert_eq!(
 9026            editor.text(cx),
 9027            "
 9028                a{{_}}
 9029                b{{_}}
 9030                c{{_}}
 9031            "
 9032            .unindent()
 9033        );
 9034        assert_eq!(
 9035            editor.selections.ranges::<Point>(cx),
 9036            [
 9037                Point::new(0, 4)..Point::new(0, 4),
 9038                Point::new(1, 4)..Point::new(1, 4),
 9039                Point::new(2, 4)..Point::new(2, 4)
 9040            ]
 9041        );
 9042
 9043        editor.backspace(&Default::default(), window, cx);
 9044        editor.backspace(&Default::default(), window, cx);
 9045        assert_eq!(
 9046            editor.text(cx),
 9047            "
 9048                a{}
 9049                b{}
 9050                c{}
 9051            "
 9052            .unindent()
 9053        );
 9054        assert_eq!(
 9055            editor.selections.ranges::<Point>(cx),
 9056            [
 9057                Point::new(0, 2)..Point::new(0, 2),
 9058                Point::new(1, 2)..Point::new(1, 2),
 9059                Point::new(2, 2)..Point::new(2, 2)
 9060            ]
 9061        );
 9062
 9063        editor.delete_to_previous_word_start(&Default::default(), window, cx);
 9064        assert_eq!(
 9065            editor.text(cx),
 9066            "
 9067                a
 9068                b
 9069                c
 9070            "
 9071            .unindent()
 9072        );
 9073        assert_eq!(
 9074            editor.selections.ranges::<Point>(cx),
 9075            [
 9076                Point::new(0, 1)..Point::new(0, 1),
 9077                Point::new(1, 1)..Point::new(1, 1),
 9078                Point::new(2, 1)..Point::new(2, 1)
 9079            ]
 9080        );
 9081    });
 9082}
 9083
 9084#[gpui::test]
 9085async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
 9086    init_test(cx, |settings| {
 9087        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 9088    });
 9089
 9090    let mut cx = EditorTestContext::new(cx).await;
 9091
 9092    let language = Arc::new(Language::new(
 9093        LanguageConfig {
 9094            brackets: BracketPairConfig {
 9095                pairs: vec![
 9096                    BracketPair {
 9097                        start: "{".to_string(),
 9098                        end: "}".to_string(),
 9099                        close: true,
 9100                        surround: true,
 9101                        newline: true,
 9102                    },
 9103                    BracketPair {
 9104                        start: "(".to_string(),
 9105                        end: ")".to_string(),
 9106                        close: true,
 9107                        surround: true,
 9108                        newline: true,
 9109                    },
 9110                    BracketPair {
 9111                        start: "[".to_string(),
 9112                        end: "]".to_string(),
 9113                        close: false,
 9114                        surround: true,
 9115                        newline: true,
 9116                    },
 9117                ],
 9118                ..Default::default()
 9119            },
 9120            autoclose_before: "})]".to_string(),
 9121            ..Default::default()
 9122        },
 9123        Some(tree_sitter_rust::LANGUAGE.into()),
 9124    ));
 9125
 9126    cx.language_registry().add(language.clone());
 9127    cx.update_buffer(|buffer, cx| {
 9128        buffer.set_language(Some(language), cx);
 9129    });
 9130
 9131    cx.set_state(
 9132        &"
 9133            {(ˇ)}
 9134            [[ˇ]]
 9135            {(ˇ)}
 9136        "
 9137        .unindent(),
 9138    );
 9139
 9140    cx.update_editor(|editor, window, cx| {
 9141        editor.backspace(&Default::default(), window, cx);
 9142        editor.backspace(&Default::default(), window, cx);
 9143    });
 9144
 9145    cx.assert_editor_state(
 9146        &"
 9147            ˇ
 9148            ˇ]]
 9149            ˇ
 9150        "
 9151        .unindent(),
 9152    );
 9153
 9154    cx.update_editor(|editor, window, cx| {
 9155        editor.handle_input("{", window, cx);
 9156        editor.handle_input("{", window, cx);
 9157        editor.move_right(&MoveRight, window, cx);
 9158        editor.move_right(&MoveRight, window, cx);
 9159        editor.move_left(&MoveLeft, window, cx);
 9160        editor.move_left(&MoveLeft, window, cx);
 9161        editor.backspace(&Default::default(), window, cx);
 9162    });
 9163
 9164    cx.assert_editor_state(
 9165        &"
 9166            {ˇ}
 9167            {ˇ}]]
 9168            {ˇ}
 9169        "
 9170        .unindent(),
 9171    );
 9172
 9173    cx.update_editor(|editor, window, cx| {
 9174        editor.backspace(&Default::default(), window, cx);
 9175    });
 9176
 9177    cx.assert_editor_state(
 9178        &"
 9179            ˇ
 9180            ˇ]]
 9181            ˇ
 9182        "
 9183        .unindent(),
 9184    );
 9185}
 9186
 9187#[gpui::test]
 9188async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
 9189    init_test(cx, |_| {});
 9190
 9191    let language = Arc::new(Language::new(
 9192        LanguageConfig::default(),
 9193        Some(tree_sitter_rust::LANGUAGE.into()),
 9194    ));
 9195
 9196    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
 9197    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9198    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9199    editor
 9200        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9201        .await;
 9202
 9203    editor.update_in(cx, |editor, window, cx| {
 9204        editor.set_auto_replace_emoji_shortcode(true);
 9205
 9206        editor.handle_input("Hello ", window, cx);
 9207        editor.handle_input(":wave", window, cx);
 9208        assert_eq!(editor.text(cx), "Hello :wave".unindent());
 9209
 9210        editor.handle_input(":", window, cx);
 9211        assert_eq!(editor.text(cx), "Hello 👋".unindent());
 9212
 9213        editor.handle_input(" :smile", window, cx);
 9214        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
 9215
 9216        editor.handle_input(":", window, cx);
 9217        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
 9218
 9219        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
 9220        editor.handle_input(":wave", window, cx);
 9221        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
 9222
 9223        editor.handle_input(":", window, cx);
 9224        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
 9225
 9226        editor.handle_input(":1", window, cx);
 9227        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
 9228
 9229        editor.handle_input(":", window, cx);
 9230        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
 9231
 9232        // Ensure shortcode does not get replaced when it is part of a word
 9233        editor.handle_input(" Test:wave", window, cx);
 9234        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
 9235
 9236        editor.handle_input(":", window, cx);
 9237        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
 9238
 9239        editor.set_auto_replace_emoji_shortcode(false);
 9240
 9241        // Ensure shortcode does not get replaced when auto replace is off
 9242        editor.handle_input(" :wave", window, cx);
 9243        assert_eq!(
 9244            editor.text(cx),
 9245            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
 9246        );
 9247
 9248        editor.handle_input(":", window, cx);
 9249        assert_eq!(
 9250            editor.text(cx),
 9251            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
 9252        );
 9253    });
 9254}
 9255
 9256#[gpui::test]
 9257async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
 9258    init_test(cx, |_| {});
 9259
 9260    let (text, insertion_ranges) = marked_text_ranges(
 9261        indoc! {"
 9262            ˇ
 9263        "},
 9264        false,
 9265    );
 9266
 9267    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
 9268    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9269
 9270    _ = editor.update_in(cx, |editor, window, cx| {
 9271        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
 9272
 9273        editor
 9274            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9275            .unwrap();
 9276
 9277        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
 9278            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
 9279            assert_eq!(editor.text(cx), expected_text);
 9280            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
 9281        }
 9282
 9283        assert(
 9284            editor,
 9285            cx,
 9286            indoc! {"
 9287            type «» =•
 9288            "},
 9289        );
 9290
 9291        assert!(editor.context_menu_visible(), "There should be a matches");
 9292    });
 9293}
 9294
 9295#[gpui::test]
 9296async fn test_snippets(cx: &mut TestAppContext) {
 9297    init_test(cx, |_| {});
 9298
 9299    let mut cx = EditorTestContext::new(cx).await;
 9300
 9301    cx.set_state(indoc! {"
 9302        a.ˇ b
 9303        a.ˇ b
 9304        a.ˇ b
 9305    "});
 9306
 9307    cx.update_editor(|editor, window, cx| {
 9308        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
 9309        let insertion_ranges = editor
 9310            .selections
 9311            .all(cx)
 9312            .iter()
 9313            .map(|s| s.range().clone())
 9314            .collect::<Vec<_>>();
 9315        editor
 9316            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9317            .unwrap();
 9318    });
 9319
 9320    cx.assert_editor_state(indoc! {"
 9321        a.f(«oneˇ», two, «threeˇ») b
 9322        a.f(«oneˇ», two, «threeˇ») b
 9323        a.f(«oneˇ», two, «threeˇ») b
 9324    "});
 9325
 9326    // Can't move earlier than the first tab stop
 9327    cx.update_editor(|editor, window, cx| {
 9328        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 9329    });
 9330    cx.assert_editor_state(indoc! {"
 9331        a.f(«oneˇ», two, «threeˇ») b
 9332        a.f(«oneˇ», two, «threeˇ») b
 9333        a.f(«oneˇ», two, «threeˇ») b
 9334    "});
 9335
 9336    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9337    cx.assert_editor_state(indoc! {"
 9338        a.f(one, «twoˇ», three) b
 9339        a.f(one, «twoˇ», three) b
 9340        a.f(one, «twoˇ», three) b
 9341    "});
 9342
 9343    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
 9344    cx.assert_editor_state(indoc! {"
 9345        a.f(«oneˇ», two, «threeˇ») b
 9346        a.f(«oneˇ», two, «threeˇ») b
 9347        a.f(«oneˇ», two, «threeˇ») b
 9348    "});
 9349
 9350    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9351    cx.assert_editor_state(indoc! {"
 9352        a.f(one, «twoˇ», three) b
 9353        a.f(one, «twoˇ», three) b
 9354        a.f(one, «twoˇ», three) b
 9355    "});
 9356    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9357    cx.assert_editor_state(indoc! {"
 9358        a.f(one, two, three)ˇ b
 9359        a.f(one, two, three)ˇ b
 9360        a.f(one, two, three)ˇ b
 9361    "});
 9362
 9363    // As soon as the last tab stop is reached, snippet state is gone
 9364    cx.update_editor(|editor, window, cx| {
 9365        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 9366    });
 9367    cx.assert_editor_state(indoc! {"
 9368        a.f(one, two, three)ˇ b
 9369        a.f(one, two, three)ˇ b
 9370        a.f(one, two, three)ˇ b
 9371    "});
 9372}
 9373
 9374#[gpui::test]
 9375async fn test_snippet_indentation(cx: &mut TestAppContext) {
 9376    init_test(cx, |_| {});
 9377
 9378    let mut cx = EditorTestContext::new(cx).await;
 9379
 9380    cx.update_editor(|editor, window, cx| {
 9381        let snippet = Snippet::parse(indoc! {"
 9382            /*
 9383             * Multiline comment with leading indentation
 9384             *
 9385             * $1
 9386             */
 9387            $0"})
 9388        .unwrap();
 9389        let insertion_ranges = editor
 9390            .selections
 9391            .all(cx)
 9392            .iter()
 9393            .map(|s| s.range().clone())
 9394            .collect::<Vec<_>>();
 9395        editor
 9396            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9397            .unwrap();
 9398    });
 9399
 9400    cx.assert_editor_state(indoc! {"
 9401        /*
 9402         * Multiline comment with leading indentation
 9403         *
 9404         * ˇ
 9405         */
 9406    "});
 9407
 9408    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9409    cx.assert_editor_state(indoc! {"
 9410        /*
 9411         * Multiline comment with leading indentation
 9412         *
 9413         *•
 9414         */
 9415        ˇ"});
 9416}
 9417
 9418#[gpui::test]
 9419async fn test_document_format_during_save(cx: &mut TestAppContext) {
 9420    init_test(cx, |_| {});
 9421
 9422    let fs = FakeFs::new(cx.executor());
 9423    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9424
 9425    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
 9426
 9427    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9428    language_registry.add(rust_lang());
 9429    let mut fake_servers = language_registry.register_fake_lsp(
 9430        "Rust",
 9431        FakeLspAdapter {
 9432            capabilities: lsp::ServerCapabilities {
 9433                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9434                ..Default::default()
 9435            },
 9436            ..Default::default()
 9437        },
 9438    );
 9439
 9440    let buffer = project
 9441        .update(cx, |project, cx| {
 9442            project.open_local_buffer(path!("/file.rs"), cx)
 9443        })
 9444        .await
 9445        .unwrap();
 9446
 9447    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9448    let (editor, cx) = cx.add_window_view(|window, cx| {
 9449        build_editor_with_project(project.clone(), buffer, window, cx)
 9450    });
 9451    editor.update_in(cx, |editor, window, cx| {
 9452        editor.set_text("one\ntwo\nthree\n", window, cx)
 9453    });
 9454    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9455
 9456    cx.executor().start_waiting();
 9457    let fake_server = fake_servers.next().await.unwrap();
 9458
 9459    {
 9460        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9461            move |params, _| async move {
 9462                assert_eq!(
 9463                    params.text_document.uri,
 9464                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9465                );
 9466                assert_eq!(params.options.tab_size, 4);
 9467                Ok(Some(vec![lsp::TextEdit::new(
 9468                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9469                    ", ".to_string(),
 9470                )]))
 9471            },
 9472        );
 9473        let save = editor
 9474            .update_in(cx, |editor, window, cx| {
 9475                editor.save(
 9476                    SaveOptions {
 9477                        format: true,
 9478                        autosave: false,
 9479                    },
 9480                    project.clone(),
 9481                    window,
 9482                    cx,
 9483                )
 9484            })
 9485            .unwrap();
 9486        cx.executor().start_waiting();
 9487        save.await;
 9488
 9489        assert_eq!(
 9490            editor.update(cx, |editor, cx| editor.text(cx)),
 9491            "one, two\nthree\n"
 9492        );
 9493        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9494    }
 9495
 9496    {
 9497        editor.update_in(cx, |editor, window, cx| {
 9498            editor.set_text("one\ntwo\nthree\n", window, cx)
 9499        });
 9500        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9501
 9502        // Ensure we can still save even if formatting hangs.
 9503        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9504            move |params, _| async move {
 9505                assert_eq!(
 9506                    params.text_document.uri,
 9507                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9508                );
 9509                futures::future::pending::<()>().await;
 9510                unreachable!()
 9511            },
 9512        );
 9513        let save = editor
 9514            .update_in(cx, |editor, window, cx| {
 9515                editor.save(
 9516                    SaveOptions {
 9517                        format: true,
 9518                        autosave: false,
 9519                    },
 9520                    project.clone(),
 9521                    window,
 9522                    cx,
 9523                )
 9524            })
 9525            .unwrap();
 9526        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9527        cx.executor().start_waiting();
 9528        save.await;
 9529        assert_eq!(
 9530            editor.update(cx, |editor, cx| editor.text(cx)),
 9531            "one\ntwo\nthree\n"
 9532        );
 9533    }
 9534
 9535    // Set rust language override and assert overridden tabsize is sent to language server
 9536    update_test_language_settings(cx, |settings| {
 9537        settings.languages.0.insert(
 9538            "Rust".into(),
 9539            LanguageSettingsContent {
 9540                tab_size: NonZeroU32::new(8),
 9541                ..Default::default()
 9542            },
 9543        );
 9544    });
 9545
 9546    {
 9547        editor.update_in(cx, |editor, window, cx| {
 9548            editor.set_text("somehting_new\n", window, cx)
 9549        });
 9550        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9551        let _formatting_request_signal = fake_server
 9552            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9553                assert_eq!(
 9554                    params.text_document.uri,
 9555                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9556                );
 9557                assert_eq!(params.options.tab_size, 8);
 9558                Ok(Some(vec![]))
 9559            });
 9560        let save = editor
 9561            .update_in(cx, |editor, window, cx| {
 9562                editor.save(
 9563                    SaveOptions {
 9564                        format: true,
 9565                        autosave: false,
 9566                    },
 9567                    project.clone(),
 9568                    window,
 9569                    cx,
 9570                )
 9571            })
 9572            .unwrap();
 9573        cx.executor().start_waiting();
 9574        save.await;
 9575    }
 9576}
 9577
 9578#[gpui::test]
 9579async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
 9580    init_test(cx, |settings| {
 9581        settings.defaults.ensure_final_newline_on_save = Some(false);
 9582    });
 9583
 9584    let fs = FakeFs::new(cx.executor());
 9585    fs.insert_file(path!("/file.txt"), "foo".into()).await;
 9586
 9587    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
 9588
 9589    let buffer = project
 9590        .update(cx, |project, cx| {
 9591            project.open_local_buffer(path!("/file.txt"), cx)
 9592        })
 9593        .await
 9594        .unwrap();
 9595
 9596    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9597    let (editor, cx) = cx.add_window_view(|window, cx| {
 9598        build_editor_with_project(project.clone(), buffer, window, cx)
 9599    });
 9600    editor.update_in(cx, |editor, window, cx| {
 9601        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 9602            s.select_ranges([0..0])
 9603        });
 9604    });
 9605    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9606
 9607    editor.update_in(cx, |editor, window, cx| {
 9608        editor.handle_input("\n", window, cx)
 9609    });
 9610    cx.run_until_parked();
 9611    save(&editor, &project, cx).await;
 9612    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
 9613
 9614    editor.update_in(cx, |editor, window, cx| {
 9615        editor.undo(&Default::default(), window, cx);
 9616    });
 9617    save(&editor, &project, cx).await;
 9618    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
 9619
 9620    editor.update_in(cx, |editor, window, cx| {
 9621        editor.redo(&Default::default(), window, cx);
 9622    });
 9623    cx.run_until_parked();
 9624    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
 9625
 9626    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
 9627        let save = editor
 9628            .update_in(cx, |editor, window, cx| {
 9629                editor.save(
 9630                    SaveOptions {
 9631                        format: true,
 9632                        autosave: false,
 9633                    },
 9634                    project.clone(),
 9635                    window,
 9636                    cx,
 9637                )
 9638            })
 9639            .unwrap();
 9640        cx.executor().start_waiting();
 9641        save.await;
 9642        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9643    }
 9644}
 9645
 9646#[gpui::test]
 9647async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
 9648    init_test(cx, |_| {});
 9649
 9650    let cols = 4;
 9651    let rows = 10;
 9652    let sample_text_1 = sample_text(rows, cols, 'a');
 9653    assert_eq!(
 9654        sample_text_1,
 9655        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
 9656    );
 9657    let sample_text_2 = sample_text(rows, cols, 'l');
 9658    assert_eq!(
 9659        sample_text_2,
 9660        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
 9661    );
 9662    let sample_text_3 = sample_text(rows, cols, 'v');
 9663    assert_eq!(
 9664        sample_text_3,
 9665        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
 9666    );
 9667
 9668    let fs = FakeFs::new(cx.executor());
 9669    fs.insert_tree(
 9670        path!("/a"),
 9671        json!({
 9672            "main.rs": sample_text_1,
 9673            "other.rs": sample_text_2,
 9674            "lib.rs": sample_text_3,
 9675        }),
 9676    )
 9677    .await;
 9678
 9679    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
 9680    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9681    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 9682
 9683    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9684    language_registry.add(rust_lang());
 9685    let mut fake_servers = language_registry.register_fake_lsp(
 9686        "Rust",
 9687        FakeLspAdapter {
 9688            capabilities: lsp::ServerCapabilities {
 9689                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9690                ..Default::default()
 9691            },
 9692            ..Default::default()
 9693        },
 9694    );
 9695
 9696    let worktree = project.update(cx, |project, cx| {
 9697        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
 9698        assert_eq!(worktrees.len(), 1);
 9699        worktrees.pop().unwrap()
 9700    });
 9701    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 9702
 9703    let buffer_1 = project
 9704        .update(cx, |project, cx| {
 9705            project.open_buffer((worktree_id, "main.rs"), cx)
 9706        })
 9707        .await
 9708        .unwrap();
 9709    let buffer_2 = project
 9710        .update(cx, |project, cx| {
 9711            project.open_buffer((worktree_id, "other.rs"), cx)
 9712        })
 9713        .await
 9714        .unwrap();
 9715    let buffer_3 = project
 9716        .update(cx, |project, cx| {
 9717            project.open_buffer((worktree_id, "lib.rs"), cx)
 9718        })
 9719        .await
 9720        .unwrap();
 9721
 9722    let multi_buffer = cx.new(|cx| {
 9723        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 9724        multi_buffer.push_excerpts(
 9725            buffer_1.clone(),
 9726            [
 9727                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9728                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9729                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9730            ],
 9731            cx,
 9732        );
 9733        multi_buffer.push_excerpts(
 9734            buffer_2.clone(),
 9735            [
 9736                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9737                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9738                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9739            ],
 9740            cx,
 9741        );
 9742        multi_buffer.push_excerpts(
 9743            buffer_3.clone(),
 9744            [
 9745                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9746                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9747                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9748            ],
 9749            cx,
 9750        );
 9751        multi_buffer
 9752    });
 9753    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
 9754        Editor::new(
 9755            EditorMode::full(),
 9756            multi_buffer,
 9757            Some(project.clone()),
 9758            window,
 9759            cx,
 9760        )
 9761    });
 9762
 9763    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 9764        editor.change_selections(
 9765            SelectionEffects::scroll(Autoscroll::Next),
 9766            window,
 9767            cx,
 9768            |s| s.select_ranges(Some(1..2)),
 9769        );
 9770        editor.insert("|one|two|three|", window, cx);
 9771    });
 9772    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 9773    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 9774        editor.change_selections(
 9775            SelectionEffects::scroll(Autoscroll::Next),
 9776            window,
 9777            cx,
 9778            |s| s.select_ranges(Some(60..70)),
 9779        );
 9780        editor.insert("|four|five|six|", window, cx);
 9781    });
 9782    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 9783
 9784    // First two buffers should be edited, but not the third one.
 9785    assert_eq!(
 9786        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 9787        "a|one|two|three|aa\nbbbb\ncccc\n\nffff\ngggg\n\njjjj\nllll\nmmmm\nnnnn|four|five|six|\nr\n\nuuuu\nvvvv\nwwww\nxxxx\n\n{{{{\n||||\n\n\u{7f}\u{7f}\u{7f}\u{7f}",
 9788    );
 9789    buffer_1.update(cx, |buffer, _| {
 9790        assert!(buffer.is_dirty());
 9791        assert_eq!(
 9792            buffer.text(),
 9793            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
 9794        )
 9795    });
 9796    buffer_2.update(cx, |buffer, _| {
 9797        assert!(buffer.is_dirty());
 9798        assert_eq!(
 9799            buffer.text(),
 9800            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
 9801        )
 9802    });
 9803    buffer_3.update(cx, |buffer, _| {
 9804        assert!(!buffer.is_dirty());
 9805        assert_eq!(buffer.text(), sample_text_3,)
 9806    });
 9807    cx.executor().run_until_parked();
 9808
 9809    cx.executor().start_waiting();
 9810    let save = multi_buffer_editor
 9811        .update_in(cx, |editor, window, cx| {
 9812            editor.save(
 9813                SaveOptions {
 9814                    format: true,
 9815                    autosave: false,
 9816                },
 9817                project.clone(),
 9818                window,
 9819                cx,
 9820            )
 9821        })
 9822        .unwrap();
 9823
 9824    let fake_server = fake_servers.next().await.unwrap();
 9825    fake_server
 9826        .server
 9827        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9828            Ok(Some(vec![lsp::TextEdit::new(
 9829                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9830                format!("[{} formatted]", params.text_document.uri),
 9831            )]))
 9832        })
 9833        .detach();
 9834    save.await;
 9835
 9836    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
 9837    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
 9838    assert_eq!(
 9839        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 9840        uri!(
 9841            "a|o[file:///a/main.rs formatted]bbbb\ncccc\n\nffff\ngggg\n\njjjj\n\nlll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|\nr\n\nuuuu\n\nvvvv\nwwww\nxxxx\n\n{{{{\n||||\n\n\u{7f}\u{7f}\u{7f}\u{7f}"
 9842        ),
 9843    );
 9844    buffer_1.update(cx, |buffer, _| {
 9845        assert!(!buffer.is_dirty());
 9846        assert_eq!(
 9847            buffer.text(),
 9848            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
 9849        )
 9850    });
 9851    buffer_2.update(cx, |buffer, _| {
 9852        assert!(!buffer.is_dirty());
 9853        assert_eq!(
 9854            buffer.text(),
 9855            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
 9856        )
 9857    });
 9858    buffer_3.update(cx, |buffer, _| {
 9859        assert!(!buffer.is_dirty());
 9860        assert_eq!(buffer.text(), sample_text_3,)
 9861    });
 9862}
 9863
 9864#[gpui::test]
 9865async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
 9866    init_test(cx, |_| {});
 9867
 9868    let fs = FakeFs::new(cx.executor());
 9869    fs.insert_tree(
 9870        path!("/dir"),
 9871        json!({
 9872            "file1.rs": "fn main() { println!(\"hello\"); }",
 9873            "file2.rs": "fn test() { println!(\"test\"); }",
 9874            "file3.rs": "fn other() { println!(\"other\"); }\n",
 9875        }),
 9876    )
 9877    .await;
 9878
 9879    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
 9880    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9881    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 9882
 9883    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9884    language_registry.add(rust_lang());
 9885
 9886    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
 9887    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 9888
 9889    // Open three buffers
 9890    let buffer_1 = project
 9891        .update(cx, |project, cx| {
 9892            project.open_buffer((worktree_id, "file1.rs"), cx)
 9893        })
 9894        .await
 9895        .unwrap();
 9896    let buffer_2 = project
 9897        .update(cx, |project, cx| {
 9898            project.open_buffer((worktree_id, "file2.rs"), cx)
 9899        })
 9900        .await
 9901        .unwrap();
 9902    let buffer_3 = project
 9903        .update(cx, |project, cx| {
 9904            project.open_buffer((worktree_id, "file3.rs"), cx)
 9905        })
 9906        .await
 9907        .unwrap();
 9908
 9909    // Create a multi-buffer with all three buffers
 9910    let multi_buffer = cx.new(|cx| {
 9911        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 9912        multi_buffer.push_excerpts(
 9913            buffer_1.clone(),
 9914            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9915            cx,
 9916        );
 9917        multi_buffer.push_excerpts(
 9918            buffer_2.clone(),
 9919            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9920            cx,
 9921        );
 9922        multi_buffer.push_excerpts(
 9923            buffer_3.clone(),
 9924            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9925            cx,
 9926        );
 9927        multi_buffer
 9928    });
 9929
 9930    let editor = cx.new_window_entity(|window, cx| {
 9931        Editor::new(
 9932            EditorMode::full(),
 9933            multi_buffer,
 9934            Some(project.clone()),
 9935            window,
 9936            cx,
 9937        )
 9938    });
 9939
 9940    // Edit only the first buffer
 9941    editor.update_in(cx, |editor, window, cx| {
 9942        editor.change_selections(
 9943            SelectionEffects::scroll(Autoscroll::Next),
 9944            window,
 9945            cx,
 9946            |s| s.select_ranges(Some(10..10)),
 9947        );
 9948        editor.insert("// edited", window, cx);
 9949    });
 9950
 9951    // Verify that only buffer 1 is dirty
 9952    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
 9953    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9954    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9955
 9956    // Get write counts after file creation (files were created with initial content)
 9957    // We expect each file to have been written once during creation
 9958    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
 9959    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
 9960    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
 9961
 9962    // Perform autosave
 9963    let save_task = editor.update_in(cx, |editor, window, cx| {
 9964        editor.save(
 9965            SaveOptions {
 9966                format: true,
 9967                autosave: true,
 9968            },
 9969            project.clone(),
 9970            window,
 9971            cx,
 9972        )
 9973    });
 9974    save_task.await.unwrap();
 9975
 9976    // Only the dirty buffer should have been saved
 9977    assert_eq!(
 9978        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
 9979        1,
 9980        "Buffer 1 was dirty, so it should have been written once during autosave"
 9981    );
 9982    assert_eq!(
 9983        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
 9984        0,
 9985        "Buffer 2 was clean, so it should not have been written during autosave"
 9986    );
 9987    assert_eq!(
 9988        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
 9989        0,
 9990        "Buffer 3 was clean, so it should not have been written during autosave"
 9991    );
 9992
 9993    // Verify buffer states after autosave
 9994    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9995    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9996    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9997
 9998    // Now perform a manual save (format = true)
 9999    let save_task = editor.update_in(cx, |editor, window, cx| {
10000        editor.save(
10001            SaveOptions {
10002                format: true,
10003                autosave: false,
10004            },
10005            project.clone(),
10006            window,
10007            cx,
10008        )
10009    });
10010    save_task.await.unwrap();
10011
10012    // During manual save, clean buffers don't get written to disk
10013    // They just get did_save called for language server notifications
10014    assert_eq!(
10015        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
10016        1,
10017        "Buffer 1 should only have been written once total (during autosave, not manual save)"
10018    );
10019    assert_eq!(
10020        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
10021        0,
10022        "Buffer 2 should not have been written at all"
10023    );
10024    assert_eq!(
10025        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
10026        0,
10027        "Buffer 3 should not have been written at all"
10028    );
10029}
10030
10031async fn setup_range_format_test(
10032    cx: &mut TestAppContext,
10033) -> (
10034    Entity<Project>,
10035    Entity<Editor>,
10036    &mut gpui::VisualTestContext,
10037    lsp::FakeLanguageServer,
10038) {
10039    init_test(cx, |_| {});
10040
10041    let fs = FakeFs::new(cx.executor());
10042    fs.insert_file(path!("/file.rs"), Default::default()).await;
10043
10044    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10045
10046    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10047    language_registry.add(rust_lang());
10048    let mut fake_servers = language_registry.register_fake_lsp(
10049        "Rust",
10050        FakeLspAdapter {
10051            capabilities: lsp::ServerCapabilities {
10052                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
10053                ..lsp::ServerCapabilities::default()
10054            },
10055            ..FakeLspAdapter::default()
10056        },
10057    );
10058
10059    let buffer = project
10060        .update(cx, |project, cx| {
10061            project.open_local_buffer(path!("/file.rs"), cx)
10062        })
10063        .await
10064        .unwrap();
10065
10066    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10067    let (editor, cx) = cx.add_window_view(|window, cx| {
10068        build_editor_with_project(project.clone(), buffer, window, cx)
10069    });
10070
10071    cx.executor().start_waiting();
10072    let fake_server = fake_servers.next().await.unwrap();
10073
10074    (project, editor, cx, fake_server)
10075}
10076
10077#[gpui::test]
10078async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
10079    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
10080
10081    editor.update_in(cx, |editor, window, cx| {
10082        editor.set_text("one\ntwo\nthree\n", window, cx)
10083    });
10084    assert!(cx.read(|cx| editor.is_dirty(cx)));
10085
10086    let save = editor
10087        .update_in(cx, |editor, window, cx| {
10088            editor.save(
10089                SaveOptions {
10090                    format: true,
10091                    autosave: false,
10092                },
10093                project.clone(),
10094                window,
10095                cx,
10096            )
10097        })
10098        .unwrap();
10099    fake_server
10100        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
10101            assert_eq!(
10102                params.text_document.uri,
10103                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10104            );
10105            assert_eq!(params.options.tab_size, 4);
10106            Ok(Some(vec![lsp::TextEdit::new(
10107                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10108                ", ".to_string(),
10109            )]))
10110        })
10111        .next()
10112        .await;
10113    cx.executor().start_waiting();
10114    save.await;
10115    assert_eq!(
10116        editor.update(cx, |editor, cx| editor.text(cx)),
10117        "one, two\nthree\n"
10118    );
10119    assert!(!cx.read(|cx| editor.is_dirty(cx)));
10120}
10121
10122#[gpui::test]
10123async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
10124    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
10125
10126    editor.update_in(cx, |editor, window, cx| {
10127        editor.set_text("one\ntwo\nthree\n", window, cx)
10128    });
10129    assert!(cx.read(|cx| editor.is_dirty(cx)));
10130
10131    // Test that save still works when formatting hangs
10132    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
10133        move |params, _| async move {
10134            assert_eq!(
10135                params.text_document.uri,
10136                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10137            );
10138            futures::future::pending::<()>().await;
10139            unreachable!()
10140        },
10141    );
10142    let save = editor
10143        .update_in(cx, |editor, window, cx| {
10144            editor.save(
10145                SaveOptions {
10146                    format: true,
10147                    autosave: false,
10148                },
10149                project.clone(),
10150                window,
10151                cx,
10152            )
10153        })
10154        .unwrap();
10155    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10156    cx.executor().start_waiting();
10157    save.await;
10158    assert_eq!(
10159        editor.update(cx, |editor, cx| editor.text(cx)),
10160        "one\ntwo\nthree\n"
10161    );
10162    assert!(!cx.read(|cx| editor.is_dirty(cx)));
10163}
10164
10165#[gpui::test]
10166async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
10167    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
10168
10169    // Buffer starts clean, no formatting should be requested
10170    let save = editor
10171        .update_in(cx, |editor, window, cx| {
10172            editor.save(
10173                SaveOptions {
10174                    format: false,
10175                    autosave: false,
10176                },
10177                project.clone(),
10178                window,
10179                cx,
10180            )
10181        })
10182        .unwrap();
10183    let _pending_format_request = fake_server
10184        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
10185            panic!("Should not be invoked");
10186        })
10187        .next();
10188    cx.executor().start_waiting();
10189    save.await;
10190    cx.run_until_parked();
10191}
10192
10193#[gpui::test]
10194async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
10195    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
10196
10197    // Set Rust language override and assert overridden tabsize is sent to language server
10198    update_test_language_settings(cx, |settings| {
10199        settings.languages.0.insert(
10200            "Rust".into(),
10201            LanguageSettingsContent {
10202                tab_size: NonZeroU32::new(8),
10203                ..Default::default()
10204            },
10205        );
10206    });
10207
10208    editor.update_in(cx, |editor, window, cx| {
10209        editor.set_text("something_new\n", window, cx)
10210    });
10211    assert!(cx.read(|cx| editor.is_dirty(cx)));
10212    let save = editor
10213        .update_in(cx, |editor, window, cx| {
10214            editor.save(
10215                SaveOptions {
10216                    format: true,
10217                    autosave: false,
10218                },
10219                project.clone(),
10220                window,
10221                cx,
10222            )
10223        })
10224        .unwrap();
10225    fake_server
10226        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
10227            assert_eq!(
10228                params.text_document.uri,
10229                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10230            );
10231            assert_eq!(params.options.tab_size, 8);
10232            Ok(Some(Vec::new()))
10233        })
10234        .next()
10235        .await;
10236    save.await;
10237}
10238
10239#[gpui::test]
10240async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
10241    init_test(cx, |settings| {
10242        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
10243            Formatter::LanguageServer { name: None },
10244        )))
10245    });
10246
10247    let fs = FakeFs::new(cx.executor());
10248    fs.insert_file(path!("/file.rs"), Default::default()).await;
10249
10250    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10251
10252    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10253    language_registry.add(Arc::new(Language::new(
10254        LanguageConfig {
10255            name: "Rust".into(),
10256            matcher: LanguageMatcher {
10257                path_suffixes: vec!["rs".to_string()],
10258                ..Default::default()
10259            },
10260            ..LanguageConfig::default()
10261        },
10262        Some(tree_sitter_rust::LANGUAGE.into()),
10263    )));
10264    update_test_language_settings(cx, |settings| {
10265        // Enable Prettier formatting for the same buffer, and ensure
10266        // LSP is called instead of Prettier.
10267        settings.defaults.prettier = Some(PrettierSettings {
10268            allowed: true,
10269            ..PrettierSettings::default()
10270        });
10271    });
10272    let mut fake_servers = language_registry.register_fake_lsp(
10273        "Rust",
10274        FakeLspAdapter {
10275            capabilities: lsp::ServerCapabilities {
10276                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10277                ..Default::default()
10278            },
10279            ..Default::default()
10280        },
10281    );
10282
10283    let buffer = project
10284        .update(cx, |project, cx| {
10285            project.open_local_buffer(path!("/file.rs"), cx)
10286        })
10287        .await
10288        .unwrap();
10289
10290    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10291    let (editor, cx) = cx.add_window_view(|window, cx| {
10292        build_editor_with_project(project.clone(), buffer, window, cx)
10293    });
10294    editor.update_in(cx, |editor, window, cx| {
10295        editor.set_text("one\ntwo\nthree\n", window, cx)
10296    });
10297
10298    cx.executor().start_waiting();
10299    let fake_server = fake_servers.next().await.unwrap();
10300
10301    let format = editor
10302        .update_in(cx, |editor, window, cx| {
10303            editor.perform_format(
10304                project.clone(),
10305                FormatTrigger::Manual,
10306                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10307                window,
10308                cx,
10309            )
10310        })
10311        .unwrap();
10312    fake_server
10313        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
10314            assert_eq!(
10315                params.text_document.uri,
10316                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10317            );
10318            assert_eq!(params.options.tab_size, 4);
10319            Ok(Some(vec![lsp::TextEdit::new(
10320                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10321                ", ".to_string(),
10322            )]))
10323        })
10324        .next()
10325        .await;
10326    cx.executor().start_waiting();
10327    format.await;
10328    assert_eq!(
10329        editor.update(cx, |editor, cx| editor.text(cx)),
10330        "one, two\nthree\n"
10331    );
10332
10333    editor.update_in(cx, |editor, window, cx| {
10334        editor.set_text("one\ntwo\nthree\n", window, cx)
10335    });
10336    // Ensure we don't lock if formatting hangs.
10337    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10338        move |params, _| async move {
10339            assert_eq!(
10340                params.text_document.uri,
10341                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10342            );
10343            futures::future::pending::<()>().await;
10344            unreachable!()
10345        },
10346    );
10347    let format = editor
10348        .update_in(cx, |editor, window, cx| {
10349            editor.perform_format(
10350                project,
10351                FormatTrigger::Manual,
10352                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10353                window,
10354                cx,
10355            )
10356        })
10357        .unwrap();
10358    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10359    cx.executor().start_waiting();
10360    format.await;
10361    assert_eq!(
10362        editor.update(cx, |editor, cx| editor.text(cx)),
10363        "one\ntwo\nthree\n"
10364    );
10365}
10366
10367#[gpui::test]
10368async fn test_multiple_formatters(cx: &mut TestAppContext) {
10369    init_test(cx, |settings| {
10370        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
10371        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
10372            Formatter::LanguageServer { name: None },
10373            Formatter::CodeActions(
10374                [
10375                    ("code-action-1".into(), true),
10376                    ("code-action-2".into(), true),
10377                ]
10378                .into_iter()
10379                .collect(),
10380            ),
10381        ])))
10382    });
10383
10384    let fs = FakeFs::new(cx.executor());
10385    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
10386        .await;
10387
10388    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10389    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10390    language_registry.add(rust_lang());
10391
10392    let mut fake_servers = language_registry.register_fake_lsp(
10393        "Rust",
10394        FakeLspAdapter {
10395            capabilities: lsp::ServerCapabilities {
10396                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10397                execute_command_provider: Some(lsp::ExecuteCommandOptions {
10398                    commands: vec!["the-command-for-code-action-1".into()],
10399                    ..Default::default()
10400                }),
10401                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10402                ..Default::default()
10403            },
10404            ..Default::default()
10405        },
10406    );
10407
10408    let buffer = project
10409        .update(cx, |project, cx| {
10410            project.open_local_buffer(path!("/file.rs"), cx)
10411        })
10412        .await
10413        .unwrap();
10414
10415    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10416    let (editor, cx) = cx.add_window_view(|window, cx| {
10417        build_editor_with_project(project.clone(), buffer, window, cx)
10418    });
10419
10420    cx.executor().start_waiting();
10421
10422    let fake_server = fake_servers.next().await.unwrap();
10423    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10424        move |_params, _| async move {
10425            Ok(Some(vec![lsp::TextEdit::new(
10426                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10427                "applied-formatting\n".to_string(),
10428            )]))
10429        },
10430    );
10431    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
10432        move |params, _| async move {
10433            assert_eq!(
10434                params.context.only,
10435                Some(vec!["code-action-1".into(), "code-action-2".into()])
10436            );
10437            let uri = lsp::Url::from_file_path(path!("/file.rs")).unwrap();
10438            Ok(Some(vec![
10439                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10440                    kind: Some("code-action-1".into()),
10441                    edit: Some(lsp::WorkspaceEdit::new(
10442                        [(
10443                            uri.clone(),
10444                            vec![lsp::TextEdit::new(
10445                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10446                                "applied-code-action-1-edit\n".to_string(),
10447                            )],
10448                        )]
10449                        .into_iter()
10450                        .collect(),
10451                    )),
10452                    command: Some(lsp::Command {
10453                        command: "the-command-for-code-action-1".into(),
10454                        ..Default::default()
10455                    }),
10456                    ..Default::default()
10457                }),
10458                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10459                    kind: Some("code-action-2".into()),
10460                    edit: Some(lsp::WorkspaceEdit::new(
10461                        [(
10462                            uri.clone(),
10463                            vec![lsp::TextEdit::new(
10464                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10465                                "applied-code-action-2-edit\n".to_string(),
10466                            )],
10467                        )]
10468                        .into_iter()
10469                        .collect(),
10470                    )),
10471                    ..Default::default()
10472                }),
10473            ]))
10474        },
10475    );
10476
10477    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
10478        move |params, _| async move { Ok(params) }
10479    });
10480
10481    let command_lock = Arc::new(futures::lock::Mutex::new(()));
10482    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
10483        let fake = fake_server.clone();
10484        let lock = command_lock.clone();
10485        move |params, _| {
10486            assert_eq!(params.command, "the-command-for-code-action-1");
10487            let fake = fake.clone();
10488            let lock = lock.clone();
10489            async move {
10490                lock.lock().await;
10491                fake.server
10492                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
10493                        label: None,
10494                        edit: lsp::WorkspaceEdit {
10495                            changes: Some(
10496                                [(
10497                                    lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
10498                                    vec![lsp::TextEdit {
10499                                        range: lsp::Range::new(
10500                                            lsp::Position::new(0, 0),
10501                                            lsp::Position::new(0, 0),
10502                                        ),
10503                                        new_text: "applied-code-action-1-command\n".into(),
10504                                    }],
10505                                )]
10506                                .into_iter()
10507                                .collect(),
10508                            ),
10509                            ..Default::default()
10510                        },
10511                    })
10512                    .await
10513                    .into_response()
10514                    .unwrap();
10515                Ok(Some(json!(null)))
10516            }
10517        }
10518    });
10519
10520    cx.executor().start_waiting();
10521    editor
10522        .update_in(cx, |editor, window, cx| {
10523            editor.perform_format(
10524                project.clone(),
10525                FormatTrigger::Manual,
10526                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10527                window,
10528                cx,
10529            )
10530        })
10531        .unwrap()
10532        .await;
10533    editor.update(cx, |editor, cx| {
10534        assert_eq!(
10535            editor.text(cx),
10536            r#"
10537                applied-code-action-2-edit
10538                applied-code-action-1-command
10539                applied-code-action-1-edit
10540                applied-formatting
10541                one
10542                two
10543                three
10544            "#
10545            .unindent()
10546        );
10547    });
10548
10549    editor.update_in(cx, |editor, window, cx| {
10550        editor.undo(&Default::default(), window, cx);
10551        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10552    });
10553
10554    // Perform a manual edit while waiting for an LSP command
10555    // that's being run as part of a formatting code action.
10556    let lock_guard = command_lock.lock().await;
10557    let format = editor
10558        .update_in(cx, |editor, window, cx| {
10559            editor.perform_format(
10560                project.clone(),
10561                FormatTrigger::Manual,
10562                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10563                window,
10564                cx,
10565            )
10566        })
10567        .unwrap();
10568    cx.run_until_parked();
10569    editor.update(cx, |editor, cx| {
10570        assert_eq!(
10571            editor.text(cx),
10572            r#"
10573                applied-code-action-1-edit
10574                applied-formatting
10575                one
10576                two
10577                three
10578            "#
10579            .unindent()
10580        );
10581
10582        editor.buffer.update(cx, |buffer, cx| {
10583            let ix = buffer.len(cx);
10584            buffer.edit([(ix..ix, "edited\n")], None, cx);
10585        });
10586    });
10587
10588    // Allow the LSP command to proceed. Because the buffer was edited,
10589    // the second code action will not be run.
10590    drop(lock_guard);
10591    format.await;
10592    editor.update_in(cx, |editor, window, cx| {
10593        assert_eq!(
10594            editor.text(cx),
10595            r#"
10596                applied-code-action-1-command
10597                applied-code-action-1-edit
10598                applied-formatting
10599                one
10600                two
10601                three
10602                edited
10603            "#
10604            .unindent()
10605        );
10606
10607        // The manual edit is undone first, because it is the last thing the user did
10608        // (even though the command completed afterwards).
10609        editor.undo(&Default::default(), window, cx);
10610        assert_eq!(
10611            editor.text(cx),
10612            r#"
10613                applied-code-action-1-command
10614                applied-code-action-1-edit
10615                applied-formatting
10616                one
10617                two
10618                three
10619            "#
10620            .unindent()
10621        );
10622
10623        // All the formatting (including the command, which completed after the manual edit)
10624        // is undone together.
10625        editor.undo(&Default::default(), window, cx);
10626        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10627    });
10628}
10629
10630#[gpui::test]
10631async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
10632    init_test(cx, |settings| {
10633        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
10634            Formatter::LanguageServer { name: None },
10635        ])))
10636    });
10637
10638    let fs = FakeFs::new(cx.executor());
10639    fs.insert_file(path!("/file.ts"), Default::default()).await;
10640
10641    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10642
10643    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10644    language_registry.add(Arc::new(Language::new(
10645        LanguageConfig {
10646            name: "TypeScript".into(),
10647            matcher: LanguageMatcher {
10648                path_suffixes: vec!["ts".to_string()],
10649                ..Default::default()
10650            },
10651            ..LanguageConfig::default()
10652        },
10653        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
10654    )));
10655    update_test_language_settings(cx, |settings| {
10656        settings.defaults.prettier = Some(PrettierSettings {
10657            allowed: true,
10658            ..PrettierSettings::default()
10659        });
10660    });
10661    let mut fake_servers = language_registry.register_fake_lsp(
10662        "TypeScript",
10663        FakeLspAdapter {
10664            capabilities: lsp::ServerCapabilities {
10665                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10666                ..Default::default()
10667            },
10668            ..Default::default()
10669        },
10670    );
10671
10672    let buffer = project
10673        .update(cx, |project, cx| {
10674            project.open_local_buffer(path!("/file.ts"), cx)
10675        })
10676        .await
10677        .unwrap();
10678
10679    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10680    let (editor, cx) = cx.add_window_view(|window, cx| {
10681        build_editor_with_project(project.clone(), buffer, window, cx)
10682    });
10683    editor.update_in(cx, |editor, window, cx| {
10684        editor.set_text(
10685            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
10686            window,
10687            cx,
10688        )
10689    });
10690
10691    cx.executor().start_waiting();
10692    let fake_server = fake_servers.next().await.unwrap();
10693
10694    let format = editor
10695        .update_in(cx, |editor, window, cx| {
10696            editor.perform_code_action_kind(
10697                project.clone(),
10698                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
10699                window,
10700                cx,
10701            )
10702        })
10703        .unwrap();
10704    fake_server
10705        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
10706            assert_eq!(
10707                params.text_document.uri,
10708                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
10709            );
10710            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
10711                lsp::CodeAction {
10712                    title: "Organize Imports".to_string(),
10713                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
10714                    edit: Some(lsp::WorkspaceEdit {
10715                        changes: Some(
10716                            [(
10717                                params.text_document.uri.clone(),
10718                                vec![lsp::TextEdit::new(
10719                                    lsp::Range::new(
10720                                        lsp::Position::new(1, 0),
10721                                        lsp::Position::new(2, 0),
10722                                    ),
10723                                    "".to_string(),
10724                                )],
10725                            )]
10726                            .into_iter()
10727                            .collect(),
10728                        ),
10729                        ..Default::default()
10730                    }),
10731                    ..Default::default()
10732                },
10733            )]))
10734        })
10735        .next()
10736        .await;
10737    cx.executor().start_waiting();
10738    format.await;
10739    assert_eq!(
10740        editor.update(cx, |editor, cx| editor.text(cx)),
10741        "import { a } from 'module';\n\nconst x = a;\n"
10742    );
10743
10744    editor.update_in(cx, |editor, window, cx| {
10745        editor.set_text(
10746            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
10747            window,
10748            cx,
10749        )
10750    });
10751    // Ensure we don't lock if code action hangs.
10752    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
10753        move |params, _| async move {
10754            assert_eq!(
10755                params.text_document.uri,
10756                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
10757            );
10758            futures::future::pending::<()>().await;
10759            unreachable!()
10760        },
10761    );
10762    let format = editor
10763        .update_in(cx, |editor, window, cx| {
10764            editor.perform_code_action_kind(
10765                project,
10766                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
10767                window,
10768                cx,
10769            )
10770        })
10771        .unwrap();
10772    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
10773    cx.executor().start_waiting();
10774    format.await;
10775    assert_eq!(
10776        editor.update(cx, |editor, cx| editor.text(cx)),
10777        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
10778    );
10779}
10780
10781#[gpui::test]
10782async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
10783    init_test(cx, |_| {});
10784
10785    let mut cx = EditorLspTestContext::new_rust(
10786        lsp::ServerCapabilities {
10787            document_formatting_provider: Some(lsp::OneOf::Left(true)),
10788            ..Default::default()
10789        },
10790        cx,
10791    )
10792    .await;
10793
10794    cx.set_state(indoc! {"
10795        one.twoˇ
10796    "});
10797
10798    // The format request takes a long time. When it completes, it inserts
10799    // a newline and an indent before the `.`
10800    cx.lsp
10801        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
10802            let executor = cx.background_executor().clone();
10803            async move {
10804                executor.timer(Duration::from_millis(100)).await;
10805                Ok(Some(vec![lsp::TextEdit {
10806                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
10807                    new_text: "\n    ".into(),
10808                }]))
10809            }
10810        });
10811
10812    // Submit a format request.
10813    let format_1 = cx
10814        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10815        .unwrap();
10816    cx.executor().run_until_parked();
10817
10818    // Submit a second format request.
10819    let format_2 = cx
10820        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10821        .unwrap();
10822    cx.executor().run_until_parked();
10823
10824    // Wait for both format requests to complete
10825    cx.executor().advance_clock(Duration::from_millis(200));
10826    cx.executor().start_waiting();
10827    format_1.await.unwrap();
10828    cx.executor().start_waiting();
10829    format_2.await.unwrap();
10830
10831    // The formatting edits only happens once.
10832    cx.assert_editor_state(indoc! {"
10833        one
10834            .twoˇ
10835    "});
10836}
10837
10838#[gpui::test]
10839async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
10840    init_test(cx, |settings| {
10841        settings.defaults.formatter = Some(SelectedFormatter::Auto)
10842    });
10843
10844    let mut cx = EditorLspTestContext::new_rust(
10845        lsp::ServerCapabilities {
10846            document_formatting_provider: Some(lsp::OneOf::Left(true)),
10847            ..Default::default()
10848        },
10849        cx,
10850    )
10851    .await;
10852
10853    // Set up a buffer white some trailing whitespace and no trailing newline.
10854    cx.set_state(
10855        &[
10856            "one ",   //
10857            "twoˇ",   //
10858            "three ", //
10859            "four",   //
10860        ]
10861        .join("\n"),
10862    );
10863
10864    // Submit a format request.
10865    let format = cx
10866        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10867        .unwrap();
10868
10869    // Record which buffer changes have been sent to the language server
10870    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
10871    cx.lsp
10872        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
10873            let buffer_changes = buffer_changes.clone();
10874            move |params, _| {
10875                buffer_changes.lock().extend(
10876                    params
10877                        .content_changes
10878                        .into_iter()
10879                        .map(|e| (e.range.unwrap(), e.text)),
10880                );
10881            }
10882        });
10883
10884    // Handle formatting requests to the language server.
10885    cx.lsp
10886        .set_request_handler::<lsp::request::Formatting, _, _>({
10887            let buffer_changes = buffer_changes.clone();
10888            move |_, _| {
10889                // When formatting is requested, trailing whitespace has already been stripped,
10890                // and the trailing newline has already been added.
10891                assert_eq!(
10892                    &buffer_changes.lock()[1..],
10893                    &[
10894                        (
10895                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
10896                            "".into()
10897                        ),
10898                        (
10899                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
10900                            "".into()
10901                        ),
10902                        (
10903                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
10904                            "\n".into()
10905                        ),
10906                    ]
10907                );
10908
10909                // Insert blank lines between each line of the buffer.
10910                async move {
10911                    Ok(Some(vec![
10912                        lsp::TextEdit {
10913                            range: lsp::Range::new(
10914                                lsp::Position::new(1, 0),
10915                                lsp::Position::new(1, 0),
10916                            ),
10917                            new_text: "\n".into(),
10918                        },
10919                        lsp::TextEdit {
10920                            range: lsp::Range::new(
10921                                lsp::Position::new(2, 0),
10922                                lsp::Position::new(2, 0),
10923                            ),
10924                            new_text: "\n".into(),
10925                        },
10926                    ]))
10927                }
10928            }
10929        });
10930
10931    // After formatting the buffer, the trailing whitespace is stripped,
10932    // a newline is appended, and the edits provided by the language server
10933    // have been applied.
10934    format.await.unwrap();
10935    cx.assert_editor_state(
10936        &[
10937            "one",   //
10938            "",      //
10939            "twoˇ",  //
10940            "",      //
10941            "three", //
10942            "four",  //
10943            "",      //
10944        ]
10945        .join("\n"),
10946    );
10947
10948    // Undoing the formatting undoes the trailing whitespace removal, the
10949    // trailing newline, and the LSP edits.
10950    cx.update_buffer(|buffer, cx| buffer.undo(cx));
10951    cx.assert_editor_state(
10952        &[
10953            "one ",   //
10954            "twoˇ",   //
10955            "three ", //
10956            "four",   //
10957        ]
10958        .join("\n"),
10959    );
10960}
10961
10962#[gpui::test]
10963async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
10964    cx: &mut TestAppContext,
10965) {
10966    init_test(cx, |_| {});
10967
10968    cx.update(|cx| {
10969        cx.update_global::<SettingsStore, _>(|settings, cx| {
10970            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10971                settings.auto_signature_help = Some(true);
10972            });
10973        });
10974    });
10975
10976    let mut cx = EditorLspTestContext::new_rust(
10977        lsp::ServerCapabilities {
10978            signature_help_provider: Some(lsp::SignatureHelpOptions {
10979                ..Default::default()
10980            }),
10981            ..Default::default()
10982        },
10983        cx,
10984    )
10985    .await;
10986
10987    let language = Language::new(
10988        LanguageConfig {
10989            name: "Rust".into(),
10990            brackets: BracketPairConfig {
10991                pairs: vec![
10992                    BracketPair {
10993                        start: "{".to_string(),
10994                        end: "}".to_string(),
10995                        close: true,
10996                        surround: true,
10997                        newline: true,
10998                    },
10999                    BracketPair {
11000                        start: "(".to_string(),
11001                        end: ")".to_string(),
11002                        close: true,
11003                        surround: true,
11004                        newline: true,
11005                    },
11006                    BracketPair {
11007                        start: "/*".to_string(),
11008                        end: " */".to_string(),
11009                        close: true,
11010                        surround: true,
11011                        newline: true,
11012                    },
11013                    BracketPair {
11014                        start: "[".to_string(),
11015                        end: "]".to_string(),
11016                        close: false,
11017                        surround: false,
11018                        newline: true,
11019                    },
11020                    BracketPair {
11021                        start: "\"".to_string(),
11022                        end: "\"".to_string(),
11023                        close: true,
11024                        surround: true,
11025                        newline: false,
11026                    },
11027                    BracketPair {
11028                        start: "<".to_string(),
11029                        end: ">".to_string(),
11030                        close: false,
11031                        surround: true,
11032                        newline: true,
11033                    },
11034                ],
11035                ..Default::default()
11036            },
11037            autoclose_before: "})]".to_string(),
11038            ..Default::default()
11039        },
11040        Some(tree_sitter_rust::LANGUAGE.into()),
11041    );
11042    let language = Arc::new(language);
11043
11044    cx.language_registry().add(language.clone());
11045    cx.update_buffer(|buffer, cx| {
11046        buffer.set_language(Some(language), cx);
11047    });
11048
11049    cx.set_state(
11050        &r#"
11051            fn main() {
11052                sampleˇ
11053            }
11054        "#
11055        .unindent(),
11056    );
11057
11058    cx.update_editor(|editor, window, cx| {
11059        editor.handle_input("(", window, cx);
11060    });
11061    cx.assert_editor_state(
11062        &"
11063            fn main() {
11064                sample(ˇ)
11065            }
11066        "
11067        .unindent(),
11068    );
11069
11070    let mocked_response = lsp::SignatureHelp {
11071        signatures: vec![lsp::SignatureInformation {
11072            label: "fn sample(param1: u8, param2: u8)".to_string(),
11073            documentation: None,
11074            parameters: Some(vec![
11075                lsp::ParameterInformation {
11076                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11077                    documentation: None,
11078                },
11079                lsp::ParameterInformation {
11080                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11081                    documentation: None,
11082                },
11083            ]),
11084            active_parameter: None,
11085        }],
11086        active_signature: Some(0),
11087        active_parameter: Some(0),
11088    };
11089    handle_signature_help_request(&mut cx, mocked_response).await;
11090
11091    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11092        .await;
11093
11094    cx.editor(|editor, _, _| {
11095        let signature_help_state = editor.signature_help_state.popover().cloned();
11096        let signature = signature_help_state.unwrap();
11097        assert_eq!(
11098            signature.signatures[signature.current_signature].label,
11099            "fn sample(param1: u8, param2: u8)"
11100        );
11101    });
11102}
11103
11104#[gpui::test]
11105async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
11106    init_test(cx, |_| {});
11107
11108    cx.update(|cx| {
11109        cx.update_global::<SettingsStore, _>(|settings, cx| {
11110            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11111                settings.auto_signature_help = Some(false);
11112                settings.show_signature_help_after_edits = Some(false);
11113            });
11114        });
11115    });
11116
11117    let mut cx = EditorLspTestContext::new_rust(
11118        lsp::ServerCapabilities {
11119            signature_help_provider: Some(lsp::SignatureHelpOptions {
11120                ..Default::default()
11121            }),
11122            ..Default::default()
11123        },
11124        cx,
11125    )
11126    .await;
11127
11128    let language = Language::new(
11129        LanguageConfig {
11130            name: "Rust".into(),
11131            brackets: BracketPairConfig {
11132                pairs: vec![
11133                    BracketPair {
11134                        start: "{".to_string(),
11135                        end: "}".to_string(),
11136                        close: true,
11137                        surround: true,
11138                        newline: true,
11139                    },
11140                    BracketPair {
11141                        start: "(".to_string(),
11142                        end: ")".to_string(),
11143                        close: true,
11144                        surround: true,
11145                        newline: true,
11146                    },
11147                    BracketPair {
11148                        start: "/*".to_string(),
11149                        end: " */".to_string(),
11150                        close: true,
11151                        surround: true,
11152                        newline: true,
11153                    },
11154                    BracketPair {
11155                        start: "[".to_string(),
11156                        end: "]".to_string(),
11157                        close: false,
11158                        surround: false,
11159                        newline: true,
11160                    },
11161                    BracketPair {
11162                        start: "\"".to_string(),
11163                        end: "\"".to_string(),
11164                        close: true,
11165                        surround: true,
11166                        newline: false,
11167                    },
11168                    BracketPair {
11169                        start: "<".to_string(),
11170                        end: ">".to_string(),
11171                        close: false,
11172                        surround: true,
11173                        newline: true,
11174                    },
11175                ],
11176                ..Default::default()
11177            },
11178            autoclose_before: "})]".to_string(),
11179            ..Default::default()
11180        },
11181        Some(tree_sitter_rust::LANGUAGE.into()),
11182    );
11183    let language = Arc::new(language);
11184
11185    cx.language_registry().add(language.clone());
11186    cx.update_buffer(|buffer, cx| {
11187        buffer.set_language(Some(language), cx);
11188    });
11189
11190    // Ensure that signature_help is not called when no signature help is enabled.
11191    cx.set_state(
11192        &r#"
11193            fn main() {
11194                sampleˇ
11195            }
11196        "#
11197        .unindent(),
11198    );
11199    cx.update_editor(|editor, window, cx| {
11200        editor.handle_input("(", window, cx);
11201    });
11202    cx.assert_editor_state(
11203        &"
11204            fn main() {
11205                sample(ˇ)
11206            }
11207        "
11208        .unindent(),
11209    );
11210    cx.editor(|editor, _, _| {
11211        assert!(editor.signature_help_state.task().is_none());
11212    });
11213
11214    let mocked_response = lsp::SignatureHelp {
11215        signatures: vec![lsp::SignatureInformation {
11216            label: "fn sample(param1: u8, param2: u8)".to_string(),
11217            documentation: None,
11218            parameters: Some(vec![
11219                lsp::ParameterInformation {
11220                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11221                    documentation: None,
11222                },
11223                lsp::ParameterInformation {
11224                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11225                    documentation: None,
11226                },
11227            ]),
11228            active_parameter: None,
11229        }],
11230        active_signature: Some(0),
11231        active_parameter: Some(0),
11232    };
11233
11234    // Ensure that signature_help is called when enabled afte edits
11235    cx.update(|_, cx| {
11236        cx.update_global::<SettingsStore, _>(|settings, cx| {
11237            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11238                settings.auto_signature_help = Some(false);
11239                settings.show_signature_help_after_edits = Some(true);
11240            });
11241        });
11242    });
11243    cx.set_state(
11244        &r#"
11245            fn main() {
11246                sampleˇ
11247            }
11248        "#
11249        .unindent(),
11250    );
11251    cx.update_editor(|editor, window, cx| {
11252        editor.handle_input("(", window, cx);
11253    });
11254    cx.assert_editor_state(
11255        &"
11256            fn main() {
11257                sample(ˇ)
11258            }
11259        "
11260        .unindent(),
11261    );
11262    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11263    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11264        .await;
11265    cx.update_editor(|editor, _, _| {
11266        let signature_help_state = editor.signature_help_state.popover().cloned();
11267        assert!(signature_help_state.is_some());
11268        let signature = signature_help_state.unwrap();
11269        assert_eq!(
11270            signature.signatures[signature.current_signature].label,
11271            "fn sample(param1: u8, param2: u8)"
11272        );
11273        editor.signature_help_state = SignatureHelpState::default();
11274    });
11275
11276    // Ensure that signature_help is called when auto signature help override is enabled
11277    cx.update(|_, cx| {
11278        cx.update_global::<SettingsStore, _>(|settings, cx| {
11279            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11280                settings.auto_signature_help = Some(true);
11281                settings.show_signature_help_after_edits = Some(false);
11282            });
11283        });
11284    });
11285    cx.set_state(
11286        &r#"
11287            fn main() {
11288                sampleˇ
11289            }
11290        "#
11291        .unindent(),
11292    );
11293    cx.update_editor(|editor, window, cx| {
11294        editor.handle_input("(", window, cx);
11295    });
11296    cx.assert_editor_state(
11297        &"
11298            fn main() {
11299                sample(ˇ)
11300            }
11301        "
11302        .unindent(),
11303    );
11304    handle_signature_help_request(&mut cx, mocked_response).await;
11305    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11306        .await;
11307    cx.editor(|editor, _, _| {
11308        let signature_help_state = editor.signature_help_state.popover().cloned();
11309        assert!(signature_help_state.is_some());
11310        let signature = signature_help_state.unwrap();
11311        assert_eq!(
11312            signature.signatures[signature.current_signature].label,
11313            "fn sample(param1: u8, param2: u8)"
11314        );
11315    });
11316}
11317
11318#[gpui::test]
11319async fn test_signature_help(cx: &mut TestAppContext) {
11320    init_test(cx, |_| {});
11321    cx.update(|cx| {
11322        cx.update_global::<SettingsStore, _>(|settings, cx| {
11323            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11324                settings.auto_signature_help = Some(true);
11325            });
11326        });
11327    });
11328
11329    let mut cx = EditorLspTestContext::new_rust(
11330        lsp::ServerCapabilities {
11331            signature_help_provider: Some(lsp::SignatureHelpOptions {
11332                ..Default::default()
11333            }),
11334            ..Default::default()
11335        },
11336        cx,
11337    )
11338    .await;
11339
11340    // A test that directly calls `show_signature_help`
11341    cx.update_editor(|editor, window, cx| {
11342        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11343    });
11344
11345    let mocked_response = lsp::SignatureHelp {
11346        signatures: vec![lsp::SignatureInformation {
11347            label: "fn sample(param1: u8, param2: u8)".to_string(),
11348            documentation: None,
11349            parameters: Some(vec![
11350                lsp::ParameterInformation {
11351                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11352                    documentation: None,
11353                },
11354                lsp::ParameterInformation {
11355                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11356                    documentation: None,
11357                },
11358            ]),
11359            active_parameter: None,
11360        }],
11361        active_signature: Some(0),
11362        active_parameter: Some(0),
11363    };
11364    handle_signature_help_request(&mut cx, mocked_response).await;
11365
11366    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11367        .await;
11368
11369    cx.editor(|editor, _, _| {
11370        let signature_help_state = editor.signature_help_state.popover().cloned();
11371        assert!(signature_help_state.is_some());
11372        let signature = signature_help_state.unwrap();
11373        assert_eq!(
11374            signature.signatures[signature.current_signature].label,
11375            "fn sample(param1: u8, param2: u8)"
11376        );
11377    });
11378
11379    // When exiting outside from inside the brackets, `signature_help` is closed.
11380    cx.set_state(indoc! {"
11381        fn main() {
11382            sample(ˇ);
11383        }
11384
11385        fn sample(param1: u8, param2: u8) {}
11386    "});
11387
11388    cx.update_editor(|editor, window, cx| {
11389        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11390            s.select_ranges([0..0])
11391        });
11392    });
11393
11394    let mocked_response = lsp::SignatureHelp {
11395        signatures: Vec::new(),
11396        active_signature: None,
11397        active_parameter: None,
11398    };
11399    handle_signature_help_request(&mut cx, mocked_response).await;
11400
11401    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11402        .await;
11403
11404    cx.editor(|editor, _, _| {
11405        assert!(!editor.signature_help_state.is_shown());
11406    });
11407
11408    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
11409    cx.set_state(indoc! {"
11410        fn main() {
11411            sample(ˇ);
11412        }
11413
11414        fn sample(param1: u8, param2: u8) {}
11415    "});
11416
11417    let mocked_response = lsp::SignatureHelp {
11418        signatures: vec![lsp::SignatureInformation {
11419            label: "fn sample(param1: u8, param2: u8)".to_string(),
11420            documentation: None,
11421            parameters: Some(vec![
11422                lsp::ParameterInformation {
11423                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11424                    documentation: None,
11425                },
11426                lsp::ParameterInformation {
11427                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11428                    documentation: None,
11429                },
11430            ]),
11431            active_parameter: None,
11432        }],
11433        active_signature: Some(0),
11434        active_parameter: Some(0),
11435    };
11436    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11437    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11438        .await;
11439    cx.editor(|editor, _, _| {
11440        assert!(editor.signature_help_state.is_shown());
11441    });
11442
11443    // Restore the popover with more parameter input
11444    cx.set_state(indoc! {"
11445        fn main() {
11446            sample(param1, param2ˇ);
11447        }
11448
11449        fn sample(param1: u8, param2: u8) {}
11450    "});
11451
11452    let mocked_response = lsp::SignatureHelp {
11453        signatures: vec![lsp::SignatureInformation {
11454            label: "fn sample(param1: u8, param2: u8)".to_string(),
11455            documentation: None,
11456            parameters: Some(vec![
11457                lsp::ParameterInformation {
11458                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11459                    documentation: None,
11460                },
11461                lsp::ParameterInformation {
11462                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11463                    documentation: None,
11464                },
11465            ]),
11466            active_parameter: None,
11467        }],
11468        active_signature: Some(0),
11469        active_parameter: Some(1),
11470    };
11471    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11472    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11473        .await;
11474
11475    // When selecting a range, the popover is gone.
11476    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
11477    cx.update_editor(|editor, window, cx| {
11478        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11479            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11480        })
11481    });
11482    cx.assert_editor_state(indoc! {"
11483        fn main() {
11484            sample(param1, «ˇparam2»);
11485        }
11486
11487        fn sample(param1: u8, param2: u8) {}
11488    "});
11489    cx.editor(|editor, _, _| {
11490        assert!(!editor.signature_help_state.is_shown());
11491    });
11492
11493    // When unselecting again, the popover is back if within the brackets.
11494    cx.update_editor(|editor, window, cx| {
11495        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11496            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11497        })
11498    });
11499    cx.assert_editor_state(indoc! {"
11500        fn main() {
11501            sample(param1, ˇparam2);
11502        }
11503
11504        fn sample(param1: u8, param2: u8) {}
11505    "});
11506    handle_signature_help_request(&mut cx, mocked_response).await;
11507    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11508        .await;
11509    cx.editor(|editor, _, _| {
11510        assert!(editor.signature_help_state.is_shown());
11511    });
11512
11513    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
11514    cx.update_editor(|editor, window, cx| {
11515        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11516            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
11517            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11518        })
11519    });
11520    cx.assert_editor_state(indoc! {"
11521        fn main() {
11522            sample(param1, ˇparam2);
11523        }
11524
11525        fn sample(param1: u8, param2: u8) {}
11526    "});
11527
11528    let mocked_response = lsp::SignatureHelp {
11529        signatures: vec![lsp::SignatureInformation {
11530            label: "fn sample(param1: u8, param2: u8)".to_string(),
11531            documentation: None,
11532            parameters: Some(vec![
11533                lsp::ParameterInformation {
11534                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11535                    documentation: None,
11536                },
11537                lsp::ParameterInformation {
11538                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11539                    documentation: None,
11540                },
11541            ]),
11542            active_parameter: None,
11543        }],
11544        active_signature: Some(0),
11545        active_parameter: Some(1),
11546    };
11547    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11548    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11549        .await;
11550    cx.update_editor(|editor, _, cx| {
11551        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
11552    });
11553    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11554        .await;
11555    cx.update_editor(|editor, window, cx| {
11556        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11557            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11558        })
11559    });
11560    cx.assert_editor_state(indoc! {"
11561        fn main() {
11562            sample(param1, «ˇparam2»);
11563        }
11564
11565        fn sample(param1: u8, param2: u8) {}
11566    "});
11567    cx.update_editor(|editor, window, cx| {
11568        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11569            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11570        })
11571    });
11572    cx.assert_editor_state(indoc! {"
11573        fn main() {
11574            sample(param1, ˇparam2);
11575        }
11576
11577        fn sample(param1: u8, param2: u8) {}
11578    "});
11579    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
11580        .await;
11581}
11582
11583#[gpui::test]
11584async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
11585    init_test(cx, |_| {});
11586
11587    let mut cx = EditorLspTestContext::new_rust(
11588        lsp::ServerCapabilities {
11589            signature_help_provider: Some(lsp::SignatureHelpOptions {
11590                ..Default::default()
11591            }),
11592            ..Default::default()
11593        },
11594        cx,
11595    )
11596    .await;
11597
11598    cx.set_state(indoc! {"
11599        fn main() {
11600            overloadedˇ
11601        }
11602    "});
11603
11604    cx.update_editor(|editor, window, cx| {
11605        editor.handle_input("(", window, cx);
11606        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11607    });
11608
11609    // Mock response with 3 signatures
11610    let mocked_response = lsp::SignatureHelp {
11611        signatures: vec![
11612            lsp::SignatureInformation {
11613                label: "fn overloaded(x: i32)".to_string(),
11614                documentation: None,
11615                parameters: Some(vec![lsp::ParameterInformation {
11616                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11617                    documentation: None,
11618                }]),
11619                active_parameter: None,
11620            },
11621            lsp::SignatureInformation {
11622                label: "fn overloaded(x: i32, y: i32)".to_string(),
11623                documentation: None,
11624                parameters: Some(vec![
11625                    lsp::ParameterInformation {
11626                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11627                        documentation: None,
11628                    },
11629                    lsp::ParameterInformation {
11630                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
11631                        documentation: None,
11632                    },
11633                ]),
11634                active_parameter: None,
11635            },
11636            lsp::SignatureInformation {
11637                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
11638                documentation: None,
11639                parameters: Some(vec![
11640                    lsp::ParameterInformation {
11641                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11642                        documentation: None,
11643                    },
11644                    lsp::ParameterInformation {
11645                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
11646                        documentation: None,
11647                    },
11648                    lsp::ParameterInformation {
11649                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
11650                        documentation: None,
11651                    },
11652                ]),
11653                active_parameter: None,
11654            },
11655        ],
11656        active_signature: Some(1),
11657        active_parameter: Some(0),
11658    };
11659    handle_signature_help_request(&mut cx, mocked_response).await;
11660
11661    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11662        .await;
11663
11664    // Verify we have multiple signatures and the right one is selected
11665    cx.editor(|editor, _, _| {
11666        let popover = editor.signature_help_state.popover().cloned().unwrap();
11667        assert_eq!(popover.signatures.len(), 3);
11668        // active_signature was 1, so that should be the current
11669        assert_eq!(popover.current_signature, 1);
11670        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
11671        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
11672        assert_eq!(
11673            popover.signatures[2].label,
11674            "fn overloaded(x: i32, y: i32, z: i32)"
11675        );
11676    });
11677
11678    // Test navigation functionality
11679    cx.update_editor(|editor, window, cx| {
11680        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
11681    });
11682
11683    cx.editor(|editor, _, _| {
11684        let popover = editor.signature_help_state.popover().cloned().unwrap();
11685        assert_eq!(popover.current_signature, 2);
11686    });
11687
11688    // Test wrap around
11689    cx.update_editor(|editor, window, cx| {
11690        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
11691    });
11692
11693    cx.editor(|editor, _, _| {
11694        let popover = editor.signature_help_state.popover().cloned().unwrap();
11695        assert_eq!(popover.current_signature, 0);
11696    });
11697
11698    // Test previous navigation
11699    cx.update_editor(|editor, window, cx| {
11700        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
11701    });
11702
11703    cx.editor(|editor, _, _| {
11704        let popover = editor.signature_help_state.popover().cloned().unwrap();
11705        assert_eq!(popover.current_signature, 2);
11706    });
11707}
11708
11709#[gpui::test]
11710async fn test_completion_mode(cx: &mut TestAppContext) {
11711    init_test(cx, |_| {});
11712    let mut cx = EditorLspTestContext::new_rust(
11713        lsp::ServerCapabilities {
11714            completion_provider: Some(lsp::CompletionOptions {
11715                resolve_provider: Some(true),
11716                ..Default::default()
11717            }),
11718            ..Default::default()
11719        },
11720        cx,
11721    )
11722    .await;
11723
11724    struct Run {
11725        run_description: &'static str,
11726        initial_state: String,
11727        buffer_marked_text: String,
11728        completion_label: &'static str,
11729        completion_text: &'static str,
11730        expected_with_insert_mode: String,
11731        expected_with_replace_mode: String,
11732        expected_with_replace_subsequence_mode: String,
11733        expected_with_replace_suffix_mode: String,
11734    }
11735
11736    let runs = [
11737        Run {
11738            run_description: "Start of word matches completion text",
11739            initial_state: "before ediˇ after".into(),
11740            buffer_marked_text: "before <edi|> after".into(),
11741            completion_label: "editor",
11742            completion_text: "editor",
11743            expected_with_insert_mode: "before editorˇ after".into(),
11744            expected_with_replace_mode: "before editorˇ after".into(),
11745            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11746            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11747        },
11748        Run {
11749            run_description: "Accept same text at the middle of the word",
11750            initial_state: "before ediˇtor after".into(),
11751            buffer_marked_text: "before <edi|tor> after".into(),
11752            completion_label: "editor",
11753            completion_text: "editor",
11754            expected_with_insert_mode: "before editorˇtor after".into(),
11755            expected_with_replace_mode: "before editorˇ after".into(),
11756            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11757            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11758        },
11759        Run {
11760            run_description: "End of word matches completion text -- cursor at end",
11761            initial_state: "before torˇ after".into(),
11762            buffer_marked_text: "before <tor|> after".into(),
11763            completion_label: "editor",
11764            completion_text: "editor",
11765            expected_with_insert_mode: "before editorˇ after".into(),
11766            expected_with_replace_mode: "before editorˇ after".into(),
11767            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11768            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11769        },
11770        Run {
11771            run_description: "End of word matches completion text -- cursor at start",
11772            initial_state: "before ˇtor after".into(),
11773            buffer_marked_text: "before <|tor> after".into(),
11774            completion_label: "editor",
11775            completion_text: "editor",
11776            expected_with_insert_mode: "before editorˇtor after".into(),
11777            expected_with_replace_mode: "before editorˇ after".into(),
11778            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11779            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11780        },
11781        Run {
11782            run_description: "Prepend text containing whitespace",
11783            initial_state: "pˇfield: bool".into(),
11784            buffer_marked_text: "<p|field>: bool".into(),
11785            completion_label: "pub ",
11786            completion_text: "pub ",
11787            expected_with_insert_mode: "pub ˇfield: bool".into(),
11788            expected_with_replace_mode: "pub ˇ: bool".into(),
11789            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
11790            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
11791        },
11792        Run {
11793            run_description: "Add element to start of list",
11794            initial_state: "[element_ˇelement_2]".into(),
11795            buffer_marked_text: "[<element_|element_2>]".into(),
11796            completion_label: "element_1",
11797            completion_text: "element_1",
11798            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
11799            expected_with_replace_mode: "[element_1ˇ]".into(),
11800            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
11801            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
11802        },
11803        Run {
11804            run_description: "Add element to start of list -- first and second elements are equal",
11805            initial_state: "[elˇelement]".into(),
11806            buffer_marked_text: "[<el|element>]".into(),
11807            completion_label: "element",
11808            completion_text: "element",
11809            expected_with_insert_mode: "[elementˇelement]".into(),
11810            expected_with_replace_mode: "[elementˇ]".into(),
11811            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
11812            expected_with_replace_suffix_mode: "[elementˇ]".into(),
11813        },
11814        Run {
11815            run_description: "Ends with matching suffix",
11816            initial_state: "SubˇError".into(),
11817            buffer_marked_text: "<Sub|Error>".into(),
11818            completion_label: "SubscriptionError",
11819            completion_text: "SubscriptionError",
11820            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
11821            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11822            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11823            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
11824        },
11825        Run {
11826            run_description: "Suffix is a subsequence -- contiguous",
11827            initial_state: "SubˇErr".into(),
11828            buffer_marked_text: "<Sub|Err>".into(),
11829            completion_label: "SubscriptionError",
11830            completion_text: "SubscriptionError",
11831            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
11832            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11833            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11834            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
11835        },
11836        Run {
11837            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
11838            initial_state: "Suˇscrirr".into(),
11839            buffer_marked_text: "<Su|scrirr>".into(),
11840            completion_label: "SubscriptionError",
11841            completion_text: "SubscriptionError",
11842            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
11843            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11844            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11845            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
11846        },
11847        Run {
11848            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
11849            initial_state: "foo(indˇix)".into(),
11850            buffer_marked_text: "foo(<ind|ix>)".into(),
11851            completion_label: "node_index",
11852            completion_text: "node_index",
11853            expected_with_insert_mode: "foo(node_indexˇix)".into(),
11854            expected_with_replace_mode: "foo(node_indexˇ)".into(),
11855            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
11856            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
11857        },
11858        Run {
11859            run_description: "Replace range ends before cursor - should extend to cursor",
11860            initial_state: "before editˇo after".into(),
11861            buffer_marked_text: "before <{ed}>it|o after".into(),
11862            completion_label: "editor",
11863            completion_text: "editor",
11864            expected_with_insert_mode: "before editorˇo after".into(),
11865            expected_with_replace_mode: "before editorˇo after".into(),
11866            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
11867            expected_with_replace_suffix_mode: "before editorˇo after".into(),
11868        },
11869        Run {
11870            run_description: "Uses label for suffix matching",
11871            initial_state: "before ediˇtor after".into(),
11872            buffer_marked_text: "before <edi|tor> after".into(),
11873            completion_label: "editor",
11874            completion_text: "editor()",
11875            expected_with_insert_mode: "before editor()ˇtor after".into(),
11876            expected_with_replace_mode: "before editor()ˇ after".into(),
11877            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
11878            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
11879        },
11880        Run {
11881            run_description: "Case insensitive subsequence and suffix matching",
11882            initial_state: "before EDiˇtoR after".into(),
11883            buffer_marked_text: "before <EDi|toR> after".into(),
11884            completion_label: "editor",
11885            completion_text: "editor",
11886            expected_with_insert_mode: "before editorˇtoR after".into(),
11887            expected_with_replace_mode: "before editorˇ after".into(),
11888            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11889            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11890        },
11891    ];
11892
11893    for run in runs {
11894        let run_variations = [
11895            (LspInsertMode::Insert, run.expected_with_insert_mode),
11896            (LspInsertMode::Replace, run.expected_with_replace_mode),
11897            (
11898                LspInsertMode::ReplaceSubsequence,
11899                run.expected_with_replace_subsequence_mode,
11900            ),
11901            (
11902                LspInsertMode::ReplaceSuffix,
11903                run.expected_with_replace_suffix_mode,
11904            ),
11905        ];
11906
11907        for (lsp_insert_mode, expected_text) in run_variations {
11908            eprintln!(
11909                "run = {:?}, mode = {lsp_insert_mode:.?}",
11910                run.run_description,
11911            );
11912
11913            update_test_language_settings(&mut cx, |settings| {
11914                settings.defaults.completions = Some(CompletionSettings {
11915                    lsp_insert_mode,
11916                    words: WordsCompletionMode::Disabled,
11917                    lsp: true,
11918                    lsp_fetch_timeout_ms: 0,
11919                });
11920            });
11921
11922            cx.set_state(&run.initial_state);
11923            cx.update_editor(|editor, window, cx| {
11924                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11925            });
11926
11927            let counter = Arc::new(AtomicUsize::new(0));
11928            handle_completion_request_with_insert_and_replace(
11929                &mut cx,
11930                &run.buffer_marked_text,
11931                vec![(run.completion_label, run.completion_text)],
11932                counter.clone(),
11933            )
11934            .await;
11935            cx.condition(|editor, _| editor.context_menu_visible())
11936                .await;
11937            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11938
11939            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11940                editor
11941                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
11942                    .unwrap()
11943            });
11944            cx.assert_editor_state(&expected_text);
11945            handle_resolve_completion_request(&mut cx, None).await;
11946            apply_additional_edits.await.unwrap();
11947        }
11948    }
11949}
11950
11951#[gpui::test]
11952async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
11953    init_test(cx, |_| {});
11954    let mut cx = EditorLspTestContext::new_rust(
11955        lsp::ServerCapabilities {
11956            completion_provider: Some(lsp::CompletionOptions {
11957                resolve_provider: Some(true),
11958                ..Default::default()
11959            }),
11960            ..Default::default()
11961        },
11962        cx,
11963    )
11964    .await;
11965
11966    let initial_state = "SubˇError";
11967    let buffer_marked_text = "<Sub|Error>";
11968    let completion_text = "SubscriptionError";
11969    let expected_with_insert_mode = "SubscriptionErrorˇError";
11970    let expected_with_replace_mode = "SubscriptionErrorˇ";
11971
11972    update_test_language_settings(&mut cx, |settings| {
11973        settings.defaults.completions = Some(CompletionSettings {
11974            words: WordsCompletionMode::Disabled,
11975            // set the opposite here to ensure that the action is overriding the default behavior
11976            lsp_insert_mode: LspInsertMode::Insert,
11977            lsp: true,
11978            lsp_fetch_timeout_ms: 0,
11979        });
11980    });
11981
11982    cx.set_state(initial_state);
11983    cx.update_editor(|editor, window, cx| {
11984        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11985    });
11986
11987    let counter = Arc::new(AtomicUsize::new(0));
11988    handle_completion_request_with_insert_and_replace(
11989        &mut cx,
11990        &buffer_marked_text,
11991        vec![(completion_text, completion_text)],
11992        counter.clone(),
11993    )
11994    .await;
11995    cx.condition(|editor, _| editor.context_menu_visible())
11996        .await;
11997    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11998
11999    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12000        editor
12001            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12002            .unwrap()
12003    });
12004    cx.assert_editor_state(&expected_with_replace_mode);
12005    handle_resolve_completion_request(&mut cx, None).await;
12006    apply_additional_edits.await.unwrap();
12007
12008    update_test_language_settings(&mut cx, |settings| {
12009        settings.defaults.completions = Some(CompletionSettings {
12010            words: WordsCompletionMode::Disabled,
12011            // set the opposite here to ensure that the action is overriding the default behavior
12012            lsp_insert_mode: LspInsertMode::Replace,
12013            lsp: true,
12014            lsp_fetch_timeout_ms: 0,
12015        });
12016    });
12017
12018    cx.set_state(initial_state);
12019    cx.update_editor(|editor, window, cx| {
12020        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12021    });
12022    handle_completion_request_with_insert_and_replace(
12023        &mut cx,
12024        &buffer_marked_text,
12025        vec![(completion_text, completion_text)],
12026        counter.clone(),
12027    )
12028    .await;
12029    cx.condition(|editor, _| editor.context_menu_visible())
12030        .await;
12031    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12032
12033    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12034        editor
12035            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
12036            .unwrap()
12037    });
12038    cx.assert_editor_state(&expected_with_insert_mode);
12039    handle_resolve_completion_request(&mut cx, None).await;
12040    apply_additional_edits.await.unwrap();
12041}
12042
12043#[gpui::test]
12044async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
12045    init_test(cx, |_| {});
12046    let mut cx = EditorLspTestContext::new_rust(
12047        lsp::ServerCapabilities {
12048            completion_provider: Some(lsp::CompletionOptions {
12049                resolve_provider: Some(true),
12050                ..Default::default()
12051            }),
12052            ..Default::default()
12053        },
12054        cx,
12055    )
12056    .await;
12057
12058    // scenario: surrounding text matches completion text
12059    let completion_text = "to_offset";
12060    let initial_state = indoc! {"
12061        1. buf.to_offˇsuffix
12062        2. buf.to_offˇsuf
12063        3. buf.to_offˇfix
12064        4. buf.to_offˇ
12065        5. into_offˇensive
12066        6. ˇsuffix
12067        7. let ˇ //
12068        8. aaˇzz
12069        9. buf.to_off«zzzzzˇ»suffix
12070        10. buf.«ˇzzzzz»suffix
12071        11. to_off«ˇzzzzz»
12072
12073        buf.to_offˇsuffix  // newest cursor
12074    "};
12075    let completion_marked_buffer = indoc! {"
12076        1. buf.to_offsuffix
12077        2. buf.to_offsuf
12078        3. buf.to_offfix
12079        4. buf.to_off
12080        5. into_offensive
12081        6. suffix
12082        7. let  //
12083        8. aazz
12084        9. buf.to_offzzzzzsuffix
12085        10. buf.zzzzzsuffix
12086        11. to_offzzzzz
12087
12088        buf.<to_off|suffix>  // newest cursor
12089    "};
12090    let expected = indoc! {"
12091        1. buf.to_offsetˇ
12092        2. buf.to_offsetˇsuf
12093        3. buf.to_offsetˇfix
12094        4. buf.to_offsetˇ
12095        5. into_offsetˇensive
12096        6. to_offsetˇsuffix
12097        7. let to_offsetˇ //
12098        8. aato_offsetˇzz
12099        9. buf.to_offsetˇ
12100        10. buf.to_offsetˇsuffix
12101        11. to_offsetˇ
12102
12103        buf.to_offsetˇ  // newest cursor
12104    "};
12105    cx.set_state(initial_state);
12106    cx.update_editor(|editor, window, cx| {
12107        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12108    });
12109    handle_completion_request_with_insert_and_replace(
12110        &mut cx,
12111        completion_marked_buffer,
12112        vec![(completion_text, completion_text)],
12113        Arc::new(AtomicUsize::new(0)),
12114    )
12115    .await;
12116    cx.condition(|editor, _| editor.context_menu_visible())
12117        .await;
12118    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12119        editor
12120            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12121            .unwrap()
12122    });
12123    cx.assert_editor_state(expected);
12124    handle_resolve_completion_request(&mut cx, None).await;
12125    apply_additional_edits.await.unwrap();
12126
12127    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
12128    let completion_text = "foo_and_bar";
12129    let initial_state = indoc! {"
12130        1. ooanbˇ
12131        2. zooanbˇ
12132        3. ooanbˇz
12133        4. zooanbˇz
12134        5. ooanˇ
12135        6. oanbˇ
12136
12137        ooanbˇ
12138    "};
12139    let completion_marked_buffer = indoc! {"
12140        1. ooanb
12141        2. zooanb
12142        3. ooanbz
12143        4. zooanbz
12144        5. ooan
12145        6. oanb
12146
12147        <ooanb|>
12148    "};
12149    let expected = indoc! {"
12150        1. foo_and_barˇ
12151        2. zfoo_and_barˇ
12152        3. foo_and_barˇz
12153        4. zfoo_and_barˇz
12154        5. ooanfoo_and_barˇ
12155        6. oanbfoo_and_barˇ
12156
12157        foo_and_barˇ
12158    "};
12159    cx.set_state(initial_state);
12160    cx.update_editor(|editor, window, cx| {
12161        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12162    });
12163    handle_completion_request_with_insert_and_replace(
12164        &mut cx,
12165        completion_marked_buffer,
12166        vec![(completion_text, completion_text)],
12167        Arc::new(AtomicUsize::new(0)),
12168    )
12169    .await;
12170    cx.condition(|editor, _| editor.context_menu_visible())
12171        .await;
12172    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12173        editor
12174            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12175            .unwrap()
12176    });
12177    cx.assert_editor_state(expected);
12178    handle_resolve_completion_request(&mut cx, None).await;
12179    apply_additional_edits.await.unwrap();
12180
12181    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
12182    // (expects the same as if it was inserted at the end)
12183    let completion_text = "foo_and_bar";
12184    let initial_state = indoc! {"
12185        1. ooˇanb
12186        2. zooˇanb
12187        3. ooˇanbz
12188        4. zooˇanbz
12189
12190        ooˇanb
12191    "};
12192    let completion_marked_buffer = indoc! {"
12193        1. ooanb
12194        2. zooanb
12195        3. ooanbz
12196        4. zooanbz
12197
12198        <oo|anb>
12199    "};
12200    let expected = indoc! {"
12201        1. foo_and_barˇ
12202        2. zfoo_and_barˇ
12203        3. foo_and_barˇz
12204        4. zfoo_and_barˇz
12205
12206        foo_and_barˇ
12207    "};
12208    cx.set_state(initial_state);
12209    cx.update_editor(|editor, window, cx| {
12210        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12211    });
12212    handle_completion_request_with_insert_and_replace(
12213        &mut cx,
12214        completion_marked_buffer,
12215        vec![(completion_text, completion_text)],
12216        Arc::new(AtomicUsize::new(0)),
12217    )
12218    .await;
12219    cx.condition(|editor, _| editor.context_menu_visible())
12220        .await;
12221    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12222        editor
12223            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12224            .unwrap()
12225    });
12226    cx.assert_editor_state(expected);
12227    handle_resolve_completion_request(&mut cx, None).await;
12228    apply_additional_edits.await.unwrap();
12229}
12230
12231// This used to crash
12232#[gpui::test]
12233async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
12234    init_test(cx, |_| {});
12235
12236    let buffer_text = indoc! {"
12237        fn main() {
12238            10.satu;
12239
12240            //
12241            // separate cursors so they open in different excerpts (manually reproducible)
12242            //
12243
12244            10.satu20;
12245        }
12246    "};
12247    let multibuffer_text_with_selections = indoc! {"
12248        fn main() {
12249            10.satuˇ;
12250
12251            //
12252
12253            //
12254
12255            10.satuˇ20;
12256        }
12257    "};
12258    let expected_multibuffer = indoc! {"
12259        fn main() {
12260            10.saturating_sub()ˇ;
12261
12262            //
12263
12264            //
12265
12266            10.saturating_sub()ˇ;
12267        }
12268    "};
12269
12270    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
12271    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
12272
12273    let fs = FakeFs::new(cx.executor());
12274    fs.insert_tree(
12275        path!("/a"),
12276        json!({
12277            "main.rs": buffer_text,
12278        }),
12279    )
12280    .await;
12281
12282    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12283    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12284    language_registry.add(rust_lang());
12285    let mut fake_servers = language_registry.register_fake_lsp(
12286        "Rust",
12287        FakeLspAdapter {
12288            capabilities: lsp::ServerCapabilities {
12289                completion_provider: Some(lsp::CompletionOptions {
12290                    resolve_provider: None,
12291                    ..lsp::CompletionOptions::default()
12292                }),
12293                ..lsp::ServerCapabilities::default()
12294            },
12295            ..FakeLspAdapter::default()
12296        },
12297    );
12298    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12299    let cx = &mut VisualTestContext::from_window(*workspace, cx);
12300    let buffer = project
12301        .update(cx, |project, cx| {
12302            project.open_local_buffer(path!("/a/main.rs"), cx)
12303        })
12304        .await
12305        .unwrap();
12306
12307    let multi_buffer = cx.new(|cx| {
12308        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
12309        multi_buffer.push_excerpts(
12310            buffer.clone(),
12311            [ExcerptRange::new(0..first_excerpt_end)],
12312            cx,
12313        );
12314        multi_buffer.push_excerpts(
12315            buffer.clone(),
12316            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
12317            cx,
12318        );
12319        multi_buffer
12320    });
12321
12322    let editor = workspace
12323        .update(cx, |_, window, cx| {
12324            cx.new(|cx| {
12325                Editor::new(
12326                    EditorMode::Full {
12327                        scale_ui_elements_with_buffer_font_size: false,
12328                        show_active_line_background: false,
12329                        sized_by_content: false,
12330                    },
12331                    multi_buffer.clone(),
12332                    Some(project.clone()),
12333                    window,
12334                    cx,
12335                )
12336            })
12337        })
12338        .unwrap();
12339
12340    let pane = workspace
12341        .update(cx, |workspace, _, _| workspace.active_pane().clone())
12342        .unwrap();
12343    pane.update_in(cx, |pane, window, cx| {
12344        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
12345    });
12346
12347    let fake_server = fake_servers.next().await.unwrap();
12348
12349    editor.update_in(cx, |editor, window, cx| {
12350        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12351            s.select_ranges([
12352                Point::new(1, 11)..Point::new(1, 11),
12353                Point::new(7, 11)..Point::new(7, 11),
12354            ])
12355        });
12356
12357        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
12358    });
12359
12360    editor.update_in(cx, |editor, window, cx| {
12361        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12362    });
12363
12364    fake_server
12365        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
12366            let completion_item = lsp::CompletionItem {
12367                label: "saturating_sub()".into(),
12368                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
12369                    lsp::InsertReplaceEdit {
12370                        new_text: "saturating_sub()".to_owned(),
12371                        insert: lsp::Range::new(
12372                            lsp::Position::new(7, 7),
12373                            lsp::Position::new(7, 11),
12374                        ),
12375                        replace: lsp::Range::new(
12376                            lsp::Position::new(7, 7),
12377                            lsp::Position::new(7, 13),
12378                        ),
12379                    },
12380                )),
12381                ..lsp::CompletionItem::default()
12382            };
12383
12384            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
12385        })
12386        .next()
12387        .await
12388        .unwrap();
12389
12390    cx.condition(&editor, |editor, _| editor.context_menu_visible())
12391        .await;
12392
12393    editor
12394        .update_in(cx, |editor, window, cx| {
12395            editor
12396                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12397                .unwrap()
12398        })
12399        .await
12400        .unwrap();
12401
12402    editor.update(cx, |editor, cx| {
12403        assert_text_with_selections(editor, expected_multibuffer, cx);
12404    })
12405}
12406
12407#[gpui::test]
12408async fn test_completion(cx: &mut TestAppContext) {
12409    init_test(cx, |_| {});
12410
12411    let mut cx = EditorLspTestContext::new_rust(
12412        lsp::ServerCapabilities {
12413            completion_provider: Some(lsp::CompletionOptions {
12414                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12415                resolve_provider: Some(true),
12416                ..Default::default()
12417            }),
12418            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12419            ..Default::default()
12420        },
12421        cx,
12422    )
12423    .await;
12424    let counter = Arc::new(AtomicUsize::new(0));
12425
12426    cx.set_state(indoc! {"
12427        oneˇ
12428        two
12429        three
12430    "});
12431    cx.simulate_keystroke(".");
12432    handle_completion_request(
12433        indoc! {"
12434            one.|<>
12435            two
12436            three
12437        "},
12438        vec!["first_completion", "second_completion"],
12439        true,
12440        counter.clone(),
12441        &mut cx,
12442    )
12443    .await;
12444    cx.condition(|editor, _| editor.context_menu_visible())
12445        .await;
12446    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12447
12448    let _handler = handle_signature_help_request(
12449        &mut cx,
12450        lsp::SignatureHelp {
12451            signatures: vec![lsp::SignatureInformation {
12452                label: "test signature".to_string(),
12453                documentation: None,
12454                parameters: Some(vec![lsp::ParameterInformation {
12455                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
12456                    documentation: None,
12457                }]),
12458                active_parameter: None,
12459            }],
12460            active_signature: None,
12461            active_parameter: None,
12462        },
12463    );
12464    cx.update_editor(|editor, window, cx| {
12465        assert!(
12466            !editor.signature_help_state.is_shown(),
12467            "No signature help was called for"
12468        );
12469        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12470    });
12471    cx.run_until_parked();
12472    cx.update_editor(|editor, _, _| {
12473        assert!(
12474            !editor.signature_help_state.is_shown(),
12475            "No signature help should be shown when completions menu is open"
12476        );
12477    });
12478
12479    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12480        editor.context_menu_next(&Default::default(), window, cx);
12481        editor
12482            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12483            .unwrap()
12484    });
12485    cx.assert_editor_state(indoc! {"
12486        one.second_completionˇ
12487        two
12488        three
12489    "});
12490
12491    handle_resolve_completion_request(
12492        &mut cx,
12493        Some(vec![
12494            (
12495                //This overlaps with the primary completion edit which is
12496                //misbehavior from the LSP spec, test that we filter it out
12497                indoc! {"
12498                    one.second_ˇcompletion
12499                    two
12500                    threeˇ
12501                "},
12502                "overlapping additional edit",
12503            ),
12504            (
12505                indoc! {"
12506                    one.second_completion
12507                    two
12508                    threeˇ
12509                "},
12510                "\nadditional edit",
12511            ),
12512        ]),
12513    )
12514    .await;
12515    apply_additional_edits.await.unwrap();
12516    cx.assert_editor_state(indoc! {"
12517        one.second_completionˇ
12518        two
12519        three
12520        additional edit
12521    "});
12522
12523    cx.set_state(indoc! {"
12524        one.second_completion
12525        twoˇ
12526        threeˇ
12527        additional edit
12528    "});
12529    cx.simulate_keystroke(" ");
12530    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12531    cx.simulate_keystroke("s");
12532    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12533
12534    cx.assert_editor_state(indoc! {"
12535        one.second_completion
12536        two sˇ
12537        three sˇ
12538        additional edit
12539    "});
12540    handle_completion_request(
12541        indoc! {"
12542            one.second_completion
12543            two s
12544            three <s|>
12545            additional edit
12546        "},
12547        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
12548        true,
12549        counter.clone(),
12550        &mut cx,
12551    )
12552    .await;
12553    cx.condition(|editor, _| editor.context_menu_visible())
12554        .await;
12555    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12556
12557    cx.simulate_keystroke("i");
12558
12559    handle_completion_request(
12560        indoc! {"
12561            one.second_completion
12562            two si
12563            three <si|>
12564            additional edit
12565        "},
12566        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
12567        true,
12568        counter.clone(),
12569        &mut cx,
12570    )
12571    .await;
12572    cx.condition(|editor, _| editor.context_menu_visible())
12573        .await;
12574    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12575
12576    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12577        editor
12578            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12579            .unwrap()
12580    });
12581    cx.assert_editor_state(indoc! {"
12582        one.second_completion
12583        two sixth_completionˇ
12584        three sixth_completionˇ
12585        additional edit
12586    "});
12587
12588    apply_additional_edits.await.unwrap();
12589
12590    update_test_language_settings(&mut cx, |settings| {
12591        settings.defaults.show_completions_on_input = Some(false);
12592    });
12593    cx.set_state("editorˇ");
12594    cx.simulate_keystroke(".");
12595    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12596    cx.simulate_keystrokes("c l o");
12597    cx.assert_editor_state("editor.cloˇ");
12598    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12599    cx.update_editor(|editor, window, cx| {
12600        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12601    });
12602    handle_completion_request(
12603        "editor.<clo|>",
12604        vec!["close", "clobber"],
12605        true,
12606        counter.clone(),
12607        &mut cx,
12608    )
12609    .await;
12610    cx.condition(|editor, _| editor.context_menu_visible())
12611        .await;
12612    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
12613
12614    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12615        editor
12616            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12617            .unwrap()
12618    });
12619    cx.assert_editor_state("editor.clobberˇ");
12620    handle_resolve_completion_request(&mut cx, None).await;
12621    apply_additional_edits.await.unwrap();
12622}
12623
12624#[gpui::test]
12625async fn test_completion_reuse(cx: &mut TestAppContext) {
12626    init_test(cx, |_| {});
12627
12628    let mut cx = EditorLspTestContext::new_rust(
12629        lsp::ServerCapabilities {
12630            completion_provider: Some(lsp::CompletionOptions {
12631                trigger_characters: Some(vec![".".to_string()]),
12632                ..Default::default()
12633            }),
12634            ..Default::default()
12635        },
12636        cx,
12637    )
12638    .await;
12639
12640    let counter = Arc::new(AtomicUsize::new(0));
12641    cx.set_state("objˇ");
12642    cx.simulate_keystroke(".");
12643
12644    // Initial completion request returns complete results
12645    let is_incomplete = false;
12646    handle_completion_request(
12647        "obj.|<>",
12648        vec!["a", "ab", "abc"],
12649        is_incomplete,
12650        counter.clone(),
12651        &mut cx,
12652    )
12653    .await;
12654    cx.run_until_parked();
12655    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12656    cx.assert_editor_state("obj.ˇ");
12657    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12658
12659    // Type "a" - filters existing completions
12660    cx.simulate_keystroke("a");
12661    cx.run_until_parked();
12662    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12663    cx.assert_editor_state("obj.aˇ");
12664    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12665
12666    // Type "b" - filters existing completions
12667    cx.simulate_keystroke("b");
12668    cx.run_until_parked();
12669    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12670    cx.assert_editor_state("obj.abˇ");
12671    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12672
12673    // Type "c" - filters existing completions
12674    cx.simulate_keystroke("c");
12675    cx.run_until_parked();
12676    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12677    cx.assert_editor_state("obj.abcˇ");
12678    check_displayed_completions(vec!["abc"], &mut cx);
12679
12680    // Backspace to delete "c" - filters existing completions
12681    cx.update_editor(|editor, window, cx| {
12682        editor.backspace(&Backspace, window, cx);
12683    });
12684    cx.run_until_parked();
12685    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12686    cx.assert_editor_state("obj.abˇ");
12687    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12688
12689    // Moving cursor to the left dismisses menu.
12690    cx.update_editor(|editor, window, cx| {
12691        editor.move_left(&MoveLeft, window, cx);
12692    });
12693    cx.run_until_parked();
12694    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12695    cx.assert_editor_state("obj.aˇb");
12696    cx.update_editor(|editor, _, _| {
12697        assert_eq!(editor.context_menu_visible(), false);
12698    });
12699
12700    // Type "b" - new request
12701    cx.simulate_keystroke("b");
12702    let is_incomplete = false;
12703    handle_completion_request(
12704        "obj.<ab|>a",
12705        vec!["ab", "abc"],
12706        is_incomplete,
12707        counter.clone(),
12708        &mut cx,
12709    )
12710    .await;
12711    cx.run_until_parked();
12712    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12713    cx.assert_editor_state("obj.abˇb");
12714    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12715
12716    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
12717    cx.update_editor(|editor, window, cx| {
12718        editor.backspace(&Backspace, window, cx);
12719    });
12720    let is_incomplete = false;
12721    handle_completion_request(
12722        "obj.<a|>b",
12723        vec!["a", "ab", "abc"],
12724        is_incomplete,
12725        counter.clone(),
12726        &mut cx,
12727    )
12728    .await;
12729    cx.run_until_parked();
12730    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12731    cx.assert_editor_state("obj.aˇb");
12732    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12733
12734    // Backspace to delete "a" - dismisses menu.
12735    cx.update_editor(|editor, window, cx| {
12736        editor.backspace(&Backspace, window, cx);
12737    });
12738    cx.run_until_parked();
12739    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12740    cx.assert_editor_state("obj.ˇb");
12741    cx.update_editor(|editor, _, _| {
12742        assert_eq!(editor.context_menu_visible(), false);
12743    });
12744}
12745
12746#[gpui::test]
12747async fn test_word_completion(cx: &mut TestAppContext) {
12748    let lsp_fetch_timeout_ms = 10;
12749    init_test(cx, |language_settings| {
12750        language_settings.defaults.completions = Some(CompletionSettings {
12751            words: WordsCompletionMode::Fallback,
12752            lsp: true,
12753            lsp_fetch_timeout_ms: 10,
12754            lsp_insert_mode: LspInsertMode::Insert,
12755        });
12756    });
12757
12758    let mut cx = EditorLspTestContext::new_rust(
12759        lsp::ServerCapabilities {
12760            completion_provider: Some(lsp::CompletionOptions {
12761                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12762                ..lsp::CompletionOptions::default()
12763            }),
12764            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12765            ..lsp::ServerCapabilities::default()
12766        },
12767        cx,
12768    )
12769    .await;
12770
12771    let throttle_completions = Arc::new(AtomicBool::new(false));
12772
12773    let lsp_throttle_completions = throttle_completions.clone();
12774    let _completion_requests_handler =
12775        cx.lsp
12776            .server
12777            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
12778                let lsp_throttle_completions = lsp_throttle_completions.clone();
12779                let cx = cx.clone();
12780                async move {
12781                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
12782                        cx.background_executor()
12783                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
12784                            .await;
12785                    }
12786                    Ok(Some(lsp::CompletionResponse::Array(vec![
12787                        lsp::CompletionItem {
12788                            label: "first".into(),
12789                            ..lsp::CompletionItem::default()
12790                        },
12791                        lsp::CompletionItem {
12792                            label: "last".into(),
12793                            ..lsp::CompletionItem::default()
12794                        },
12795                    ])))
12796                }
12797            });
12798
12799    cx.set_state(indoc! {"
12800        oneˇ
12801        two
12802        three
12803    "});
12804    cx.simulate_keystroke(".");
12805    cx.executor().run_until_parked();
12806    cx.condition(|editor, _| editor.context_menu_visible())
12807        .await;
12808    cx.update_editor(|editor, window, cx| {
12809        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12810        {
12811            assert_eq!(
12812                completion_menu_entries(&menu),
12813                &["first", "last"],
12814                "When LSP server is fast to reply, no fallback word completions are used"
12815            );
12816        } else {
12817            panic!("expected completion menu to be open");
12818        }
12819        editor.cancel(&Cancel, window, cx);
12820    });
12821    cx.executor().run_until_parked();
12822    cx.condition(|editor, _| !editor.context_menu_visible())
12823        .await;
12824
12825    throttle_completions.store(true, atomic::Ordering::Release);
12826    cx.simulate_keystroke(".");
12827    cx.executor()
12828        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
12829    cx.executor().run_until_parked();
12830    cx.condition(|editor, _| editor.context_menu_visible())
12831        .await;
12832    cx.update_editor(|editor, _, _| {
12833        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12834        {
12835            assert_eq!(completion_menu_entries(&menu), &["one", "three", "two"],
12836                "When LSP server is slow, document words can be shown instead, if configured accordingly");
12837        } else {
12838            panic!("expected completion menu to be open");
12839        }
12840    });
12841}
12842
12843#[gpui::test]
12844async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
12845    init_test(cx, |language_settings| {
12846        language_settings.defaults.completions = Some(CompletionSettings {
12847            words: WordsCompletionMode::Enabled,
12848            lsp: true,
12849            lsp_fetch_timeout_ms: 0,
12850            lsp_insert_mode: LspInsertMode::Insert,
12851        });
12852    });
12853
12854    let mut cx = EditorLspTestContext::new_rust(
12855        lsp::ServerCapabilities {
12856            completion_provider: Some(lsp::CompletionOptions {
12857                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12858                ..lsp::CompletionOptions::default()
12859            }),
12860            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12861            ..lsp::ServerCapabilities::default()
12862        },
12863        cx,
12864    )
12865    .await;
12866
12867    let _completion_requests_handler =
12868        cx.lsp
12869            .server
12870            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12871                Ok(Some(lsp::CompletionResponse::Array(vec![
12872                    lsp::CompletionItem {
12873                        label: "first".into(),
12874                        ..lsp::CompletionItem::default()
12875                    },
12876                    lsp::CompletionItem {
12877                        label: "last".into(),
12878                        ..lsp::CompletionItem::default()
12879                    },
12880                ])))
12881            });
12882
12883    cx.set_state(indoc! {"ˇ
12884        first
12885        last
12886        second
12887    "});
12888    cx.simulate_keystroke(".");
12889    cx.executor().run_until_parked();
12890    cx.condition(|editor, _| editor.context_menu_visible())
12891        .await;
12892    cx.update_editor(|editor, _, _| {
12893        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12894        {
12895            assert_eq!(
12896                completion_menu_entries(&menu),
12897                &["first", "last", "second"],
12898                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
12899            );
12900        } else {
12901            panic!("expected completion menu to be open");
12902        }
12903    });
12904}
12905
12906#[gpui::test]
12907async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
12908    init_test(cx, |language_settings| {
12909        language_settings.defaults.completions = Some(CompletionSettings {
12910            words: WordsCompletionMode::Disabled,
12911            lsp: true,
12912            lsp_fetch_timeout_ms: 0,
12913            lsp_insert_mode: LspInsertMode::Insert,
12914        });
12915    });
12916
12917    let mut cx = EditorLspTestContext::new_rust(
12918        lsp::ServerCapabilities {
12919            completion_provider: Some(lsp::CompletionOptions {
12920                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12921                ..lsp::CompletionOptions::default()
12922            }),
12923            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12924            ..lsp::ServerCapabilities::default()
12925        },
12926        cx,
12927    )
12928    .await;
12929
12930    let _completion_requests_handler =
12931        cx.lsp
12932            .server
12933            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12934                panic!("LSP completions should not be queried when dealing with word completions")
12935            });
12936
12937    cx.set_state(indoc! {"ˇ
12938        first
12939        last
12940        second
12941    "});
12942    cx.update_editor(|editor, window, cx| {
12943        editor.show_word_completions(&ShowWordCompletions, window, cx);
12944    });
12945    cx.executor().run_until_parked();
12946    cx.condition(|editor, _| editor.context_menu_visible())
12947        .await;
12948    cx.update_editor(|editor, _, _| {
12949        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12950        {
12951            assert_eq!(
12952                completion_menu_entries(&menu),
12953                &["first", "last", "second"],
12954                "`ShowWordCompletions` action should show word completions"
12955            );
12956        } else {
12957            panic!("expected completion menu to be open");
12958        }
12959    });
12960
12961    cx.simulate_keystroke("l");
12962    cx.executor().run_until_parked();
12963    cx.condition(|editor, _| editor.context_menu_visible())
12964        .await;
12965    cx.update_editor(|editor, _, _| {
12966        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12967        {
12968            assert_eq!(
12969                completion_menu_entries(&menu),
12970                &["last"],
12971                "After showing word completions, further editing should filter them and not query the LSP"
12972            );
12973        } else {
12974            panic!("expected completion menu to be open");
12975        }
12976    });
12977}
12978
12979#[gpui::test]
12980async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
12981    init_test(cx, |language_settings| {
12982        language_settings.defaults.completions = Some(CompletionSettings {
12983            words: WordsCompletionMode::Fallback,
12984            lsp: false,
12985            lsp_fetch_timeout_ms: 0,
12986            lsp_insert_mode: LspInsertMode::Insert,
12987        });
12988    });
12989
12990    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12991
12992    cx.set_state(indoc! {"ˇ
12993        0_usize
12994        let
12995        33
12996        4.5f32
12997    "});
12998    cx.update_editor(|editor, window, cx| {
12999        editor.show_completions(&ShowCompletions::default(), window, cx);
13000    });
13001    cx.executor().run_until_parked();
13002    cx.condition(|editor, _| editor.context_menu_visible())
13003        .await;
13004    cx.update_editor(|editor, window, cx| {
13005        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13006        {
13007            assert_eq!(
13008                completion_menu_entries(&menu),
13009                &["let"],
13010                "With no digits in the completion query, no digits should be in the word completions"
13011            );
13012        } else {
13013            panic!("expected completion menu to be open");
13014        }
13015        editor.cancel(&Cancel, window, cx);
13016    });
13017
13018    cx.set_state(indoc! {"13019        0_usize
13020        let
13021        3
13022        33.35f32
13023    "});
13024    cx.update_editor(|editor, window, cx| {
13025        editor.show_completions(&ShowCompletions::default(), window, cx);
13026    });
13027    cx.executor().run_until_parked();
13028    cx.condition(|editor, _| editor.context_menu_visible())
13029        .await;
13030    cx.update_editor(|editor, _, _| {
13031        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13032        {
13033            assert_eq!(completion_menu_entries(&menu), &["33", "35f32"], "The digit is in the completion query, \
13034                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
13035        } else {
13036            panic!("expected completion menu to be open");
13037        }
13038    });
13039}
13040
13041fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
13042    let position = || lsp::Position {
13043        line: params.text_document_position.position.line,
13044        character: params.text_document_position.position.character,
13045    };
13046    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13047        range: lsp::Range {
13048            start: position(),
13049            end: position(),
13050        },
13051        new_text: text.to_string(),
13052    }))
13053}
13054
13055#[gpui::test]
13056async fn test_multiline_completion(cx: &mut TestAppContext) {
13057    init_test(cx, |_| {});
13058
13059    let fs = FakeFs::new(cx.executor());
13060    fs.insert_tree(
13061        path!("/a"),
13062        json!({
13063            "main.ts": "a",
13064        }),
13065    )
13066    .await;
13067
13068    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13069    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13070    let typescript_language = Arc::new(Language::new(
13071        LanguageConfig {
13072            name: "TypeScript".into(),
13073            matcher: LanguageMatcher {
13074                path_suffixes: vec!["ts".to_string()],
13075                ..LanguageMatcher::default()
13076            },
13077            line_comments: vec!["// ".into()],
13078            ..LanguageConfig::default()
13079        },
13080        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
13081    ));
13082    language_registry.add(typescript_language.clone());
13083    let mut fake_servers = language_registry.register_fake_lsp(
13084        "TypeScript",
13085        FakeLspAdapter {
13086            capabilities: lsp::ServerCapabilities {
13087                completion_provider: Some(lsp::CompletionOptions {
13088                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13089                    ..lsp::CompletionOptions::default()
13090                }),
13091                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13092                ..lsp::ServerCapabilities::default()
13093            },
13094            // Emulate vtsls label generation
13095            label_for_completion: Some(Box::new(|item, _| {
13096                let text = if let Some(description) = item
13097                    .label_details
13098                    .as_ref()
13099                    .and_then(|label_details| label_details.description.as_ref())
13100                {
13101                    format!("{} {}", item.label, description)
13102                } else if let Some(detail) = &item.detail {
13103                    format!("{} {}", item.label, detail)
13104                } else {
13105                    item.label.clone()
13106                };
13107                let len = text.len();
13108                Some(language::CodeLabel {
13109                    text,
13110                    runs: Vec::new(),
13111                    filter_range: 0..len,
13112                })
13113            })),
13114            ..FakeLspAdapter::default()
13115        },
13116    );
13117    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13118    let cx = &mut VisualTestContext::from_window(*workspace, cx);
13119    let worktree_id = workspace
13120        .update(cx, |workspace, _window, cx| {
13121            workspace.project().update(cx, |project, cx| {
13122                project.worktrees(cx).next().unwrap().read(cx).id()
13123            })
13124        })
13125        .unwrap();
13126    let _buffer = project
13127        .update(cx, |project, cx| {
13128            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
13129        })
13130        .await
13131        .unwrap();
13132    let editor = workspace
13133        .update(cx, |workspace, window, cx| {
13134            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
13135        })
13136        .unwrap()
13137        .await
13138        .unwrap()
13139        .downcast::<Editor>()
13140        .unwrap();
13141    let fake_server = fake_servers.next().await.unwrap();
13142
13143    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
13144    let multiline_label_2 = "a\nb\nc\n";
13145    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
13146    let multiline_description = "d\ne\nf\n";
13147    let multiline_detail_2 = "g\nh\ni\n";
13148
13149    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
13150        move |params, _| async move {
13151            Ok(Some(lsp::CompletionResponse::Array(vec![
13152                lsp::CompletionItem {
13153                    label: multiline_label.to_string(),
13154                    text_edit: gen_text_edit(&params, "new_text_1"),
13155                    ..lsp::CompletionItem::default()
13156                },
13157                lsp::CompletionItem {
13158                    label: "single line label 1".to_string(),
13159                    detail: Some(multiline_detail.to_string()),
13160                    text_edit: gen_text_edit(&params, "new_text_2"),
13161                    ..lsp::CompletionItem::default()
13162                },
13163                lsp::CompletionItem {
13164                    label: "single line label 2".to_string(),
13165                    label_details: Some(lsp::CompletionItemLabelDetails {
13166                        description: Some(multiline_description.to_string()),
13167                        detail: None,
13168                    }),
13169                    text_edit: gen_text_edit(&params, "new_text_2"),
13170                    ..lsp::CompletionItem::default()
13171                },
13172                lsp::CompletionItem {
13173                    label: multiline_label_2.to_string(),
13174                    detail: Some(multiline_detail_2.to_string()),
13175                    text_edit: gen_text_edit(&params, "new_text_3"),
13176                    ..lsp::CompletionItem::default()
13177                },
13178                lsp::CompletionItem {
13179                    label: "Label with many     spaces and \t but without newlines".to_string(),
13180                    detail: Some(
13181                        "Details with many     spaces and \t but without newlines".to_string(),
13182                    ),
13183                    text_edit: gen_text_edit(&params, "new_text_4"),
13184                    ..lsp::CompletionItem::default()
13185                },
13186            ])))
13187        },
13188    );
13189
13190    editor.update_in(cx, |editor, window, cx| {
13191        cx.focus_self(window);
13192        editor.move_to_end(&MoveToEnd, window, cx);
13193        editor.handle_input(".", window, cx);
13194    });
13195    cx.run_until_parked();
13196    completion_handle.next().await.unwrap();
13197
13198    editor.update(cx, |editor, _| {
13199        assert!(editor.context_menu_visible());
13200        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13201        {
13202            let completion_labels = menu
13203                .completions
13204                .borrow()
13205                .iter()
13206                .map(|c| c.label.text.clone())
13207                .collect::<Vec<_>>();
13208            assert_eq!(
13209                completion_labels,
13210                &[
13211                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
13212                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
13213                    "single line label 2 d e f ",
13214                    "a b c g h i ",
13215                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
13216                ],
13217                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
13218            );
13219
13220            for completion in menu
13221                .completions
13222                .borrow()
13223                .iter() {
13224                    assert_eq!(
13225                        completion.label.filter_range,
13226                        0..completion.label.text.len(),
13227                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
13228                    );
13229                }
13230        } else {
13231            panic!("expected completion menu to be open");
13232        }
13233    });
13234}
13235
13236#[gpui::test]
13237async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
13238    init_test(cx, |_| {});
13239    let mut cx = EditorLspTestContext::new_rust(
13240        lsp::ServerCapabilities {
13241            completion_provider: Some(lsp::CompletionOptions {
13242                trigger_characters: Some(vec![".".to_string()]),
13243                ..Default::default()
13244            }),
13245            ..Default::default()
13246        },
13247        cx,
13248    )
13249    .await;
13250    cx.lsp
13251        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13252            Ok(Some(lsp::CompletionResponse::Array(vec![
13253                lsp::CompletionItem {
13254                    label: "first".into(),
13255                    ..Default::default()
13256                },
13257                lsp::CompletionItem {
13258                    label: "last".into(),
13259                    ..Default::default()
13260                },
13261            ])))
13262        });
13263    cx.set_state("variableˇ");
13264    cx.simulate_keystroke(".");
13265    cx.executor().run_until_parked();
13266
13267    cx.update_editor(|editor, _, _| {
13268        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13269        {
13270            assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
13271        } else {
13272            panic!("expected completion menu to be open");
13273        }
13274    });
13275
13276    cx.update_editor(|editor, window, cx| {
13277        editor.move_page_down(&MovePageDown::default(), window, cx);
13278        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13279        {
13280            assert!(
13281                menu.selected_item == 1,
13282                "expected PageDown to select the last item from the context menu"
13283            );
13284        } else {
13285            panic!("expected completion menu to stay open after PageDown");
13286        }
13287    });
13288
13289    cx.update_editor(|editor, window, cx| {
13290        editor.move_page_up(&MovePageUp::default(), window, cx);
13291        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13292        {
13293            assert!(
13294                menu.selected_item == 0,
13295                "expected PageUp to select the first item from the context menu"
13296            );
13297        } else {
13298            panic!("expected completion menu to stay open after PageUp");
13299        }
13300    });
13301}
13302
13303#[gpui::test]
13304async fn test_as_is_completions(cx: &mut TestAppContext) {
13305    init_test(cx, |_| {});
13306    let mut cx = EditorLspTestContext::new_rust(
13307        lsp::ServerCapabilities {
13308            completion_provider: Some(lsp::CompletionOptions {
13309                ..Default::default()
13310            }),
13311            ..Default::default()
13312        },
13313        cx,
13314    )
13315    .await;
13316    cx.lsp
13317        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13318            Ok(Some(lsp::CompletionResponse::Array(vec![
13319                lsp::CompletionItem {
13320                    label: "unsafe".into(),
13321                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13322                        range: lsp::Range {
13323                            start: lsp::Position {
13324                                line: 1,
13325                                character: 2,
13326                            },
13327                            end: lsp::Position {
13328                                line: 1,
13329                                character: 3,
13330                            },
13331                        },
13332                        new_text: "unsafe".to_string(),
13333                    })),
13334                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
13335                    ..Default::default()
13336                },
13337            ])))
13338        });
13339    cx.set_state("fn a() {}\n");
13340    cx.executor().run_until_parked();
13341    cx.update_editor(|editor, window, cx| {
13342        editor.show_completions(
13343            &ShowCompletions {
13344                trigger: Some("\n".into()),
13345            },
13346            window,
13347            cx,
13348        );
13349    });
13350    cx.executor().run_until_parked();
13351
13352    cx.update_editor(|editor, window, cx| {
13353        editor.confirm_completion(&Default::default(), window, cx)
13354    });
13355    cx.executor().run_until_parked();
13356    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
13357}
13358
13359#[gpui::test]
13360async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
13361    init_test(cx, |_| {});
13362
13363    let mut cx = EditorLspTestContext::new_rust(
13364        lsp::ServerCapabilities {
13365            completion_provider: Some(lsp::CompletionOptions {
13366                trigger_characters: Some(vec![".".to_string()]),
13367                resolve_provider: Some(true),
13368                ..Default::default()
13369            }),
13370            ..Default::default()
13371        },
13372        cx,
13373    )
13374    .await;
13375
13376    cx.set_state("fn main() { let a = 2ˇ; }");
13377    cx.simulate_keystroke(".");
13378    let completion_item = lsp::CompletionItem {
13379        label: "Some".into(),
13380        kind: Some(lsp::CompletionItemKind::SNIPPET),
13381        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
13382        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
13383            kind: lsp::MarkupKind::Markdown,
13384            value: "```rust\nSome(2)\n```".to_string(),
13385        })),
13386        deprecated: Some(false),
13387        sort_text: Some("Some".to_string()),
13388        filter_text: Some("Some".to_string()),
13389        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
13390        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13391            range: lsp::Range {
13392                start: lsp::Position {
13393                    line: 0,
13394                    character: 22,
13395                },
13396                end: lsp::Position {
13397                    line: 0,
13398                    character: 22,
13399                },
13400            },
13401            new_text: "Some(2)".to_string(),
13402        })),
13403        additional_text_edits: Some(vec![lsp::TextEdit {
13404            range: lsp::Range {
13405                start: lsp::Position {
13406                    line: 0,
13407                    character: 20,
13408                },
13409                end: lsp::Position {
13410                    line: 0,
13411                    character: 22,
13412                },
13413            },
13414            new_text: "".to_string(),
13415        }]),
13416        ..Default::default()
13417    };
13418
13419    let closure_completion_item = completion_item.clone();
13420    let counter = Arc::new(AtomicUsize::new(0));
13421    let counter_clone = counter.clone();
13422    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
13423        let task_completion_item = closure_completion_item.clone();
13424        counter_clone.fetch_add(1, atomic::Ordering::Release);
13425        async move {
13426            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
13427                is_incomplete: true,
13428                item_defaults: None,
13429                items: vec![task_completion_item],
13430            })))
13431        }
13432    });
13433
13434    cx.condition(|editor, _| editor.context_menu_visible())
13435        .await;
13436    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
13437    assert!(request.next().await.is_some());
13438    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13439
13440    cx.simulate_keystrokes("S o m");
13441    cx.condition(|editor, _| editor.context_menu_visible())
13442        .await;
13443    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
13444    assert!(request.next().await.is_some());
13445    assert!(request.next().await.is_some());
13446    assert!(request.next().await.is_some());
13447    request.close();
13448    assert!(request.next().await.is_none());
13449    assert_eq!(
13450        counter.load(atomic::Ordering::Acquire),
13451        4,
13452        "With the completions menu open, only one LSP request should happen per input"
13453    );
13454}
13455
13456#[gpui::test]
13457async fn test_toggle_comment(cx: &mut TestAppContext) {
13458    init_test(cx, |_| {});
13459    let mut cx = EditorTestContext::new(cx).await;
13460    let language = Arc::new(Language::new(
13461        LanguageConfig {
13462            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
13463            ..Default::default()
13464        },
13465        Some(tree_sitter_rust::LANGUAGE.into()),
13466    ));
13467    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
13468
13469    // If multiple selections intersect a line, the line is only toggled once.
13470    cx.set_state(indoc! {"
13471        fn a() {
13472            «//b();
13473            ˇ»// «c();
13474            //ˇ»  d();
13475        }
13476    "});
13477
13478    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13479
13480    cx.assert_editor_state(indoc! {"
13481        fn a() {
13482            «b();
13483            c();
13484            ˇ» d();
13485        }
13486    "});
13487
13488    // The comment prefix is inserted at the same column for every line in a
13489    // selection.
13490    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13491
13492    cx.assert_editor_state(indoc! {"
13493        fn a() {
13494            // «b();
13495            // c();
13496            ˇ»//  d();
13497        }
13498    "});
13499
13500    // If a selection ends at the beginning of a line, that line is not toggled.
13501    cx.set_selections_state(indoc! {"
13502        fn a() {
13503            // b();
13504            «// c();
13505        ˇ»    //  d();
13506        }
13507    "});
13508
13509    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13510
13511    cx.assert_editor_state(indoc! {"
13512        fn a() {
13513            // b();
13514            «c();
13515        ˇ»    //  d();
13516        }
13517    "});
13518
13519    // If a selection span a single line and is empty, the line is toggled.
13520    cx.set_state(indoc! {"
13521        fn a() {
13522            a();
13523            b();
13524        ˇ
13525        }
13526    "});
13527
13528    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13529
13530    cx.assert_editor_state(indoc! {"
13531        fn a() {
13532            a();
13533            b();
13534        //•ˇ
13535        }
13536    "});
13537
13538    // If a selection span multiple lines, empty lines are not toggled.
13539    cx.set_state(indoc! {"
13540        fn a() {
13541            «a();
13542
13543            c();ˇ»
13544        }
13545    "});
13546
13547    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13548
13549    cx.assert_editor_state(indoc! {"
13550        fn a() {
13551            // «a();
13552
13553            // c();ˇ»
13554        }
13555    "});
13556
13557    // If a selection includes multiple comment prefixes, all lines are uncommented.
13558    cx.set_state(indoc! {"
13559        fn a() {
13560            «// a();
13561            /// b();
13562            //! c();ˇ»
13563        }
13564    "});
13565
13566    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13567
13568    cx.assert_editor_state(indoc! {"
13569        fn a() {
13570            «a();
13571            b();
13572            c();ˇ»
13573        }
13574    "});
13575}
13576
13577#[gpui::test]
13578async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
13579    init_test(cx, |_| {});
13580    let mut cx = EditorTestContext::new(cx).await;
13581    let language = Arc::new(Language::new(
13582        LanguageConfig {
13583            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
13584            ..Default::default()
13585        },
13586        Some(tree_sitter_rust::LANGUAGE.into()),
13587    ));
13588    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
13589
13590    let toggle_comments = &ToggleComments {
13591        advance_downwards: false,
13592        ignore_indent: true,
13593    };
13594
13595    // If multiple selections intersect a line, the line is only toggled once.
13596    cx.set_state(indoc! {"
13597        fn a() {
13598        //    «b();
13599        //    c();
13600        //    ˇ» d();
13601        }
13602    "});
13603
13604    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13605
13606    cx.assert_editor_state(indoc! {"
13607        fn a() {
13608            «b();
13609            c();
13610            ˇ» d();
13611        }
13612    "});
13613
13614    // The comment prefix is inserted at the beginning of each line
13615    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13616
13617    cx.assert_editor_state(indoc! {"
13618        fn a() {
13619        //    «b();
13620        //    c();
13621        //    ˇ» d();
13622        }
13623    "});
13624
13625    // If a selection ends at the beginning of a line, that line is not toggled.
13626    cx.set_selections_state(indoc! {"
13627        fn a() {
13628        //    b();
13629        //    «c();
13630        ˇ»//     d();
13631        }
13632    "});
13633
13634    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13635
13636    cx.assert_editor_state(indoc! {"
13637        fn a() {
13638        //    b();
13639            «c();
13640        ˇ»//     d();
13641        }
13642    "});
13643
13644    // If a selection span a single line and is empty, the line is toggled.
13645    cx.set_state(indoc! {"
13646        fn a() {
13647            a();
13648            b();
13649        ˇ
13650        }
13651    "});
13652
13653    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13654
13655    cx.assert_editor_state(indoc! {"
13656        fn a() {
13657            a();
13658            b();
13659        //ˇ
13660        }
13661    "});
13662
13663    // If a selection span multiple lines, empty lines are not toggled.
13664    cx.set_state(indoc! {"
13665        fn a() {
13666            «a();
13667
13668            c();ˇ»
13669        }
13670    "});
13671
13672    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13673
13674    cx.assert_editor_state(indoc! {"
13675        fn a() {
13676        //    «a();
13677
13678        //    c();ˇ»
13679        }
13680    "});
13681
13682    // If a selection includes multiple comment prefixes, all lines are uncommented.
13683    cx.set_state(indoc! {"
13684        fn a() {
13685        //    «a();
13686        ///    b();
13687        //!    c();ˇ»
13688        }
13689    "});
13690
13691    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13692
13693    cx.assert_editor_state(indoc! {"
13694        fn a() {
13695            «a();
13696            b();
13697            c();ˇ»
13698        }
13699    "});
13700}
13701
13702#[gpui::test]
13703async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
13704    init_test(cx, |_| {});
13705
13706    let language = Arc::new(Language::new(
13707        LanguageConfig {
13708            line_comments: vec!["// ".into()],
13709            ..Default::default()
13710        },
13711        Some(tree_sitter_rust::LANGUAGE.into()),
13712    ));
13713
13714    let mut cx = EditorTestContext::new(cx).await;
13715
13716    cx.language_registry().add(language.clone());
13717    cx.update_buffer(|buffer, cx| {
13718        buffer.set_language(Some(language), cx);
13719    });
13720
13721    let toggle_comments = &ToggleComments {
13722        advance_downwards: true,
13723        ignore_indent: false,
13724    };
13725
13726    // Single cursor on one line -> advance
13727    // Cursor moves horizontally 3 characters as well on non-blank line
13728    cx.set_state(indoc!(
13729        "fn a() {
13730             ˇdog();
13731             cat();
13732        }"
13733    ));
13734    cx.update_editor(|editor, window, cx| {
13735        editor.toggle_comments(toggle_comments, window, cx);
13736    });
13737    cx.assert_editor_state(indoc!(
13738        "fn a() {
13739             // dog();
13740             catˇ();
13741        }"
13742    ));
13743
13744    // Single selection on one line -> don't advance
13745    cx.set_state(indoc!(
13746        "fn a() {
13747             «dog()ˇ»;
13748             cat();
13749        }"
13750    ));
13751    cx.update_editor(|editor, window, cx| {
13752        editor.toggle_comments(toggle_comments, window, cx);
13753    });
13754    cx.assert_editor_state(indoc!(
13755        "fn a() {
13756             // «dog()ˇ»;
13757             cat();
13758        }"
13759    ));
13760
13761    // Multiple cursors on one line -> advance
13762    cx.set_state(indoc!(
13763        "fn a() {
13764             ˇdˇog();
13765             cat();
13766        }"
13767    ));
13768    cx.update_editor(|editor, window, cx| {
13769        editor.toggle_comments(toggle_comments, window, cx);
13770    });
13771    cx.assert_editor_state(indoc!(
13772        "fn a() {
13773             // dog();
13774             catˇ(ˇ);
13775        }"
13776    ));
13777
13778    // Multiple cursors on one line, with selection -> don't advance
13779    cx.set_state(indoc!(
13780        "fn a() {
13781             ˇdˇog«()ˇ»;
13782             cat();
13783        }"
13784    ));
13785    cx.update_editor(|editor, window, cx| {
13786        editor.toggle_comments(toggle_comments, window, cx);
13787    });
13788    cx.assert_editor_state(indoc!(
13789        "fn a() {
13790             // ˇdˇog«()ˇ»;
13791             cat();
13792        }"
13793    ));
13794
13795    // Single cursor on one line -> advance
13796    // Cursor moves to column 0 on blank line
13797    cx.set_state(indoc!(
13798        "fn a() {
13799             ˇdog();
13800
13801             cat();
13802        }"
13803    ));
13804    cx.update_editor(|editor, window, cx| {
13805        editor.toggle_comments(toggle_comments, window, cx);
13806    });
13807    cx.assert_editor_state(indoc!(
13808        "fn a() {
13809             // dog();
13810        ˇ
13811             cat();
13812        }"
13813    ));
13814
13815    // Single cursor on one line -> advance
13816    // Cursor starts and ends at column 0
13817    cx.set_state(indoc!(
13818        "fn a() {
13819         ˇ    dog();
13820             cat();
13821        }"
13822    ));
13823    cx.update_editor(|editor, window, cx| {
13824        editor.toggle_comments(toggle_comments, window, cx);
13825    });
13826    cx.assert_editor_state(indoc!(
13827        "fn a() {
13828             // dog();
13829         ˇ    cat();
13830        }"
13831    ));
13832}
13833
13834#[gpui::test]
13835async fn test_toggle_block_comment(cx: &mut TestAppContext) {
13836    init_test(cx, |_| {});
13837
13838    let mut cx = EditorTestContext::new(cx).await;
13839
13840    let html_language = Arc::new(
13841        Language::new(
13842            LanguageConfig {
13843                name: "HTML".into(),
13844                block_comment: Some(BlockCommentConfig {
13845                    start: "<!-- ".into(),
13846                    prefix: "".into(),
13847                    end: " -->".into(),
13848                    tab_size: 0,
13849                }),
13850                ..Default::default()
13851            },
13852            Some(tree_sitter_html::LANGUAGE.into()),
13853        )
13854        .with_injection_query(
13855            r#"
13856            (script_element
13857                (raw_text) @injection.content
13858                (#set! injection.language "javascript"))
13859            "#,
13860        )
13861        .unwrap(),
13862    );
13863
13864    let javascript_language = Arc::new(Language::new(
13865        LanguageConfig {
13866            name: "JavaScript".into(),
13867            line_comments: vec!["// ".into()],
13868            ..Default::default()
13869        },
13870        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
13871    ));
13872
13873    cx.language_registry().add(html_language.clone());
13874    cx.language_registry().add(javascript_language.clone());
13875    cx.update_buffer(|buffer, cx| {
13876        buffer.set_language(Some(html_language), cx);
13877    });
13878
13879    // Toggle comments for empty selections
13880    cx.set_state(
13881        &r#"
13882            <p>A</p>ˇ
13883            <p>B</p>ˇ
13884            <p>C</p>ˇ
13885        "#
13886        .unindent(),
13887    );
13888    cx.update_editor(|editor, window, cx| {
13889        editor.toggle_comments(&ToggleComments::default(), window, cx)
13890    });
13891    cx.assert_editor_state(
13892        &r#"
13893            <!-- <p>A</p>ˇ -->
13894            <!-- <p>B</p>ˇ -->
13895            <!-- <p>C</p>ˇ -->
13896        "#
13897        .unindent(),
13898    );
13899    cx.update_editor(|editor, window, cx| {
13900        editor.toggle_comments(&ToggleComments::default(), window, cx)
13901    });
13902    cx.assert_editor_state(
13903        &r#"
13904            <p>A</p>ˇ
13905            <p>B</p>ˇ
13906            <p>C</p>ˇ
13907        "#
13908        .unindent(),
13909    );
13910
13911    // Toggle comments for mixture of empty and non-empty selections, where
13912    // multiple selections occupy a given line.
13913    cx.set_state(
13914        &r#"
13915            <p>A«</p>
13916            <p>ˇ»B</p>ˇ
13917            <p>C«</p>
13918            <p>ˇ»D</p>ˇ
13919        "#
13920        .unindent(),
13921    );
13922
13923    cx.update_editor(|editor, window, cx| {
13924        editor.toggle_comments(&ToggleComments::default(), window, cx)
13925    });
13926    cx.assert_editor_state(
13927        &r#"
13928            <!-- <p>A«</p>
13929            <p>ˇ»B</p>ˇ -->
13930            <!-- <p>C«</p>
13931            <p>ˇ»D</p>ˇ -->
13932        "#
13933        .unindent(),
13934    );
13935    cx.update_editor(|editor, window, cx| {
13936        editor.toggle_comments(&ToggleComments::default(), window, cx)
13937    });
13938    cx.assert_editor_state(
13939        &r#"
13940            <p>A«</p>
13941            <p>ˇ»B</p>ˇ
13942            <p>C«</p>
13943            <p>ˇ»D</p>ˇ
13944        "#
13945        .unindent(),
13946    );
13947
13948    // Toggle comments when different languages are active for different
13949    // selections.
13950    cx.set_state(
13951        &r#"
13952            ˇ<script>
13953                ˇvar x = new Y();
13954            ˇ</script>
13955        "#
13956        .unindent(),
13957    );
13958    cx.executor().run_until_parked();
13959    cx.update_editor(|editor, window, cx| {
13960        editor.toggle_comments(&ToggleComments::default(), window, cx)
13961    });
13962    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
13963    // Uncommenting and commenting from this position brings in even more wrong artifacts.
13964    cx.assert_editor_state(
13965        &r#"
13966            <!-- ˇ<script> -->
13967                // ˇvar x = new Y();
13968            <!-- ˇ</script> -->
13969        "#
13970        .unindent(),
13971    );
13972}
13973
13974#[gpui::test]
13975fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
13976    init_test(cx, |_| {});
13977
13978    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
13979    let multibuffer = cx.new(|cx| {
13980        let mut multibuffer = MultiBuffer::new(ReadWrite);
13981        multibuffer.push_excerpts(
13982            buffer.clone(),
13983            [
13984                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
13985                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
13986            ],
13987            cx,
13988        );
13989        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
13990        multibuffer
13991    });
13992
13993    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
13994    editor.update_in(cx, |editor, window, cx| {
13995        assert_eq!(editor.text(cx), "aaaa\nbbbb");
13996        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13997            s.select_ranges([
13998                Point::new(0, 0)..Point::new(0, 0),
13999                Point::new(1, 0)..Point::new(1, 0),
14000            ])
14001        });
14002
14003        editor.handle_input("X", window, cx);
14004        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
14005        assert_eq!(
14006            editor.selections.ranges(cx),
14007            [
14008                Point::new(0, 1)..Point::new(0, 1),
14009                Point::new(1, 1)..Point::new(1, 1),
14010            ]
14011        );
14012
14013        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
14014        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14015            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
14016        });
14017        editor.backspace(&Default::default(), window, cx);
14018        assert_eq!(editor.text(cx), "Xa\nbbb");
14019        assert_eq!(
14020            editor.selections.ranges(cx),
14021            [Point::new(1, 0)..Point::new(1, 0)]
14022        );
14023
14024        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14025            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
14026        });
14027        editor.backspace(&Default::default(), window, cx);
14028        assert_eq!(editor.text(cx), "X\nbb");
14029        assert_eq!(
14030            editor.selections.ranges(cx),
14031            [Point::new(0, 1)..Point::new(0, 1)]
14032        );
14033    });
14034}
14035
14036#[gpui::test]
14037fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
14038    init_test(cx, |_| {});
14039
14040    let markers = vec![('[', ']').into(), ('(', ')').into()];
14041    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
14042        indoc! {"
14043            [aaaa
14044            (bbbb]
14045            cccc)",
14046        },
14047        markers.clone(),
14048    );
14049    let excerpt_ranges = markers.into_iter().map(|marker| {
14050        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
14051        ExcerptRange::new(context.clone())
14052    });
14053    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
14054    let multibuffer = cx.new(|cx| {
14055        let mut multibuffer = MultiBuffer::new(ReadWrite);
14056        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
14057        multibuffer
14058    });
14059
14060    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
14061    editor.update_in(cx, |editor, window, cx| {
14062        let (expected_text, selection_ranges) = marked_text_ranges(
14063            indoc! {"
14064                aaaa
14065                bˇbbb
14066                bˇbbˇb
14067                cccc"
14068            },
14069            true,
14070        );
14071        assert_eq!(editor.text(cx), expected_text);
14072        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14073            s.select_ranges(selection_ranges)
14074        });
14075
14076        editor.handle_input("X", window, cx);
14077
14078        let (expected_text, expected_selections) = marked_text_ranges(
14079            indoc! {"
14080                aaaa
14081                bXˇbbXb
14082                bXˇbbXˇb
14083                cccc"
14084            },
14085            false,
14086        );
14087        assert_eq!(editor.text(cx), expected_text);
14088        assert_eq!(editor.selections.ranges(cx), expected_selections);
14089
14090        editor.newline(&Newline, window, cx);
14091        let (expected_text, expected_selections) = marked_text_ranges(
14092            indoc! {"
14093                aaaa
14094                bX
14095                ˇbbX
14096                b
14097                bX
14098                ˇbbX
14099                ˇb
14100                cccc"
14101            },
14102            false,
14103        );
14104        assert_eq!(editor.text(cx), expected_text);
14105        assert_eq!(editor.selections.ranges(cx), expected_selections);
14106    });
14107}
14108
14109#[gpui::test]
14110fn test_refresh_selections(cx: &mut TestAppContext) {
14111    init_test(cx, |_| {});
14112
14113    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
14114    let mut excerpt1_id = None;
14115    let multibuffer = cx.new(|cx| {
14116        let mut multibuffer = MultiBuffer::new(ReadWrite);
14117        excerpt1_id = multibuffer
14118            .push_excerpts(
14119                buffer.clone(),
14120                [
14121                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
14122                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
14123                ],
14124                cx,
14125            )
14126            .into_iter()
14127            .next();
14128        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
14129        multibuffer
14130    });
14131
14132    let editor = cx.add_window(|window, cx| {
14133        let mut editor = build_editor(multibuffer.clone(), window, cx);
14134        let snapshot = editor.snapshot(window, cx);
14135        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14136            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
14137        });
14138        editor.begin_selection(
14139            Point::new(2, 1).to_display_point(&snapshot),
14140            true,
14141            1,
14142            window,
14143            cx,
14144        );
14145        assert_eq!(
14146            editor.selections.ranges(cx),
14147            [
14148                Point::new(1, 3)..Point::new(1, 3),
14149                Point::new(2, 1)..Point::new(2, 1),
14150            ]
14151        );
14152        editor
14153    });
14154
14155    // Refreshing selections is a no-op when excerpts haven't changed.
14156    _ = editor.update(cx, |editor, window, cx| {
14157        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14158        assert_eq!(
14159            editor.selections.ranges(cx),
14160            [
14161                Point::new(1, 3)..Point::new(1, 3),
14162                Point::new(2, 1)..Point::new(2, 1),
14163            ]
14164        );
14165    });
14166
14167    multibuffer.update(cx, |multibuffer, cx| {
14168        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
14169    });
14170    _ = editor.update(cx, |editor, window, cx| {
14171        // Removing an excerpt causes the first selection to become degenerate.
14172        assert_eq!(
14173            editor.selections.ranges(cx),
14174            [
14175                Point::new(0, 0)..Point::new(0, 0),
14176                Point::new(0, 1)..Point::new(0, 1)
14177            ]
14178        );
14179
14180        // Refreshing selections will relocate the first selection to the original buffer
14181        // location.
14182        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14183        assert_eq!(
14184            editor.selections.ranges(cx),
14185            [
14186                Point::new(0, 1)..Point::new(0, 1),
14187                Point::new(0, 3)..Point::new(0, 3)
14188            ]
14189        );
14190        assert!(editor.selections.pending_anchor().is_some());
14191    });
14192}
14193
14194#[gpui::test]
14195fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
14196    init_test(cx, |_| {});
14197
14198    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
14199    let mut excerpt1_id = None;
14200    let multibuffer = cx.new(|cx| {
14201        let mut multibuffer = MultiBuffer::new(ReadWrite);
14202        excerpt1_id = multibuffer
14203            .push_excerpts(
14204                buffer.clone(),
14205                [
14206                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
14207                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
14208                ],
14209                cx,
14210            )
14211            .into_iter()
14212            .next();
14213        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
14214        multibuffer
14215    });
14216
14217    let editor = cx.add_window(|window, cx| {
14218        let mut editor = build_editor(multibuffer.clone(), window, cx);
14219        let snapshot = editor.snapshot(window, cx);
14220        editor.begin_selection(
14221            Point::new(1, 3).to_display_point(&snapshot),
14222            false,
14223            1,
14224            window,
14225            cx,
14226        );
14227        assert_eq!(
14228            editor.selections.ranges(cx),
14229            [Point::new(1, 3)..Point::new(1, 3)]
14230        );
14231        editor
14232    });
14233
14234    multibuffer.update(cx, |multibuffer, cx| {
14235        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
14236    });
14237    _ = editor.update(cx, |editor, window, cx| {
14238        assert_eq!(
14239            editor.selections.ranges(cx),
14240            [Point::new(0, 0)..Point::new(0, 0)]
14241        );
14242
14243        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
14244        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14245        assert_eq!(
14246            editor.selections.ranges(cx),
14247            [Point::new(0, 3)..Point::new(0, 3)]
14248        );
14249        assert!(editor.selections.pending_anchor().is_some());
14250    });
14251}
14252
14253#[gpui::test]
14254async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
14255    init_test(cx, |_| {});
14256
14257    let language = Arc::new(
14258        Language::new(
14259            LanguageConfig {
14260                brackets: BracketPairConfig {
14261                    pairs: vec![
14262                        BracketPair {
14263                            start: "{".to_string(),
14264                            end: "}".to_string(),
14265                            close: true,
14266                            surround: true,
14267                            newline: true,
14268                        },
14269                        BracketPair {
14270                            start: "/* ".to_string(),
14271                            end: " */".to_string(),
14272                            close: true,
14273                            surround: true,
14274                            newline: true,
14275                        },
14276                    ],
14277                    ..Default::default()
14278                },
14279                ..Default::default()
14280            },
14281            Some(tree_sitter_rust::LANGUAGE.into()),
14282        )
14283        .with_indents_query("")
14284        .unwrap(),
14285    );
14286
14287    let text = concat!(
14288        "{   }\n",     //
14289        "  x\n",       //
14290        "  /*   */\n", //
14291        "x\n",         //
14292        "{{} }\n",     //
14293    );
14294
14295    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
14296    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
14297    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
14298    editor
14299        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
14300        .await;
14301
14302    editor.update_in(cx, |editor, window, cx| {
14303        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14304            s.select_display_ranges([
14305                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
14306                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
14307                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
14308            ])
14309        });
14310        editor.newline(&Newline, window, cx);
14311
14312        assert_eq!(
14313            editor.buffer().read(cx).read(cx).text(),
14314            concat!(
14315                "{ \n",    // Suppress rustfmt
14316                "\n",      //
14317                "}\n",     //
14318                "  x\n",   //
14319                "  /* \n", //
14320                "  \n",    //
14321                "  */\n",  //
14322                "x\n",     //
14323                "{{} \n",  //
14324                "}\n",     //
14325            )
14326        );
14327    });
14328}
14329
14330#[gpui::test]
14331fn test_highlighted_ranges(cx: &mut TestAppContext) {
14332    init_test(cx, |_| {});
14333
14334    let editor = cx.add_window(|window, cx| {
14335        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
14336        build_editor(buffer.clone(), window, cx)
14337    });
14338
14339    _ = editor.update(cx, |editor, window, cx| {
14340        struct Type1;
14341        struct Type2;
14342
14343        let buffer = editor.buffer.read(cx).snapshot(cx);
14344
14345        let anchor_range =
14346            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
14347
14348        editor.highlight_background::<Type1>(
14349            &[
14350                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
14351                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
14352                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
14353                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
14354            ],
14355            |_| Hsla::red(),
14356            cx,
14357        );
14358        editor.highlight_background::<Type2>(
14359            &[
14360                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
14361                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
14362                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
14363                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
14364            ],
14365            |_| Hsla::green(),
14366            cx,
14367        );
14368
14369        let snapshot = editor.snapshot(window, cx);
14370        let mut highlighted_ranges = editor.background_highlights_in_range(
14371            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
14372            &snapshot,
14373            cx.theme(),
14374        );
14375        // Enforce a consistent ordering based on color without relying on the ordering of the
14376        // highlight's `TypeId` which is non-executor.
14377        highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
14378        assert_eq!(
14379            highlighted_ranges,
14380            &[
14381                (
14382                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
14383                    Hsla::red(),
14384                ),
14385                (
14386                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
14387                    Hsla::red(),
14388                ),
14389                (
14390                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
14391                    Hsla::green(),
14392                ),
14393                (
14394                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
14395                    Hsla::green(),
14396                ),
14397            ]
14398        );
14399        assert_eq!(
14400            editor.background_highlights_in_range(
14401                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
14402                &snapshot,
14403                cx.theme(),
14404            ),
14405            &[(
14406                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
14407                Hsla::red(),
14408            )]
14409        );
14410    });
14411}
14412
14413#[gpui::test]
14414async fn test_following(cx: &mut TestAppContext) {
14415    init_test(cx, |_| {});
14416
14417    let fs = FakeFs::new(cx.executor());
14418    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
14419
14420    let buffer = project.update(cx, |project, cx| {
14421        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
14422        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
14423    });
14424    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
14425    let follower = cx.update(|cx| {
14426        cx.open_window(
14427            WindowOptions {
14428                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
14429                    gpui::Point::new(px(0.), px(0.)),
14430                    gpui::Point::new(px(10.), px(80.)),
14431                ))),
14432                ..Default::default()
14433            },
14434            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
14435        )
14436        .unwrap()
14437    });
14438
14439    let is_still_following = Rc::new(RefCell::new(true));
14440    let follower_edit_event_count = Rc::new(RefCell::new(0));
14441    let pending_update = Rc::new(RefCell::new(None));
14442    let leader_entity = leader.root(cx).unwrap();
14443    let follower_entity = follower.root(cx).unwrap();
14444    _ = follower.update(cx, {
14445        let update = pending_update.clone();
14446        let is_still_following = is_still_following.clone();
14447        let follower_edit_event_count = follower_edit_event_count.clone();
14448        |_, window, cx| {
14449            cx.subscribe_in(
14450                &leader_entity,
14451                window,
14452                move |_, leader, event, window, cx| {
14453                    leader.read(cx).add_event_to_update_proto(
14454                        event,
14455                        &mut update.borrow_mut(),
14456                        window,
14457                        cx,
14458                    );
14459                },
14460            )
14461            .detach();
14462
14463            cx.subscribe_in(
14464                &follower_entity,
14465                window,
14466                move |_, _, event: &EditorEvent, _window, _cx| {
14467                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
14468                        *is_still_following.borrow_mut() = false;
14469                    }
14470
14471                    if let EditorEvent::BufferEdited = event {
14472                        *follower_edit_event_count.borrow_mut() += 1;
14473                    }
14474                },
14475            )
14476            .detach();
14477        }
14478    });
14479
14480    // Update the selections only
14481    _ = leader.update(cx, |leader, window, cx| {
14482        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14483            s.select_ranges([1..1])
14484        });
14485    });
14486    follower
14487        .update(cx, |follower, window, cx| {
14488            follower.apply_update_proto(
14489                &project,
14490                pending_update.borrow_mut().take().unwrap(),
14491                window,
14492                cx,
14493            )
14494        })
14495        .unwrap()
14496        .await
14497        .unwrap();
14498    _ = follower.update(cx, |follower, _, cx| {
14499        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
14500    });
14501    assert!(*is_still_following.borrow());
14502    assert_eq!(*follower_edit_event_count.borrow(), 0);
14503
14504    // Update the scroll position only
14505    _ = leader.update(cx, |leader, window, cx| {
14506        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
14507    });
14508    follower
14509        .update(cx, |follower, window, cx| {
14510            follower.apply_update_proto(
14511                &project,
14512                pending_update.borrow_mut().take().unwrap(),
14513                window,
14514                cx,
14515            )
14516        })
14517        .unwrap()
14518        .await
14519        .unwrap();
14520    assert_eq!(
14521        follower
14522            .update(cx, |follower, _, cx| follower.scroll_position(cx))
14523            .unwrap(),
14524        gpui::Point::new(1.5, 3.5)
14525    );
14526    assert!(*is_still_following.borrow());
14527    assert_eq!(*follower_edit_event_count.borrow(), 0);
14528
14529    // Update the selections and scroll position. The follower's scroll position is updated
14530    // via autoscroll, not via the leader's exact scroll position.
14531    _ = leader.update(cx, |leader, window, cx| {
14532        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14533            s.select_ranges([0..0])
14534        });
14535        leader.request_autoscroll(Autoscroll::newest(), cx);
14536        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
14537    });
14538    follower
14539        .update(cx, |follower, window, cx| {
14540            follower.apply_update_proto(
14541                &project,
14542                pending_update.borrow_mut().take().unwrap(),
14543                window,
14544                cx,
14545            )
14546        })
14547        .unwrap()
14548        .await
14549        .unwrap();
14550    _ = follower.update(cx, |follower, _, cx| {
14551        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
14552        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
14553    });
14554    assert!(*is_still_following.borrow());
14555
14556    // Creating a pending selection that precedes another selection
14557    _ = leader.update(cx, |leader, window, cx| {
14558        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14559            s.select_ranges([1..1])
14560        });
14561        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
14562    });
14563    follower
14564        .update(cx, |follower, window, cx| {
14565            follower.apply_update_proto(
14566                &project,
14567                pending_update.borrow_mut().take().unwrap(),
14568                window,
14569                cx,
14570            )
14571        })
14572        .unwrap()
14573        .await
14574        .unwrap();
14575    _ = follower.update(cx, |follower, _, cx| {
14576        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
14577    });
14578    assert!(*is_still_following.borrow());
14579
14580    // Extend the pending selection so that it surrounds another selection
14581    _ = leader.update(cx, |leader, window, cx| {
14582        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
14583    });
14584    follower
14585        .update(cx, |follower, window, cx| {
14586            follower.apply_update_proto(
14587                &project,
14588                pending_update.borrow_mut().take().unwrap(),
14589                window,
14590                cx,
14591            )
14592        })
14593        .unwrap()
14594        .await
14595        .unwrap();
14596    _ = follower.update(cx, |follower, _, cx| {
14597        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
14598    });
14599
14600    // Scrolling locally breaks the follow
14601    _ = follower.update(cx, |follower, window, cx| {
14602        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
14603        follower.set_scroll_anchor(
14604            ScrollAnchor {
14605                anchor: top_anchor,
14606                offset: gpui::Point::new(0.0, 0.5),
14607            },
14608            window,
14609            cx,
14610        );
14611    });
14612    assert!(!(*is_still_following.borrow()));
14613}
14614
14615#[gpui::test]
14616async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
14617    init_test(cx, |_| {});
14618
14619    let fs = FakeFs::new(cx.executor());
14620    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
14621    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14622    let pane = workspace
14623        .update(cx, |workspace, _, _| workspace.active_pane().clone())
14624        .unwrap();
14625
14626    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14627
14628    let leader = pane.update_in(cx, |_, window, cx| {
14629        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
14630        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
14631    });
14632
14633    // Start following the editor when it has no excerpts.
14634    let mut state_message =
14635        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
14636    let workspace_entity = workspace.root(cx).unwrap();
14637    let follower_1 = cx
14638        .update_window(*workspace.deref(), |_, window, cx| {
14639            Editor::from_state_proto(
14640                workspace_entity,
14641                ViewId {
14642                    creator: CollaboratorId::PeerId(PeerId::default()),
14643                    id: 0,
14644                },
14645                &mut state_message,
14646                window,
14647                cx,
14648            )
14649        })
14650        .unwrap()
14651        .unwrap()
14652        .await
14653        .unwrap();
14654
14655    let update_message = Rc::new(RefCell::new(None));
14656    follower_1.update_in(cx, {
14657        let update = update_message.clone();
14658        |_, window, cx| {
14659            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
14660                leader.read(cx).add_event_to_update_proto(
14661                    event,
14662                    &mut update.borrow_mut(),
14663                    window,
14664                    cx,
14665                );
14666            })
14667            .detach();
14668        }
14669    });
14670
14671    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
14672        (
14673            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
14674            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
14675        )
14676    });
14677
14678    // Insert some excerpts.
14679    leader.update(cx, |leader, cx| {
14680        leader.buffer.update(cx, |multibuffer, cx| {
14681            multibuffer.set_excerpts_for_path(
14682                PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
14683                buffer_1.clone(),
14684                vec![
14685                    Point::row_range(0..3),
14686                    Point::row_range(1..6),
14687                    Point::row_range(12..15),
14688                ],
14689                0,
14690                cx,
14691            );
14692            multibuffer.set_excerpts_for_path(
14693                PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
14694                buffer_2.clone(),
14695                vec![Point::row_range(0..6), Point::row_range(8..12)],
14696                0,
14697                cx,
14698            );
14699        });
14700    });
14701
14702    // Apply the update of adding the excerpts.
14703    follower_1
14704        .update_in(cx, |follower, window, cx| {
14705            follower.apply_update_proto(
14706                &project,
14707                update_message.borrow().clone().unwrap(),
14708                window,
14709                cx,
14710            )
14711        })
14712        .await
14713        .unwrap();
14714    assert_eq!(
14715        follower_1.update(cx, |editor, cx| editor.text(cx)),
14716        leader.update(cx, |editor, cx| editor.text(cx))
14717    );
14718    update_message.borrow_mut().take();
14719
14720    // Start following separately after it already has excerpts.
14721    let mut state_message =
14722        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
14723    let workspace_entity = workspace.root(cx).unwrap();
14724    let follower_2 = cx
14725        .update_window(*workspace.deref(), |_, window, cx| {
14726            Editor::from_state_proto(
14727                workspace_entity,
14728                ViewId {
14729                    creator: CollaboratorId::PeerId(PeerId::default()),
14730                    id: 0,
14731                },
14732                &mut state_message,
14733                window,
14734                cx,
14735            )
14736        })
14737        .unwrap()
14738        .unwrap()
14739        .await
14740        .unwrap();
14741    assert_eq!(
14742        follower_2.update(cx, |editor, cx| editor.text(cx)),
14743        leader.update(cx, |editor, cx| editor.text(cx))
14744    );
14745
14746    // Remove some excerpts.
14747    leader.update(cx, |leader, cx| {
14748        leader.buffer.update(cx, |multibuffer, cx| {
14749            let excerpt_ids = multibuffer.excerpt_ids();
14750            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
14751            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
14752        });
14753    });
14754
14755    // Apply the update of removing the excerpts.
14756    follower_1
14757        .update_in(cx, |follower, window, cx| {
14758            follower.apply_update_proto(
14759                &project,
14760                update_message.borrow().clone().unwrap(),
14761                window,
14762                cx,
14763            )
14764        })
14765        .await
14766        .unwrap();
14767    follower_2
14768        .update_in(cx, |follower, window, cx| {
14769            follower.apply_update_proto(
14770                &project,
14771                update_message.borrow().clone().unwrap(),
14772                window,
14773                cx,
14774            )
14775        })
14776        .await
14777        .unwrap();
14778    update_message.borrow_mut().take();
14779    assert_eq!(
14780        follower_1.update(cx, |editor, cx| editor.text(cx)),
14781        leader.update(cx, |editor, cx| editor.text(cx))
14782    );
14783}
14784
14785#[gpui::test]
14786async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14787    init_test(cx, |_| {});
14788
14789    let mut cx = EditorTestContext::new(cx).await;
14790    let lsp_store =
14791        cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
14792
14793    cx.set_state(indoc! {"
14794        ˇfn func(abc def: i32) -> u32 {
14795        }
14796    "});
14797
14798    cx.update(|_, cx| {
14799        lsp_store.update(cx, |lsp_store, cx| {
14800            lsp_store
14801                .update_diagnostics(
14802                    LanguageServerId(0),
14803                    lsp::PublishDiagnosticsParams {
14804                        uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
14805                        version: None,
14806                        diagnostics: vec![
14807                            lsp::Diagnostic {
14808                                range: lsp::Range::new(
14809                                    lsp::Position::new(0, 11),
14810                                    lsp::Position::new(0, 12),
14811                                ),
14812                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14813                                ..Default::default()
14814                            },
14815                            lsp::Diagnostic {
14816                                range: lsp::Range::new(
14817                                    lsp::Position::new(0, 12),
14818                                    lsp::Position::new(0, 15),
14819                                ),
14820                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14821                                ..Default::default()
14822                            },
14823                            lsp::Diagnostic {
14824                                range: lsp::Range::new(
14825                                    lsp::Position::new(0, 25),
14826                                    lsp::Position::new(0, 28),
14827                                ),
14828                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14829                                ..Default::default()
14830                            },
14831                        ],
14832                    },
14833                    None,
14834                    DiagnosticSourceKind::Pushed,
14835                    &[],
14836                    cx,
14837                )
14838                .unwrap()
14839        });
14840    });
14841
14842    executor.run_until_parked();
14843
14844    cx.update_editor(|editor, window, cx| {
14845        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
14846    });
14847
14848    cx.assert_editor_state(indoc! {"
14849        fn func(abc def: i32) -> ˇu32 {
14850        }
14851    "});
14852
14853    cx.update_editor(|editor, window, cx| {
14854        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
14855    });
14856
14857    cx.assert_editor_state(indoc! {"
14858        fn func(abc ˇdef: i32) -> u32 {
14859        }
14860    "});
14861
14862    cx.update_editor(|editor, window, cx| {
14863        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
14864    });
14865
14866    cx.assert_editor_state(indoc! {"
14867        fn func(abcˇ def: i32) -> u32 {
14868        }
14869    "});
14870
14871    cx.update_editor(|editor, window, cx| {
14872        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
14873    });
14874
14875    cx.assert_editor_state(indoc! {"
14876        fn func(abc def: i32) -> ˇu32 {
14877        }
14878    "});
14879}
14880
14881#[gpui::test]
14882async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14883    init_test(cx, |_| {});
14884
14885    let mut cx = EditorTestContext::new(cx).await;
14886
14887    let diff_base = r#"
14888        use some::mod;
14889
14890        const A: u32 = 42;
14891
14892        fn main() {
14893            println!("hello");
14894
14895            println!("world");
14896        }
14897        "#
14898    .unindent();
14899
14900    // Edits are modified, removed, modified, added
14901    cx.set_state(
14902        &r#"
14903        use some::modified;
14904
14905        ˇ
14906        fn main() {
14907            println!("hello there");
14908
14909            println!("around the");
14910            println!("world");
14911        }
14912        "#
14913        .unindent(),
14914    );
14915
14916    cx.set_head_text(&diff_base);
14917    executor.run_until_parked();
14918
14919    cx.update_editor(|editor, window, cx| {
14920        //Wrap around the bottom of the buffer
14921        for _ in 0..3 {
14922            editor.go_to_next_hunk(&GoToHunk, window, cx);
14923        }
14924    });
14925
14926    cx.assert_editor_state(
14927        &r#"
14928        ˇuse some::modified;
14929
14930
14931        fn main() {
14932            println!("hello there");
14933
14934            println!("around the");
14935            println!("world");
14936        }
14937        "#
14938        .unindent(),
14939    );
14940
14941    cx.update_editor(|editor, window, cx| {
14942        //Wrap around the top of the buffer
14943        for _ in 0..2 {
14944            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14945        }
14946    });
14947
14948    cx.assert_editor_state(
14949        &r#"
14950        use some::modified;
14951
14952
14953        fn main() {
14954        ˇ    println!("hello there");
14955
14956            println!("around the");
14957            println!("world");
14958        }
14959        "#
14960        .unindent(),
14961    );
14962
14963    cx.update_editor(|editor, window, cx| {
14964        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
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.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14984    });
14985
14986    cx.assert_editor_state(
14987        &r#"
14988        ˇuse some::modified;
14989
14990
14991        fn main() {
14992            println!("hello there");
14993
14994            println!("around the");
14995            println!("world");
14996        }
14997        "#
14998        .unindent(),
14999    );
15000
15001    cx.update_editor(|editor, window, cx| {
15002        for _ in 0..2 {
15003            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
15004        }
15005    });
15006
15007    cx.assert_editor_state(
15008        &r#"
15009        use some::modified;
15010
15011
15012        fn main() {
15013        ˇ    println!("hello there");
15014
15015            println!("around the");
15016            println!("world");
15017        }
15018        "#
15019        .unindent(),
15020    );
15021
15022    cx.update_editor(|editor, window, cx| {
15023        editor.fold(&Fold, window, cx);
15024    });
15025
15026    cx.update_editor(|editor, window, cx| {
15027        editor.go_to_next_hunk(&GoToHunk, window, cx);
15028    });
15029
15030    cx.assert_editor_state(
15031        &r#"
15032        ˇuse some::modified;
15033
15034
15035        fn main() {
15036            println!("hello there");
15037
15038            println!("around the");
15039            println!("world");
15040        }
15041        "#
15042        .unindent(),
15043    );
15044}
15045
15046#[test]
15047fn test_split_words() {
15048    fn split(text: &str) -> Vec<&str> {
15049        split_words(text).collect()
15050    }
15051
15052    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
15053    assert_eq!(split("hello_world"), &["hello_", "world"]);
15054    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
15055    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
15056    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
15057    assert_eq!(split("helloworld"), &["helloworld"]);
15058
15059    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
15060}
15061
15062#[gpui::test]
15063async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
15064    init_test(cx, |_| {});
15065
15066    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
15067    let mut assert = |before, after| {
15068        let _state_context = cx.set_state(before);
15069        cx.run_until_parked();
15070        cx.update_editor(|editor, window, cx| {
15071            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
15072        });
15073        cx.run_until_parked();
15074        cx.assert_editor_state(after);
15075    };
15076
15077    // Outside bracket jumps to outside of matching bracket
15078    assert("console.logˇ(var);", "console.log(var)ˇ;");
15079    assert("console.log(var)ˇ;", "console.logˇ(var);");
15080
15081    // Inside bracket jumps to inside of matching bracket
15082    assert("console.log(ˇvar);", "console.log(varˇ);");
15083    assert("console.log(varˇ);", "console.log(ˇvar);");
15084
15085    // When outside a bracket and inside, favor jumping to the inside bracket
15086    assert(
15087        "console.log('foo', [1, 2, 3]ˇ);",
15088        "console.log(ˇ'foo', [1, 2, 3]);",
15089    );
15090    assert(
15091        "console.log(ˇ'foo', [1, 2, 3]);",
15092        "console.log('foo', [1, 2, 3]ˇ);",
15093    );
15094
15095    // Bias forward if two options are equally likely
15096    assert(
15097        "let result = curried_fun()ˇ();",
15098        "let result = curried_fun()()ˇ;",
15099    );
15100
15101    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
15102    assert(
15103        indoc! {"
15104            function test() {
15105                console.log('test')ˇ
15106            }"},
15107        indoc! {"
15108            function test() {
15109                console.logˇ('test')
15110            }"},
15111    );
15112}
15113
15114#[gpui::test]
15115async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
15116    init_test(cx, |_| {});
15117
15118    let fs = FakeFs::new(cx.executor());
15119    fs.insert_tree(
15120        path!("/a"),
15121        json!({
15122            "main.rs": "fn main() { let a = 5; }",
15123            "other.rs": "// Test file",
15124        }),
15125    )
15126    .await;
15127    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15128
15129    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15130    language_registry.add(Arc::new(Language::new(
15131        LanguageConfig {
15132            name: "Rust".into(),
15133            matcher: LanguageMatcher {
15134                path_suffixes: vec!["rs".to_string()],
15135                ..Default::default()
15136            },
15137            brackets: BracketPairConfig {
15138                pairs: vec![BracketPair {
15139                    start: "{".to_string(),
15140                    end: "}".to_string(),
15141                    close: true,
15142                    surround: true,
15143                    newline: true,
15144                }],
15145                disabled_scopes_by_bracket_ix: Vec::new(),
15146            },
15147            ..Default::default()
15148        },
15149        Some(tree_sitter_rust::LANGUAGE.into()),
15150    )));
15151    let mut fake_servers = language_registry.register_fake_lsp(
15152        "Rust",
15153        FakeLspAdapter {
15154            capabilities: lsp::ServerCapabilities {
15155                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
15156                    first_trigger_character: "{".to_string(),
15157                    more_trigger_character: None,
15158                }),
15159                ..Default::default()
15160            },
15161            ..Default::default()
15162        },
15163    );
15164
15165    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15166
15167    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15168
15169    let worktree_id = workspace
15170        .update(cx, |workspace, _, cx| {
15171            workspace.project().update(cx, |project, cx| {
15172                project.worktrees(cx).next().unwrap().read(cx).id()
15173            })
15174        })
15175        .unwrap();
15176
15177    let buffer = project
15178        .update(cx, |project, cx| {
15179            project.open_local_buffer(path!("/a/main.rs"), cx)
15180        })
15181        .await
15182        .unwrap();
15183    let editor_handle = workspace
15184        .update(cx, |workspace, window, cx| {
15185            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
15186        })
15187        .unwrap()
15188        .await
15189        .unwrap()
15190        .downcast::<Editor>()
15191        .unwrap();
15192
15193    cx.executor().start_waiting();
15194    let fake_server = fake_servers.next().await.unwrap();
15195
15196    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
15197        |params, _| async move {
15198            assert_eq!(
15199                params.text_document_position.text_document.uri,
15200                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
15201            );
15202            assert_eq!(
15203                params.text_document_position.position,
15204                lsp::Position::new(0, 21),
15205            );
15206
15207            Ok(Some(vec![lsp::TextEdit {
15208                new_text: "]".to_string(),
15209                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15210            }]))
15211        },
15212    );
15213
15214    editor_handle.update_in(cx, |editor, window, cx| {
15215        window.focus(&editor.focus_handle(cx));
15216        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15217            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
15218        });
15219        editor.handle_input("{", window, cx);
15220    });
15221
15222    cx.executor().run_until_parked();
15223
15224    buffer.update(cx, |buffer, _| {
15225        assert_eq!(
15226            buffer.text(),
15227            "fn main() { let a = {5}; }",
15228            "No extra braces from on type formatting should appear in the buffer"
15229        )
15230    });
15231}
15232
15233#[gpui::test(iterations = 20, seeds(31))]
15234async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
15235    init_test(cx, |_| {});
15236
15237    let mut cx = EditorLspTestContext::new_rust(
15238        lsp::ServerCapabilities {
15239            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
15240                first_trigger_character: ".".to_string(),
15241                more_trigger_character: None,
15242            }),
15243            ..Default::default()
15244        },
15245        cx,
15246    )
15247    .await;
15248
15249    cx.update_buffer(|buffer, _| {
15250        // This causes autoindent to be async.
15251        buffer.set_sync_parse_timeout(Duration::ZERO)
15252    });
15253
15254    cx.set_state("fn c() {\n    d()ˇ\n}\n");
15255    cx.simulate_keystroke("\n");
15256    cx.run_until_parked();
15257
15258    let buffer_cloned =
15259        cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap().clone());
15260    let mut request =
15261        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
15262            let buffer_cloned = buffer_cloned.clone();
15263            async move {
15264                buffer_cloned.update(&mut cx, |buffer, _| {
15265                    assert_eq!(
15266                        buffer.text(),
15267                        "fn c() {\n    d()\n        .\n}\n",
15268                        "OnTypeFormatting should triggered after autoindent applied"
15269                    )
15270                })?;
15271
15272                Ok(Some(vec![]))
15273            }
15274        });
15275
15276    cx.simulate_keystroke(".");
15277    cx.run_until_parked();
15278
15279    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
15280    assert!(request.next().await.is_some());
15281    request.close();
15282    assert!(request.next().await.is_none());
15283}
15284
15285#[gpui::test]
15286async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
15287    init_test(cx, |_| {});
15288
15289    let fs = FakeFs::new(cx.executor());
15290    fs.insert_tree(
15291        path!("/a"),
15292        json!({
15293            "main.rs": "fn main() { let a = 5; }",
15294            "other.rs": "// Test file",
15295        }),
15296    )
15297    .await;
15298
15299    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15300
15301    let server_restarts = Arc::new(AtomicUsize::new(0));
15302    let closure_restarts = Arc::clone(&server_restarts);
15303    let language_server_name = "test language server";
15304    let language_name: LanguageName = "Rust".into();
15305
15306    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15307    language_registry.add(Arc::new(Language::new(
15308        LanguageConfig {
15309            name: language_name.clone(),
15310            matcher: LanguageMatcher {
15311                path_suffixes: vec!["rs".to_string()],
15312                ..Default::default()
15313            },
15314            ..Default::default()
15315        },
15316        Some(tree_sitter_rust::LANGUAGE.into()),
15317    )));
15318    let mut fake_servers = language_registry.register_fake_lsp(
15319        "Rust",
15320        FakeLspAdapter {
15321            name: language_server_name,
15322            initialization_options: Some(json!({
15323                "testOptionValue": true
15324            })),
15325            initializer: Some(Box::new(move |fake_server| {
15326                let task_restarts = Arc::clone(&closure_restarts);
15327                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
15328                    task_restarts.fetch_add(1, atomic::Ordering::Release);
15329                    futures::future::ready(Ok(()))
15330                });
15331            })),
15332            ..Default::default()
15333        },
15334    );
15335
15336    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15337    let _buffer = project
15338        .update(cx, |project, cx| {
15339            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
15340        })
15341        .await
15342        .unwrap();
15343    let _fake_server = fake_servers.next().await.unwrap();
15344    update_test_language_settings(cx, |language_settings| {
15345        language_settings.languages.0.insert(
15346            language_name.clone(),
15347            LanguageSettingsContent {
15348                tab_size: NonZeroU32::new(8),
15349                ..Default::default()
15350            },
15351        );
15352    });
15353    cx.executor().run_until_parked();
15354    assert_eq!(
15355        server_restarts.load(atomic::Ordering::Acquire),
15356        0,
15357        "Should not restart LSP server on an unrelated change"
15358    );
15359
15360    update_test_project_settings(cx, |project_settings| {
15361        project_settings.lsp.insert(
15362            "Some other server name".into(),
15363            LspSettings {
15364                binary: None,
15365                settings: None,
15366                initialization_options: Some(json!({
15367                    "some other init value": 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        0,
15377        "Should not restart LSP server on an unrelated LSP settings change"
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: Some(json!({
15387                    "anotherInitValue": false
15388                })),
15389                enable_lsp_tasks: false,
15390            },
15391        );
15392    });
15393    cx.executor().run_until_parked();
15394    assert_eq!(
15395        server_restarts.load(atomic::Ordering::Acquire),
15396        1,
15397        "Should restart LSP server on a related LSP settings change"
15398    );
15399
15400    update_test_project_settings(cx, |project_settings| {
15401        project_settings.lsp.insert(
15402            language_server_name.into(),
15403            LspSettings {
15404                binary: None,
15405                settings: None,
15406                initialization_options: Some(json!({
15407                    "anotherInitValue": false
15408                })),
15409                enable_lsp_tasks: false,
15410            },
15411        );
15412    });
15413    cx.executor().run_until_parked();
15414    assert_eq!(
15415        server_restarts.load(atomic::Ordering::Acquire),
15416        1,
15417        "Should not restart LSP server on a related LSP settings change that is the same"
15418    );
15419
15420    update_test_project_settings(cx, |project_settings| {
15421        project_settings.lsp.insert(
15422            language_server_name.into(),
15423            LspSettings {
15424                binary: None,
15425                settings: None,
15426                initialization_options: None,
15427                enable_lsp_tasks: false,
15428            },
15429        );
15430    });
15431    cx.executor().run_until_parked();
15432    assert_eq!(
15433        server_restarts.load(atomic::Ordering::Acquire),
15434        2,
15435        "Should restart LSP server on another related LSP settings change"
15436    );
15437}
15438
15439#[gpui::test]
15440async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
15441    init_test(cx, |_| {});
15442
15443    let mut cx = EditorLspTestContext::new_rust(
15444        lsp::ServerCapabilities {
15445            completion_provider: Some(lsp::CompletionOptions {
15446                trigger_characters: Some(vec![".".to_string()]),
15447                resolve_provider: Some(true),
15448                ..Default::default()
15449            }),
15450            ..Default::default()
15451        },
15452        cx,
15453    )
15454    .await;
15455
15456    cx.set_state("fn main() { let a = 2ˇ; }");
15457    cx.simulate_keystroke(".");
15458    let completion_item = lsp::CompletionItem {
15459        label: "some".into(),
15460        kind: Some(lsp::CompletionItemKind::SNIPPET),
15461        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15462        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15463            kind: lsp::MarkupKind::Markdown,
15464            value: "```rust\nSome(2)\n```".to_string(),
15465        })),
15466        deprecated: Some(false),
15467        sort_text: Some("fffffff2".to_string()),
15468        filter_text: Some("some".to_string()),
15469        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15470        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15471            range: lsp::Range {
15472                start: lsp::Position {
15473                    line: 0,
15474                    character: 22,
15475                },
15476                end: lsp::Position {
15477                    line: 0,
15478                    character: 22,
15479                },
15480            },
15481            new_text: "Some(2)".to_string(),
15482        })),
15483        additional_text_edits: Some(vec![lsp::TextEdit {
15484            range: lsp::Range {
15485                start: lsp::Position {
15486                    line: 0,
15487                    character: 20,
15488                },
15489                end: lsp::Position {
15490                    line: 0,
15491                    character: 22,
15492                },
15493            },
15494            new_text: "".to_string(),
15495        }]),
15496        ..Default::default()
15497    };
15498
15499    let closure_completion_item = completion_item.clone();
15500    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15501        let task_completion_item = closure_completion_item.clone();
15502        async move {
15503            Ok(Some(lsp::CompletionResponse::Array(vec![
15504                task_completion_item,
15505            ])))
15506        }
15507    });
15508
15509    request.next().await;
15510
15511    cx.condition(|editor, _| editor.context_menu_visible())
15512        .await;
15513    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15514        editor
15515            .confirm_completion(&ConfirmCompletion::default(), window, cx)
15516            .unwrap()
15517    });
15518    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
15519
15520    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
15521        let task_completion_item = completion_item.clone();
15522        async move { Ok(task_completion_item) }
15523    })
15524    .next()
15525    .await
15526    .unwrap();
15527    apply_additional_edits.await.unwrap();
15528    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
15529}
15530
15531#[gpui::test]
15532async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
15533    init_test(cx, |_| {});
15534
15535    let mut cx = EditorLspTestContext::new_rust(
15536        lsp::ServerCapabilities {
15537            completion_provider: Some(lsp::CompletionOptions {
15538                trigger_characters: Some(vec![".".to_string()]),
15539                resolve_provider: Some(true),
15540                ..Default::default()
15541            }),
15542            ..Default::default()
15543        },
15544        cx,
15545    )
15546    .await;
15547
15548    cx.set_state("fn main() { let a = 2ˇ; }");
15549    cx.simulate_keystroke(".");
15550
15551    let item1 = lsp::CompletionItem {
15552        label: "method id()".to_string(),
15553        filter_text: Some("id".to_string()),
15554        detail: None,
15555        documentation: None,
15556        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15557            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15558            new_text: ".id".to_string(),
15559        })),
15560        ..lsp::CompletionItem::default()
15561    };
15562
15563    let item2 = lsp::CompletionItem {
15564        label: "other".to_string(),
15565        filter_text: Some("other".to_string()),
15566        detail: None,
15567        documentation: None,
15568        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15569            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15570            new_text: ".other".to_string(),
15571        })),
15572        ..lsp::CompletionItem::default()
15573    };
15574
15575    let item1 = item1.clone();
15576    cx.set_request_handler::<lsp::request::Completion, _, _>({
15577        let item1 = item1.clone();
15578        move |_, _, _| {
15579            let item1 = item1.clone();
15580            let item2 = item2.clone();
15581            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
15582        }
15583    })
15584    .next()
15585    .await;
15586
15587    cx.condition(|editor, _| editor.context_menu_visible())
15588        .await;
15589    cx.update_editor(|editor, _, _| {
15590        let context_menu = editor.context_menu.borrow_mut();
15591        let context_menu = context_menu
15592            .as_ref()
15593            .expect("Should have the context menu deployed");
15594        match context_menu {
15595            CodeContextMenu::Completions(completions_menu) => {
15596                let completions = completions_menu.completions.borrow_mut();
15597                assert_eq!(
15598                    completions
15599                        .iter()
15600                        .map(|completion| &completion.label.text)
15601                        .collect::<Vec<_>>(),
15602                    vec!["method id()", "other"]
15603                )
15604            }
15605            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15606        }
15607    });
15608
15609    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
15610        let item1 = item1.clone();
15611        move |_, item_to_resolve, _| {
15612            let item1 = item1.clone();
15613            async move {
15614                if item1 == item_to_resolve {
15615                    Ok(lsp::CompletionItem {
15616                        label: "method id()".to_string(),
15617                        filter_text: Some("id".to_string()),
15618                        detail: Some("Now resolved!".to_string()),
15619                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
15620                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15621                            range: lsp::Range::new(
15622                                lsp::Position::new(0, 22),
15623                                lsp::Position::new(0, 22),
15624                            ),
15625                            new_text: ".id".to_string(),
15626                        })),
15627                        ..lsp::CompletionItem::default()
15628                    })
15629                } else {
15630                    Ok(item_to_resolve)
15631                }
15632            }
15633        }
15634    })
15635    .next()
15636    .await
15637    .unwrap();
15638    cx.run_until_parked();
15639
15640    cx.update_editor(|editor, window, cx| {
15641        editor.context_menu_next(&Default::default(), window, cx);
15642    });
15643
15644    cx.update_editor(|editor, _, _| {
15645        let context_menu = editor.context_menu.borrow_mut();
15646        let context_menu = context_menu
15647            .as_ref()
15648            .expect("Should have the context menu deployed");
15649        match context_menu {
15650            CodeContextMenu::Completions(completions_menu) => {
15651                let completions = completions_menu.completions.borrow_mut();
15652                assert_eq!(
15653                    completions
15654                        .iter()
15655                        .map(|completion| &completion.label.text)
15656                        .collect::<Vec<_>>(),
15657                    vec!["method id() Now resolved!", "other"],
15658                    "Should update first completion label, but not second as the filter text did not match."
15659                );
15660            }
15661            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15662        }
15663    });
15664}
15665
15666#[gpui::test]
15667async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
15668    init_test(cx, |_| {});
15669    let mut cx = EditorLspTestContext::new_rust(
15670        lsp::ServerCapabilities {
15671            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
15672            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
15673            completion_provider: Some(lsp::CompletionOptions {
15674                resolve_provider: Some(true),
15675                ..Default::default()
15676            }),
15677            ..Default::default()
15678        },
15679        cx,
15680    )
15681    .await;
15682    cx.set_state(indoc! {"
15683        struct TestStruct {
15684            field: i32
15685        }
15686
15687        fn mainˇ() {
15688            let unused_var = 42;
15689            let test_struct = TestStruct { field: 42 };
15690        }
15691    "});
15692    let symbol_range = cx.lsp_range(indoc! {"
15693        struct TestStruct {
15694            field: i32
15695        }
15696
15697        «fn main»() {
15698            let unused_var = 42;
15699            let test_struct = TestStruct { field: 42 };
15700        }
15701    "});
15702    let mut hover_requests =
15703        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
15704            Ok(Some(lsp::Hover {
15705                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
15706                    kind: lsp::MarkupKind::Markdown,
15707                    value: "Function documentation".to_string(),
15708                }),
15709                range: Some(symbol_range),
15710            }))
15711        });
15712
15713    // Case 1: Test that code action menu hide hover popover
15714    cx.dispatch_action(Hover);
15715    hover_requests.next().await;
15716    cx.condition(|editor, _| editor.hover_state.visible()).await;
15717    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
15718        move |_, _, _| async move {
15719            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
15720                lsp::CodeAction {
15721                    title: "Remove unused variable".to_string(),
15722                    kind: Some(CodeActionKind::QUICKFIX),
15723                    edit: Some(lsp::WorkspaceEdit {
15724                        changes: Some(
15725                            [(
15726                                lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
15727                                vec![lsp::TextEdit {
15728                                    range: lsp::Range::new(
15729                                        lsp::Position::new(5, 4),
15730                                        lsp::Position::new(5, 27),
15731                                    ),
15732                                    new_text: "".to_string(),
15733                                }],
15734                            )]
15735                            .into_iter()
15736                            .collect(),
15737                        ),
15738                        ..Default::default()
15739                    }),
15740                    ..Default::default()
15741                },
15742            )]))
15743        },
15744    );
15745    cx.update_editor(|editor, window, cx| {
15746        editor.toggle_code_actions(
15747            &ToggleCodeActions {
15748                deployed_from: None,
15749                quick_launch: false,
15750            },
15751            window,
15752            cx,
15753        );
15754    });
15755    code_action_requests.next().await;
15756    cx.run_until_parked();
15757    cx.condition(|editor, _| editor.context_menu_visible())
15758        .await;
15759    cx.update_editor(|editor, _, _| {
15760        assert!(
15761            !editor.hover_state.visible(),
15762            "Hover popover should be hidden when code action menu is shown"
15763        );
15764        // Hide code actions
15765        editor.context_menu.take();
15766    });
15767
15768    // Case 2: Test that code completions hide hover popover
15769    cx.dispatch_action(Hover);
15770    hover_requests.next().await;
15771    cx.condition(|editor, _| editor.hover_state.visible()).await;
15772    let counter = Arc::new(AtomicUsize::new(0));
15773    let mut completion_requests =
15774        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15775            let counter = counter.clone();
15776            async move {
15777                counter.fetch_add(1, atomic::Ordering::Release);
15778                Ok(Some(lsp::CompletionResponse::Array(vec![
15779                    lsp::CompletionItem {
15780                        label: "main".into(),
15781                        kind: Some(lsp::CompletionItemKind::FUNCTION),
15782                        detail: Some("() -> ()".to_string()),
15783                        ..Default::default()
15784                    },
15785                    lsp::CompletionItem {
15786                        label: "TestStruct".into(),
15787                        kind: Some(lsp::CompletionItemKind::STRUCT),
15788                        detail: Some("struct TestStruct".to_string()),
15789                        ..Default::default()
15790                    },
15791                ])))
15792            }
15793        });
15794    cx.update_editor(|editor, window, cx| {
15795        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15796    });
15797    completion_requests.next().await;
15798    cx.condition(|editor, _| editor.context_menu_visible())
15799        .await;
15800    cx.update_editor(|editor, _, _| {
15801        assert!(
15802            !editor.hover_state.visible(),
15803            "Hover popover should be hidden when completion menu is shown"
15804        );
15805    });
15806}
15807
15808#[gpui::test]
15809async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
15810    init_test(cx, |_| {});
15811
15812    let mut cx = EditorLspTestContext::new_rust(
15813        lsp::ServerCapabilities {
15814            completion_provider: Some(lsp::CompletionOptions {
15815                trigger_characters: Some(vec![".".to_string()]),
15816                resolve_provider: Some(true),
15817                ..Default::default()
15818            }),
15819            ..Default::default()
15820        },
15821        cx,
15822    )
15823    .await;
15824
15825    cx.set_state("fn main() { let a = 2ˇ; }");
15826    cx.simulate_keystroke(".");
15827
15828    let unresolved_item_1 = lsp::CompletionItem {
15829        label: "id".to_string(),
15830        filter_text: Some("id".to_string()),
15831        detail: None,
15832        documentation: None,
15833        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15834            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15835            new_text: ".id".to_string(),
15836        })),
15837        ..lsp::CompletionItem::default()
15838    };
15839    let resolved_item_1 = lsp::CompletionItem {
15840        additional_text_edits: Some(vec![lsp::TextEdit {
15841            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
15842            new_text: "!!".to_string(),
15843        }]),
15844        ..unresolved_item_1.clone()
15845    };
15846    let unresolved_item_2 = lsp::CompletionItem {
15847        label: "other".to_string(),
15848        filter_text: Some("other".to_string()),
15849        detail: None,
15850        documentation: None,
15851        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15852            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15853            new_text: ".other".to_string(),
15854        })),
15855        ..lsp::CompletionItem::default()
15856    };
15857    let resolved_item_2 = lsp::CompletionItem {
15858        additional_text_edits: Some(vec![lsp::TextEdit {
15859            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
15860            new_text: "??".to_string(),
15861        }]),
15862        ..unresolved_item_2.clone()
15863    };
15864
15865    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
15866    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
15867    cx.lsp
15868        .server
15869        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
15870            let unresolved_item_1 = unresolved_item_1.clone();
15871            let resolved_item_1 = resolved_item_1.clone();
15872            let unresolved_item_2 = unresolved_item_2.clone();
15873            let resolved_item_2 = resolved_item_2.clone();
15874            let resolve_requests_1 = resolve_requests_1.clone();
15875            let resolve_requests_2 = resolve_requests_2.clone();
15876            move |unresolved_request, _| {
15877                let unresolved_item_1 = unresolved_item_1.clone();
15878                let resolved_item_1 = resolved_item_1.clone();
15879                let unresolved_item_2 = unresolved_item_2.clone();
15880                let resolved_item_2 = resolved_item_2.clone();
15881                let resolve_requests_1 = resolve_requests_1.clone();
15882                let resolve_requests_2 = resolve_requests_2.clone();
15883                async move {
15884                    if unresolved_request == unresolved_item_1 {
15885                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
15886                        Ok(resolved_item_1.clone())
15887                    } else if unresolved_request == unresolved_item_2 {
15888                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
15889                        Ok(resolved_item_2.clone())
15890                    } else {
15891                        panic!("Unexpected completion item {unresolved_request:?}")
15892                    }
15893                }
15894            }
15895        })
15896        .detach();
15897
15898    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15899        let unresolved_item_1 = unresolved_item_1.clone();
15900        let unresolved_item_2 = unresolved_item_2.clone();
15901        async move {
15902            Ok(Some(lsp::CompletionResponse::Array(vec![
15903                unresolved_item_1,
15904                unresolved_item_2,
15905            ])))
15906        }
15907    })
15908    .next()
15909    .await;
15910
15911    cx.condition(|editor, _| editor.context_menu_visible())
15912        .await;
15913    cx.update_editor(|editor, _, _| {
15914        let context_menu = editor.context_menu.borrow_mut();
15915        let context_menu = context_menu
15916            .as_ref()
15917            .expect("Should have the context menu deployed");
15918        match context_menu {
15919            CodeContextMenu::Completions(completions_menu) => {
15920                let completions = completions_menu.completions.borrow_mut();
15921                assert_eq!(
15922                    completions
15923                        .iter()
15924                        .map(|completion| &completion.label.text)
15925                        .collect::<Vec<_>>(),
15926                    vec!["id", "other"]
15927                )
15928            }
15929            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15930        }
15931    });
15932    cx.run_until_parked();
15933
15934    cx.update_editor(|editor, window, cx| {
15935        editor.context_menu_next(&ContextMenuNext, window, cx);
15936    });
15937    cx.run_until_parked();
15938    cx.update_editor(|editor, window, cx| {
15939        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
15940    });
15941    cx.run_until_parked();
15942    cx.update_editor(|editor, window, cx| {
15943        editor.context_menu_next(&ContextMenuNext, window, cx);
15944    });
15945    cx.run_until_parked();
15946    cx.update_editor(|editor, window, cx| {
15947        editor
15948            .compose_completion(&ComposeCompletion::default(), window, cx)
15949            .expect("No task returned")
15950    })
15951    .await
15952    .expect("Completion failed");
15953    cx.run_until_parked();
15954
15955    cx.update_editor(|editor, _, cx| {
15956        assert_eq!(
15957            resolve_requests_1.load(atomic::Ordering::Acquire),
15958            1,
15959            "Should always resolve once despite multiple selections"
15960        );
15961        assert_eq!(
15962            resolve_requests_2.load(atomic::Ordering::Acquire),
15963            1,
15964            "Should always resolve once after multiple selections and applying the completion"
15965        );
15966        assert_eq!(
15967            editor.text(cx),
15968            "fn main() { let a = ??.other; }",
15969            "Should use resolved data when applying the completion"
15970        );
15971    });
15972}
15973
15974#[gpui::test]
15975async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
15976    init_test(cx, |_| {});
15977
15978    let item_0 = lsp::CompletionItem {
15979        label: "abs".into(),
15980        insert_text: Some("abs".into()),
15981        data: Some(json!({ "very": "special"})),
15982        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
15983        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
15984            lsp::InsertReplaceEdit {
15985                new_text: "abs".to_string(),
15986                insert: lsp::Range::default(),
15987                replace: lsp::Range::default(),
15988            },
15989        )),
15990        ..lsp::CompletionItem::default()
15991    };
15992    let items = iter::once(item_0.clone())
15993        .chain((11..51).map(|i| lsp::CompletionItem {
15994            label: format!("item_{}", i),
15995            insert_text: Some(format!("item_{}", i)),
15996            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15997            ..lsp::CompletionItem::default()
15998        }))
15999        .collect::<Vec<_>>();
16000
16001    let default_commit_characters = vec!["?".to_string()];
16002    let default_data = json!({ "default": "data"});
16003    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
16004    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
16005    let default_edit_range = lsp::Range {
16006        start: lsp::Position {
16007            line: 0,
16008            character: 5,
16009        },
16010        end: lsp::Position {
16011            line: 0,
16012            character: 5,
16013        },
16014    };
16015
16016    let mut cx = EditorLspTestContext::new_rust(
16017        lsp::ServerCapabilities {
16018            completion_provider: Some(lsp::CompletionOptions {
16019                trigger_characters: Some(vec![".".to_string()]),
16020                resolve_provider: Some(true),
16021                ..Default::default()
16022            }),
16023            ..Default::default()
16024        },
16025        cx,
16026    )
16027    .await;
16028
16029    cx.set_state("fn main() { let a = 2ˇ; }");
16030    cx.simulate_keystroke(".");
16031
16032    let completion_data = default_data.clone();
16033    let completion_characters = default_commit_characters.clone();
16034    let completion_items = items.clone();
16035    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16036        let default_data = completion_data.clone();
16037        let default_commit_characters = completion_characters.clone();
16038        let items = completion_items.clone();
16039        async move {
16040            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16041                items,
16042                item_defaults: Some(lsp::CompletionListItemDefaults {
16043                    data: Some(default_data.clone()),
16044                    commit_characters: Some(default_commit_characters.clone()),
16045                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
16046                        default_edit_range,
16047                    )),
16048                    insert_text_format: Some(default_insert_text_format),
16049                    insert_text_mode: Some(default_insert_text_mode),
16050                }),
16051                ..lsp::CompletionList::default()
16052            })))
16053        }
16054    })
16055    .next()
16056    .await;
16057
16058    let resolved_items = Arc::new(Mutex::new(Vec::new()));
16059    cx.lsp
16060        .server
16061        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
16062            let closure_resolved_items = resolved_items.clone();
16063            move |item_to_resolve, _| {
16064                let closure_resolved_items = closure_resolved_items.clone();
16065                async move {
16066                    closure_resolved_items.lock().push(item_to_resolve.clone());
16067                    Ok(item_to_resolve)
16068                }
16069            }
16070        })
16071        .detach();
16072
16073    cx.condition(|editor, _| editor.context_menu_visible())
16074        .await;
16075    cx.run_until_parked();
16076    cx.update_editor(|editor, _, _| {
16077        let menu = editor.context_menu.borrow_mut();
16078        match menu.as_ref().expect("should have the completions menu") {
16079            CodeContextMenu::Completions(completions_menu) => {
16080                assert_eq!(
16081                    completions_menu
16082                        .entries
16083                        .borrow()
16084                        .iter()
16085                        .map(|mat| mat.string.clone())
16086                        .collect::<Vec<String>>(),
16087                    items
16088                        .iter()
16089                        .map(|completion| completion.label.clone())
16090                        .collect::<Vec<String>>()
16091                );
16092            }
16093            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
16094        }
16095    });
16096    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
16097    // with 4 from the end.
16098    assert_eq!(
16099        *resolved_items.lock(),
16100        [&items[0..16], &items[items.len() - 4..items.len()]]
16101            .concat()
16102            .iter()
16103            .cloned()
16104            .map(|mut item| {
16105                if item.data.is_none() {
16106                    item.data = Some(default_data.clone());
16107                }
16108                item
16109            })
16110            .collect::<Vec<lsp::CompletionItem>>(),
16111        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
16112    );
16113    resolved_items.lock().clear();
16114
16115    cx.update_editor(|editor, window, cx| {
16116        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
16117    });
16118    cx.run_until_parked();
16119    // Completions that have already been resolved are skipped.
16120    assert_eq!(
16121        *resolved_items.lock(),
16122        items[items.len() - 17..items.len() - 4]
16123            .iter()
16124            .cloned()
16125            .map(|mut item| {
16126                if item.data.is_none() {
16127                    item.data = Some(default_data.clone());
16128                }
16129                item
16130            })
16131            .collect::<Vec<lsp::CompletionItem>>()
16132    );
16133    resolved_items.lock().clear();
16134}
16135
16136#[gpui::test]
16137async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
16138    init_test(cx, |_| {});
16139
16140    let mut cx = EditorLspTestContext::new(
16141        Language::new(
16142            LanguageConfig {
16143                matcher: LanguageMatcher {
16144                    path_suffixes: vec!["jsx".into()],
16145                    ..Default::default()
16146                },
16147                overrides: [(
16148                    "element".into(),
16149                    LanguageConfigOverride {
16150                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
16151                        ..Default::default()
16152                    },
16153                )]
16154                .into_iter()
16155                .collect(),
16156                ..Default::default()
16157            },
16158            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
16159        )
16160        .with_override_query("(jsx_self_closing_element) @element")
16161        .unwrap(),
16162        lsp::ServerCapabilities {
16163            completion_provider: Some(lsp::CompletionOptions {
16164                trigger_characters: Some(vec![":".to_string()]),
16165                ..Default::default()
16166            }),
16167            ..Default::default()
16168        },
16169        cx,
16170    )
16171    .await;
16172
16173    cx.lsp
16174        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16175            Ok(Some(lsp::CompletionResponse::Array(vec![
16176                lsp::CompletionItem {
16177                    label: "bg-blue".into(),
16178                    ..Default::default()
16179                },
16180                lsp::CompletionItem {
16181                    label: "bg-red".into(),
16182                    ..Default::default()
16183                },
16184                lsp::CompletionItem {
16185                    label: "bg-yellow".into(),
16186                    ..Default::default()
16187                },
16188            ])))
16189        });
16190
16191    cx.set_state(r#"<p class="bgˇ" />"#);
16192
16193    // Trigger completion when typing a dash, because the dash is an extra
16194    // word character in the 'element' scope, which contains the cursor.
16195    cx.simulate_keystroke("-");
16196    cx.executor().run_until_parked();
16197    cx.update_editor(|editor, _, _| {
16198        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16199        {
16200            assert_eq!(
16201                completion_menu_entries(&menu),
16202                &["bg-blue", "bg-red", "bg-yellow"]
16203            );
16204        } else {
16205            panic!("expected completion menu to be open");
16206        }
16207    });
16208
16209    cx.simulate_keystroke("l");
16210    cx.executor().run_until_parked();
16211    cx.update_editor(|editor, _, _| {
16212        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16213        {
16214            assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
16215        } else {
16216            panic!("expected completion menu to be open");
16217        }
16218    });
16219
16220    // When filtering completions, consider the character after the '-' to
16221    // be the start of a subword.
16222    cx.set_state(r#"<p class="yelˇ" />"#);
16223    cx.simulate_keystroke("l");
16224    cx.executor().run_until_parked();
16225    cx.update_editor(|editor, _, _| {
16226        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16227        {
16228            assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
16229        } else {
16230            panic!("expected completion menu to be open");
16231        }
16232    });
16233}
16234
16235fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
16236    let entries = menu.entries.borrow();
16237    entries.iter().map(|mat| mat.string.clone()).collect()
16238}
16239
16240#[gpui::test]
16241async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
16242    init_test(cx, |settings| {
16243        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
16244            Formatter::Prettier,
16245        )))
16246    });
16247
16248    let fs = FakeFs::new(cx.executor());
16249    fs.insert_file(path!("/file.ts"), Default::default()).await;
16250
16251    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
16252    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16253
16254    language_registry.add(Arc::new(Language::new(
16255        LanguageConfig {
16256            name: "TypeScript".into(),
16257            matcher: LanguageMatcher {
16258                path_suffixes: vec!["ts".to_string()],
16259                ..Default::default()
16260            },
16261            ..Default::default()
16262        },
16263        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
16264    )));
16265    update_test_language_settings(cx, |settings| {
16266        settings.defaults.prettier = Some(PrettierSettings {
16267            allowed: true,
16268            ..PrettierSettings::default()
16269        });
16270    });
16271
16272    let test_plugin = "test_plugin";
16273    let _ = language_registry.register_fake_lsp(
16274        "TypeScript",
16275        FakeLspAdapter {
16276            prettier_plugins: vec![test_plugin],
16277            ..Default::default()
16278        },
16279    );
16280
16281    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
16282    let buffer = project
16283        .update(cx, |project, cx| {
16284            project.open_local_buffer(path!("/file.ts"), cx)
16285        })
16286        .await
16287        .unwrap();
16288
16289    let buffer_text = "one\ntwo\nthree\n";
16290    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16291    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16292    editor.update_in(cx, |editor, window, cx| {
16293        editor.set_text(buffer_text, window, cx)
16294    });
16295
16296    editor
16297        .update_in(cx, |editor, window, cx| {
16298            editor.perform_format(
16299                project.clone(),
16300                FormatTrigger::Manual,
16301                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
16302                window,
16303                cx,
16304            )
16305        })
16306        .unwrap()
16307        .await;
16308    assert_eq!(
16309        editor.update(cx, |editor, cx| editor.text(cx)),
16310        buffer_text.to_string() + prettier_format_suffix,
16311        "Test prettier formatting was not applied to the original buffer text",
16312    );
16313
16314    update_test_language_settings(cx, |settings| {
16315        settings.defaults.formatter = Some(SelectedFormatter::Auto)
16316    });
16317    let format = editor.update_in(cx, |editor, window, cx| {
16318        editor.perform_format(
16319            project.clone(),
16320            FormatTrigger::Manual,
16321            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
16322            window,
16323            cx,
16324        )
16325    });
16326    format.await.unwrap();
16327    assert_eq!(
16328        editor.update(cx, |editor, cx| editor.text(cx)),
16329        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
16330        "Autoformatting (via test prettier) was not applied to the original buffer text",
16331    );
16332}
16333
16334#[gpui::test]
16335async fn test_addition_reverts(cx: &mut TestAppContext) {
16336    init_test(cx, |_| {});
16337    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16338    let base_text = indoc! {r#"
16339        struct Row;
16340        struct Row1;
16341        struct Row2;
16342
16343        struct Row4;
16344        struct Row5;
16345        struct Row6;
16346
16347        struct Row8;
16348        struct Row9;
16349        struct Row10;"#};
16350
16351    // When addition hunks are not adjacent to carets, no hunk revert is performed
16352    assert_hunk_revert(
16353        indoc! {r#"struct Row;
16354                   struct Row1;
16355                   struct Row1.1;
16356                   struct Row1.2;
16357                   struct Row2;ˇ
16358
16359                   struct Row4;
16360                   struct Row5;
16361                   struct Row6;
16362
16363                   struct Row8;
16364                   ˇstruct Row9;
16365                   struct Row9.1;
16366                   struct Row9.2;
16367                   struct Row9.3;
16368                   struct Row10;"#},
16369        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
16370        indoc! {r#"struct Row;
16371                   struct Row1;
16372                   struct Row1.1;
16373                   struct Row1.2;
16374                   struct Row2;ˇ
16375
16376                   struct Row4;
16377                   struct Row5;
16378                   struct Row6;
16379
16380                   struct Row8;
16381                   ˇstruct Row9;
16382                   struct Row9.1;
16383                   struct Row9.2;
16384                   struct Row9.3;
16385                   struct Row10;"#},
16386        base_text,
16387        &mut cx,
16388    );
16389    // Same for selections
16390    assert_hunk_revert(
16391        indoc! {r#"struct Row;
16392                   struct Row1;
16393                   struct Row2;
16394                   struct Row2.1;
16395                   struct Row2.2;
16396                   «ˇ
16397                   struct Row4;
16398                   struct» Row5;
16399                   «struct Row6;
16400                   ˇ»
16401                   struct Row9.1;
16402                   struct Row9.2;
16403                   struct Row9.3;
16404                   struct Row8;
16405                   struct Row9;
16406                   struct Row10;"#},
16407        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
16408        indoc! {r#"struct Row;
16409                   struct Row1;
16410                   struct Row2;
16411                   struct Row2.1;
16412                   struct Row2.2;
16413                   «ˇ
16414                   struct Row4;
16415                   struct» Row5;
16416                   «struct Row6;
16417                   ˇ»
16418                   struct Row9.1;
16419                   struct Row9.2;
16420                   struct Row9.3;
16421                   struct Row8;
16422                   struct Row9;
16423                   struct Row10;"#},
16424        base_text,
16425        &mut cx,
16426    );
16427
16428    // When carets and selections intersect the addition hunks, those are reverted.
16429    // Adjacent carets got merged.
16430    assert_hunk_revert(
16431        indoc! {r#"struct Row;
16432                   ˇ// something on the top
16433                   struct Row1;
16434                   struct Row2;
16435                   struct Roˇw3.1;
16436                   struct Row2.2;
16437                   struct Row2.3;ˇ
16438
16439                   struct Row4;
16440                   struct ˇRow5.1;
16441                   struct Row5.2;
16442                   struct «Rowˇ»5.3;
16443                   struct Row5;
16444                   struct Row6;
16445                   ˇ
16446                   struct Row9.1;
16447                   struct «Rowˇ»9.2;
16448                   struct «ˇRow»9.3;
16449                   struct Row8;
16450                   struct Row9;
16451                   «ˇ// something on bottom»
16452                   struct Row10;"#},
16453        vec![
16454            DiffHunkStatusKind::Added,
16455            DiffHunkStatusKind::Added,
16456            DiffHunkStatusKind::Added,
16457            DiffHunkStatusKind::Added,
16458            DiffHunkStatusKind::Added,
16459        ],
16460        indoc! {r#"struct Row;
16461                   ˇstruct Row1;
16462                   struct Row2;
16463                   ˇ
16464                   struct Row4;
16465                   ˇstruct Row5;
16466                   struct Row6;
16467                   ˇ
16468                   ˇstruct Row8;
16469                   struct Row9;
16470                   ˇstruct Row10;"#},
16471        base_text,
16472        &mut cx,
16473    );
16474}
16475
16476#[gpui::test]
16477async fn test_modification_reverts(cx: &mut TestAppContext) {
16478    init_test(cx, |_| {});
16479    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16480    let base_text = indoc! {r#"
16481        struct Row;
16482        struct Row1;
16483        struct Row2;
16484
16485        struct Row4;
16486        struct Row5;
16487        struct Row6;
16488
16489        struct Row8;
16490        struct Row9;
16491        struct Row10;"#};
16492
16493    // Modification hunks behave the same as the addition ones.
16494    assert_hunk_revert(
16495        indoc! {r#"struct Row;
16496                   struct Row1;
16497                   struct Row33;
16498                   ˇ
16499                   struct Row4;
16500                   struct Row5;
16501                   struct Row6;
16502                   ˇ
16503                   struct Row99;
16504                   struct Row9;
16505                   struct Row10;"#},
16506        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
16507        indoc! {r#"struct Row;
16508                   struct Row1;
16509                   struct Row33;
16510                   ˇ
16511                   struct Row4;
16512                   struct Row5;
16513                   struct Row6;
16514                   ˇ
16515                   struct Row99;
16516                   struct Row9;
16517                   struct Row10;"#},
16518        base_text,
16519        &mut cx,
16520    );
16521    assert_hunk_revert(
16522        indoc! {r#"struct Row;
16523                   struct Row1;
16524                   struct Row33;
16525                   «ˇ
16526                   struct Row4;
16527                   struct» Row5;
16528                   «struct Row6;
16529                   ˇ»
16530                   struct Row99;
16531                   struct Row9;
16532                   struct Row10;"#},
16533        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
16534        indoc! {r#"struct Row;
16535                   struct Row1;
16536                   struct Row33;
16537                   «ˇ
16538                   struct Row4;
16539                   struct» Row5;
16540                   «struct Row6;
16541                   ˇ»
16542                   struct Row99;
16543                   struct Row9;
16544                   struct Row10;"#},
16545        base_text,
16546        &mut cx,
16547    );
16548
16549    assert_hunk_revert(
16550        indoc! {r#"ˇstruct Row1.1;
16551                   struct Row1;
16552                   «ˇstr»uct Row22;
16553
16554                   struct ˇRow44;
16555                   struct Row5;
16556                   struct «Rˇ»ow66;ˇ
16557
16558                   «struˇ»ct Row88;
16559                   struct Row9;
16560                   struct Row1011;ˇ"#},
16561        vec![
16562            DiffHunkStatusKind::Modified,
16563            DiffHunkStatusKind::Modified,
16564            DiffHunkStatusKind::Modified,
16565            DiffHunkStatusKind::Modified,
16566            DiffHunkStatusKind::Modified,
16567            DiffHunkStatusKind::Modified,
16568        ],
16569        indoc! {r#"struct Row;
16570                   ˇstruct Row1;
16571                   struct Row2;
16572                   ˇ
16573                   struct Row4;
16574                   ˇstruct Row5;
16575                   struct Row6;
16576                   ˇ
16577                   struct Row8;
16578                   ˇstruct Row9;
16579                   struct Row10;ˇ"#},
16580        base_text,
16581        &mut cx,
16582    );
16583}
16584
16585#[gpui::test]
16586async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
16587    init_test(cx, |_| {});
16588    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16589    let base_text = indoc! {r#"
16590        one
16591
16592        two
16593        three
16594        "#};
16595
16596    cx.set_head_text(base_text);
16597    cx.set_state("\nˇ\n");
16598    cx.executor().run_until_parked();
16599    cx.update_editor(|editor, _window, cx| {
16600        editor.expand_selected_diff_hunks(cx);
16601    });
16602    cx.executor().run_until_parked();
16603    cx.update_editor(|editor, window, cx| {
16604        editor.backspace(&Default::default(), window, cx);
16605    });
16606    cx.run_until_parked();
16607    cx.assert_state_with_diff(
16608        indoc! {r#"
16609
16610        - two
16611        - threeˇ
16612        +
16613        "#}
16614        .to_string(),
16615    );
16616}
16617
16618#[gpui::test]
16619async fn test_deletion_reverts(cx: &mut TestAppContext) {
16620    init_test(cx, |_| {});
16621    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16622    let base_text = indoc! {r#"struct Row;
16623struct Row1;
16624struct Row2;
16625
16626struct Row4;
16627struct Row5;
16628struct Row6;
16629
16630struct Row8;
16631struct Row9;
16632struct Row10;"#};
16633
16634    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
16635    assert_hunk_revert(
16636        indoc! {r#"struct Row;
16637                   struct Row2;
16638
16639                   ˇstruct Row4;
16640                   struct Row5;
16641                   struct Row6;
16642                   ˇ
16643                   struct Row8;
16644                   struct Row10;"#},
16645        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16646        indoc! {r#"struct Row;
16647                   struct Row2;
16648
16649                   ˇstruct Row4;
16650                   struct Row5;
16651                   struct Row6;
16652                   ˇ
16653                   struct Row8;
16654                   struct Row10;"#},
16655        base_text,
16656        &mut cx,
16657    );
16658    assert_hunk_revert(
16659        indoc! {r#"struct Row;
16660                   struct Row2;
16661
16662                   «ˇstruct Row4;
16663                   struct» Row5;
16664                   «struct Row6;
16665                   ˇ»
16666                   struct Row8;
16667                   struct Row10;"#},
16668        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16669        indoc! {r#"struct Row;
16670                   struct Row2;
16671
16672                   «ˇstruct Row4;
16673                   struct» Row5;
16674                   «struct Row6;
16675                   ˇ»
16676                   struct Row8;
16677                   struct Row10;"#},
16678        base_text,
16679        &mut cx,
16680    );
16681
16682    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
16683    assert_hunk_revert(
16684        indoc! {r#"struct Row;
16685                   ˇstruct Row2;
16686
16687                   struct Row4;
16688                   struct Row5;
16689                   struct Row6;
16690
16691                   struct Row8;ˇ
16692                   struct Row10;"#},
16693        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16694        indoc! {r#"struct Row;
16695                   struct Row1;
16696                   ˇstruct Row2;
16697
16698                   struct Row4;
16699                   struct Row5;
16700                   struct Row6;
16701
16702                   struct Row8;ˇ
16703                   struct Row9;
16704                   struct Row10;"#},
16705        base_text,
16706        &mut cx,
16707    );
16708    assert_hunk_revert(
16709        indoc! {r#"struct Row;
16710                   struct Row2«ˇ;
16711                   struct Row4;
16712                   struct» Row5;
16713                   «struct Row6;
16714
16715                   struct Row8;ˇ»
16716                   struct Row10;"#},
16717        vec![
16718            DiffHunkStatusKind::Deleted,
16719            DiffHunkStatusKind::Deleted,
16720            DiffHunkStatusKind::Deleted,
16721        ],
16722        indoc! {r#"struct Row;
16723                   struct Row1;
16724                   struct Row2«ˇ;
16725
16726                   struct Row4;
16727                   struct» Row5;
16728                   «struct Row6;
16729
16730                   struct Row8;ˇ»
16731                   struct Row9;
16732                   struct Row10;"#},
16733        base_text,
16734        &mut cx,
16735    );
16736}
16737
16738#[gpui::test]
16739async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
16740    init_test(cx, |_| {});
16741
16742    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
16743    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
16744    let base_text_3 =
16745        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
16746
16747    let text_1 = edit_first_char_of_every_line(base_text_1);
16748    let text_2 = edit_first_char_of_every_line(base_text_2);
16749    let text_3 = edit_first_char_of_every_line(base_text_3);
16750
16751    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
16752    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
16753    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
16754
16755    let multibuffer = cx.new(|cx| {
16756        let mut multibuffer = MultiBuffer::new(ReadWrite);
16757        multibuffer.push_excerpts(
16758            buffer_1.clone(),
16759            [
16760                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16761                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16762                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16763            ],
16764            cx,
16765        );
16766        multibuffer.push_excerpts(
16767            buffer_2.clone(),
16768            [
16769                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16770                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16771                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16772            ],
16773            cx,
16774        );
16775        multibuffer.push_excerpts(
16776            buffer_3.clone(),
16777            [
16778                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16779                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16780                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16781            ],
16782            cx,
16783        );
16784        multibuffer
16785    });
16786
16787    let fs = FakeFs::new(cx.executor());
16788    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
16789    let (editor, cx) = cx
16790        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
16791    editor.update_in(cx, |editor, _window, cx| {
16792        for (buffer, diff_base) in [
16793            (buffer_1.clone(), base_text_1),
16794            (buffer_2.clone(), base_text_2),
16795            (buffer_3.clone(), base_text_3),
16796        ] {
16797            let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
16798            editor
16799                .buffer
16800                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
16801        }
16802    });
16803    cx.executor().run_until_parked();
16804
16805    editor.update_in(cx, |editor, window, cx| {
16806        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}");
16807        editor.select_all(&SelectAll, window, cx);
16808        editor.git_restore(&Default::default(), window, cx);
16809    });
16810    cx.executor().run_until_parked();
16811
16812    // When all ranges are selected, all buffer hunks are reverted.
16813    editor.update(cx, |editor, cx| {
16814        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");
16815    });
16816    buffer_1.update(cx, |buffer, _| {
16817        assert_eq!(buffer.text(), base_text_1);
16818    });
16819    buffer_2.update(cx, |buffer, _| {
16820        assert_eq!(buffer.text(), base_text_2);
16821    });
16822    buffer_3.update(cx, |buffer, _| {
16823        assert_eq!(buffer.text(), base_text_3);
16824    });
16825
16826    editor.update_in(cx, |editor, window, cx| {
16827        editor.undo(&Default::default(), window, cx);
16828    });
16829
16830    editor.update_in(cx, |editor, window, cx| {
16831        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16832            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
16833        });
16834        editor.git_restore(&Default::default(), window, cx);
16835    });
16836
16837    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
16838    // but not affect buffer_2 and its related excerpts.
16839    editor.update(cx, |editor, cx| {
16840        assert_eq!(
16841            editor.text(cx),
16842            "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}"
16843        );
16844    });
16845    buffer_1.update(cx, |buffer, _| {
16846        assert_eq!(buffer.text(), base_text_1);
16847    });
16848    buffer_2.update(cx, |buffer, _| {
16849        assert_eq!(
16850            buffer.text(),
16851            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
16852        );
16853    });
16854    buffer_3.update(cx, |buffer, _| {
16855        assert_eq!(
16856            buffer.text(),
16857            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
16858        );
16859    });
16860
16861    fn edit_first_char_of_every_line(text: &str) -> String {
16862        text.split('\n')
16863            .map(|line| format!("X{}", &line[1..]))
16864            .collect::<Vec<_>>()
16865            .join("\n")
16866    }
16867}
16868
16869#[gpui::test]
16870async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
16871    init_test(cx, |_| {});
16872
16873    let cols = 4;
16874    let rows = 10;
16875    let sample_text_1 = sample_text(rows, cols, 'a');
16876    assert_eq!(
16877        sample_text_1,
16878        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
16879    );
16880    let sample_text_2 = sample_text(rows, cols, 'l');
16881    assert_eq!(
16882        sample_text_2,
16883        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
16884    );
16885    let sample_text_3 = sample_text(rows, cols, 'v');
16886    assert_eq!(
16887        sample_text_3,
16888        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
16889    );
16890
16891    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
16892    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
16893    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
16894
16895    let multi_buffer = cx.new(|cx| {
16896        let mut multibuffer = MultiBuffer::new(ReadWrite);
16897        multibuffer.push_excerpts(
16898            buffer_1.clone(),
16899            [
16900                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16901                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16902                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16903            ],
16904            cx,
16905        );
16906        multibuffer.push_excerpts(
16907            buffer_2.clone(),
16908            [
16909                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16910                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16911                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16912            ],
16913            cx,
16914        );
16915        multibuffer.push_excerpts(
16916            buffer_3.clone(),
16917            [
16918                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16919                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16920                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16921            ],
16922            cx,
16923        );
16924        multibuffer
16925    });
16926
16927    let fs = FakeFs::new(cx.executor());
16928    fs.insert_tree(
16929        "/a",
16930        json!({
16931            "main.rs": sample_text_1,
16932            "other.rs": sample_text_2,
16933            "lib.rs": sample_text_3,
16934        }),
16935    )
16936    .await;
16937    let project = Project::test(fs, ["/a".as_ref()], cx).await;
16938    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16939    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16940    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16941        Editor::new(
16942            EditorMode::full(),
16943            multi_buffer,
16944            Some(project.clone()),
16945            window,
16946            cx,
16947        )
16948    });
16949    let multibuffer_item_id = workspace
16950        .update(cx, |workspace, window, cx| {
16951            assert!(
16952                workspace.active_item(cx).is_none(),
16953                "active item should be None before the first item is added"
16954            );
16955            workspace.add_item_to_active_pane(
16956                Box::new(multi_buffer_editor.clone()),
16957                None,
16958                true,
16959                window,
16960                cx,
16961            );
16962            let active_item = workspace
16963                .active_item(cx)
16964                .expect("should have an active item after adding the multi buffer");
16965            assert!(
16966                !active_item.is_singleton(cx),
16967                "A multi buffer was expected to active after adding"
16968            );
16969            active_item.item_id()
16970        })
16971        .unwrap();
16972    cx.executor().run_until_parked();
16973
16974    multi_buffer_editor.update_in(cx, |editor, window, cx| {
16975        editor.change_selections(
16976            SelectionEffects::scroll(Autoscroll::Next),
16977            window,
16978            cx,
16979            |s| s.select_ranges(Some(1..2)),
16980        );
16981        editor.open_excerpts(&OpenExcerpts, window, cx);
16982    });
16983    cx.executor().run_until_parked();
16984    let first_item_id = workspace
16985        .update(cx, |workspace, window, cx| {
16986            let active_item = workspace
16987                .active_item(cx)
16988                .expect("should have an active item after navigating into the 1st buffer");
16989            let first_item_id = active_item.item_id();
16990            assert_ne!(
16991                first_item_id, multibuffer_item_id,
16992                "Should navigate into the 1st buffer and activate it"
16993            );
16994            assert!(
16995                active_item.is_singleton(cx),
16996                "New active item should be a singleton buffer"
16997            );
16998            assert_eq!(
16999                active_item
17000                    .act_as::<Editor>(cx)
17001                    .expect("should have navigated into an editor for the 1st buffer")
17002                    .read(cx)
17003                    .text(cx),
17004                sample_text_1
17005            );
17006
17007            workspace
17008                .go_back(workspace.active_pane().downgrade(), window, cx)
17009                .detach_and_log_err(cx);
17010
17011            first_item_id
17012        })
17013        .unwrap();
17014    cx.executor().run_until_parked();
17015    workspace
17016        .update(cx, |workspace, _, cx| {
17017            let active_item = workspace
17018                .active_item(cx)
17019                .expect("should have an active item after navigating back");
17020            assert_eq!(
17021                active_item.item_id(),
17022                multibuffer_item_id,
17023                "Should navigate back to the multi buffer"
17024            );
17025            assert!(!active_item.is_singleton(cx));
17026        })
17027        .unwrap();
17028
17029    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17030        editor.change_selections(
17031            SelectionEffects::scroll(Autoscroll::Next),
17032            window,
17033            cx,
17034            |s| s.select_ranges(Some(39..40)),
17035        );
17036        editor.open_excerpts(&OpenExcerpts, window, cx);
17037    });
17038    cx.executor().run_until_parked();
17039    let second_item_id = workspace
17040        .update(cx, |workspace, window, cx| {
17041            let active_item = workspace
17042                .active_item(cx)
17043                .expect("should have an active item after navigating into the 2nd buffer");
17044            let second_item_id = active_item.item_id();
17045            assert_ne!(
17046                second_item_id, multibuffer_item_id,
17047                "Should navigate away from the multibuffer"
17048            );
17049            assert_ne!(
17050                second_item_id, first_item_id,
17051                "Should navigate into the 2nd buffer and activate it"
17052            );
17053            assert!(
17054                active_item.is_singleton(cx),
17055                "New active item should be a singleton buffer"
17056            );
17057            assert_eq!(
17058                active_item
17059                    .act_as::<Editor>(cx)
17060                    .expect("should have navigated into an editor")
17061                    .read(cx)
17062                    .text(cx),
17063                sample_text_2
17064            );
17065
17066            workspace
17067                .go_back(workspace.active_pane().downgrade(), window, cx)
17068                .detach_and_log_err(cx);
17069
17070            second_item_id
17071        })
17072        .unwrap();
17073    cx.executor().run_until_parked();
17074    workspace
17075        .update(cx, |workspace, _, cx| {
17076            let active_item = workspace
17077                .active_item(cx)
17078                .expect("should have an active item after navigating back from the 2nd buffer");
17079            assert_eq!(
17080                active_item.item_id(),
17081                multibuffer_item_id,
17082                "Should navigate back from the 2nd buffer to the multi buffer"
17083            );
17084            assert!(!active_item.is_singleton(cx));
17085        })
17086        .unwrap();
17087
17088    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17089        editor.change_selections(
17090            SelectionEffects::scroll(Autoscroll::Next),
17091            window,
17092            cx,
17093            |s| s.select_ranges(Some(70..70)),
17094        );
17095        editor.open_excerpts(&OpenExcerpts, window, cx);
17096    });
17097    cx.executor().run_until_parked();
17098    workspace
17099        .update(cx, |workspace, window, cx| {
17100            let active_item = workspace
17101                .active_item(cx)
17102                .expect("should have an active item after navigating into the 3rd buffer");
17103            let third_item_id = active_item.item_id();
17104            assert_ne!(
17105                third_item_id, multibuffer_item_id,
17106                "Should navigate into the 3rd buffer and activate it"
17107            );
17108            assert_ne!(third_item_id, first_item_id);
17109            assert_ne!(third_item_id, second_item_id);
17110            assert!(
17111                active_item.is_singleton(cx),
17112                "New active item should be a singleton buffer"
17113            );
17114            assert_eq!(
17115                active_item
17116                    .act_as::<Editor>(cx)
17117                    .expect("should have navigated into an editor")
17118                    .read(cx)
17119                    .text(cx),
17120                sample_text_3
17121            );
17122
17123            workspace
17124                .go_back(workspace.active_pane().downgrade(), window, cx)
17125                .detach_and_log_err(cx);
17126        })
17127        .unwrap();
17128    cx.executor().run_until_parked();
17129    workspace
17130        .update(cx, |workspace, _, cx| {
17131            let active_item = workspace
17132                .active_item(cx)
17133                .expect("should have an active item after navigating back from the 3rd buffer");
17134            assert_eq!(
17135                active_item.item_id(),
17136                multibuffer_item_id,
17137                "Should navigate back from the 3rd buffer to the multi buffer"
17138            );
17139            assert!(!active_item.is_singleton(cx));
17140        })
17141        .unwrap();
17142}
17143
17144#[gpui::test]
17145async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17146    init_test(cx, |_| {});
17147
17148    let mut cx = EditorTestContext::new(cx).await;
17149
17150    let diff_base = r#"
17151        use some::mod;
17152
17153        const A: u32 = 42;
17154
17155        fn main() {
17156            println!("hello");
17157
17158            println!("world");
17159        }
17160        "#
17161    .unindent();
17162
17163    cx.set_state(
17164        &r#"
17165        use some::modified;
17166
17167        ˇ
17168        fn main() {
17169            println!("hello there");
17170
17171            println!("around the");
17172            println!("world");
17173        }
17174        "#
17175        .unindent(),
17176    );
17177
17178    cx.set_head_text(&diff_base);
17179    executor.run_until_parked();
17180
17181    cx.update_editor(|editor, window, cx| {
17182        editor.go_to_next_hunk(&GoToHunk, window, cx);
17183        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17184    });
17185    executor.run_until_parked();
17186    cx.assert_state_with_diff(
17187        r#"
17188          use some::modified;
17189
17190
17191          fn main() {
17192        -     println!("hello");
17193        + ˇ    println!("hello there");
17194
17195              println!("around the");
17196              println!("world");
17197          }
17198        "#
17199        .unindent(),
17200    );
17201
17202    cx.update_editor(|editor, window, cx| {
17203        for _ in 0..2 {
17204            editor.go_to_next_hunk(&GoToHunk, window, cx);
17205            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17206        }
17207    });
17208    executor.run_until_parked();
17209    cx.assert_state_with_diff(
17210        r#"
17211        - use some::mod;
17212        + ˇuse some::modified;
17213
17214
17215          fn main() {
17216        -     println!("hello");
17217        +     println!("hello there");
17218
17219        +     println!("around the");
17220              println!("world");
17221          }
17222        "#
17223        .unindent(),
17224    );
17225
17226    cx.update_editor(|editor, window, cx| {
17227        editor.go_to_next_hunk(&GoToHunk, window, cx);
17228        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17229    });
17230    executor.run_until_parked();
17231    cx.assert_state_with_diff(
17232        r#"
17233        - use some::mod;
17234        + use some::modified;
17235
17236        - const A: u32 = 42;
17237          ˇ
17238          fn main() {
17239        -     println!("hello");
17240        +     println!("hello there");
17241
17242        +     println!("around the");
17243              println!("world");
17244          }
17245        "#
17246        .unindent(),
17247    );
17248
17249    cx.update_editor(|editor, window, cx| {
17250        editor.cancel(&Cancel, window, cx);
17251    });
17252
17253    cx.assert_state_with_diff(
17254        r#"
17255          use some::modified;
17256
17257          ˇ
17258          fn main() {
17259              println!("hello there");
17260
17261              println!("around the");
17262              println!("world");
17263          }
17264        "#
17265        .unindent(),
17266    );
17267}
17268
17269#[gpui::test]
17270async fn test_diff_base_change_with_expanded_diff_hunks(
17271    executor: BackgroundExecutor,
17272    cx: &mut TestAppContext,
17273) {
17274    init_test(cx, |_| {});
17275
17276    let mut cx = EditorTestContext::new(cx).await;
17277
17278    let diff_base = r#"
17279        use some::mod1;
17280        use some::mod2;
17281
17282        const A: u32 = 42;
17283        const B: u32 = 42;
17284        const C: u32 = 42;
17285
17286        fn main() {
17287            println!("hello");
17288
17289            println!("world");
17290        }
17291        "#
17292    .unindent();
17293
17294    cx.set_state(
17295        &r#"
17296        use some::mod2;
17297
17298        const A: u32 = 42;
17299        const C: u32 = 42;
17300
17301        fn main(ˇ) {
17302            //println!("hello");
17303
17304            println!("world");
17305            //
17306            //
17307        }
17308        "#
17309        .unindent(),
17310    );
17311
17312    cx.set_head_text(&diff_base);
17313    executor.run_until_parked();
17314
17315    cx.update_editor(|editor, window, cx| {
17316        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17317    });
17318    executor.run_until_parked();
17319    cx.assert_state_with_diff(
17320        r#"
17321        - use some::mod1;
17322          use some::mod2;
17323
17324          const A: u32 = 42;
17325        - const B: u32 = 42;
17326          const C: u32 = 42;
17327
17328          fn main(ˇ) {
17329        -     println!("hello");
17330        +     //println!("hello");
17331
17332              println!("world");
17333        +     //
17334        +     //
17335          }
17336        "#
17337        .unindent(),
17338    );
17339
17340    cx.set_head_text("new diff base!");
17341    executor.run_until_parked();
17342    cx.assert_state_with_diff(
17343        r#"
17344        - new diff base!
17345        + use some::mod2;
17346        +
17347        + const A: u32 = 42;
17348        + const C: u32 = 42;
17349        +
17350        + fn main(ˇ) {
17351        +     //println!("hello");
17352        +
17353        +     println!("world");
17354        +     //
17355        +     //
17356        + }
17357        "#
17358        .unindent(),
17359    );
17360}
17361
17362#[gpui::test]
17363async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
17364    init_test(cx, |_| {});
17365
17366    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
17367    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
17368    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
17369    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
17370    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
17371    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
17372
17373    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
17374    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
17375    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
17376
17377    let multi_buffer = cx.new(|cx| {
17378        let mut multibuffer = MultiBuffer::new(ReadWrite);
17379        multibuffer.push_excerpts(
17380            buffer_1.clone(),
17381            [
17382                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17383                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17384                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17385            ],
17386            cx,
17387        );
17388        multibuffer.push_excerpts(
17389            buffer_2.clone(),
17390            [
17391                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17392                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17393                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17394            ],
17395            cx,
17396        );
17397        multibuffer.push_excerpts(
17398            buffer_3.clone(),
17399            [
17400                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17401                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17402                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17403            ],
17404            cx,
17405        );
17406        multibuffer
17407    });
17408
17409    let editor =
17410        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
17411    editor
17412        .update(cx, |editor, _window, cx| {
17413            for (buffer, diff_base) in [
17414                (buffer_1.clone(), file_1_old),
17415                (buffer_2.clone(), file_2_old),
17416                (buffer_3.clone(), file_3_old),
17417            ] {
17418                let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
17419                editor
17420                    .buffer
17421                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
17422            }
17423        })
17424        .unwrap();
17425
17426    let mut cx = EditorTestContext::for_editor(editor, cx).await;
17427    cx.run_until_parked();
17428
17429    cx.assert_editor_state(
17430        &"
17431            ˇaaa
17432            ccc
17433            ddd
17434
17435            ggg
17436            hhh
17437
17438
17439            lll
17440            mmm
17441            NNN
17442
17443            qqq
17444            rrr
17445
17446            uuu
17447            111
17448            222
17449            333
17450
17451            666
17452            777
17453
17454            000
17455            !!!"
17456        .unindent(),
17457    );
17458
17459    cx.update_editor(|editor, window, cx| {
17460        editor.select_all(&SelectAll, window, cx);
17461        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17462    });
17463    cx.executor().run_until_parked();
17464
17465    cx.assert_state_with_diff(
17466        "
17467            «aaa
17468          - bbb
17469            ccc
17470            ddd
17471
17472            ggg
17473            hhh
17474
17475
17476            lll
17477            mmm
17478          - nnn
17479          + NNN
17480
17481            qqq
17482            rrr
17483
17484            uuu
17485            111
17486            222
17487            333
17488
17489          + 666
17490            777
17491
17492            000
17493            !!!ˇ»"
17494            .unindent(),
17495    );
17496}
17497
17498#[gpui::test]
17499async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
17500    init_test(cx, |_| {});
17501
17502    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
17503    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
17504
17505    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
17506    let multi_buffer = cx.new(|cx| {
17507        let mut multibuffer = MultiBuffer::new(ReadWrite);
17508        multibuffer.push_excerpts(
17509            buffer.clone(),
17510            [
17511                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
17512                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
17513                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
17514            ],
17515            cx,
17516        );
17517        multibuffer
17518    });
17519
17520    let editor =
17521        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
17522    editor
17523        .update(cx, |editor, _window, cx| {
17524            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
17525            editor
17526                .buffer
17527                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
17528        })
17529        .unwrap();
17530
17531    let mut cx = EditorTestContext::for_editor(editor, cx).await;
17532    cx.run_until_parked();
17533
17534    cx.update_editor(|editor, window, cx| {
17535        editor.expand_all_diff_hunks(&Default::default(), window, cx)
17536    });
17537    cx.executor().run_until_parked();
17538
17539    // When the start of a hunk coincides with the start of its excerpt,
17540    // the hunk is expanded. When the start of a a hunk is earlier than
17541    // the start of its excerpt, the hunk is not expanded.
17542    cx.assert_state_with_diff(
17543        "
17544            ˇaaa
17545          - bbb
17546          + BBB
17547
17548          - ddd
17549          - eee
17550          + DDD
17551          + EEE
17552            fff
17553
17554            iii
17555        "
17556        .unindent(),
17557    );
17558}
17559
17560#[gpui::test]
17561async fn test_edits_around_expanded_insertion_hunks(
17562    executor: BackgroundExecutor,
17563    cx: &mut TestAppContext,
17564) {
17565    init_test(cx, |_| {});
17566
17567    let mut cx = EditorTestContext::new(cx).await;
17568
17569    let diff_base = r#"
17570        use some::mod1;
17571        use some::mod2;
17572
17573        const A: u32 = 42;
17574
17575        fn main() {
17576            println!("hello");
17577
17578            println!("world");
17579        }
17580        "#
17581    .unindent();
17582    executor.run_until_parked();
17583    cx.set_state(
17584        &r#"
17585        use some::mod1;
17586        use some::mod2;
17587
17588        const A: u32 = 42;
17589        const B: u32 = 42;
17590        const C: u32 = 42;
17591        ˇ
17592
17593        fn main() {
17594            println!("hello");
17595
17596            println!("world");
17597        }
17598        "#
17599        .unindent(),
17600    );
17601
17602    cx.set_head_text(&diff_base);
17603    executor.run_until_parked();
17604
17605    cx.update_editor(|editor, window, cx| {
17606        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17607    });
17608    executor.run_until_parked();
17609
17610    cx.assert_state_with_diff(
17611        r#"
17612        use some::mod1;
17613        use some::mod2;
17614
17615        const A: u32 = 42;
17616      + const B: u32 = 42;
17617      + const C: u32 = 42;
17618      + ˇ
17619
17620        fn main() {
17621            println!("hello");
17622
17623            println!("world");
17624        }
17625      "#
17626        .unindent(),
17627    );
17628
17629    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
17630    executor.run_until_parked();
17631
17632    cx.assert_state_with_diff(
17633        r#"
17634        use some::mod1;
17635        use some::mod2;
17636
17637        const A: u32 = 42;
17638      + const B: u32 = 42;
17639      + const C: u32 = 42;
17640      + const D: u32 = 42;
17641      + ˇ
17642
17643        fn main() {
17644            println!("hello");
17645
17646            println!("world");
17647        }
17648      "#
17649        .unindent(),
17650    );
17651
17652    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
17653    executor.run_until_parked();
17654
17655    cx.assert_state_with_diff(
17656        r#"
17657        use some::mod1;
17658        use some::mod2;
17659
17660        const A: u32 = 42;
17661      + const B: u32 = 42;
17662      + const C: u32 = 42;
17663      + const D: u32 = 42;
17664      + const E: u32 = 42;
17665      + ˇ
17666
17667        fn main() {
17668            println!("hello");
17669
17670            println!("world");
17671        }
17672      "#
17673        .unindent(),
17674    );
17675
17676    cx.update_editor(|editor, window, cx| {
17677        editor.delete_line(&DeleteLine, window, cx);
17678    });
17679    executor.run_until_parked();
17680
17681    cx.assert_state_with_diff(
17682        r#"
17683        use some::mod1;
17684        use some::mod2;
17685
17686        const A: u32 = 42;
17687      + const B: u32 = 42;
17688      + const C: u32 = 42;
17689      + const D: u32 = 42;
17690      + const E: u32 = 42;
17691        ˇ
17692        fn main() {
17693            println!("hello");
17694
17695            println!("world");
17696        }
17697      "#
17698        .unindent(),
17699    );
17700
17701    cx.update_editor(|editor, window, cx| {
17702        editor.move_up(&MoveUp, window, cx);
17703        editor.delete_line(&DeleteLine, window, cx);
17704        editor.move_up(&MoveUp, window, cx);
17705        editor.delete_line(&DeleteLine, window, cx);
17706        editor.move_up(&MoveUp, window, cx);
17707        editor.delete_line(&DeleteLine, window, cx);
17708    });
17709    executor.run_until_parked();
17710    cx.assert_state_with_diff(
17711        r#"
17712        use some::mod1;
17713        use some::mod2;
17714
17715        const A: u32 = 42;
17716      + const B: u32 = 42;
17717        ˇ
17718        fn main() {
17719            println!("hello");
17720
17721            println!("world");
17722        }
17723      "#
17724        .unindent(),
17725    );
17726
17727    cx.update_editor(|editor, window, cx| {
17728        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
17729        editor.delete_line(&DeleteLine, window, cx);
17730    });
17731    executor.run_until_parked();
17732    cx.assert_state_with_diff(
17733        r#"
17734        ˇ
17735        fn main() {
17736            println!("hello");
17737
17738            println!("world");
17739        }
17740      "#
17741        .unindent(),
17742    );
17743}
17744
17745#[gpui::test]
17746async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
17747    init_test(cx, |_| {});
17748
17749    let mut cx = EditorTestContext::new(cx).await;
17750    cx.set_head_text(indoc! { "
17751        one
17752        two
17753        three
17754        four
17755        five
17756        "
17757    });
17758    cx.set_state(indoc! { "
17759        one
17760        ˇthree
17761        five
17762    "});
17763    cx.run_until_parked();
17764    cx.update_editor(|editor, window, cx| {
17765        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17766    });
17767    cx.assert_state_with_diff(
17768        indoc! { "
17769        one
17770      - two
17771        ˇthree
17772      - four
17773        five
17774    "}
17775        .to_string(),
17776    );
17777    cx.update_editor(|editor, window, cx| {
17778        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17779    });
17780
17781    cx.assert_state_with_diff(
17782        indoc! { "
17783        one
17784        ˇthree
17785        five
17786    "}
17787        .to_string(),
17788    );
17789
17790    cx.set_state(indoc! { "
17791        one
17792        ˇTWO
17793        three
17794        four
17795        five
17796    "});
17797    cx.run_until_parked();
17798    cx.update_editor(|editor, window, cx| {
17799        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17800    });
17801
17802    cx.assert_state_with_diff(
17803        indoc! { "
17804            one
17805          - two
17806          + ˇTWO
17807            three
17808            four
17809            five
17810        "}
17811        .to_string(),
17812    );
17813    cx.update_editor(|editor, window, cx| {
17814        editor.move_up(&Default::default(), window, cx);
17815        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17816    });
17817    cx.assert_state_with_diff(
17818        indoc! { "
17819            one
17820            ˇTWO
17821            three
17822            four
17823            five
17824        "}
17825        .to_string(),
17826    );
17827}
17828
17829#[gpui::test]
17830async fn test_edits_around_expanded_deletion_hunks(
17831    executor: BackgroundExecutor,
17832    cx: &mut TestAppContext,
17833) {
17834    init_test(cx, |_| {});
17835
17836    let mut cx = EditorTestContext::new(cx).await;
17837
17838    let diff_base = r#"
17839        use some::mod1;
17840        use some::mod2;
17841
17842        const A: u32 = 42;
17843        const B: u32 = 42;
17844        const C: u32 = 42;
17845
17846
17847        fn main() {
17848            println!("hello");
17849
17850            println!("world");
17851        }
17852    "#
17853    .unindent();
17854    executor.run_until_parked();
17855    cx.set_state(
17856        &r#"
17857        use some::mod1;
17858        use some::mod2;
17859
17860        ˇconst B: u32 = 42;
17861        const C: u32 = 42;
17862
17863
17864        fn main() {
17865            println!("hello");
17866
17867            println!("world");
17868        }
17869        "#
17870        .unindent(),
17871    );
17872
17873    cx.set_head_text(&diff_base);
17874    executor.run_until_parked();
17875
17876    cx.update_editor(|editor, window, cx| {
17877        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17878    });
17879    executor.run_until_parked();
17880
17881    cx.assert_state_with_diff(
17882        r#"
17883        use some::mod1;
17884        use some::mod2;
17885
17886      - const A: u32 = 42;
17887        ˇconst B: u32 = 42;
17888        const C: u32 = 42;
17889
17890
17891        fn main() {
17892            println!("hello");
17893
17894            println!("world");
17895        }
17896      "#
17897        .unindent(),
17898    );
17899
17900    cx.update_editor(|editor, window, cx| {
17901        editor.delete_line(&DeleteLine, window, cx);
17902    });
17903    executor.run_until_parked();
17904    cx.assert_state_with_diff(
17905        r#"
17906        use some::mod1;
17907        use some::mod2;
17908
17909      - const A: u32 = 42;
17910      - const B: u32 = 42;
17911        ˇconst C: u32 = 42;
17912
17913
17914        fn main() {
17915            println!("hello");
17916
17917            println!("world");
17918        }
17919      "#
17920        .unindent(),
17921    );
17922
17923    cx.update_editor(|editor, window, cx| {
17924        editor.delete_line(&DeleteLine, window, cx);
17925    });
17926    executor.run_until_parked();
17927    cx.assert_state_with_diff(
17928        r#"
17929        use some::mod1;
17930        use some::mod2;
17931
17932      - const A: u32 = 42;
17933      - const B: u32 = 42;
17934      - const C: u32 = 42;
17935        ˇ
17936
17937        fn main() {
17938            println!("hello");
17939
17940            println!("world");
17941        }
17942      "#
17943        .unindent(),
17944    );
17945
17946    cx.update_editor(|editor, window, cx| {
17947        editor.handle_input("replacement", window, cx);
17948    });
17949    executor.run_until_parked();
17950    cx.assert_state_with_diff(
17951        r#"
17952        use some::mod1;
17953        use some::mod2;
17954
17955      - const A: u32 = 42;
17956      - const B: u32 = 42;
17957      - const C: u32 = 42;
17958      -
17959      + replacementˇ
17960
17961        fn main() {
17962            println!("hello");
17963
17964            println!("world");
17965        }
17966      "#
17967        .unindent(),
17968    );
17969}
17970
17971#[gpui::test]
17972async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17973    init_test(cx, |_| {});
17974
17975    let mut cx = EditorTestContext::new(cx).await;
17976
17977    let base_text = r#"
17978        one
17979        two
17980        three
17981        four
17982        five
17983    "#
17984    .unindent();
17985    executor.run_until_parked();
17986    cx.set_state(
17987        &r#"
17988        one
17989        two
17990        fˇour
17991        five
17992        "#
17993        .unindent(),
17994    );
17995
17996    cx.set_head_text(&base_text);
17997    executor.run_until_parked();
17998
17999    cx.update_editor(|editor, window, cx| {
18000        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18001    });
18002    executor.run_until_parked();
18003
18004    cx.assert_state_with_diff(
18005        r#"
18006          one
18007          two
18008        - three
18009          fˇour
18010          five
18011        "#
18012        .unindent(),
18013    );
18014
18015    cx.update_editor(|editor, window, cx| {
18016        editor.backspace(&Backspace, window, cx);
18017        editor.backspace(&Backspace, window, cx);
18018    });
18019    executor.run_until_parked();
18020    cx.assert_state_with_diff(
18021        r#"
18022          one
18023          two
18024        - threeˇ
18025        - four
18026        + our
18027          five
18028        "#
18029        .unindent(),
18030    );
18031}
18032
18033#[gpui::test]
18034async fn test_edit_after_expanded_modification_hunk(
18035    executor: BackgroundExecutor,
18036    cx: &mut TestAppContext,
18037) {
18038    init_test(cx, |_| {});
18039
18040    let mut cx = EditorTestContext::new(cx).await;
18041
18042    let diff_base = r#"
18043        use some::mod1;
18044        use some::mod2;
18045
18046        const A: u32 = 42;
18047        const B: u32 = 42;
18048        const C: u32 = 42;
18049        const D: u32 = 42;
18050
18051
18052        fn main() {
18053            println!("hello");
18054
18055            println!("world");
18056        }"#
18057    .unindent();
18058
18059    cx.set_state(
18060        &r#"
18061        use some::mod1;
18062        use some::mod2;
18063
18064        const A: u32 = 42;
18065        const B: u32 = 42;
18066        const C: u32 = 43ˇ
18067        const D: u32 = 42;
18068
18069
18070        fn main() {
18071            println!("hello");
18072
18073            println!("world");
18074        }"#
18075        .unindent(),
18076    );
18077
18078    cx.set_head_text(&diff_base);
18079    executor.run_until_parked();
18080    cx.update_editor(|editor, window, cx| {
18081        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18082    });
18083    executor.run_until_parked();
18084
18085    cx.assert_state_with_diff(
18086        r#"
18087        use some::mod1;
18088        use some::mod2;
18089
18090        const A: u32 = 42;
18091        const B: u32 = 42;
18092      - const C: u32 = 42;
18093      + const C: u32 = 43ˇ
18094        const D: u32 = 42;
18095
18096
18097        fn main() {
18098            println!("hello");
18099
18100            println!("world");
18101        }"#
18102        .unindent(),
18103    );
18104
18105    cx.update_editor(|editor, window, cx| {
18106        editor.handle_input("\nnew_line\n", window, cx);
18107    });
18108    executor.run_until_parked();
18109
18110    cx.assert_state_with_diff(
18111        r#"
18112        use some::mod1;
18113        use some::mod2;
18114
18115        const A: u32 = 42;
18116        const B: u32 = 42;
18117      - const C: u32 = 42;
18118      + const C: u32 = 43
18119      + new_line
18120      + ˇ
18121        const D: u32 = 42;
18122
18123
18124        fn main() {
18125            println!("hello");
18126
18127            println!("world");
18128        }"#
18129        .unindent(),
18130    );
18131}
18132
18133#[gpui::test]
18134async fn test_stage_and_unstage_added_file_hunk(
18135    executor: BackgroundExecutor,
18136    cx: &mut TestAppContext,
18137) {
18138    init_test(cx, |_| {});
18139
18140    let mut cx = EditorTestContext::new(cx).await;
18141    cx.update_editor(|editor, _, cx| {
18142        editor.set_expand_all_diff_hunks(cx);
18143    });
18144
18145    let working_copy = r#"
18146            ˇfn main() {
18147                println!("hello, world!");
18148            }
18149        "#
18150    .unindent();
18151
18152    cx.set_state(&working_copy);
18153    executor.run_until_parked();
18154
18155    cx.assert_state_with_diff(
18156        r#"
18157            + ˇfn main() {
18158            +     println!("hello, world!");
18159            + }
18160        "#
18161        .unindent(),
18162    );
18163    cx.assert_index_text(None);
18164
18165    cx.update_editor(|editor, window, cx| {
18166        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18167    });
18168    executor.run_until_parked();
18169    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
18170    cx.assert_state_with_diff(
18171        r#"
18172            + ˇfn main() {
18173            +     println!("hello, world!");
18174            + }
18175        "#
18176        .unindent(),
18177    );
18178
18179    cx.update_editor(|editor, window, cx| {
18180        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18181    });
18182    executor.run_until_parked();
18183    cx.assert_index_text(None);
18184}
18185
18186async fn setup_indent_guides_editor(
18187    text: &str,
18188    cx: &mut TestAppContext,
18189) -> (BufferId, EditorTestContext) {
18190    init_test(cx, |_| {});
18191
18192    let mut cx = EditorTestContext::new(cx).await;
18193
18194    let buffer_id = cx.update_editor(|editor, window, cx| {
18195        editor.set_text(text, window, cx);
18196        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
18197
18198        buffer_ids[0]
18199    });
18200
18201    (buffer_id, cx)
18202}
18203
18204fn assert_indent_guides(
18205    range: Range<u32>,
18206    expected: Vec<IndentGuide>,
18207    active_indices: Option<Vec<usize>>,
18208    cx: &mut EditorTestContext,
18209) {
18210    let indent_guides = cx.update_editor(|editor, window, cx| {
18211        let snapshot = editor.snapshot(window, cx).display_snapshot;
18212        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
18213            editor,
18214            MultiBufferRow(range.start)..MultiBufferRow(range.end),
18215            true,
18216            &snapshot,
18217            cx,
18218        );
18219
18220        indent_guides.sort_by(|a, b| {
18221            a.depth.cmp(&b.depth).then(
18222                a.start_row
18223                    .cmp(&b.start_row)
18224                    .then(a.end_row.cmp(&b.end_row)),
18225            )
18226        });
18227        indent_guides
18228    });
18229
18230    if let Some(expected) = active_indices {
18231        let active_indices = cx.update_editor(|editor, window, cx| {
18232            let snapshot = editor.snapshot(window, cx).display_snapshot;
18233            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
18234        });
18235
18236        assert_eq!(
18237            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
18238            expected,
18239            "Active indent guide indices do not match"
18240        );
18241    }
18242
18243    assert_eq!(indent_guides, expected, "Indent guides do not match");
18244}
18245
18246fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
18247    IndentGuide {
18248        buffer_id,
18249        start_row: MultiBufferRow(start_row),
18250        end_row: MultiBufferRow(end_row),
18251        depth,
18252        tab_size: 4,
18253        settings: IndentGuideSettings {
18254            enabled: true,
18255            line_width: 1,
18256            active_line_width: 1,
18257            ..Default::default()
18258        },
18259    }
18260}
18261
18262#[gpui::test]
18263async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
18264    let (buffer_id, mut cx) = setup_indent_guides_editor(
18265        &"
18266        fn main() {
18267            let a = 1;
18268        }"
18269        .unindent(),
18270        cx,
18271    )
18272    .await;
18273
18274    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
18275}
18276
18277#[gpui::test]
18278async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
18279    let (buffer_id, mut cx) = setup_indent_guides_editor(
18280        &"
18281        fn main() {
18282            let a = 1;
18283            let b = 2;
18284        }"
18285        .unindent(),
18286        cx,
18287    )
18288    .await;
18289
18290    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
18291}
18292
18293#[gpui::test]
18294async fn test_indent_guide_nested(cx: &mut TestAppContext) {
18295    let (buffer_id, mut cx) = setup_indent_guides_editor(
18296        &"
18297        fn main() {
18298            let a = 1;
18299            if a == 3 {
18300                let b = 2;
18301            } else {
18302                let c = 3;
18303            }
18304        }"
18305        .unindent(),
18306        cx,
18307    )
18308    .await;
18309
18310    assert_indent_guides(
18311        0..8,
18312        vec![
18313            indent_guide(buffer_id, 1, 6, 0),
18314            indent_guide(buffer_id, 3, 3, 1),
18315            indent_guide(buffer_id, 5, 5, 1),
18316        ],
18317        None,
18318        &mut cx,
18319    );
18320}
18321
18322#[gpui::test]
18323async fn test_indent_guide_tab(cx: &mut TestAppContext) {
18324    let (buffer_id, mut cx) = setup_indent_guides_editor(
18325        &"
18326        fn main() {
18327            let a = 1;
18328                let b = 2;
18329            let c = 3;
18330        }"
18331        .unindent(),
18332        cx,
18333    )
18334    .await;
18335
18336    assert_indent_guides(
18337        0..5,
18338        vec![
18339            indent_guide(buffer_id, 1, 3, 0),
18340            indent_guide(buffer_id, 2, 2, 1),
18341        ],
18342        None,
18343        &mut cx,
18344    );
18345}
18346
18347#[gpui::test]
18348async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
18349    let (buffer_id, mut cx) = setup_indent_guides_editor(
18350        &"
18351        fn main() {
18352            let a = 1;
18353
18354            let c = 3;
18355        }"
18356        .unindent(),
18357        cx,
18358    )
18359    .await;
18360
18361    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
18362}
18363
18364#[gpui::test]
18365async fn test_indent_guide_complex(cx: &mut TestAppContext) {
18366    let (buffer_id, mut cx) = setup_indent_guides_editor(
18367        &"
18368        fn main() {
18369            let a = 1;
18370
18371            let c = 3;
18372
18373            if a == 3 {
18374                let b = 2;
18375            } else {
18376                let c = 3;
18377            }
18378        }"
18379        .unindent(),
18380        cx,
18381    )
18382    .await;
18383
18384    assert_indent_guides(
18385        0..11,
18386        vec![
18387            indent_guide(buffer_id, 1, 9, 0),
18388            indent_guide(buffer_id, 6, 6, 1),
18389            indent_guide(buffer_id, 8, 8, 1),
18390        ],
18391        None,
18392        &mut cx,
18393    );
18394}
18395
18396#[gpui::test]
18397async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
18398    let (buffer_id, mut cx) = setup_indent_guides_editor(
18399        &"
18400        fn main() {
18401            let a = 1;
18402
18403            let c = 3;
18404
18405            if a == 3 {
18406                let b = 2;
18407            } else {
18408                let c = 3;
18409            }
18410        }"
18411        .unindent(),
18412        cx,
18413    )
18414    .await;
18415
18416    assert_indent_guides(
18417        1..11,
18418        vec![
18419            indent_guide(buffer_id, 1, 9, 0),
18420            indent_guide(buffer_id, 6, 6, 1),
18421            indent_guide(buffer_id, 8, 8, 1),
18422        ],
18423        None,
18424        &mut cx,
18425    );
18426}
18427
18428#[gpui::test]
18429async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
18430    let (buffer_id, mut cx) = setup_indent_guides_editor(
18431        &"
18432        fn main() {
18433            let a = 1;
18434
18435            let c = 3;
18436
18437            if a == 3 {
18438                let b = 2;
18439            } else {
18440                let c = 3;
18441            }
18442        }"
18443        .unindent(),
18444        cx,
18445    )
18446    .await;
18447
18448    assert_indent_guides(
18449        1..10,
18450        vec![
18451            indent_guide(buffer_id, 1, 9, 0),
18452            indent_guide(buffer_id, 6, 6, 1),
18453            indent_guide(buffer_id, 8, 8, 1),
18454        ],
18455        None,
18456        &mut cx,
18457    );
18458}
18459
18460#[gpui::test]
18461async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
18462    let (buffer_id, mut cx) = setup_indent_guides_editor(
18463        &"
18464        fn main() {
18465            if a {
18466                b(
18467                    c,
18468                    d,
18469                )
18470            } else {
18471                e(
18472                    f
18473                )
18474            }
18475        }"
18476        .unindent(),
18477        cx,
18478    )
18479    .await;
18480
18481    assert_indent_guides(
18482        0..11,
18483        vec![
18484            indent_guide(buffer_id, 1, 10, 0),
18485            indent_guide(buffer_id, 2, 5, 1),
18486            indent_guide(buffer_id, 7, 9, 1),
18487            indent_guide(buffer_id, 3, 4, 2),
18488            indent_guide(buffer_id, 8, 8, 2),
18489        ],
18490        None,
18491        &mut cx,
18492    );
18493
18494    cx.update_editor(|editor, window, cx| {
18495        editor.fold_at(MultiBufferRow(2), window, cx);
18496        assert_eq!(
18497            editor.display_text(cx),
18498            "
18499            fn main() {
18500                if a {
18501                    b(⋯
18502                    )
18503                } else {
18504                    e(
18505                        f
18506                    )
18507                }
18508            }"
18509            .unindent()
18510        );
18511    });
18512
18513    assert_indent_guides(
18514        0..11,
18515        vec![
18516            indent_guide(buffer_id, 1, 10, 0),
18517            indent_guide(buffer_id, 2, 5, 1),
18518            indent_guide(buffer_id, 7, 9, 1),
18519            indent_guide(buffer_id, 8, 8, 2),
18520        ],
18521        None,
18522        &mut cx,
18523    );
18524}
18525
18526#[gpui::test]
18527async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
18528    let (buffer_id, mut cx) = setup_indent_guides_editor(
18529        &"
18530        block1
18531            block2
18532                block3
18533                    block4
18534            block2
18535        block1
18536        block1"
18537            .unindent(),
18538        cx,
18539    )
18540    .await;
18541
18542    assert_indent_guides(
18543        1..10,
18544        vec![
18545            indent_guide(buffer_id, 1, 4, 0),
18546            indent_guide(buffer_id, 2, 3, 1),
18547            indent_guide(buffer_id, 3, 3, 2),
18548        ],
18549        None,
18550        &mut cx,
18551    );
18552}
18553
18554#[gpui::test]
18555async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
18556    let (buffer_id, mut cx) = setup_indent_guides_editor(
18557        &"
18558        block1
18559            block2
18560                block3
18561
18562        block1
18563        block1"
18564            .unindent(),
18565        cx,
18566    )
18567    .await;
18568
18569    assert_indent_guides(
18570        0..6,
18571        vec![
18572            indent_guide(buffer_id, 1, 2, 0),
18573            indent_guide(buffer_id, 2, 2, 1),
18574        ],
18575        None,
18576        &mut cx,
18577    );
18578}
18579
18580#[gpui::test]
18581async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
18582    let (buffer_id, mut cx) = setup_indent_guides_editor(
18583        &"
18584        function component() {
18585        \treturn (
18586        \t\t\t
18587        \t\t<div>
18588        \t\t\t<abc></abc>
18589        \t\t</div>
18590        \t)
18591        }"
18592        .unindent(),
18593        cx,
18594    )
18595    .await;
18596
18597    assert_indent_guides(
18598        0..8,
18599        vec![
18600            indent_guide(buffer_id, 1, 6, 0),
18601            indent_guide(buffer_id, 2, 5, 1),
18602            indent_guide(buffer_id, 4, 4, 2),
18603        ],
18604        None,
18605        &mut cx,
18606    );
18607}
18608
18609#[gpui::test]
18610async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
18611    let (buffer_id, mut cx) = setup_indent_guides_editor(
18612        &"
18613        function component() {
18614        \treturn (
18615        \t
18616        \t\t<div>
18617        \t\t\t<abc></abc>
18618        \t\t</div>
18619        \t)
18620        }"
18621        .unindent(),
18622        cx,
18623    )
18624    .await;
18625
18626    assert_indent_guides(
18627        0..8,
18628        vec![
18629            indent_guide(buffer_id, 1, 6, 0),
18630            indent_guide(buffer_id, 2, 5, 1),
18631            indent_guide(buffer_id, 4, 4, 2),
18632        ],
18633        None,
18634        &mut cx,
18635    );
18636}
18637
18638#[gpui::test]
18639async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
18640    let (buffer_id, mut cx) = setup_indent_guides_editor(
18641        &"
18642        block1
18643
18644
18645
18646            block2
18647        "
18648        .unindent(),
18649        cx,
18650    )
18651    .await;
18652
18653    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
18654}
18655
18656#[gpui::test]
18657async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
18658    let (buffer_id, mut cx) = setup_indent_guides_editor(
18659        &"
18660        def a:
18661        \tb = 3
18662        \tif True:
18663        \t\tc = 4
18664        \t\td = 5
18665        \tprint(b)
18666        "
18667        .unindent(),
18668        cx,
18669    )
18670    .await;
18671
18672    assert_indent_guides(
18673        0..6,
18674        vec![
18675            indent_guide(buffer_id, 1, 5, 0),
18676            indent_guide(buffer_id, 3, 4, 1),
18677        ],
18678        None,
18679        &mut cx,
18680    );
18681}
18682
18683#[gpui::test]
18684async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
18685    let (buffer_id, mut cx) = setup_indent_guides_editor(
18686        &"
18687    fn main() {
18688        let a = 1;
18689    }"
18690        .unindent(),
18691        cx,
18692    )
18693    .await;
18694
18695    cx.update_editor(|editor, window, cx| {
18696        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18697            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18698        });
18699    });
18700
18701    assert_indent_guides(
18702        0..3,
18703        vec![indent_guide(buffer_id, 1, 1, 0)],
18704        Some(vec![0]),
18705        &mut cx,
18706    );
18707}
18708
18709#[gpui::test]
18710async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
18711    let (buffer_id, mut cx) = setup_indent_guides_editor(
18712        &"
18713    fn main() {
18714        if 1 == 2 {
18715            let a = 1;
18716        }
18717    }"
18718        .unindent(),
18719        cx,
18720    )
18721    .await;
18722
18723    cx.update_editor(|editor, window, cx| {
18724        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18725            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18726        });
18727    });
18728
18729    assert_indent_guides(
18730        0..4,
18731        vec![
18732            indent_guide(buffer_id, 1, 3, 0),
18733            indent_guide(buffer_id, 2, 2, 1),
18734        ],
18735        Some(vec![1]),
18736        &mut cx,
18737    );
18738
18739    cx.update_editor(|editor, window, cx| {
18740        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18741            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
18742        });
18743    });
18744
18745    assert_indent_guides(
18746        0..4,
18747        vec![
18748            indent_guide(buffer_id, 1, 3, 0),
18749            indent_guide(buffer_id, 2, 2, 1),
18750        ],
18751        Some(vec![1]),
18752        &mut cx,
18753    );
18754
18755    cx.update_editor(|editor, window, cx| {
18756        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18757            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
18758        });
18759    });
18760
18761    assert_indent_guides(
18762        0..4,
18763        vec![
18764            indent_guide(buffer_id, 1, 3, 0),
18765            indent_guide(buffer_id, 2, 2, 1),
18766        ],
18767        Some(vec![0]),
18768        &mut cx,
18769    );
18770}
18771
18772#[gpui::test]
18773async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
18774    let (buffer_id, mut cx) = setup_indent_guides_editor(
18775        &"
18776    fn main() {
18777        let a = 1;
18778
18779        let b = 2;
18780    }"
18781        .unindent(),
18782        cx,
18783    )
18784    .await;
18785
18786    cx.update_editor(|editor, window, cx| {
18787        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18788            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
18789        });
18790    });
18791
18792    assert_indent_guides(
18793        0..5,
18794        vec![indent_guide(buffer_id, 1, 3, 0)],
18795        Some(vec![0]),
18796        &mut cx,
18797    );
18798}
18799
18800#[gpui::test]
18801async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
18802    let (buffer_id, mut cx) = setup_indent_guides_editor(
18803        &"
18804    def m:
18805        a = 1
18806        pass"
18807            .unindent(),
18808        cx,
18809    )
18810    .await;
18811
18812    cx.update_editor(|editor, window, cx| {
18813        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18814            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18815        });
18816    });
18817
18818    assert_indent_guides(
18819        0..3,
18820        vec![indent_guide(buffer_id, 1, 2, 0)],
18821        Some(vec![0]),
18822        &mut cx,
18823    );
18824}
18825
18826#[gpui::test]
18827async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
18828    init_test(cx, |_| {});
18829    let mut cx = EditorTestContext::new(cx).await;
18830    let text = indoc! {
18831        "
18832        impl A {
18833            fn b() {
18834                0;
18835                3;
18836                5;
18837                6;
18838                7;
18839            }
18840        }
18841        "
18842    };
18843    let base_text = indoc! {
18844        "
18845        impl A {
18846            fn b() {
18847                0;
18848                1;
18849                2;
18850                3;
18851                4;
18852            }
18853            fn c() {
18854                5;
18855                6;
18856                7;
18857            }
18858        }
18859        "
18860    };
18861
18862    cx.update_editor(|editor, window, cx| {
18863        editor.set_text(text, window, cx);
18864
18865        editor.buffer().update(cx, |multibuffer, cx| {
18866            let buffer = multibuffer.as_singleton().unwrap();
18867            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
18868
18869            multibuffer.set_all_diff_hunks_expanded(cx);
18870            multibuffer.add_diff(diff, cx);
18871
18872            buffer.read(cx).remote_id()
18873        })
18874    });
18875    cx.run_until_parked();
18876
18877    cx.assert_state_with_diff(
18878        indoc! { "
18879          impl A {
18880              fn b() {
18881                  0;
18882        -         1;
18883        -         2;
18884                  3;
18885        -         4;
18886        -     }
18887        -     fn c() {
18888                  5;
18889                  6;
18890                  7;
18891              }
18892          }
18893          ˇ"
18894        }
18895        .to_string(),
18896    );
18897
18898    let mut actual_guides = cx.update_editor(|editor, window, cx| {
18899        editor
18900            .snapshot(window, cx)
18901            .buffer_snapshot
18902            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
18903            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
18904            .collect::<Vec<_>>()
18905    });
18906    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
18907    assert_eq!(
18908        actual_guides,
18909        vec![
18910            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
18911            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
18912            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
18913        ]
18914    );
18915}
18916
18917#[gpui::test]
18918async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18919    init_test(cx, |_| {});
18920    let mut cx = EditorTestContext::new(cx).await;
18921
18922    let diff_base = r#"
18923        a
18924        b
18925        c
18926        "#
18927    .unindent();
18928
18929    cx.set_state(
18930        &r#"
18931        ˇA
18932        b
18933        C
18934        "#
18935        .unindent(),
18936    );
18937    cx.set_head_text(&diff_base);
18938    cx.update_editor(|editor, window, cx| {
18939        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18940    });
18941    executor.run_until_parked();
18942
18943    let both_hunks_expanded = r#"
18944        - a
18945        + ˇA
18946          b
18947        - c
18948        + C
18949        "#
18950    .unindent();
18951
18952    cx.assert_state_with_diff(both_hunks_expanded.clone());
18953
18954    let hunk_ranges = cx.update_editor(|editor, window, cx| {
18955        let snapshot = editor.snapshot(window, cx);
18956        let hunks = editor
18957            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18958            .collect::<Vec<_>>();
18959        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
18960        let buffer_id = hunks[0].buffer_id;
18961        hunks
18962            .into_iter()
18963            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
18964            .collect::<Vec<_>>()
18965    });
18966    assert_eq!(hunk_ranges.len(), 2);
18967
18968    cx.update_editor(|editor, _, cx| {
18969        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18970    });
18971    executor.run_until_parked();
18972
18973    let second_hunk_expanded = r#"
18974          ˇA
18975          b
18976        - c
18977        + C
18978        "#
18979    .unindent();
18980
18981    cx.assert_state_with_diff(second_hunk_expanded);
18982
18983    cx.update_editor(|editor, _, cx| {
18984        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18985    });
18986    executor.run_until_parked();
18987
18988    cx.assert_state_with_diff(both_hunks_expanded.clone());
18989
18990    cx.update_editor(|editor, _, cx| {
18991        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
18992    });
18993    executor.run_until_parked();
18994
18995    let first_hunk_expanded = r#"
18996        - a
18997        + ˇA
18998          b
18999          C
19000        "#
19001    .unindent();
19002
19003    cx.assert_state_with_diff(first_hunk_expanded);
19004
19005    cx.update_editor(|editor, _, cx| {
19006        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
19007    });
19008    executor.run_until_parked();
19009
19010    cx.assert_state_with_diff(both_hunks_expanded);
19011
19012    cx.set_state(
19013        &r#"
19014        ˇA
19015        b
19016        "#
19017        .unindent(),
19018    );
19019    cx.run_until_parked();
19020
19021    // TODO this cursor position seems bad
19022    cx.assert_state_with_diff(
19023        r#"
19024        - ˇa
19025        + A
19026          b
19027        "#
19028        .unindent(),
19029    );
19030
19031    cx.update_editor(|editor, window, cx| {
19032        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19033    });
19034
19035    cx.assert_state_with_diff(
19036        r#"
19037            - ˇa
19038            + A
19039              b
19040            - c
19041            "#
19042        .unindent(),
19043    );
19044
19045    let hunk_ranges = cx.update_editor(|editor, window, cx| {
19046        let snapshot = editor.snapshot(window, cx);
19047        let hunks = editor
19048            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19049            .collect::<Vec<_>>();
19050        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
19051        let buffer_id = hunks[0].buffer_id;
19052        hunks
19053            .into_iter()
19054            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
19055            .collect::<Vec<_>>()
19056    });
19057    assert_eq!(hunk_ranges.len(), 2);
19058
19059    cx.update_editor(|editor, _, cx| {
19060        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
19061    });
19062    executor.run_until_parked();
19063
19064    cx.assert_state_with_diff(
19065        r#"
19066        - ˇa
19067        + A
19068          b
19069        "#
19070        .unindent(),
19071    );
19072}
19073
19074#[gpui::test]
19075async fn test_toggle_deletion_hunk_at_start_of_file(
19076    executor: BackgroundExecutor,
19077    cx: &mut TestAppContext,
19078) {
19079    init_test(cx, |_| {});
19080    let mut cx = EditorTestContext::new(cx).await;
19081
19082    let diff_base = r#"
19083        a
19084        b
19085        c
19086        "#
19087    .unindent();
19088
19089    cx.set_state(
19090        &r#"
19091        ˇb
19092        c
19093        "#
19094        .unindent(),
19095    );
19096    cx.set_head_text(&diff_base);
19097    cx.update_editor(|editor, window, cx| {
19098        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19099    });
19100    executor.run_until_parked();
19101
19102    let hunk_expanded = r#"
19103        - a
19104          ˇb
19105          c
19106        "#
19107    .unindent();
19108
19109    cx.assert_state_with_diff(hunk_expanded.clone());
19110
19111    let hunk_ranges = cx.update_editor(|editor, window, cx| {
19112        let snapshot = editor.snapshot(window, cx);
19113        let hunks = editor
19114            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19115            .collect::<Vec<_>>();
19116        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
19117        let buffer_id = hunks[0].buffer_id;
19118        hunks
19119            .into_iter()
19120            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
19121            .collect::<Vec<_>>()
19122    });
19123    assert_eq!(hunk_ranges.len(), 1);
19124
19125    cx.update_editor(|editor, _, cx| {
19126        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19127    });
19128    executor.run_until_parked();
19129
19130    let hunk_collapsed = r#"
19131          ˇb
19132          c
19133        "#
19134    .unindent();
19135
19136    cx.assert_state_with_diff(hunk_collapsed);
19137
19138    cx.update_editor(|editor, _, cx| {
19139        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19140    });
19141    executor.run_until_parked();
19142
19143    cx.assert_state_with_diff(hunk_expanded.clone());
19144}
19145
19146#[gpui::test]
19147async fn test_display_diff_hunks(cx: &mut TestAppContext) {
19148    init_test(cx, |_| {});
19149
19150    let fs = FakeFs::new(cx.executor());
19151    fs.insert_tree(
19152        path!("/test"),
19153        json!({
19154            ".git": {},
19155            "file-1": "ONE\n",
19156            "file-2": "TWO\n",
19157            "file-3": "THREE\n",
19158        }),
19159    )
19160    .await;
19161
19162    fs.set_head_for_repo(
19163        path!("/test/.git").as_ref(),
19164        &[
19165            ("file-1".into(), "one\n".into()),
19166            ("file-2".into(), "two\n".into()),
19167            ("file-3".into(), "three\n".into()),
19168        ],
19169        "deadbeef",
19170    );
19171
19172    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
19173    let mut buffers = vec![];
19174    for i in 1..=3 {
19175        let buffer = project
19176            .update(cx, |project, cx| {
19177                let path = format!(path!("/test/file-{}"), i);
19178                project.open_local_buffer(path, cx)
19179            })
19180            .await
19181            .unwrap();
19182        buffers.push(buffer);
19183    }
19184
19185    let multibuffer = cx.new(|cx| {
19186        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
19187        multibuffer.set_all_diff_hunks_expanded(cx);
19188        for buffer in &buffers {
19189            let snapshot = buffer.read(cx).snapshot();
19190            multibuffer.set_excerpts_for_path(
19191                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
19192                buffer.clone(),
19193                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
19194                DEFAULT_MULTIBUFFER_CONTEXT,
19195                cx,
19196            );
19197        }
19198        multibuffer
19199    });
19200
19201    let editor = cx.add_window(|window, cx| {
19202        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
19203    });
19204    cx.run_until_parked();
19205
19206    let snapshot = editor
19207        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19208        .unwrap();
19209    let hunks = snapshot
19210        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
19211        .map(|hunk| match hunk {
19212            DisplayDiffHunk::Unfolded {
19213                display_row_range, ..
19214            } => display_row_range,
19215            DisplayDiffHunk::Folded { .. } => unreachable!(),
19216        })
19217        .collect::<Vec<_>>();
19218    assert_eq!(
19219        hunks,
19220        [
19221            DisplayRow(2)..DisplayRow(4),
19222            DisplayRow(7)..DisplayRow(9),
19223            DisplayRow(12)..DisplayRow(14),
19224        ]
19225    );
19226}
19227
19228#[gpui::test]
19229async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
19230    init_test(cx, |_| {});
19231
19232    let mut cx = EditorTestContext::new(cx).await;
19233    cx.set_head_text(indoc! { "
19234        one
19235        two
19236        three
19237        four
19238        five
19239        "
19240    });
19241    cx.set_index_text(indoc! { "
19242        one
19243        two
19244        three
19245        four
19246        five
19247        "
19248    });
19249    cx.set_state(indoc! {"
19250        one
19251        TWO
19252        ˇTHREE
19253        FOUR
19254        five
19255    "});
19256    cx.run_until_parked();
19257    cx.update_editor(|editor, window, cx| {
19258        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19259    });
19260    cx.run_until_parked();
19261    cx.assert_index_text(Some(indoc! {"
19262        one
19263        TWO
19264        THREE
19265        FOUR
19266        five
19267    "}));
19268    cx.set_state(indoc! { "
19269        one
19270        TWO
19271        ˇTHREE-HUNDRED
19272        FOUR
19273        five
19274    "});
19275    cx.run_until_parked();
19276    cx.update_editor(|editor, window, cx| {
19277        let snapshot = editor.snapshot(window, cx);
19278        let hunks = editor
19279            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19280            .collect::<Vec<_>>();
19281        assert_eq!(hunks.len(), 1);
19282        assert_eq!(
19283            hunks[0].status(),
19284            DiffHunkStatus {
19285                kind: DiffHunkStatusKind::Modified,
19286                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
19287            }
19288        );
19289
19290        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19291    });
19292    cx.run_until_parked();
19293    cx.assert_index_text(Some(indoc! {"
19294        one
19295        TWO
19296        THREE-HUNDRED
19297        FOUR
19298        five
19299    "}));
19300}
19301
19302#[gpui::test]
19303fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
19304    init_test(cx, |_| {});
19305
19306    let editor = cx.add_window(|window, cx| {
19307        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
19308        build_editor(buffer, window, cx)
19309    });
19310
19311    let render_args = Arc::new(Mutex::new(None));
19312    let snapshot = editor
19313        .update(cx, |editor, window, cx| {
19314            let snapshot = editor.buffer().read(cx).snapshot(cx);
19315            let range =
19316                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
19317
19318            struct RenderArgs {
19319                row: MultiBufferRow,
19320                folded: bool,
19321                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
19322            }
19323
19324            let crease = Crease::inline(
19325                range,
19326                FoldPlaceholder::test(),
19327                {
19328                    let toggle_callback = render_args.clone();
19329                    move |row, folded, callback, _window, _cx| {
19330                        *toggle_callback.lock() = Some(RenderArgs {
19331                            row,
19332                            folded,
19333                            callback,
19334                        });
19335                        div()
19336                    }
19337                },
19338                |_row, _folded, _window, _cx| div(),
19339            );
19340
19341            editor.insert_creases(Some(crease), cx);
19342            let snapshot = editor.snapshot(window, cx);
19343            let _div = snapshot.render_crease_toggle(
19344                MultiBufferRow(1),
19345                false,
19346                cx.entity().clone(),
19347                window,
19348                cx,
19349            );
19350            snapshot
19351        })
19352        .unwrap();
19353
19354    let render_args = render_args.lock().take().unwrap();
19355    assert_eq!(render_args.row, MultiBufferRow(1));
19356    assert!(!render_args.folded);
19357    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
19358
19359    cx.update_window(*editor, |_, window, cx| {
19360        (render_args.callback)(true, window, cx)
19361    })
19362    .unwrap();
19363    let snapshot = editor
19364        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19365        .unwrap();
19366    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
19367
19368    cx.update_window(*editor, |_, window, cx| {
19369        (render_args.callback)(false, window, cx)
19370    })
19371    .unwrap();
19372    let snapshot = editor
19373        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19374        .unwrap();
19375    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
19376}
19377
19378#[gpui::test]
19379async fn test_input_text(cx: &mut TestAppContext) {
19380    init_test(cx, |_| {});
19381    let mut cx = EditorTestContext::new(cx).await;
19382
19383    cx.set_state(
19384        &r#"ˇone
19385        two
19386
19387        three
19388        fourˇ
19389        five
19390
19391        siˇx"#
19392            .unindent(),
19393    );
19394
19395    cx.dispatch_action(HandleInput(String::new()));
19396    cx.assert_editor_state(
19397        &r#"ˇone
19398        two
19399
19400        three
19401        fourˇ
19402        five
19403
19404        siˇx"#
19405            .unindent(),
19406    );
19407
19408    cx.dispatch_action(HandleInput("AAAA".to_string()));
19409    cx.assert_editor_state(
19410        &r#"AAAAˇone
19411        two
19412
19413        three
19414        fourAAAAˇ
19415        five
19416
19417        siAAAAˇx"#
19418            .unindent(),
19419    );
19420}
19421
19422#[gpui::test]
19423async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
19424    init_test(cx, |_| {});
19425
19426    let mut cx = EditorTestContext::new(cx).await;
19427    cx.set_state(
19428        r#"let foo = 1;
19429let foo = 2;
19430let foo = 3;
19431let fooˇ = 4;
19432let foo = 5;
19433let foo = 6;
19434let foo = 7;
19435let foo = 8;
19436let foo = 9;
19437let foo = 10;
19438let foo = 11;
19439let foo = 12;
19440let foo = 13;
19441let foo = 14;
19442let foo = 15;"#,
19443    );
19444
19445    cx.update_editor(|e, window, cx| {
19446        assert_eq!(
19447            e.next_scroll_position,
19448            NextScrollCursorCenterTopBottom::Center,
19449            "Default next scroll direction is center",
19450        );
19451
19452        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19453        assert_eq!(
19454            e.next_scroll_position,
19455            NextScrollCursorCenterTopBottom::Top,
19456            "After center, next scroll direction should be top",
19457        );
19458
19459        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19460        assert_eq!(
19461            e.next_scroll_position,
19462            NextScrollCursorCenterTopBottom::Bottom,
19463            "After top, next scroll direction should be bottom",
19464        );
19465
19466        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19467        assert_eq!(
19468            e.next_scroll_position,
19469            NextScrollCursorCenterTopBottom::Center,
19470            "After bottom, scrolling should start over",
19471        );
19472
19473        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19474        assert_eq!(
19475            e.next_scroll_position,
19476            NextScrollCursorCenterTopBottom::Top,
19477            "Scrolling continues if retriggered fast enough"
19478        );
19479    });
19480
19481    cx.executor()
19482        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
19483    cx.executor().run_until_parked();
19484    cx.update_editor(|e, _, _| {
19485        assert_eq!(
19486            e.next_scroll_position,
19487            NextScrollCursorCenterTopBottom::Center,
19488            "If scrolling is not triggered fast enough, it should reset"
19489        );
19490    });
19491}
19492
19493#[gpui::test]
19494async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
19495    init_test(cx, |_| {});
19496    let mut cx = EditorLspTestContext::new_rust(
19497        lsp::ServerCapabilities {
19498            definition_provider: Some(lsp::OneOf::Left(true)),
19499            references_provider: Some(lsp::OneOf::Left(true)),
19500            ..lsp::ServerCapabilities::default()
19501        },
19502        cx,
19503    )
19504    .await;
19505
19506    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
19507        let go_to_definition = cx
19508            .lsp
19509            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
19510                move |params, _| async move {
19511                    if empty_go_to_definition {
19512                        Ok(None)
19513                    } else {
19514                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
19515                            uri: params.text_document_position_params.text_document.uri,
19516                            range: lsp::Range::new(
19517                                lsp::Position::new(4, 3),
19518                                lsp::Position::new(4, 6),
19519                            ),
19520                        })))
19521                    }
19522                },
19523            );
19524        let references = cx
19525            .lsp
19526            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
19527                Ok(Some(vec![lsp::Location {
19528                    uri: params.text_document_position.text_document.uri,
19529                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
19530                }]))
19531            });
19532        (go_to_definition, references)
19533    };
19534
19535    cx.set_state(
19536        &r#"fn one() {
19537            let mut a = ˇtwo();
19538        }
19539
19540        fn two() {}"#
19541            .unindent(),
19542    );
19543    set_up_lsp_handlers(false, &mut cx);
19544    let navigated = cx
19545        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19546        .await
19547        .expect("Failed to navigate to definition");
19548    assert_eq!(
19549        navigated,
19550        Navigated::Yes,
19551        "Should have navigated to definition from the GetDefinition response"
19552    );
19553    cx.assert_editor_state(
19554        &r#"fn one() {
19555            let mut a = two();
19556        }
19557
19558        fn «twoˇ»() {}"#
19559            .unindent(),
19560    );
19561
19562    let editors = cx.update_workspace(|workspace, _, cx| {
19563        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19564    });
19565    cx.update_editor(|_, _, test_editor_cx| {
19566        assert_eq!(
19567            editors.len(),
19568            1,
19569            "Initially, only one, test, editor should be open in the workspace"
19570        );
19571        assert_eq!(
19572            test_editor_cx.entity(),
19573            editors.last().expect("Asserted len is 1").clone()
19574        );
19575    });
19576
19577    set_up_lsp_handlers(true, &mut cx);
19578    let navigated = cx
19579        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19580        .await
19581        .expect("Failed to navigate to lookup references");
19582    assert_eq!(
19583        navigated,
19584        Navigated::Yes,
19585        "Should have navigated to references as a fallback after empty GoToDefinition response"
19586    );
19587    // We should not change the selections in the existing file,
19588    // if opening another milti buffer with the references
19589    cx.assert_editor_state(
19590        &r#"fn one() {
19591            let mut a = two();
19592        }
19593
19594        fn «twoˇ»() {}"#
19595            .unindent(),
19596    );
19597    let editors = cx.update_workspace(|workspace, _, cx| {
19598        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19599    });
19600    cx.update_editor(|_, _, test_editor_cx| {
19601        assert_eq!(
19602            editors.len(),
19603            2,
19604            "After falling back to references search, we open a new editor with the results"
19605        );
19606        let references_fallback_text = editors
19607            .into_iter()
19608            .find(|new_editor| *new_editor != test_editor_cx.entity())
19609            .expect("Should have one non-test editor now")
19610            .read(test_editor_cx)
19611            .text(test_editor_cx);
19612        assert_eq!(
19613            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
19614            "Should use the range from the references response and not the GoToDefinition one"
19615        );
19616    });
19617}
19618
19619#[gpui::test]
19620async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
19621    init_test(cx, |_| {});
19622    cx.update(|cx| {
19623        let mut editor_settings = EditorSettings::get_global(cx).clone();
19624        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
19625        EditorSettings::override_global(editor_settings, cx);
19626    });
19627    let mut cx = EditorLspTestContext::new_rust(
19628        lsp::ServerCapabilities {
19629            definition_provider: Some(lsp::OneOf::Left(true)),
19630            references_provider: Some(lsp::OneOf::Left(true)),
19631            ..lsp::ServerCapabilities::default()
19632        },
19633        cx,
19634    )
19635    .await;
19636    let original_state = r#"fn one() {
19637        let mut a = ˇtwo();
19638    }
19639
19640    fn two() {}"#
19641        .unindent();
19642    cx.set_state(&original_state);
19643
19644    let mut go_to_definition = cx
19645        .lsp
19646        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
19647            move |_, _| async move { Ok(None) },
19648        );
19649    let _references = cx
19650        .lsp
19651        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
19652            panic!("Should not call for references with no go to definition fallback")
19653        });
19654
19655    let navigated = cx
19656        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19657        .await
19658        .expect("Failed to navigate to lookup references");
19659    go_to_definition
19660        .next()
19661        .await
19662        .expect("Should have called the go_to_definition handler");
19663
19664    assert_eq!(
19665        navigated,
19666        Navigated::No,
19667        "Should have navigated to references as a fallback after empty GoToDefinition response"
19668    );
19669    cx.assert_editor_state(&original_state);
19670    let editors = cx.update_workspace(|workspace, _, cx| {
19671        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19672    });
19673    cx.update_editor(|_, _, _| {
19674        assert_eq!(
19675            editors.len(),
19676            1,
19677            "After unsuccessful fallback, no other editor should have been opened"
19678        );
19679    });
19680}
19681
19682#[gpui::test]
19683async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
19684    init_test(cx, |_| {});
19685
19686    let language = Arc::new(Language::new(
19687        LanguageConfig::default(),
19688        Some(tree_sitter_rust::LANGUAGE.into()),
19689    ));
19690
19691    let text = r#"
19692        #[cfg(test)]
19693        mod tests() {
19694            #[test]
19695            fn runnable_1() {
19696                let a = 1;
19697            }
19698
19699            #[test]
19700            fn runnable_2() {
19701                let a = 1;
19702                let b = 2;
19703            }
19704        }
19705    "#
19706    .unindent();
19707
19708    let fs = FakeFs::new(cx.executor());
19709    fs.insert_file("/file.rs", Default::default()).await;
19710
19711    let project = Project::test(fs, ["/a".as_ref()], cx).await;
19712    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19713    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19714    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
19715    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
19716
19717    let editor = cx.new_window_entity(|window, cx| {
19718        Editor::new(
19719            EditorMode::full(),
19720            multi_buffer,
19721            Some(project.clone()),
19722            window,
19723            cx,
19724        )
19725    });
19726
19727    editor.update_in(cx, |editor, window, cx| {
19728        let snapshot = editor.buffer().read(cx).snapshot(cx);
19729        editor.tasks.insert(
19730            (buffer.read(cx).remote_id(), 3),
19731            RunnableTasks {
19732                templates: vec![],
19733                offset: snapshot.anchor_before(43),
19734                column: 0,
19735                extra_variables: HashMap::default(),
19736                context_range: BufferOffset(43)..BufferOffset(85),
19737            },
19738        );
19739        editor.tasks.insert(
19740            (buffer.read(cx).remote_id(), 8),
19741            RunnableTasks {
19742                templates: vec![],
19743                offset: snapshot.anchor_before(86),
19744                column: 0,
19745                extra_variables: HashMap::default(),
19746                context_range: BufferOffset(86)..BufferOffset(191),
19747            },
19748        );
19749
19750        // Test finding task when cursor is inside function body
19751        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19752            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
19753        });
19754        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
19755        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
19756
19757        // Test finding task when cursor is on function name
19758        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19759            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
19760        });
19761        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
19762        assert_eq!(row, 8, "Should find task when cursor is on function name");
19763    });
19764}
19765
19766#[gpui::test]
19767async fn test_folding_buffers(cx: &mut TestAppContext) {
19768    init_test(cx, |_| {});
19769
19770    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
19771    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
19772    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
19773
19774    let fs = FakeFs::new(cx.executor());
19775    fs.insert_tree(
19776        path!("/a"),
19777        json!({
19778            "first.rs": sample_text_1,
19779            "second.rs": sample_text_2,
19780            "third.rs": sample_text_3,
19781        }),
19782    )
19783    .await;
19784    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19785    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19786    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19787    let worktree = project.update(cx, |project, cx| {
19788        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19789        assert_eq!(worktrees.len(), 1);
19790        worktrees.pop().unwrap()
19791    });
19792    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19793
19794    let buffer_1 = project
19795        .update(cx, |project, cx| {
19796            project.open_buffer((worktree_id, "first.rs"), cx)
19797        })
19798        .await
19799        .unwrap();
19800    let buffer_2 = project
19801        .update(cx, |project, cx| {
19802            project.open_buffer((worktree_id, "second.rs"), cx)
19803        })
19804        .await
19805        .unwrap();
19806    let buffer_3 = project
19807        .update(cx, |project, cx| {
19808            project.open_buffer((worktree_id, "third.rs"), cx)
19809        })
19810        .await
19811        .unwrap();
19812
19813    let multi_buffer = cx.new(|cx| {
19814        let mut multi_buffer = MultiBuffer::new(ReadWrite);
19815        multi_buffer.push_excerpts(
19816            buffer_1.clone(),
19817            [
19818                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19819                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19820                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19821            ],
19822            cx,
19823        );
19824        multi_buffer.push_excerpts(
19825            buffer_2.clone(),
19826            [
19827                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19828                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19829                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19830            ],
19831            cx,
19832        );
19833        multi_buffer.push_excerpts(
19834            buffer_3.clone(),
19835            [
19836                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19837                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19838                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19839            ],
19840            cx,
19841        );
19842        multi_buffer
19843    });
19844    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19845        Editor::new(
19846            EditorMode::full(),
19847            multi_buffer.clone(),
19848            Some(project.clone()),
19849            window,
19850            cx,
19851        )
19852    });
19853
19854    assert_eq!(
19855        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19856        "\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",
19857    );
19858
19859    multi_buffer_editor.update(cx, |editor, cx| {
19860        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
19861    });
19862    assert_eq!(
19863        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19864        "\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",
19865        "After folding the first buffer, its text should not be displayed"
19866    );
19867
19868    multi_buffer_editor.update(cx, |editor, cx| {
19869        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
19870    });
19871    assert_eq!(
19872        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19873        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
19874        "After folding the second buffer, its text should not be displayed"
19875    );
19876
19877    multi_buffer_editor.update(cx, |editor, cx| {
19878        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
19879    });
19880    assert_eq!(
19881        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19882        "\n\n\n\n\n",
19883        "After folding the third buffer, its text should not be displayed"
19884    );
19885
19886    // Emulate selection inside the fold logic, that should work
19887    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19888        editor
19889            .snapshot(window, cx)
19890            .next_line_boundary(Point::new(0, 4));
19891    });
19892
19893    multi_buffer_editor.update(cx, |editor, cx| {
19894        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
19895    });
19896    assert_eq!(
19897        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19898        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
19899        "After unfolding the second buffer, its text should be displayed"
19900    );
19901
19902    // Typing inside of buffer 1 causes that buffer to be unfolded.
19903    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19904        assert_eq!(
19905            multi_buffer
19906                .read(cx)
19907                .snapshot(cx)
19908                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
19909                .collect::<String>(),
19910            "bbbb"
19911        );
19912        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
19913            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
19914        });
19915        editor.handle_input("B", window, cx);
19916    });
19917
19918    assert_eq!(
19919        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19920        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
19921        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
19922    );
19923
19924    multi_buffer_editor.update(cx, |editor, cx| {
19925        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
19926    });
19927    assert_eq!(
19928        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19929        "\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",
19930        "After unfolding the all buffers, all original text should be displayed"
19931    );
19932}
19933
19934#[gpui::test]
19935async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
19936    init_test(cx, |_| {});
19937
19938    let sample_text_1 = "1111\n2222\n3333".to_string();
19939    let sample_text_2 = "4444\n5555\n6666".to_string();
19940    let sample_text_3 = "7777\n8888\n9999".to_string();
19941
19942    let fs = FakeFs::new(cx.executor());
19943    fs.insert_tree(
19944        path!("/a"),
19945        json!({
19946            "first.rs": sample_text_1,
19947            "second.rs": sample_text_2,
19948            "third.rs": sample_text_3,
19949        }),
19950    )
19951    .await;
19952    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19953    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19954    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19955    let worktree = project.update(cx, |project, cx| {
19956        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19957        assert_eq!(worktrees.len(), 1);
19958        worktrees.pop().unwrap()
19959    });
19960    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19961
19962    let buffer_1 = project
19963        .update(cx, |project, cx| {
19964            project.open_buffer((worktree_id, "first.rs"), cx)
19965        })
19966        .await
19967        .unwrap();
19968    let buffer_2 = project
19969        .update(cx, |project, cx| {
19970            project.open_buffer((worktree_id, "second.rs"), cx)
19971        })
19972        .await
19973        .unwrap();
19974    let buffer_3 = project
19975        .update(cx, |project, cx| {
19976            project.open_buffer((worktree_id, "third.rs"), cx)
19977        })
19978        .await
19979        .unwrap();
19980
19981    let multi_buffer = cx.new(|cx| {
19982        let mut multi_buffer = MultiBuffer::new(ReadWrite);
19983        multi_buffer.push_excerpts(
19984            buffer_1.clone(),
19985            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19986            cx,
19987        );
19988        multi_buffer.push_excerpts(
19989            buffer_2.clone(),
19990            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19991            cx,
19992        );
19993        multi_buffer.push_excerpts(
19994            buffer_3.clone(),
19995            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19996            cx,
19997        );
19998        multi_buffer
19999    });
20000
20001    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20002        Editor::new(
20003            EditorMode::full(),
20004            multi_buffer,
20005            Some(project.clone()),
20006            window,
20007            cx,
20008        )
20009    });
20010
20011    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
20012    assert_eq!(
20013        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20014        full_text,
20015    );
20016
20017    multi_buffer_editor.update(cx, |editor, cx| {
20018        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
20019    });
20020    assert_eq!(
20021        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20022        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
20023        "After folding the first buffer, its text should not be displayed"
20024    );
20025
20026    multi_buffer_editor.update(cx, |editor, cx| {
20027        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
20028    });
20029
20030    assert_eq!(
20031        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20032        "\n\n\n\n\n\n7777\n8888\n9999",
20033        "After folding the second buffer, its text should not be displayed"
20034    );
20035
20036    multi_buffer_editor.update(cx, |editor, cx| {
20037        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
20038    });
20039    assert_eq!(
20040        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20041        "\n\n\n\n\n",
20042        "After folding the third buffer, its text should not be displayed"
20043    );
20044
20045    multi_buffer_editor.update(cx, |editor, cx| {
20046        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
20047    });
20048    assert_eq!(
20049        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20050        "\n\n\n\n4444\n5555\n6666\n\n",
20051        "After unfolding the second buffer, its text should be displayed"
20052    );
20053
20054    multi_buffer_editor.update(cx, |editor, cx| {
20055        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
20056    });
20057    assert_eq!(
20058        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20059        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
20060        "After unfolding the first buffer, its text should be displayed"
20061    );
20062
20063    multi_buffer_editor.update(cx, |editor, cx| {
20064        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
20065    });
20066    assert_eq!(
20067        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20068        full_text,
20069        "After unfolding all buffers, all original text should be displayed"
20070    );
20071}
20072
20073#[gpui::test]
20074async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
20075    init_test(cx, |_| {});
20076
20077    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
20078
20079    let fs = FakeFs::new(cx.executor());
20080    fs.insert_tree(
20081        path!("/a"),
20082        json!({
20083            "main.rs": sample_text,
20084        }),
20085    )
20086    .await;
20087    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20088    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20089    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20090    let worktree = project.update(cx, |project, cx| {
20091        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
20092        assert_eq!(worktrees.len(), 1);
20093        worktrees.pop().unwrap()
20094    });
20095    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
20096
20097    let buffer_1 = project
20098        .update(cx, |project, cx| {
20099            project.open_buffer((worktree_id, "main.rs"), cx)
20100        })
20101        .await
20102        .unwrap();
20103
20104    let multi_buffer = cx.new(|cx| {
20105        let mut multi_buffer = MultiBuffer::new(ReadWrite);
20106        multi_buffer.push_excerpts(
20107            buffer_1.clone(),
20108            [ExcerptRange::new(
20109                Point::new(0, 0)
20110                    ..Point::new(
20111                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
20112                        0,
20113                    ),
20114            )],
20115            cx,
20116        );
20117        multi_buffer
20118    });
20119    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20120        Editor::new(
20121            EditorMode::full(),
20122            multi_buffer,
20123            Some(project.clone()),
20124            window,
20125            cx,
20126        )
20127    });
20128
20129    let selection_range = Point::new(1, 0)..Point::new(2, 0);
20130    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20131        enum TestHighlight {}
20132        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
20133        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
20134        editor.highlight_text::<TestHighlight>(
20135            vec![highlight_range.clone()],
20136            HighlightStyle::color(Hsla::green()),
20137            cx,
20138        );
20139        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20140            s.select_ranges(Some(highlight_range))
20141        });
20142    });
20143
20144    let full_text = format!("\n\n{sample_text}");
20145    assert_eq!(
20146        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20147        full_text,
20148    );
20149}
20150
20151#[gpui::test]
20152async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
20153    init_test(cx, |_| {});
20154    cx.update(|cx| {
20155        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
20156            "keymaps/default-linux.json",
20157            cx,
20158        )
20159        .unwrap();
20160        cx.bind_keys(default_key_bindings);
20161    });
20162
20163    let (editor, cx) = cx.add_window_view(|window, cx| {
20164        let multi_buffer = MultiBuffer::build_multi(
20165            [
20166                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
20167                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
20168                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
20169                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
20170            ],
20171            cx,
20172        );
20173        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
20174
20175        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
20176        // fold all but the second buffer, so that we test navigating between two
20177        // adjacent folded buffers, as well as folded buffers at the start and
20178        // end the multibuffer
20179        editor.fold_buffer(buffer_ids[0], cx);
20180        editor.fold_buffer(buffer_ids[2], cx);
20181        editor.fold_buffer(buffer_ids[3], cx);
20182
20183        editor
20184    });
20185    cx.simulate_resize(size(px(1000.), px(1000.)));
20186
20187    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
20188    cx.assert_excerpts_with_selections(indoc! {"
20189        [EXCERPT]
20190        ˇ[FOLDED]
20191        [EXCERPT]
20192        a1
20193        b1
20194        [EXCERPT]
20195        [FOLDED]
20196        [EXCERPT]
20197        [FOLDED]
20198        "
20199    });
20200    cx.simulate_keystroke("down");
20201    cx.assert_excerpts_with_selections(indoc! {"
20202        [EXCERPT]
20203        [FOLDED]
20204        [EXCERPT]
20205        ˇa1
20206        b1
20207        [EXCERPT]
20208        [FOLDED]
20209        [EXCERPT]
20210        [FOLDED]
20211        "
20212    });
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    cx.simulate_keystroke("down");
20227    cx.assert_excerpts_with_selections(indoc! {"
20228        [EXCERPT]
20229        [FOLDED]
20230        [EXCERPT]
20231        a1
20232        b1
20233        ˇ[EXCERPT]
20234        [FOLDED]
20235        [EXCERPT]
20236        [FOLDED]
20237        "
20238    });
20239    cx.simulate_keystroke("down");
20240    cx.assert_excerpts_with_selections(indoc! {"
20241        [EXCERPT]
20242        [FOLDED]
20243        [EXCERPT]
20244        a1
20245        b1
20246        [EXCERPT]
20247        ˇ[FOLDED]
20248        [EXCERPT]
20249        [FOLDED]
20250        "
20251    });
20252    for _ in 0..5 {
20253        cx.simulate_keystroke("down");
20254        cx.assert_excerpts_with_selections(indoc! {"
20255            [EXCERPT]
20256            [FOLDED]
20257            [EXCERPT]
20258            a1
20259            b1
20260            [EXCERPT]
20261            [FOLDED]
20262            [EXCERPT]
20263            ˇ[FOLDED]
20264            "
20265        });
20266    }
20267
20268    cx.simulate_keystroke("up");
20269    cx.assert_excerpts_with_selections(indoc! {"
20270        [EXCERPT]
20271        [FOLDED]
20272        [EXCERPT]
20273        a1
20274        b1
20275        [EXCERPT]
20276        ˇ[FOLDED]
20277        [EXCERPT]
20278        [FOLDED]
20279        "
20280    });
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    cx.simulate_keystroke("up");
20295    cx.assert_excerpts_with_selections(indoc! {"
20296        [EXCERPT]
20297        [FOLDED]
20298        [EXCERPT]
20299        a1
20300        ˇb1
20301        [EXCERPT]
20302        [FOLDED]
20303        [EXCERPT]
20304        [FOLDED]
20305        "
20306    });
20307    cx.simulate_keystroke("up");
20308    cx.assert_excerpts_with_selections(indoc! {"
20309        [EXCERPT]
20310        [FOLDED]
20311        [EXCERPT]
20312        ˇa1
20313        b1
20314        [EXCERPT]
20315        [FOLDED]
20316        [EXCERPT]
20317        [FOLDED]
20318        "
20319    });
20320    for _ in 0..5 {
20321        cx.simulate_keystroke("up");
20322        cx.assert_excerpts_with_selections(indoc! {"
20323            [EXCERPT]
20324            ˇ[FOLDED]
20325            [EXCERPT]
20326            a1
20327            b1
20328            [EXCERPT]
20329            [FOLDED]
20330            [EXCERPT]
20331            [FOLDED]
20332            "
20333        });
20334    }
20335}
20336
20337#[gpui::test]
20338async fn test_inline_completion_text(cx: &mut TestAppContext) {
20339    init_test(cx, |_| {});
20340
20341    // Simple insertion
20342    assert_highlighted_edits(
20343        "Hello, world!",
20344        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
20345        true,
20346        cx,
20347        |highlighted_edits, cx| {
20348            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
20349            assert_eq!(highlighted_edits.highlights.len(), 1);
20350            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
20351            assert_eq!(
20352                highlighted_edits.highlights[0].1.background_color,
20353                Some(cx.theme().status().created_background)
20354            );
20355        },
20356    )
20357    .await;
20358
20359    // Replacement
20360    assert_highlighted_edits(
20361        "This is a test.",
20362        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
20363        false,
20364        cx,
20365        |highlighted_edits, cx| {
20366            assert_eq!(highlighted_edits.text, "That is a test.");
20367            assert_eq!(highlighted_edits.highlights.len(), 1);
20368            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
20369            assert_eq!(
20370                highlighted_edits.highlights[0].1.background_color,
20371                Some(cx.theme().status().created_background)
20372            );
20373        },
20374    )
20375    .await;
20376
20377    // Multiple edits
20378    assert_highlighted_edits(
20379        "Hello, world!",
20380        vec![
20381            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
20382            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
20383        ],
20384        false,
20385        cx,
20386        |highlighted_edits, cx| {
20387            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
20388            assert_eq!(highlighted_edits.highlights.len(), 2);
20389            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
20390            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
20391            assert_eq!(
20392                highlighted_edits.highlights[0].1.background_color,
20393                Some(cx.theme().status().created_background)
20394            );
20395            assert_eq!(
20396                highlighted_edits.highlights[1].1.background_color,
20397                Some(cx.theme().status().created_background)
20398            );
20399        },
20400    )
20401    .await;
20402
20403    // Multiple lines with edits
20404    assert_highlighted_edits(
20405        "First line\nSecond line\nThird line\nFourth line",
20406        vec![
20407            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
20408            (
20409                Point::new(2, 0)..Point::new(2, 10),
20410                "New third line".to_string(),
20411            ),
20412            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
20413        ],
20414        false,
20415        cx,
20416        |highlighted_edits, cx| {
20417            assert_eq!(
20418                highlighted_edits.text,
20419                "Second modified\nNew third line\nFourth updated line"
20420            );
20421            assert_eq!(highlighted_edits.highlights.len(), 3);
20422            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
20423            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
20424            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
20425            for highlight in &highlighted_edits.highlights {
20426                assert_eq!(
20427                    highlight.1.background_color,
20428                    Some(cx.theme().status().created_background)
20429                );
20430            }
20431        },
20432    )
20433    .await;
20434}
20435
20436#[gpui::test]
20437async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
20438    init_test(cx, |_| {});
20439
20440    // Deletion
20441    assert_highlighted_edits(
20442        "Hello, world!",
20443        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
20444        true,
20445        cx,
20446        |highlighted_edits, cx| {
20447            assert_eq!(highlighted_edits.text, "Hello, world!");
20448            assert_eq!(highlighted_edits.highlights.len(), 1);
20449            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
20450            assert_eq!(
20451                highlighted_edits.highlights[0].1.background_color,
20452                Some(cx.theme().status().deleted_background)
20453            );
20454        },
20455    )
20456    .await;
20457
20458    // Insertion
20459    assert_highlighted_edits(
20460        "Hello, world!",
20461        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
20462        true,
20463        cx,
20464        |highlighted_edits, cx| {
20465            assert_eq!(highlighted_edits.highlights.len(), 1);
20466            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
20467            assert_eq!(
20468                highlighted_edits.highlights[0].1.background_color,
20469                Some(cx.theme().status().created_background)
20470            );
20471        },
20472    )
20473    .await;
20474}
20475
20476async fn assert_highlighted_edits(
20477    text: &str,
20478    edits: Vec<(Range<Point>, String)>,
20479    include_deletions: bool,
20480    cx: &mut TestAppContext,
20481    assertion_fn: impl Fn(HighlightedText, &App),
20482) {
20483    let window = cx.add_window(|window, cx| {
20484        let buffer = MultiBuffer::build_simple(text, cx);
20485        Editor::new(EditorMode::full(), buffer, None, window, cx)
20486    });
20487    let cx = &mut VisualTestContext::from_window(*window, cx);
20488
20489    let (buffer, snapshot) = window
20490        .update(cx, |editor, _window, cx| {
20491            (
20492                editor.buffer().clone(),
20493                editor.buffer().read(cx).snapshot(cx),
20494            )
20495        })
20496        .unwrap();
20497
20498    let edits = edits
20499        .into_iter()
20500        .map(|(range, edit)| {
20501            (
20502                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
20503                edit,
20504            )
20505        })
20506        .collect::<Vec<_>>();
20507
20508    let text_anchor_edits = edits
20509        .clone()
20510        .into_iter()
20511        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
20512        .collect::<Vec<_>>();
20513
20514    let edit_preview = window
20515        .update(cx, |_, _window, cx| {
20516            buffer
20517                .read(cx)
20518                .as_singleton()
20519                .unwrap()
20520                .read(cx)
20521                .preview_edits(text_anchor_edits.into(), cx)
20522        })
20523        .unwrap()
20524        .await;
20525
20526    cx.update(|_window, cx| {
20527        let highlighted_edits = inline_completion_edit_text(
20528            &snapshot.as_singleton().unwrap().2,
20529            &edits,
20530            &edit_preview,
20531            include_deletions,
20532            cx,
20533        );
20534        assertion_fn(highlighted_edits, cx)
20535    });
20536}
20537
20538#[track_caller]
20539fn assert_breakpoint(
20540    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
20541    path: &Arc<Path>,
20542    expected: Vec<(u32, Breakpoint)>,
20543) {
20544    if expected.len() == 0usize {
20545        assert!(!breakpoints.contains_key(path), "{}", path.display());
20546    } else {
20547        let mut breakpoint = breakpoints
20548            .get(path)
20549            .unwrap()
20550            .into_iter()
20551            .map(|breakpoint| {
20552                (
20553                    breakpoint.row,
20554                    Breakpoint {
20555                        message: breakpoint.message.clone(),
20556                        state: breakpoint.state,
20557                        condition: breakpoint.condition.clone(),
20558                        hit_condition: breakpoint.hit_condition.clone(),
20559                    },
20560                )
20561            })
20562            .collect::<Vec<_>>();
20563
20564        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
20565
20566        assert_eq!(expected, breakpoint);
20567    }
20568}
20569
20570fn add_log_breakpoint_at_cursor(
20571    editor: &mut Editor,
20572    log_message: &str,
20573    window: &mut Window,
20574    cx: &mut Context<Editor>,
20575) {
20576    let (anchor, bp) = editor
20577        .breakpoints_at_cursors(window, cx)
20578        .first()
20579        .and_then(|(anchor, bp)| {
20580            if let Some(bp) = bp {
20581                Some((*anchor, bp.clone()))
20582            } else {
20583                None
20584            }
20585        })
20586        .unwrap_or_else(|| {
20587            let cursor_position: Point = editor.selections.newest(cx).head();
20588
20589            let breakpoint_position = editor
20590                .snapshot(window, cx)
20591                .display_snapshot
20592                .buffer_snapshot
20593                .anchor_before(Point::new(cursor_position.row, 0));
20594
20595            (breakpoint_position, Breakpoint::new_log(&log_message))
20596        });
20597
20598    editor.edit_breakpoint_at_anchor(
20599        anchor,
20600        bp,
20601        BreakpointEditAction::EditLogMessage(log_message.into()),
20602        cx,
20603    );
20604}
20605
20606#[gpui::test]
20607async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
20608    init_test(cx, |_| {});
20609
20610    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20611    let fs = FakeFs::new(cx.executor());
20612    fs.insert_tree(
20613        path!("/a"),
20614        json!({
20615            "main.rs": sample_text,
20616        }),
20617    )
20618    .await;
20619    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20620    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20621    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20622
20623    let fs = FakeFs::new(cx.executor());
20624    fs.insert_tree(
20625        path!("/a"),
20626        json!({
20627            "main.rs": sample_text,
20628        }),
20629    )
20630    .await;
20631    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20632    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20633    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20634    let worktree_id = workspace
20635        .update(cx, |workspace, _window, cx| {
20636            workspace.project().update(cx, |project, cx| {
20637                project.worktrees(cx).next().unwrap().read(cx).id()
20638            })
20639        })
20640        .unwrap();
20641
20642    let buffer = project
20643        .update(cx, |project, cx| {
20644            project.open_buffer((worktree_id, "main.rs"), cx)
20645        })
20646        .await
20647        .unwrap();
20648
20649    let (editor, cx) = cx.add_window_view(|window, cx| {
20650        Editor::new(
20651            EditorMode::full(),
20652            MultiBuffer::build_from_buffer(buffer, cx),
20653            Some(project.clone()),
20654            window,
20655            cx,
20656        )
20657    });
20658
20659    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20660    let abs_path = project.read_with(cx, |project, cx| {
20661        project
20662            .absolute_path(&project_path, cx)
20663            .map(|path_buf| Arc::from(path_buf.to_owned()))
20664            .unwrap()
20665    });
20666
20667    // assert we can add breakpoint on the first line
20668    editor.update_in(cx, |editor, window, cx| {
20669        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20670        editor.move_to_end(&MoveToEnd, window, cx);
20671        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20672    });
20673
20674    let breakpoints = editor.update(cx, |editor, cx| {
20675        editor
20676            .breakpoint_store()
20677            .as_ref()
20678            .unwrap()
20679            .read(cx)
20680            .all_source_breakpoints(cx)
20681            .clone()
20682    });
20683
20684    assert_eq!(1, breakpoints.len());
20685    assert_breakpoint(
20686        &breakpoints,
20687        &abs_path,
20688        vec![
20689            (0, Breakpoint::new_standard()),
20690            (3, Breakpoint::new_standard()),
20691        ],
20692    );
20693
20694    editor.update_in(cx, |editor, window, cx| {
20695        editor.move_to_beginning(&MoveToBeginning, window, cx);
20696        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20697    });
20698
20699    let breakpoints = editor.update(cx, |editor, cx| {
20700        editor
20701            .breakpoint_store()
20702            .as_ref()
20703            .unwrap()
20704            .read(cx)
20705            .all_source_breakpoints(cx)
20706            .clone()
20707    });
20708
20709    assert_eq!(1, breakpoints.len());
20710    assert_breakpoint(
20711        &breakpoints,
20712        &abs_path,
20713        vec![(3, Breakpoint::new_standard())],
20714    );
20715
20716    editor.update_in(cx, |editor, window, cx| {
20717        editor.move_to_end(&MoveToEnd, window, cx);
20718        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20719    });
20720
20721    let breakpoints = editor.update(cx, |editor, cx| {
20722        editor
20723            .breakpoint_store()
20724            .as_ref()
20725            .unwrap()
20726            .read(cx)
20727            .all_source_breakpoints(cx)
20728            .clone()
20729    });
20730
20731    assert_eq!(0, breakpoints.len());
20732    assert_breakpoint(&breakpoints, &abs_path, vec![]);
20733}
20734
20735#[gpui::test]
20736async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
20737    init_test(cx, |_| {});
20738
20739    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20740
20741    let fs = FakeFs::new(cx.executor());
20742    fs.insert_tree(
20743        path!("/a"),
20744        json!({
20745            "main.rs": sample_text,
20746        }),
20747    )
20748    .await;
20749    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20750    let (workspace, cx) =
20751        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
20752
20753    let worktree_id = workspace.update(cx, |workspace, cx| {
20754        workspace.project().update(cx, |project, cx| {
20755            project.worktrees(cx).next().unwrap().read(cx).id()
20756        })
20757    });
20758
20759    let buffer = project
20760        .update(cx, |project, cx| {
20761            project.open_buffer((worktree_id, "main.rs"), cx)
20762        })
20763        .await
20764        .unwrap();
20765
20766    let (editor, cx) = cx.add_window_view(|window, cx| {
20767        Editor::new(
20768            EditorMode::full(),
20769            MultiBuffer::build_from_buffer(buffer, cx),
20770            Some(project.clone()),
20771            window,
20772            cx,
20773        )
20774    });
20775
20776    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20777    let abs_path = project.read_with(cx, |project, cx| {
20778        project
20779            .absolute_path(&project_path, cx)
20780            .map(|path_buf| Arc::from(path_buf.to_owned()))
20781            .unwrap()
20782    });
20783
20784    editor.update_in(cx, |editor, window, cx| {
20785        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
20786    });
20787
20788    let breakpoints = editor.update(cx, |editor, cx| {
20789        editor
20790            .breakpoint_store()
20791            .as_ref()
20792            .unwrap()
20793            .read(cx)
20794            .all_source_breakpoints(cx)
20795            .clone()
20796    });
20797
20798    assert_breakpoint(
20799        &breakpoints,
20800        &abs_path,
20801        vec![(0, Breakpoint::new_log("hello world"))],
20802    );
20803
20804    // Removing a log message from a log breakpoint should remove it
20805    editor.update_in(cx, |editor, window, cx| {
20806        add_log_breakpoint_at_cursor(editor, "", window, cx);
20807    });
20808
20809    let breakpoints = editor.update(cx, |editor, cx| {
20810        editor
20811            .breakpoint_store()
20812            .as_ref()
20813            .unwrap()
20814            .read(cx)
20815            .all_source_breakpoints(cx)
20816            .clone()
20817    });
20818
20819    assert_breakpoint(&breakpoints, &abs_path, vec![]);
20820
20821    editor.update_in(cx, |editor, window, cx| {
20822        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20823        editor.move_to_end(&MoveToEnd, window, cx);
20824        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20825        // Not adding a log message to a standard breakpoint shouldn't remove it
20826        add_log_breakpoint_at_cursor(editor, "", window, cx);
20827    });
20828
20829    let breakpoints = editor.update(cx, |editor, cx| {
20830        editor
20831            .breakpoint_store()
20832            .as_ref()
20833            .unwrap()
20834            .read(cx)
20835            .all_source_breakpoints(cx)
20836            .clone()
20837    });
20838
20839    assert_breakpoint(
20840        &breakpoints,
20841        &abs_path,
20842        vec![
20843            (0, Breakpoint::new_standard()),
20844            (3, Breakpoint::new_standard()),
20845        ],
20846    );
20847
20848    editor.update_in(cx, |editor, window, cx| {
20849        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
20850    });
20851
20852    let breakpoints = editor.update(cx, |editor, cx| {
20853        editor
20854            .breakpoint_store()
20855            .as_ref()
20856            .unwrap()
20857            .read(cx)
20858            .all_source_breakpoints(cx)
20859            .clone()
20860    });
20861
20862    assert_breakpoint(
20863        &breakpoints,
20864        &abs_path,
20865        vec![
20866            (0, Breakpoint::new_standard()),
20867            (3, Breakpoint::new_log("hello world")),
20868        ],
20869    );
20870
20871    editor.update_in(cx, |editor, window, cx| {
20872        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
20873    });
20874
20875    let breakpoints = editor.update(cx, |editor, cx| {
20876        editor
20877            .breakpoint_store()
20878            .as_ref()
20879            .unwrap()
20880            .read(cx)
20881            .all_source_breakpoints(cx)
20882            .clone()
20883    });
20884
20885    assert_breakpoint(
20886        &breakpoints,
20887        &abs_path,
20888        vec![
20889            (0, Breakpoint::new_standard()),
20890            (3, Breakpoint::new_log("hello Earth!!")),
20891        ],
20892    );
20893}
20894
20895/// This also tests that Editor::breakpoint_at_cursor_head is working properly
20896/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
20897/// or when breakpoints were placed out of order. This tests for a regression too
20898#[gpui::test]
20899async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
20900    init_test(cx, |_| {});
20901
20902    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20903    let fs = FakeFs::new(cx.executor());
20904    fs.insert_tree(
20905        path!("/a"),
20906        json!({
20907            "main.rs": sample_text,
20908        }),
20909    )
20910    .await;
20911    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20912    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20913    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20914
20915    let fs = FakeFs::new(cx.executor());
20916    fs.insert_tree(
20917        path!("/a"),
20918        json!({
20919            "main.rs": sample_text,
20920        }),
20921    )
20922    .await;
20923    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20924    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20925    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20926    let worktree_id = workspace
20927        .update(cx, |workspace, _window, cx| {
20928            workspace.project().update(cx, |project, cx| {
20929                project.worktrees(cx).next().unwrap().read(cx).id()
20930            })
20931        })
20932        .unwrap();
20933
20934    let buffer = project
20935        .update(cx, |project, cx| {
20936            project.open_buffer((worktree_id, "main.rs"), cx)
20937        })
20938        .await
20939        .unwrap();
20940
20941    let (editor, cx) = cx.add_window_view(|window, cx| {
20942        Editor::new(
20943            EditorMode::full(),
20944            MultiBuffer::build_from_buffer(buffer, cx),
20945            Some(project.clone()),
20946            window,
20947            cx,
20948        )
20949    });
20950
20951    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20952    let abs_path = project.read_with(cx, |project, cx| {
20953        project
20954            .absolute_path(&project_path, cx)
20955            .map(|path_buf| Arc::from(path_buf.to_owned()))
20956            .unwrap()
20957    });
20958
20959    // assert we can add breakpoint on the first line
20960    editor.update_in(cx, |editor, window, cx| {
20961        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20962        editor.move_to_end(&MoveToEnd, window, cx);
20963        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20964        editor.move_up(&MoveUp, window, cx);
20965        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20966    });
20967
20968    let breakpoints = editor.update(cx, |editor, cx| {
20969        editor
20970            .breakpoint_store()
20971            .as_ref()
20972            .unwrap()
20973            .read(cx)
20974            .all_source_breakpoints(cx)
20975            .clone()
20976    });
20977
20978    assert_eq!(1, breakpoints.len());
20979    assert_breakpoint(
20980        &breakpoints,
20981        &abs_path,
20982        vec![
20983            (0, Breakpoint::new_standard()),
20984            (2, Breakpoint::new_standard()),
20985            (3, Breakpoint::new_standard()),
20986        ],
20987    );
20988
20989    editor.update_in(cx, |editor, window, cx| {
20990        editor.move_to_beginning(&MoveToBeginning, window, cx);
20991        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20992        editor.move_to_end(&MoveToEnd, window, cx);
20993        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20994        // Disabling a breakpoint that doesn't exist should do nothing
20995        editor.move_up(&MoveUp, window, cx);
20996        editor.move_up(&MoveUp, window, cx);
20997        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20998    });
20999
21000    let breakpoints = editor.update(cx, |editor, cx| {
21001        editor
21002            .breakpoint_store()
21003            .as_ref()
21004            .unwrap()
21005            .read(cx)
21006            .all_source_breakpoints(cx)
21007            .clone()
21008    });
21009
21010    let disable_breakpoint = {
21011        let mut bp = Breakpoint::new_standard();
21012        bp.state = BreakpointState::Disabled;
21013        bp
21014    };
21015
21016    assert_eq!(1, breakpoints.len());
21017    assert_breakpoint(
21018        &breakpoints,
21019        &abs_path,
21020        vec![
21021            (0, disable_breakpoint.clone()),
21022            (2, Breakpoint::new_standard()),
21023            (3, disable_breakpoint.clone()),
21024        ],
21025    );
21026
21027    editor.update_in(cx, |editor, window, cx| {
21028        editor.move_to_beginning(&MoveToBeginning, window, cx);
21029        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
21030        editor.move_to_end(&MoveToEnd, window, cx);
21031        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
21032        editor.move_up(&MoveUp, window, cx);
21033        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21034    });
21035
21036    let breakpoints = editor.update(cx, |editor, cx| {
21037        editor
21038            .breakpoint_store()
21039            .as_ref()
21040            .unwrap()
21041            .read(cx)
21042            .all_source_breakpoints(cx)
21043            .clone()
21044    });
21045
21046    assert_eq!(1, breakpoints.len());
21047    assert_breakpoint(
21048        &breakpoints,
21049        &abs_path,
21050        vec![
21051            (0, Breakpoint::new_standard()),
21052            (2, disable_breakpoint),
21053            (3, Breakpoint::new_standard()),
21054        ],
21055    );
21056}
21057
21058#[gpui::test]
21059async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
21060    init_test(cx, |_| {});
21061    let capabilities = lsp::ServerCapabilities {
21062        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
21063            prepare_provider: Some(true),
21064            work_done_progress_options: Default::default(),
21065        })),
21066        ..Default::default()
21067    };
21068    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
21069
21070    cx.set_state(indoc! {"
21071        struct Fˇoo {}
21072    "});
21073
21074    cx.update_editor(|editor, _, cx| {
21075        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
21076        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
21077        editor.highlight_background::<DocumentHighlightRead>(
21078            &[highlight_range],
21079            |theme| theme.colors().editor_document_highlight_read_background,
21080            cx,
21081        );
21082    });
21083
21084    let mut prepare_rename_handler = cx
21085        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
21086            move |_, _, _| async move {
21087                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
21088                    start: lsp::Position {
21089                        line: 0,
21090                        character: 7,
21091                    },
21092                    end: lsp::Position {
21093                        line: 0,
21094                        character: 10,
21095                    },
21096                })))
21097            },
21098        );
21099    let prepare_rename_task = cx
21100        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
21101        .expect("Prepare rename was not started");
21102    prepare_rename_handler.next().await.unwrap();
21103    prepare_rename_task.await.expect("Prepare rename failed");
21104
21105    let mut rename_handler =
21106        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
21107            let edit = lsp::TextEdit {
21108                range: lsp::Range {
21109                    start: lsp::Position {
21110                        line: 0,
21111                        character: 7,
21112                    },
21113                    end: lsp::Position {
21114                        line: 0,
21115                        character: 10,
21116                    },
21117                },
21118                new_text: "FooRenamed".to_string(),
21119            };
21120            Ok(Some(lsp::WorkspaceEdit::new(
21121                // Specify the same edit twice
21122                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
21123            )))
21124        });
21125    let rename_task = cx
21126        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
21127        .expect("Confirm rename was not started");
21128    rename_handler.next().await.unwrap();
21129    rename_task.await.expect("Confirm rename failed");
21130    cx.run_until_parked();
21131
21132    // Despite two edits, only one is actually applied as those are identical
21133    cx.assert_editor_state(indoc! {"
21134        struct FooRenamedˇ {}
21135    "});
21136}
21137
21138#[gpui::test]
21139async fn test_rename_without_prepare(cx: &mut TestAppContext) {
21140    init_test(cx, |_| {});
21141    // These capabilities indicate that the server does not support prepare rename.
21142    let capabilities = lsp::ServerCapabilities {
21143        rename_provider: Some(lsp::OneOf::Left(true)),
21144        ..Default::default()
21145    };
21146    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
21147
21148    cx.set_state(indoc! {"
21149        struct Fˇoo {}
21150    "});
21151
21152    cx.update_editor(|editor, _window, cx| {
21153        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
21154        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
21155        editor.highlight_background::<DocumentHighlightRead>(
21156            &[highlight_range],
21157            |theme| theme.colors().editor_document_highlight_read_background,
21158            cx,
21159        );
21160    });
21161
21162    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
21163        .expect("Prepare rename was not started")
21164        .await
21165        .expect("Prepare rename failed");
21166
21167    let mut rename_handler =
21168        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
21169            let edit = lsp::TextEdit {
21170                range: lsp::Range {
21171                    start: lsp::Position {
21172                        line: 0,
21173                        character: 7,
21174                    },
21175                    end: lsp::Position {
21176                        line: 0,
21177                        character: 10,
21178                    },
21179                },
21180                new_text: "FooRenamed".to_string(),
21181            };
21182            Ok(Some(lsp::WorkspaceEdit::new(
21183                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
21184            )))
21185        });
21186    let rename_task = cx
21187        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
21188        .expect("Confirm rename was not started");
21189    rename_handler.next().await.unwrap();
21190    rename_task.await.expect("Confirm rename failed");
21191    cx.run_until_parked();
21192
21193    // Correct range is renamed, as `surrounding_word` is used to find it.
21194    cx.assert_editor_state(indoc! {"
21195        struct FooRenamedˇ {}
21196    "});
21197}
21198
21199#[gpui::test]
21200async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
21201    init_test(cx, |_| {});
21202    let mut cx = EditorTestContext::new(cx).await;
21203
21204    let language = Arc::new(
21205        Language::new(
21206            LanguageConfig::default(),
21207            Some(tree_sitter_html::LANGUAGE.into()),
21208        )
21209        .with_brackets_query(
21210            r#"
21211            ("<" @open "/>" @close)
21212            ("</" @open ">" @close)
21213            ("<" @open ">" @close)
21214            ("\"" @open "\"" @close)
21215            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
21216        "#,
21217        )
21218        .unwrap(),
21219    );
21220    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
21221
21222    cx.set_state(indoc! {"
21223        <span>ˇ</span>
21224    "});
21225    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21226    cx.assert_editor_state(indoc! {"
21227        <span>
21228        ˇ
21229        </span>
21230    "});
21231
21232    cx.set_state(indoc! {"
21233        <span><span></span>ˇ</span>
21234    "});
21235    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21236    cx.assert_editor_state(indoc! {"
21237        <span><span></span>
21238        ˇ</span>
21239    "});
21240
21241    cx.set_state(indoc! {"
21242        <span>ˇ
21243        </span>
21244    "});
21245    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21246    cx.assert_editor_state(indoc! {"
21247        <span>
21248        ˇ
21249        </span>
21250    "});
21251}
21252
21253#[gpui::test(iterations = 10)]
21254async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
21255    init_test(cx, |_| {});
21256
21257    let fs = FakeFs::new(cx.executor());
21258    fs.insert_tree(
21259        path!("/dir"),
21260        json!({
21261            "a.ts": "a",
21262        }),
21263    )
21264    .await;
21265
21266    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
21267    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21268    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21269
21270    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21271    language_registry.add(Arc::new(Language::new(
21272        LanguageConfig {
21273            name: "TypeScript".into(),
21274            matcher: LanguageMatcher {
21275                path_suffixes: vec!["ts".to_string()],
21276                ..Default::default()
21277            },
21278            ..Default::default()
21279        },
21280        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
21281    )));
21282    let mut fake_language_servers = language_registry.register_fake_lsp(
21283        "TypeScript",
21284        FakeLspAdapter {
21285            capabilities: lsp::ServerCapabilities {
21286                code_lens_provider: Some(lsp::CodeLensOptions {
21287                    resolve_provider: Some(true),
21288                }),
21289                execute_command_provider: Some(lsp::ExecuteCommandOptions {
21290                    commands: vec!["_the/command".to_string()],
21291                    ..lsp::ExecuteCommandOptions::default()
21292                }),
21293                ..lsp::ServerCapabilities::default()
21294            },
21295            ..FakeLspAdapter::default()
21296        },
21297    );
21298
21299    let editor = workspace
21300        .update(cx, |workspace, window, cx| {
21301            workspace.open_abs_path(
21302                PathBuf::from(path!("/dir/a.ts")),
21303                OpenOptions::default(),
21304                window,
21305                cx,
21306            )
21307        })
21308        .unwrap()
21309        .await
21310        .unwrap()
21311        .downcast::<Editor>()
21312        .unwrap();
21313    cx.executor().run_until_parked();
21314
21315    let fake_server = fake_language_servers.next().await.unwrap();
21316
21317    let buffer = editor.update(cx, |editor, cx| {
21318        editor
21319            .buffer()
21320            .read(cx)
21321            .as_singleton()
21322            .expect("have opened a single file by path")
21323    });
21324
21325    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
21326    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
21327    drop(buffer_snapshot);
21328    let actions = cx
21329        .update_window(*workspace, |_, window, cx| {
21330            project.code_actions(&buffer, anchor..anchor, window, cx)
21331        })
21332        .unwrap();
21333
21334    fake_server
21335        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
21336            Ok(Some(vec![
21337                lsp::CodeLens {
21338                    range: lsp::Range::default(),
21339                    command: Some(lsp::Command {
21340                        title: "Code lens command".to_owned(),
21341                        command: "_the/command".to_owned(),
21342                        arguments: None,
21343                    }),
21344                    data: None,
21345                },
21346                lsp::CodeLens {
21347                    range: lsp::Range::default(),
21348                    command: Some(lsp::Command {
21349                        title: "Command not in capabilities".to_owned(),
21350                        command: "not in capabilities".to_owned(),
21351                        arguments: None,
21352                    }),
21353                    data: None,
21354                },
21355                lsp::CodeLens {
21356                    range: lsp::Range {
21357                        start: lsp::Position {
21358                            line: 1,
21359                            character: 1,
21360                        },
21361                        end: lsp::Position {
21362                            line: 1,
21363                            character: 1,
21364                        },
21365                    },
21366                    command: Some(lsp::Command {
21367                        title: "Command not in range".to_owned(),
21368                        command: "_the/command".to_owned(),
21369                        arguments: None,
21370                    }),
21371                    data: None,
21372                },
21373            ]))
21374        })
21375        .next()
21376        .await;
21377
21378    let actions = actions.await.unwrap();
21379    assert_eq!(
21380        actions.len(),
21381        1,
21382        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
21383    );
21384    let action = actions[0].clone();
21385    let apply = project.update(cx, |project, cx| {
21386        project.apply_code_action(buffer.clone(), action, true, cx)
21387    });
21388
21389    // Resolving the code action does not populate its edits. In absence of
21390    // edits, we must execute the given command.
21391    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
21392        |mut lens, _| async move {
21393            let lens_command = lens.command.as_mut().expect("should have a command");
21394            assert_eq!(lens_command.title, "Code lens command");
21395            lens_command.arguments = Some(vec![json!("the-argument")]);
21396            Ok(lens)
21397        },
21398    );
21399
21400    // While executing the command, the language server sends the editor
21401    // a `workspaceEdit` request.
21402    fake_server
21403        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
21404            let fake = fake_server.clone();
21405            move |params, _| {
21406                assert_eq!(params.command, "_the/command");
21407                let fake = fake.clone();
21408                async move {
21409                    fake.server
21410                        .request::<lsp::request::ApplyWorkspaceEdit>(
21411                            lsp::ApplyWorkspaceEditParams {
21412                                label: None,
21413                                edit: lsp::WorkspaceEdit {
21414                                    changes: Some(
21415                                        [(
21416                                            lsp::Url::from_file_path(path!("/dir/a.ts")).unwrap(),
21417                                            vec![lsp::TextEdit {
21418                                                range: lsp::Range::new(
21419                                                    lsp::Position::new(0, 0),
21420                                                    lsp::Position::new(0, 0),
21421                                                ),
21422                                                new_text: "X".into(),
21423                                            }],
21424                                        )]
21425                                        .into_iter()
21426                                        .collect(),
21427                                    ),
21428                                    ..lsp::WorkspaceEdit::default()
21429                                },
21430                            },
21431                        )
21432                        .await
21433                        .into_response()
21434                        .unwrap();
21435                    Ok(Some(json!(null)))
21436                }
21437            }
21438        })
21439        .next()
21440        .await;
21441
21442    // Applying the code lens command returns a project transaction containing the edits
21443    // sent by the language server in its `workspaceEdit` request.
21444    let transaction = apply.await.unwrap();
21445    assert!(transaction.0.contains_key(&buffer));
21446    buffer.update(cx, |buffer, cx| {
21447        assert_eq!(buffer.text(), "Xa");
21448        buffer.undo(cx);
21449        assert_eq!(buffer.text(), "a");
21450    });
21451
21452    let actions_after_edits = cx
21453        .update_window(*workspace, |_, window, cx| {
21454            project.code_actions(&buffer, anchor..anchor, window, cx)
21455        })
21456        .unwrap()
21457        .await
21458        .unwrap();
21459    assert_eq!(
21460        actions, actions_after_edits,
21461        "For the same selection, same code lens actions should be returned"
21462    );
21463
21464    let _responses =
21465        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
21466            panic!("No more code lens requests are expected");
21467        });
21468    editor.update_in(cx, |editor, window, cx| {
21469        editor.select_all(&SelectAll, window, cx);
21470    });
21471    cx.executor().run_until_parked();
21472    let new_actions = cx
21473        .update_window(*workspace, |_, window, cx| {
21474            project.code_actions(&buffer, anchor..anchor, window, cx)
21475        })
21476        .unwrap()
21477        .await
21478        .unwrap();
21479    assert_eq!(
21480        actions, new_actions,
21481        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
21482    );
21483}
21484
21485#[gpui::test]
21486async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
21487    init_test(cx, |_| {});
21488
21489    let fs = FakeFs::new(cx.executor());
21490    let main_text = r#"fn main() {
21491println!("1");
21492println!("2");
21493println!("3");
21494println!("4");
21495println!("5");
21496}"#;
21497    let lib_text = "mod foo {}";
21498    fs.insert_tree(
21499        path!("/a"),
21500        json!({
21501            "lib.rs": lib_text,
21502            "main.rs": main_text,
21503        }),
21504    )
21505    .await;
21506
21507    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21508    let (workspace, cx) =
21509        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21510    let worktree_id = workspace.update(cx, |workspace, cx| {
21511        workspace.project().update(cx, |project, cx| {
21512            project.worktrees(cx).next().unwrap().read(cx).id()
21513        })
21514    });
21515
21516    let expected_ranges = vec![
21517        Point::new(0, 0)..Point::new(0, 0),
21518        Point::new(1, 0)..Point::new(1, 1),
21519        Point::new(2, 0)..Point::new(2, 2),
21520        Point::new(3, 0)..Point::new(3, 3),
21521    ];
21522
21523    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21524    let editor_1 = workspace
21525        .update_in(cx, |workspace, window, cx| {
21526            workspace.open_path(
21527                (worktree_id, "main.rs"),
21528                Some(pane_1.downgrade()),
21529                true,
21530                window,
21531                cx,
21532            )
21533        })
21534        .unwrap()
21535        .await
21536        .downcast::<Editor>()
21537        .unwrap();
21538    pane_1.update(cx, |pane, cx| {
21539        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21540        open_editor.update(cx, |editor, cx| {
21541            assert_eq!(
21542                editor.display_text(cx),
21543                main_text,
21544                "Original main.rs text on initial open",
21545            );
21546            assert_eq!(
21547                editor
21548                    .selections
21549                    .all::<Point>(cx)
21550                    .into_iter()
21551                    .map(|s| s.range())
21552                    .collect::<Vec<_>>(),
21553                vec![Point::zero()..Point::zero()],
21554                "Default selections on initial open",
21555            );
21556        })
21557    });
21558    editor_1.update_in(cx, |editor, window, cx| {
21559        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21560            s.select_ranges(expected_ranges.clone());
21561        });
21562    });
21563
21564    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
21565        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
21566    });
21567    let editor_2 = workspace
21568        .update_in(cx, |workspace, window, cx| {
21569            workspace.open_path(
21570                (worktree_id, "main.rs"),
21571                Some(pane_2.downgrade()),
21572                true,
21573                window,
21574                cx,
21575            )
21576        })
21577        .unwrap()
21578        .await
21579        .downcast::<Editor>()
21580        .unwrap();
21581    pane_2.update(cx, |pane, cx| {
21582        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21583        open_editor.update(cx, |editor, cx| {
21584            assert_eq!(
21585                editor.display_text(cx),
21586                main_text,
21587                "Original main.rs text on initial open in another panel",
21588            );
21589            assert_eq!(
21590                editor
21591                    .selections
21592                    .all::<Point>(cx)
21593                    .into_iter()
21594                    .map(|s| s.range())
21595                    .collect::<Vec<_>>(),
21596                vec![Point::zero()..Point::zero()],
21597                "Default selections on initial open in another panel",
21598            );
21599        })
21600    });
21601
21602    editor_2.update_in(cx, |editor, window, cx| {
21603        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
21604    });
21605
21606    let _other_editor_1 = workspace
21607        .update_in(cx, |workspace, window, cx| {
21608            workspace.open_path(
21609                (worktree_id, "lib.rs"),
21610                Some(pane_1.downgrade()),
21611                true,
21612                window,
21613                cx,
21614            )
21615        })
21616        .unwrap()
21617        .await
21618        .downcast::<Editor>()
21619        .unwrap();
21620    pane_1
21621        .update_in(cx, |pane, window, cx| {
21622            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
21623        })
21624        .await
21625        .unwrap();
21626    drop(editor_1);
21627    pane_1.update(cx, |pane, cx| {
21628        pane.active_item()
21629            .unwrap()
21630            .downcast::<Editor>()
21631            .unwrap()
21632            .update(cx, |editor, cx| {
21633                assert_eq!(
21634                    editor.display_text(cx),
21635                    lib_text,
21636                    "Other file should be open and active",
21637                );
21638            });
21639        assert_eq!(pane.items().count(), 1, "No other editors should be open");
21640    });
21641
21642    let _other_editor_2 = workspace
21643        .update_in(cx, |workspace, window, cx| {
21644            workspace.open_path(
21645                (worktree_id, "lib.rs"),
21646                Some(pane_2.downgrade()),
21647                true,
21648                window,
21649                cx,
21650            )
21651        })
21652        .unwrap()
21653        .await
21654        .downcast::<Editor>()
21655        .unwrap();
21656    pane_2
21657        .update_in(cx, |pane, window, cx| {
21658            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
21659        })
21660        .await
21661        .unwrap();
21662    drop(editor_2);
21663    pane_2.update(cx, |pane, cx| {
21664        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21665        open_editor.update(cx, |editor, cx| {
21666            assert_eq!(
21667                editor.display_text(cx),
21668                lib_text,
21669                "Other file should be open and active in another panel too",
21670            );
21671        });
21672        assert_eq!(
21673            pane.items().count(),
21674            1,
21675            "No other editors should be open in another pane",
21676        );
21677    });
21678
21679    let _editor_1_reopened = workspace
21680        .update_in(cx, |workspace, window, cx| {
21681            workspace.open_path(
21682                (worktree_id, "main.rs"),
21683                Some(pane_1.downgrade()),
21684                true,
21685                window,
21686                cx,
21687            )
21688        })
21689        .unwrap()
21690        .await
21691        .downcast::<Editor>()
21692        .unwrap();
21693    let _editor_2_reopened = workspace
21694        .update_in(cx, |workspace, window, cx| {
21695            workspace.open_path(
21696                (worktree_id, "main.rs"),
21697                Some(pane_2.downgrade()),
21698                true,
21699                window,
21700                cx,
21701            )
21702        })
21703        .unwrap()
21704        .await
21705        .downcast::<Editor>()
21706        .unwrap();
21707    pane_1.update(cx, |pane, cx| {
21708        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21709        open_editor.update(cx, |editor, cx| {
21710            assert_eq!(
21711                editor.display_text(cx),
21712                main_text,
21713                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
21714            );
21715            assert_eq!(
21716                editor
21717                    .selections
21718                    .all::<Point>(cx)
21719                    .into_iter()
21720                    .map(|s| s.range())
21721                    .collect::<Vec<_>>(),
21722                expected_ranges,
21723                "Previous editor in the 1st panel had selections and should get them restored on reopen",
21724            );
21725        })
21726    });
21727    pane_2.update(cx, |pane, cx| {
21728        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21729        open_editor.update(cx, |editor, cx| {
21730            assert_eq!(
21731                editor.display_text(cx),
21732                r#"fn main() {
21733⋯rintln!("1");
21734⋯intln!("2");
21735⋯ntln!("3");
21736println!("4");
21737println!("5");
21738}"#,
21739                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
21740            );
21741            assert_eq!(
21742                editor
21743                    .selections
21744                    .all::<Point>(cx)
21745                    .into_iter()
21746                    .map(|s| s.range())
21747                    .collect::<Vec<_>>(),
21748                vec![Point::zero()..Point::zero()],
21749                "Previous editor in the 2nd pane had no selections changed hence should restore none",
21750            );
21751        })
21752    });
21753}
21754
21755#[gpui::test]
21756async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
21757    init_test(cx, |_| {});
21758
21759    let fs = FakeFs::new(cx.executor());
21760    let main_text = r#"fn main() {
21761println!("1");
21762println!("2");
21763println!("3");
21764println!("4");
21765println!("5");
21766}"#;
21767    let lib_text = "mod foo {}";
21768    fs.insert_tree(
21769        path!("/a"),
21770        json!({
21771            "lib.rs": lib_text,
21772            "main.rs": main_text,
21773        }),
21774    )
21775    .await;
21776
21777    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21778    let (workspace, cx) =
21779        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21780    let worktree_id = workspace.update(cx, |workspace, cx| {
21781        workspace.project().update(cx, |project, cx| {
21782            project.worktrees(cx).next().unwrap().read(cx).id()
21783        })
21784    });
21785
21786    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21787    let editor = workspace
21788        .update_in(cx, |workspace, window, cx| {
21789            workspace.open_path(
21790                (worktree_id, "main.rs"),
21791                Some(pane.downgrade()),
21792                true,
21793                window,
21794                cx,
21795            )
21796        })
21797        .unwrap()
21798        .await
21799        .downcast::<Editor>()
21800        .unwrap();
21801    pane.update(cx, |pane, cx| {
21802        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21803        open_editor.update(cx, |editor, cx| {
21804            assert_eq!(
21805                editor.display_text(cx),
21806                main_text,
21807                "Original main.rs text on initial open",
21808            );
21809        })
21810    });
21811    editor.update_in(cx, |editor, window, cx| {
21812        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
21813    });
21814
21815    cx.update_global(|store: &mut SettingsStore, cx| {
21816        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
21817            s.restore_on_file_reopen = Some(false);
21818        });
21819    });
21820    editor.update_in(cx, |editor, window, cx| {
21821        editor.fold_ranges(
21822            vec![
21823                Point::new(1, 0)..Point::new(1, 1),
21824                Point::new(2, 0)..Point::new(2, 2),
21825                Point::new(3, 0)..Point::new(3, 3),
21826            ],
21827            false,
21828            window,
21829            cx,
21830        );
21831    });
21832    pane.update_in(cx, |pane, window, cx| {
21833        pane.close_all_items(&CloseAllItems::default(), window, cx)
21834    })
21835    .await
21836    .unwrap();
21837    pane.update(cx, |pane, _| {
21838        assert!(pane.active_item().is_none());
21839    });
21840    cx.update_global(|store: &mut SettingsStore, cx| {
21841        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
21842            s.restore_on_file_reopen = Some(true);
21843        });
21844    });
21845
21846    let _editor_reopened = workspace
21847        .update_in(cx, |workspace, window, cx| {
21848            workspace.open_path(
21849                (worktree_id, "main.rs"),
21850                Some(pane.downgrade()),
21851                true,
21852                window,
21853                cx,
21854            )
21855        })
21856        .unwrap()
21857        .await
21858        .downcast::<Editor>()
21859        .unwrap();
21860    pane.update(cx, |pane, cx| {
21861        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21862        open_editor.update(cx, |editor, cx| {
21863            assert_eq!(
21864                editor.display_text(cx),
21865                main_text,
21866                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
21867            );
21868        })
21869    });
21870}
21871
21872#[gpui::test]
21873async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
21874    struct EmptyModalView {
21875        focus_handle: gpui::FocusHandle,
21876    }
21877    impl EventEmitter<DismissEvent> for EmptyModalView {}
21878    impl Render for EmptyModalView {
21879        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
21880            div()
21881        }
21882    }
21883    impl Focusable for EmptyModalView {
21884        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
21885            self.focus_handle.clone()
21886        }
21887    }
21888    impl workspace::ModalView for EmptyModalView {}
21889    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
21890        EmptyModalView {
21891            focus_handle: cx.focus_handle(),
21892        }
21893    }
21894
21895    init_test(cx, |_| {});
21896
21897    let fs = FakeFs::new(cx.executor());
21898    let project = Project::test(fs, [], cx).await;
21899    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21900    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
21901    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21902    let editor = cx.new_window_entity(|window, cx| {
21903        Editor::new(
21904            EditorMode::full(),
21905            buffer,
21906            Some(project.clone()),
21907            window,
21908            cx,
21909        )
21910    });
21911    workspace
21912        .update(cx, |workspace, window, cx| {
21913            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
21914        })
21915        .unwrap();
21916    editor.update_in(cx, |editor, window, cx| {
21917        editor.open_context_menu(&OpenContextMenu, window, cx);
21918        assert!(editor.mouse_context_menu.is_some());
21919    });
21920    workspace
21921        .update(cx, |workspace, window, cx| {
21922            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
21923        })
21924        .unwrap();
21925    cx.read(|cx| {
21926        assert!(editor.read(cx).mouse_context_menu.is_none());
21927    });
21928}
21929
21930#[gpui::test]
21931async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
21932    init_test(cx, |_| {});
21933
21934    let fs = FakeFs::new(cx.executor());
21935    fs.insert_file(path!("/file.html"), Default::default())
21936        .await;
21937
21938    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
21939
21940    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21941    let html_language = Arc::new(Language::new(
21942        LanguageConfig {
21943            name: "HTML".into(),
21944            matcher: LanguageMatcher {
21945                path_suffixes: vec!["html".to_string()],
21946                ..LanguageMatcher::default()
21947            },
21948            brackets: BracketPairConfig {
21949                pairs: vec![BracketPair {
21950                    start: "<".into(),
21951                    end: ">".into(),
21952                    close: true,
21953                    ..Default::default()
21954                }],
21955                ..Default::default()
21956            },
21957            ..Default::default()
21958        },
21959        Some(tree_sitter_html::LANGUAGE.into()),
21960    ));
21961    language_registry.add(html_language);
21962    let mut fake_servers = language_registry.register_fake_lsp(
21963        "HTML",
21964        FakeLspAdapter {
21965            capabilities: lsp::ServerCapabilities {
21966                completion_provider: Some(lsp::CompletionOptions {
21967                    resolve_provider: Some(true),
21968                    ..Default::default()
21969                }),
21970                ..Default::default()
21971            },
21972            ..Default::default()
21973        },
21974    );
21975
21976    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21977    let cx = &mut VisualTestContext::from_window(*workspace, cx);
21978
21979    let worktree_id = workspace
21980        .update(cx, |workspace, _window, cx| {
21981            workspace.project().update(cx, |project, cx| {
21982                project.worktrees(cx).next().unwrap().read(cx).id()
21983            })
21984        })
21985        .unwrap();
21986    project
21987        .update(cx, |project, cx| {
21988            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
21989        })
21990        .await
21991        .unwrap();
21992    let editor = workspace
21993        .update(cx, |workspace, window, cx| {
21994            workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
21995        })
21996        .unwrap()
21997        .await
21998        .unwrap()
21999        .downcast::<Editor>()
22000        .unwrap();
22001
22002    let fake_server = fake_servers.next().await.unwrap();
22003    editor.update_in(cx, |editor, window, cx| {
22004        editor.set_text("<ad></ad>", window, cx);
22005        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22006            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
22007        });
22008        let Some((buffer, _)) = editor
22009            .buffer
22010            .read(cx)
22011            .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
22012        else {
22013            panic!("Failed to get buffer for selection position");
22014        };
22015        let buffer = buffer.read(cx);
22016        let buffer_id = buffer.remote_id();
22017        let opening_range =
22018            buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3));
22019        let closing_range =
22020            buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8));
22021        let mut linked_ranges = HashMap::default();
22022        linked_ranges.insert(
22023            buffer_id,
22024            vec![(opening_range.clone(), vec![closing_range.clone()])],
22025        );
22026        editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
22027    });
22028    let mut completion_handle =
22029        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
22030            Ok(Some(lsp::CompletionResponse::Array(vec![
22031                lsp::CompletionItem {
22032                    label: "head".to_string(),
22033                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22034                        lsp::InsertReplaceEdit {
22035                            new_text: "head".to_string(),
22036                            insert: lsp::Range::new(
22037                                lsp::Position::new(0, 1),
22038                                lsp::Position::new(0, 3),
22039                            ),
22040                            replace: lsp::Range::new(
22041                                lsp::Position::new(0, 1),
22042                                lsp::Position::new(0, 3),
22043                            ),
22044                        },
22045                    )),
22046                    ..Default::default()
22047                },
22048            ])))
22049        });
22050    editor.update_in(cx, |editor, window, cx| {
22051        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
22052    });
22053    cx.run_until_parked();
22054    completion_handle.next().await.unwrap();
22055    editor.update(cx, |editor, _| {
22056        assert!(
22057            editor.context_menu_visible(),
22058            "Completion menu should be visible"
22059        );
22060    });
22061    editor.update_in(cx, |editor, window, cx| {
22062        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
22063    });
22064    cx.executor().run_until_parked();
22065    editor.update(cx, |editor, cx| {
22066        assert_eq!(editor.text(cx), "<head></head>");
22067    });
22068}
22069
22070#[gpui::test]
22071async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
22072    init_test(cx, |_| {});
22073
22074    let fs = FakeFs::new(cx.executor());
22075    fs.insert_tree(
22076        path!("/root"),
22077        json!({
22078            "a": {
22079                "main.rs": "fn main() {}",
22080            },
22081            "foo": {
22082                "bar": {
22083                    "external_file.rs": "pub mod external {}",
22084                }
22085            }
22086        }),
22087    )
22088    .await;
22089
22090    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
22091    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22092    language_registry.add(rust_lang());
22093    let _fake_servers = language_registry.register_fake_lsp(
22094        "Rust",
22095        FakeLspAdapter {
22096            ..FakeLspAdapter::default()
22097        },
22098    );
22099    let (workspace, cx) =
22100        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22101    let worktree_id = workspace.update(cx, |workspace, cx| {
22102        workspace.project().update(cx, |project, cx| {
22103            project.worktrees(cx).next().unwrap().read(cx).id()
22104        })
22105    });
22106
22107    let assert_language_servers_count =
22108        |expected: usize, context: &str, cx: &mut VisualTestContext| {
22109            project.update(cx, |project, cx| {
22110                let current = project
22111                    .lsp_store()
22112                    .read(cx)
22113                    .as_local()
22114                    .unwrap()
22115                    .language_servers
22116                    .len();
22117                assert_eq!(expected, current, "{context}");
22118            });
22119        };
22120
22121    assert_language_servers_count(
22122        0,
22123        "No servers should be running before any file is open",
22124        cx,
22125    );
22126    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
22127    let main_editor = workspace
22128        .update_in(cx, |workspace, window, cx| {
22129            workspace.open_path(
22130                (worktree_id, "main.rs"),
22131                Some(pane.downgrade()),
22132                true,
22133                window,
22134                cx,
22135            )
22136        })
22137        .unwrap()
22138        .await
22139        .downcast::<Editor>()
22140        .unwrap();
22141    pane.update(cx, |pane, cx| {
22142        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22143        open_editor.update(cx, |editor, cx| {
22144            assert_eq!(
22145                editor.display_text(cx),
22146                "fn main() {}",
22147                "Original main.rs text on initial open",
22148            );
22149        });
22150        assert_eq!(open_editor, main_editor);
22151    });
22152    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
22153
22154    let external_editor = workspace
22155        .update_in(cx, |workspace, window, cx| {
22156            workspace.open_abs_path(
22157                PathBuf::from("/root/foo/bar/external_file.rs"),
22158                OpenOptions::default(),
22159                window,
22160                cx,
22161            )
22162        })
22163        .await
22164        .expect("opening external file")
22165        .downcast::<Editor>()
22166        .expect("downcasted external file's open element to editor");
22167    pane.update(cx, |pane, cx| {
22168        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22169        open_editor.update(cx, |editor, cx| {
22170            assert_eq!(
22171                editor.display_text(cx),
22172                "pub mod external {}",
22173                "External file is open now",
22174            );
22175        });
22176        assert_eq!(open_editor, external_editor);
22177    });
22178    assert_language_servers_count(
22179        1,
22180        "Second, external, *.rs file should join the existing server",
22181        cx,
22182    );
22183
22184    pane.update_in(cx, |pane, window, cx| {
22185        pane.close_active_item(&CloseActiveItem::default(), window, cx)
22186    })
22187    .await
22188    .unwrap();
22189    pane.update_in(cx, |pane, window, cx| {
22190        pane.navigate_backward(window, cx);
22191    });
22192    cx.run_until_parked();
22193    pane.update(cx, |pane, cx| {
22194        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22195        open_editor.update(cx, |editor, cx| {
22196            assert_eq!(
22197                editor.display_text(cx),
22198                "pub mod external {}",
22199                "External file is open now",
22200            );
22201        });
22202    });
22203    assert_language_servers_count(
22204        1,
22205        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
22206        cx,
22207    );
22208
22209    cx.update(|_, cx| {
22210        workspace::reload(&workspace::Reload::default(), cx);
22211    });
22212    assert_language_servers_count(
22213        1,
22214        "After reloading the worktree with local and external files opened, only one project should be started",
22215        cx,
22216    );
22217}
22218
22219#[gpui::test]
22220async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
22221    init_test(cx, |_| {});
22222
22223    let mut cx = EditorTestContext::new(cx).await;
22224    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22225    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22226
22227    // test cursor move to start of each line on tab
22228    // for `if`, `elif`, `else`, `while`, `with` and `for`
22229    cx.set_state(indoc! {"
22230        def main():
22231        ˇ    for item in items:
22232        ˇ        while item.active:
22233        ˇ            if item.value > 10:
22234        ˇ                continue
22235        ˇ            elif item.value < 0:
22236        ˇ                break
22237        ˇ            else:
22238        ˇ                with item.context() as ctx:
22239        ˇ                    yield count
22240        ˇ        else:
22241        ˇ            log('while else')
22242        ˇ    else:
22243        ˇ        log('for else')
22244    "});
22245    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22246    cx.assert_editor_state(indoc! {"
22247        def main():
22248            ˇfor item in items:
22249                ˇwhile item.active:
22250                    ˇif item.value > 10:
22251                        ˇcontinue
22252                    ˇelif item.value < 0:
22253                        ˇbreak
22254                    ˇelse:
22255                        ˇwith item.context() as ctx:
22256                            ˇyield count
22257                ˇelse:
22258                    ˇlog('while else')
22259            ˇelse:
22260                ˇlog('for else')
22261    "});
22262    // test relative indent is preserved when tab
22263    // for `if`, `elif`, `else`, `while`, `with` and `for`
22264    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22265    cx.assert_editor_state(indoc! {"
22266        def main():
22267                ˇfor item in items:
22268                    ˇwhile item.active:
22269                        ˇif item.value > 10:
22270                            ˇcontinue
22271                        ˇelif item.value < 0:
22272                            ˇbreak
22273                        ˇelse:
22274                            ˇwith item.context() as ctx:
22275                                ˇyield count
22276                    ˇelse:
22277                        ˇlog('while else')
22278                ˇelse:
22279                    ˇlog('for else')
22280    "});
22281
22282    // test cursor move to start of each line on tab
22283    // for `try`, `except`, `else`, `finally`, `match` and `def`
22284    cx.set_state(indoc! {"
22285        def main():
22286        ˇ    try:
22287        ˇ        fetch()
22288        ˇ    except ValueError:
22289        ˇ        handle_error()
22290        ˇ    else:
22291        ˇ        match value:
22292        ˇ            case _:
22293        ˇ    finally:
22294        ˇ        def status():
22295        ˇ            return 0
22296    "});
22297    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22298    cx.assert_editor_state(indoc! {"
22299        def main():
22300            ˇtry:
22301                ˇfetch()
22302            ˇexcept ValueError:
22303                ˇhandle_error()
22304            ˇelse:
22305                ˇmatch value:
22306                    ˇcase _:
22307            ˇfinally:
22308                ˇdef status():
22309                    ˇreturn 0
22310    "});
22311    // test relative indent is preserved when tab
22312    // for `try`, `except`, `else`, `finally`, `match` and `def`
22313    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22314    cx.assert_editor_state(indoc! {"
22315        def main():
22316                ˇtry:
22317                    ˇfetch()
22318                ˇexcept ValueError:
22319                    ˇhandle_error()
22320                ˇelse:
22321                    ˇmatch value:
22322                        ˇcase _:
22323                ˇfinally:
22324                    ˇdef status():
22325                        ˇreturn 0
22326    "});
22327}
22328
22329#[gpui::test]
22330async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
22331    init_test(cx, |_| {});
22332
22333    let mut cx = EditorTestContext::new(cx).await;
22334    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22335    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22336
22337    // test `else` auto outdents when typed inside `if` block
22338    cx.set_state(indoc! {"
22339        def main():
22340            if i == 2:
22341                return
22342                ˇ
22343    "});
22344    cx.update_editor(|editor, window, cx| {
22345        editor.handle_input("else:", window, cx);
22346    });
22347    cx.assert_editor_state(indoc! {"
22348        def main():
22349            if i == 2:
22350                return
22351            else:ˇ
22352    "});
22353
22354    // test `except` auto outdents when typed inside `try` block
22355    cx.set_state(indoc! {"
22356        def main():
22357            try:
22358                i = 2
22359                ˇ
22360    "});
22361    cx.update_editor(|editor, window, cx| {
22362        editor.handle_input("except:", window, cx);
22363    });
22364    cx.assert_editor_state(indoc! {"
22365        def main():
22366            try:
22367                i = 2
22368            except:ˇ
22369    "});
22370
22371    // test `else` auto outdents when typed inside `except` block
22372    cx.set_state(indoc! {"
22373        def main():
22374            try:
22375                i = 2
22376            except:
22377                j = 2
22378                ˇ
22379    "});
22380    cx.update_editor(|editor, window, cx| {
22381        editor.handle_input("else:", window, cx);
22382    });
22383    cx.assert_editor_state(indoc! {"
22384        def main():
22385            try:
22386                i = 2
22387            except:
22388                j = 2
22389            else:ˇ
22390    "});
22391
22392    // test `finally` auto outdents when typed inside `else` block
22393    cx.set_state(indoc! {"
22394        def main():
22395            try:
22396                i = 2
22397            except:
22398                j = 2
22399            else:
22400                k = 2
22401                ˇ
22402    "});
22403    cx.update_editor(|editor, window, cx| {
22404        editor.handle_input("finally:", window, cx);
22405    });
22406    cx.assert_editor_state(indoc! {"
22407        def main():
22408            try:
22409                i = 2
22410            except:
22411                j = 2
22412            else:
22413                k = 2
22414            finally:ˇ
22415    "});
22416
22417    // test `else` does not outdents when typed inside `except` block right after for block
22418    cx.set_state(indoc! {"
22419        def main():
22420            try:
22421                i = 2
22422            except:
22423                for i in range(n):
22424                    pass
22425                ˇ
22426    "});
22427    cx.update_editor(|editor, window, cx| {
22428        editor.handle_input("else:", window, cx);
22429    });
22430    cx.assert_editor_state(indoc! {"
22431        def main():
22432            try:
22433                i = 2
22434            except:
22435                for i in range(n):
22436                    pass
22437                else:ˇ
22438    "});
22439
22440    // test `finally` auto outdents when typed inside `else` block right after for block
22441    cx.set_state(indoc! {"
22442        def main():
22443            try:
22444                i = 2
22445            except:
22446                j = 2
22447            else:
22448                for i in range(n):
22449                    pass
22450                ˇ
22451    "});
22452    cx.update_editor(|editor, window, cx| {
22453        editor.handle_input("finally:", window, cx);
22454    });
22455    cx.assert_editor_state(indoc! {"
22456        def main():
22457            try:
22458                i = 2
22459            except:
22460                j = 2
22461            else:
22462                for i in range(n):
22463                    pass
22464            finally:ˇ
22465    "});
22466
22467    // test `except` outdents to inner "try" block
22468    cx.set_state(indoc! {"
22469        def main():
22470            try:
22471                i = 2
22472                if i == 2:
22473                    try:
22474                        i = 3
22475                        ˇ
22476    "});
22477    cx.update_editor(|editor, window, cx| {
22478        editor.handle_input("except:", window, cx);
22479    });
22480    cx.assert_editor_state(indoc! {"
22481        def main():
22482            try:
22483                i = 2
22484                if i == 2:
22485                    try:
22486                        i = 3
22487                    except:ˇ
22488    "});
22489
22490    // test `except` outdents to outer "try" block
22491    cx.set_state(indoc! {"
22492        def main():
22493            try:
22494                i = 2
22495                if i == 2:
22496                    try:
22497                        i = 3
22498                ˇ
22499    "});
22500    cx.update_editor(|editor, window, cx| {
22501        editor.handle_input("except:", window, cx);
22502    });
22503    cx.assert_editor_state(indoc! {"
22504        def main():
22505            try:
22506                i = 2
22507                if i == 2:
22508                    try:
22509                        i = 3
22510            except:ˇ
22511    "});
22512
22513    // test `else` stays at correct indent when typed after `for` block
22514    cx.set_state(indoc! {"
22515        def main():
22516            for i in range(10):
22517                if i == 3:
22518                    break
22519            ˇ
22520    "});
22521    cx.update_editor(|editor, window, cx| {
22522        editor.handle_input("else:", window, cx);
22523    });
22524    cx.assert_editor_state(indoc! {"
22525        def main():
22526            for i in range(10):
22527                if i == 3:
22528                    break
22529            else:ˇ
22530    "});
22531
22532    // test does not outdent on typing after line with square brackets
22533    cx.set_state(indoc! {"
22534        def f() -> list[str]:
22535            ˇ
22536    "});
22537    cx.update_editor(|editor, window, cx| {
22538        editor.handle_input("a", window, cx);
22539    });
22540    cx.assert_editor_state(indoc! {"
22541        def f() -> list[str]:
2254222543    "});
22544
22545    // test does not outdent on typing : after case keyword
22546    cx.set_state(indoc! {"
22547        match 1:
22548            caseˇ
22549    "});
22550    cx.update_editor(|editor, window, cx| {
22551        editor.handle_input(":", window, cx);
22552    });
22553    cx.assert_editor_state(indoc! {"
22554        match 1:
22555            case:ˇ
22556    "});
22557}
22558
22559#[gpui::test]
22560async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
22561    init_test(cx, |_| {});
22562    update_test_language_settings(cx, |settings| {
22563        settings.defaults.extend_comment_on_newline = Some(false);
22564    });
22565    let mut cx = EditorTestContext::new(cx).await;
22566    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22567    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22568
22569    // test correct indent after newline on comment
22570    cx.set_state(indoc! {"
22571        # COMMENT:ˇ
22572    "});
22573    cx.update_editor(|editor, window, cx| {
22574        editor.newline(&Newline, window, cx);
22575    });
22576    cx.assert_editor_state(indoc! {"
22577        # COMMENT:
22578        ˇ
22579    "});
22580
22581    // test correct indent after newline in brackets
22582    cx.set_state(indoc! {"
22583        {ˇ}
22584    "});
22585    cx.update_editor(|editor, window, cx| {
22586        editor.newline(&Newline, window, cx);
22587    });
22588    cx.run_until_parked();
22589    cx.assert_editor_state(indoc! {"
22590        {
22591            ˇ
22592        }
22593    "});
22594
22595    cx.set_state(indoc! {"
22596        (ˇ)
22597    "});
22598    cx.update_editor(|editor, window, cx| {
22599        editor.newline(&Newline, window, cx);
22600    });
22601    cx.run_until_parked();
22602    cx.assert_editor_state(indoc! {"
22603        (
22604            ˇ
22605        )
22606    "});
22607
22608    // do not indent after empty lists or dictionaries
22609    cx.set_state(indoc! {"
22610        a = []ˇ
22611    "});
22612    cx.update_editor(|editor, window, cx| {
22613        editor.newline(&Newline, window, cx);
22614    });
22615    cx.run_until_parked();
22616    cx.assert_editor_state(indoc! {"
22617        a = []
22618        ˇ
22619    "});
22620}
22621
22622fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
22623    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
22624    point..point
22625}
22626
22627#[track_caller]
22628fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
22629    let (text, ranges) = marked_text_ranges(marked_text, true);
22630    assert_eq!(editor.text(cx), text);
22631    assert_eq!(
22632        editor.selections.ranges(cx),
22633        ranges,
22634        "Assert selections are {}",
22635        marked_text
22636    );
22637}
22638
22639pub fn handle_signature_help_request(
22640    cx: &mut EditorLspTestContext,
22641    mocked_response: lsp::SignatureHelp,
22642) -> impl Future<Output = ()> + use<> {
22643    let mut request =
22644        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
22645            let mocked_response = mocked_response.clone();
22646            async move { Ok(Some(mocked_response)) }
22647        });
22648
22649    async move {
22650        request.next().await;
22651    }
22652}
22653
22654#[track_caller]
22655pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
22656    cx.update_editor(|editor, _, _| {
22657        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
22658            let entries = menu.entries.borrow();
22659            let entries = entries
22660                .iter()
22661                .map(|entry| entry.string.as_str())
22662                .collect::<Vec<_>>();
22663            assert_eq!(entries, expected);
22664        } else {
22665            panic!("Expected completions menu");
22666        }
22667    });
22668}
22669
22670/// Handle completion request passing a marked string specifying where the completion
22671/// should be triggered from using '|' character, what range should be replaced, and what completions
22672/// should be returned using '<' and '>' to delimit the range.
22673///
22674/// Also see `handle_completion_request_with_insert_and_replace`.
22675#[track_caller]
22676pub fn handle_completion_request(
22677    marked_string: &str,
22678    completions: Vec<&'static str>,
22679    is_incomplete: bool,
22680    counter: Arc<AtomicUsize>,
22681    cx: &mut EditorLspTestContext,
22682) -> impl Future<Output = ()> {
22683    let complete_from_marker: TextRangeMarker = '|'.into();
22684    let replace_range_marker: TextRangeMarker = ('<', '>').into();
22685    let (_, mut marked_ranges) = marked_text_ranges_by(
22686        marked_string,
22687        vec![complete_from_marker.clone(), replace_range_marker.clone()],
22688    );
22689
22690    let complete_from_position =
22691        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
22692    let replace_range =
22693        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
22694
22695    let mut request =
22696        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
22697            let completions = completions.clone();
22698            counter.fetch_add(1, atomic::Ordering::Release);
22699            async move {
22700                assert_eq!(params.text_document_position.text_document.uri, url.clone());
22701                assert_eq!(
22702                    params.text_document_position.position,
22703                    complete_from_position
22704                );
22705                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
22706                    is_incomplete: is_incomplete,
22707                    item_defaults: None,
22708                    items: completions
22709                        .iter()
22710                        .map(|completion_text| lsp::CompletionItem {
22711                            label: completion_text.to_string(),
22712                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
22713                                range: replace_range,
22714                                new_text: completion_text.to_string(),
22715                            })),
22716                            ..Default::default()
22717                        })
22718                        .collect(),
22719                })))
22720            }
22721        });
22722
22723    async move {
22724        request.next().await;
22725    }
22726}
22727
22728/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
22729/// given instead, which also contains an `insert` range.
22730///
22731/// This function uses markers to define ranges:
22732/// - `|` marks the cursor position
22733/// - `<>` marks the replace range
22734/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
22735pub fn handle_completion_request_with_insert_and_replace(
22736    cx: &mut EditorLspTestContext,
22737    marked_string: &str,
22738    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
22739    counter: Arc<AtomicUsize>,
22740) -> impl Future<Output = ()> {
22741    let complete_from_marker: TextRangeMarker = '|'.into();
22742    let replace_range_marker: TextRangeMarker = ('<', '>').into();
22743    let insert_range_marker: TextRangeMarker = ('{', '}').into();
22744
22745    let (_, mut marked_ranges) = marked_text_ranges_by(
22746        marked_string,
22747        vec![
22748            complete_from_marker.clone(),
22749            replace_range_marker.clone(),
22750            insert_range_marker.clone(),
22751        ],
22752    );
22753
22754    let complete_from_position =
22755        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
22756    let replace_range =
22757        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
22758
22759    let insert_range = match marked_ranges.remove(&insert_range_marker) {
22760        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
22761        _ => lsp::Range {
22762            start: replace_range.start,
22763            end: complete_from_position,
22764        },
22765    };
22766
22767    let mut request =
22768        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
22769            let completions = completions.clone();
22770            counter.fetch_add(1, atomic::Ordering::Release);
22771            async move {
22772                assert_eq!(params.text_document_position.text_document.uri, url.clone());
22773                assert_eq!(
22774                    params.text_document_position.position, complete_from_position,
22775                    "marker `|` position doesn't match",
22776                );
22777                Ok(Some(lsp::CompletionResponse::Array(
22778                    completions
22779                        .iter()
22780                        .map(|(label, new_text)| lsp::CompletionItem {
22781                            label: label.to_string(),
22782                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22783                                lsp::InsertReplaceEdit {
22784                                    insert: insert_range,
22785                                    replace: replace_range,
22786                                    new_text: new_text.to_string(),
22787                                },
22788                            )),
22789                            ..Default::default()
22790                        })
22791                        .collect(),
22792                )))
22793            }
22794        });
22795
22796    async move {
22797        request.next().await;
22798    }
22799}
22800
22801fn handle_resolve_completion_request(
22802    cx: &mut EditorLspTestContext,
22803    edits: Option<Vec<(&'static str, &'static str)>>,
22804) -> impl Future<Output = ()> {
22805    let edits = edits.map(|edits| {
22806        edits
22807            .iter()
22808            .map(|(marked_string, new_text)| {
22809                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
22810                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
22811                lsp::TextEdit::new(replace_range, new_text.to_string())
22812            })
22813            .collect::<Vec<_>>()
22814    });
22815
22816    let mut request =
22817        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
22818            let edits = edits.clone();
22819            async move {
22820                Ok(lsp::CompletionItem {
22821                    additional_text_edits: edits,
22822                    ..Default::default()
22823                })
22824            }
22825        });
22826
22827    async move {
22828        request.next().await;
22829    }
22830}
22831
22832pub(crate) fn update_test_language_settings(
22833    cx: &mut TestAppContext,
22834    f: impl Fn(&mut AllLanguageSettingsContent),
22835) {
22836    cx.update(|cx| {
22837        SettingsStore::update_global(cx, |store, cx| {
22838            store.update_user_settings::<AllLanguageSettings>(cx, f);
22839        });
22840    });
22841}
22842
22843pub(crate) fn update_test_project_settings(
22844    cx: &mut TestAppContext,
22845    f: impl Fn(&mut ProjectSettings),
22846) {
22847    cx.update(|cx| {
22848        SettingsStore::update_global(cx, |store, cx| {
22849            store.update_user_settings::<ProjectSettings>(cx, f);
22850        });
22851    });
22852}
22853
22854pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
22855    cx.update(|cx| {
22856        assets::Assets.load_test_fonts(cx);
22857        let store = SettingsStore::test(cx);
22858        cx.set_global(store);
22859        theme::init(theme::LoadThemes::JustBase, cx);
22860        release_channel::init(SemanticVersion::default(), cx);
22861        client::init_settings(cx);
22862        language::init(cx);
22863        Project::init_settings(cx);
22864        workspace::init_settings(cx);
22865        crate::init(cx);
22866    });
22867    zlog::init_test();
22868    update_test_language_settings(cx, f);
22869}
22870
22871#[track_caller]
22872fn assert_hunk_revert(
22873    not_reverted_text_with_selections: &str,
22874    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
22875    expected_reverted_text_with_selections: &str,
22876    base_text: &str,
22877    cx: &mut EditorLspTestContext,
22878) {
22879    cx.set_state(not_reverted_text_with_selections);
22880    cx.set_head_text(base_text);
22881    cx.executor().run_until_parked();
22882
22883    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
22884        let snapshot = editor.snapshot(window, cx);
22885        let reverted_hunk_statuses = snapshot
22886            .buffer_snapshot
22887            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
22888            .map(|hunk| hunk.status().kind)
22889            .collect::<Vec<_>>();
22890
22891        editor.git_restore(&Default::default(), window, cx);
22892        reverted_hunk_statuses
22893    });
22894    cx.executor().run_until_parked();
22895    cx.assert_editor_state(expected_reverted_text_with_selections);
22896    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
22897}
22898
22899#[gpui::test(iterations = 10)]
22900async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
22901    init_test(cx, |_| {});
22902
22903    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
22904    let counter = diagnostic_requests.clone();
22905
22906    let fs = FakeFs::new(cx.executor());
22907    fs.insert_tree(
22908        path!("/a"),
22909        json!({
22910            "first.rs": "fn main() { let a = 5; }",
22911            "second.rs": "// Test file",
22912        }),
22913    )
22914    .await;
22915
22916    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22917    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22918    let cx = &mut VisualTestContext::from_window(*workspace, cx);
22919
22920    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22921    language_registry.add(rust_lang());
22922    let mut fake_servers = language_registry.register_fake_lsp(
22923        "Rust",
22924        FakeLspAdapter {
22925            capabilities: lsp::ServerCapabilities {
22926                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
22927                    lsp::DiagnosticOptions {
22928                        identifier: None,
22929                        inter_file_dependencies: true,
22930                        workspace_diagnostics: true,
22931                        work_done_progress_options: Default::default(),
22932                    },
22933                )),
22934                ..Default::default()
22935            },
22936            ..Default::default()
22937        },
22938    );
22939
22940    let editor = workspace
22941        .update(cx, |workspace, window, cx| {
22942            workspace.open_abs_path(
22943                PathBuf::from(path!("/a/first.rs")),
22944                OpenOptions::default(),
22945                window,
22946                cx,
22947            )
22948        })
22949        .unwrap()
22950        .await
22951        .unwrap()
22952        .downcast::<Editor>()
22953        .unwrap();
22954    let fake_server = fake_servers.next().await.unwrap();
22955    let server_id = fake_server.server.server_id();
22956    let mut first_request = fake_server
22957        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
22958            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
22959            let result_id = Some(new_result_id.to_string());
22960            assert_eq!(
22961                params.text_document.uri,
22962                lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
22963            );
22964            async move {
22965                Ok(lsp::DocumentDiagnosticReportResult::Report(
22966                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
22967                        related_documents: None,
22968                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
22969                            items: Vec::new(),
22970                            result_id,
22971                        },
22972                    }),
22973                ))
22974            }
22975        });
22976
22977    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
22978        project.update(cx, |project, cx| {
22979            let buffer_id = editor
22980                .read(cx)
22981                .buffer()
22982                .read(cx)
22983                .as_singleton()
22984                .expect("created a singleton buffer")
22985                .read(cx)
22986                .remote_id();
22987            let buffer_result_id = project
22988                .lsp_store()
22989                .read(cx)
22990                .result_id(server_id, buffer_id, cx);
22991            assert_eq!(expected, buffer_result_id);
22992        });
22993    };
22994
22995    ensure_result_id(None, cx);
22996    cx.executor().advance_clock(Duration::from_millis(60));
22997    cx.executor().run_until_parked();
22998    assert_eq!(
22999        diagnostic_requests.load(atomic::Ordering::Acquire),
23000        1,
23001        "Opening file should trigger diagnostic request"
23002    );
23003    first_request
23004        .next()
23005        .await
23006        .expect("should have sent the first diagnostics pull request");
23007    ensure_result_id(Some("1".to_string()), cx);
23008
23009    // Editing should trigger diagnostics
23010    editor.update_in(cx, |editor, window, cx| {
23011        editor.handle_input("2", window, cx)
23012    });
23013    cx.executor().advance_clock(Duration::from_millis(60));
23014    cx.executor().run_until_parked();
23015    assert_eq!(
23016        diagnostic_requests.load(atomic::Ordering::Acquire),
23017        2,
23018        "Editing should trigger diagnostic request"
23019    );
23020    ensure_result_id(Some("2".to_string()), cx);
23021
23022    // Moving cursor should not trigger diagnostic request
23023    editor.update_in(cx, |editor, window, cx| {
23024        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23025            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
23026        });
23027    });
23028    cx.executor().advance_clock(Duration::from_millis(60));
23029    cx.executor().run_until_parked();
23030    assert_eq!(
23031        diagnostic_requests.load(atomic::Ordering::Acquire),
23032        2,
23033        "Cursor movement should not trigger diagnostic request"
23034    );
23035    ensure_result_id(Some("2".to_string()), cx);
23036    // Multiple rapid edits should be debounced
23037    for _ in 0..5 {
23038        editor.update_in(cx, |editor, window, cx| {
23039            editor.handle_input("x", window, cx)
23040        });
23041    }
23042    cx.executor().advance_clock(Duration::from_millis(60));
23043    cx.executor().run_until_parked();
23044
23045    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
23046    assert!(
23047        final_requests <= 4,
23048        "Multiple rapid edits should be debounced (got {final_requests} requests)",
23049    );
23050    ensure_result_id(Some(final_requests.to_string()), cx);
23051}
23052
23053#[gpui::test]
23054async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
23055    // Regression test for issue #11671
23056    // Previously, adding a cursor after moving multiple cursors would reset
23057    // the cursor count instead of adding to the existing cursors.
23058    init_test(cx, |_| {});
23059    let mut cx = EditorTestContext::new(cx).await;
23060
23061    // Create a simple buffer with cursor at start
23062    cx.set_state(indoc! {"
23063        ˇaaaa
23064        bbbb
23065        cccc
23066        dddd
23067        eeee
23068        ffff
23069        gggg
23070        hhhh"});
23071
23072    // Add 2 cursors below (so we have 3 total)
23073    cx.update_editor(|editor, window, cx| {
23074        editor.add_selection_below(&Default::default(), window, cx);
23075        editor.add_selection_below(&Default::default(), window, cx);
23076    });
23077
23078    // Verify we have 3 cursors
23079    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
23080    assert_eq!(
23081        initial_count, 3,
23082        "Should have 3 cursors after adding 2 below"
23083    );
23084
23085    // Move down one line
23086    cx.update_editor(|editor, window, cx| {
23087        editor.move_down(&MoveDown, window, cx);
23088    });
23089
23090    // Add another cursor below
23091    cx.update_editor(|editor, window, cx| {
23092        editor.add_selection_below(&Default::default(), window, cx);
23093    });
23094
23095    // Should now have 4 cursors (3 original + 1 new)
23096    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
23097    assert_eq!(
23098        final_count, 4,
23099        "Should have 4 cursors after moving and adding another"
23100    );
23101}
23102
23103#[gpui::test(iterations = 10)]
23104async fn test_document_colors(cx: &mut TestAppContext) {
23105    let expected_color = Rgba {
23106        r: 0.33,
23107        g: 0.33,
23108        b: 0.33,
23109        a: 0.33,
23110    };
23111
23112    init_test(cx, |_| {});
23113
23114    let fs = FakeFs::new(cx.executor());
23115    fs.insert_tree(
23116        path!("/a"),
23117        json!({
23118            "first.rs": "fn main() { let a = 5; }",
23119        }),
23120    )
23121    .await;
23122
23123    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23124    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23125    let cx = &mut VisualTestContext::from_window(*workspace, cx);
23126
23127    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23128    language_registry.add(rust_lang());
23129    let mut fake_servers = language_registry.register_fake_lsp(
23130        "Rust",
23131        FakeLspAdapter {
23132            capabilities: lsp::ServerCapabilities {
23133                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
23134                ..lsp::ServerCapabilities::default()
23135            },
23136            name: "rust-analyzer",
23137            ..FakeLspAdapter::default()
23138        },
23139    );
23140    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
23141        "Rust",
23142        FakeLspAdapter {
23143            capabilities: lsp::ServerCapabilities {
23144                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
23145                ..lsp::ServerCapabilities::default()
23146            },
23147            name: "not-rust-analyzer",
23148            ..FakeLspAdapter::default()
23149        },
23150    );
23151
23152    let editor = workspace
23153        .update(cx, |workspace, window, cx| {
23154            workspace.open_abs_path(
23155                PathBuf::from(path!("/a/first.rs")),
23156                OpenOptions::default(),
23157                window,
23158                cx,
23159            )
23160        })
23161        .unwrap()
23162        .await
23163        .unwrap()
23164        .downcast::<Editor>()
23165        .unwrap();
23166    let fake_language_server = fake_servers.next().await.unwrap();
23167    let fake_language_server_without_capabilities =
23168        fake_servers_without_capabilities.next().await.unwrap();
23169    let requests_made = Arc::new(AtomicUsize::new(0));
23170    let closure_requests_made = Arc::clone(&requests_made);
23171    let mut color_request_handle = fake_language_server
23172        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
23173            let requests_made = Arc::clone(&closure_requests_made);
23174            async move {
23175                assert_eq!(
23176                    params.text_document.uri,
23177                    lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
23178                );
23179                requests_made.fetch_add(1, atomic::Ordering::Release);
23180                Ok(vec![
23181                    lsp::ColorInformation {
23182                        range: lsp::Range {
23183                            start: lsp::Position {
23184                                line: 0,
23185                                character: 0,
23186                            },
23187                            end: lsp::Position {
23188                                line: 0,
23189                                character: 1,
23190                            },
23191                        },
23192                        color: lsp::Color {
23193                            red: 0.33,
23194                            green: 0.33,
23195                            blue: 0.33,
23196                            alpha: 0.33,
23197                        },
23198                    },
23199                    lsp::ColorInformation {
23200                        range: lsp::Range {
23201                            start: lsp::Position {
23202                                line: 0,
23203                                character: 0,
23204                            },
23205                            end: lsp::Position {
23206                                line: 0,
23207                                character: 1,
23208                            },
23209                        },
23210                        color: lsp::Color {
23211                            red: 0.33,
23212                            green: 0.33,
23213                            blue: 0.33,
23214                            alpha: 0.33,
23215                        },
23216                    },
23217                ])
23218            }
23219        });
23220
23221    let _handle = fake_language_server_without_capabilities
23222        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
23223            panic!("Should not be called");
23224        });
23225    cx.executor().advance_clock(Duration::from_millis(100));
23226    color_request_handle.next().await.unwrap();
23227    cx.run_until_parked();
23228    assert_eq!(
23229        1,
23230        requests_made.load(atomic::Ordering::Acquire),
23231        "Should query for colors once per editor open"
23232    );
23233    editor.update_in(cx, |editor, _, cx| {
23234        assert_eq!(
23235            vec![expected_color],
23236            extract_color_inlays(editor, cx),
23237            "Should have an initial inlay"
23238        );
23239    });
23240
23241    // opening another file in a split should not influence the LSP query counter
23242    workspace
23243        .update(cx, |workspace, window, cx| {
23244            assert_eq!(
23245                workspace.panes().len(),
23246                1,
23247                "Should have one pane with one editor"
23248            );
23249            workspace.move_item_to_pane_in_direction(
23250                &MoveItemToPaneInDirection {
23251                    direction: SplitDirection::Right,
23252                    focus: false,
23253                    clone: true,
23254                },
23255                window,
23256                cx,
23257            );
23258        })
23259        .unwrap();
23260    cx.run_until_parked();
23261    workspace
23262        .update(cx, |workspace, _, cx| {
23263            let panes = workspace.panes();
23264            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
23265            for pane in panes {
23266                let editor = pane
23267                    .read(cx)
23268                    .active_item()
23269                    .and_then(|item| item.downcast::<Editor>())
23270                    .expect("Should have opened an editor in each split");
23271                let editor_file = editor
23272                    .read(cx)
23273                    .buffer()
23274                    .read(cx)
23275                    .as_singleton()
23276                    .expect("test deals with singleton buffers")
23277                    .read(cx)
23278                    .file()
23279                    .expect("test buffese should have a file")
23280                    .path();
23281                assert_eq!(
23282                    editor_file.as_ref(),
23283                    Path::new("first.rs"),
23284                    "Both editors should be opened for the same file"
23285                )
23286            }
23287        })
23288        .unwrap();
23289
23290    cx.executor().advance_clock(Duration::from_millis(500));
23291    let save = editor.update_in(cx, |editor, window, cx| {
23292        editor.move_to_end(&MoveToEnd, window, cx);
23293        editor.handle_input("dirty", window, cx);
23294        editor.save(
23295            SaveOptions {
23296                format: true,
23297                autosave: true,
23298            },
23299            project.clone(),
23300            window,
23301            cx,
23302        )
23303    });
23304    save.await.unwrap();
23305
23306    color_request_handle.next().await.unwrap();
23307    cx.run_until_parked();
23308    assert_eq!(
23309        3,
23310        requests_made.load(atomic::Ordering::Acquire),
23311        "Should query for colors once per save and once per formatting after save"
23312    );
23313
23314    drop(editor);
23315    let close = workspace
23316        .update(cx, |workspace, window, cx| {
23317            workspace.active_pane().update(cx, |pane, cx| {
23318                pane.close_active_item(&CloseActiveItem::default(), window, cx)
23319            })
23320        })
23321        .unwrap();
23322    close.await.unwrap();
23323    let close = workspace
23324        .update(cx, |workspace, window, cx| {
23325            workspace.active_pane().update(cx, |pane, cx| {
23326                pane.close_active_item(&CloseActiveItem::default(), window, cx)
23327            })
23328        })
23329        .unwrap();
23330    close.await.unwrap();
23331    assert_eq!(
23332        3,
23333        requests_made.load(atomic::Ordering::Acquire),
23334        "After saving and closing all editors, no extra requests should be made"
23335    );
23336    workspace
23337        .update(cx, |workspace, _, cx| {
23338            assert!(
23339                workspace.active_item(cx).is_none(),
23340                "Should close all editors"
23341            )
23342        })
23343        .unwrap();
23344
23345    workspace
23346        .update(cx, |workspace, window, cx| {
23347            workspace.active_pane().update(cx, |pane, cx| {
23348                pane.navigate_backward(window, cx);
23349            })
23350        })
23351        .unwrap();
23352    cx.executor().advance_clock(Duration::from_millis(100));
23353    cx.run_until_parked();
23354    let editor = workspace
23355        .update(cx, |workspace, _, cx| {
23356            workspace
23357                .active_item(cx)
23358                .expect("Should have reopened the editor again after navigating back")
23359                .downcast::<Editor>()
23360                .expect("Should be an editor")
23361        })
23362        .unwrap();
23363    color_request_handle.next().await.unwrap();
23364    assert_eq!(
23365        3,
23366        requests_made.load(atomic::Ordering::Acquire),
23367        "Cache should be reused on buffer close and reopen"
23368    );
23369    editor.update(cx, |editor, cx| {
23370        assert_eq!(
23371            vec![expected_color],
23372            extract_color_inlays(editor, cx),
23373            "Should have an initial inlay"
23374        );
23375    });
23376}
23377
23378#[gpui::test]
23379async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
23380    init_test(cx, |_| {});
23381    let (editor, cx) = cx.add_window_view(Editor::single_line);
23382    editor.update_in(cx, |editor, window, cx| {
23383        editor.set_text("oops\n\nwow\n", window, cx)
23384    });
23385    cx.run_until_parked();
23386    editor.update(cx, |editor, cx| {
23387        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
23388    });
23389    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
23390    cx.run_until_parked();
23391    editor.update(cx, |editor, cx| {
23392        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
23393    });
23394}
23395
23396#[track_caller]
23397fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
23398    editor
23399        .all_inlays(cx)
23400        .into_iter()
23401        .filter_map(|inlay| inlay.get_color())
23402        .map(Rgba::from)
23403        .collect()
23404}