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,
   29        LanguageSettingsContent, LspInsertMode, PrettierSettings,
   30    },
   31    tree_sitter_python,
   32};
   33use language_settings::{Formatter, IndentGuideSettings};
   34use lsp::CompletionParams;
   35use multi_buffer::{IndentGuide, PathKey};
   36use parking_lot::Mutex;
   37use pretty_assertions::{assert_eq, assert_ne};
   38use project::{
   39    FakeFs,
   40    debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
   41    project_settings::{LspSettings, ProjectSettings},
   42};
   43use serde_json::{self, json};
   44use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
   45use std::{
   46    iter,
   47    sync::atomic::{self, AtomicUsize},
   48};
   49use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
   50use text::ToPoint as _;
   51use unindent::Unindent;
   52use util::{
   53    assert_set_eq, path,
   54    test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text},
   55    uri,
   56};
   57use workspace::{
   58    CloseActiveItem, CloseAllItems, CloseInactiveItems, MoveItemToPaneInDirection, NavigationEntry,
   59    OpenOptions, ViewId,
   60    item::{FollowEvent, FollowableItem, Item, ItemHandle, SaveOptions},
   61};
   62
   63#[gpui::test]
   64fn test_edit_events(cx: &mut TestAppContext) {
   65    init_test(cx, |_| {});
   66
   67    let buffer = cx.new(|cx| {
   68        let mut buffer = language::Buffer::local("123456", cx);
   69        buffer.set_group_interval(Duration::from_secs(1));
   70        buffer
   71    });
   72
   73    let events = Rc::new(RefCell::new(Vec::new()));
   74    let editor1 = cx.add_window({
   75        let events = events.clone();
   76        |window, cx| {
   77            let entity = cx.entity().clone();
   78            cx.subscribe_in(
   79                &entity,
   80                window,
   81                move |_, _, event: &EditorEvent, _, _| match event {
   82                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
   83                    EditorEvent::BufferEdited => {
   84                        events.borrow_mut().push(("editor1", "buffer edited"))
   85                    }
   86                    _ => {}
   87                },
   88            )
   89            .detach();
   90            Editor::for_buffer(buffer.clone(), None, window, cx)
   91        }
   92    });
   93
   94    let editor2 = cx.add_window({
   95        let events = events.clone();
   96        |window, cx| {
   97            cx.subscribe_in(
   98                &cx.entity().clone(),
   99                window,
  100                move |_, _, event: &EditorEvent, _, _| match event {
  101                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
  102                    EditorEvent::BufferEdited => {
  103                        events.borrow_mut().push(("editor2", "buffer edited"))
  104                    }
  105                    _ => {}
  106                },
  107            )
  108            .detach();
  109            Editor::for_buffer(buffer.clone(), None, window, cx)
  110        }
  111    });
  112
  113    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  114
  115    // Mutating editor 1 will emit an `Edited` event only for that editor.
  116    _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
  117    assert_eq!(
  118        mem::take(&mut *events.borrow_mut()),
  119        [
  120            ("editor1", "edited"),
  121            ("editor1", "buffer edited"),
  122            ("editor2", "buffer edited"),
  123        ]
  124    );
  125
  126    // Mutating editor 2 will emit an `Edited` event only for that editor.
  127    _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
  128    assert_eq!(
  129        mem::take(&mut *events.borrow_mut()),
  130        [
  131            ("editor2", "edited"),
  132            ("editor1", "buffer edited"),
  133            ("editor2", "buffer edited"),
  134        ]
  135    );
  136
  137    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  138    _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  139    assert_eq!(
  140        mem::take(&mut *events.borrow_mut()),
  141        [
  142            ("editor1", "edited"),
  143            ("editor1", "buffer edited"),
  144            ("editor2", "buffer edited"),
  145        ]
  146    );
  147
  148    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  149    _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  150    assert_eq!(
  151        mem::take(&mut *events.borrow_mut()),
  152        [
  153            ("editor1", "edited"),
  154            ("editor1", "buffer edited"),
  155            ("editor2", "buffer edited"),
  156        ]
  157    );
  158
  159    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  160    _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  161    assert_eq!(
  162        mem::take(&mut *events.borrow_mut()),
  163        [
  164            ("editor2", "edited"),
  165            ("editor1", "buffer edited"),
  166            ("editor2", "buffer edited"),
  167        ]
  168    );
  169
  170    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  171    _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  172    assert_eq!(
  173        mem::take(&mut *events.borrow_mut()),
  174        [
  175            ("editor2", "edited"),
  176            ("editor1", "buffer edited"),
  177            ("editor2", "buffer edited"),
  178        ]
  179    );
  180
  181    // No event is emitted when the mutation is a no-op.
  182    _ = editor2.update(cx, |editor, window, cx| {
  183        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  184            s.select_ranges([0..0])
  185        });
  186
  187        editor.backspace(&Backspace, window, cx);
  188    });
  189    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  190}
  191
  192#[gpui::test]
  193fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  194    init_test(cx, |_| {});
  195
  196    let mut now = Instant::now();
  197    let group_interval = Duration::from_millis(1);
  198    let buffer = cx.new(|cx| {
  199        let mut buf = language::Buffer::local("123456", cx);
  200        buf.set_group_interval(group_interval);
  201        buf
  202    });
  203    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  204    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
  205
  206    _ = editor.update(cx, |editor, window, cx| {
  207        editor.start_transaction_at(now, window, cx);
  208        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  209            s.select_ranges([2..4])
  210        });
  211
  212        editor.insert("cd", window, cx);
  213        editor.end_transaction_at(now, cx);
  214        assert_eq!(editor.text(cx), "12cd56");
  215        assert_eq!(editor.selections.ranges(cx), vec![4..4]);
  216
  217        editor.start_transaction_at(now, window, cx);
  218        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  219            s.select_ranges([4..5])
  220        });
  221        editor.insert("e", window, cx);
  222        editor.end_transaction_at(now, cx);
  223        assert_eq!(editor.text(cx), "12cde6");
  224        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  225
  226        now += group_interval + Duration::from_millis(1);
  227        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  228            s.select_ranges([2..2])
  229        });
  230
  231        // Simulate an edit in another editor
  232        buffer.update(cx, |buffer, cx| {
  233            buffer.start_transaction_at(now, cx);
  234            buffer.edit([(0..1, "a")], None, cx);
  235            buffer.edit([(1..1, "b")], None, cx);
  236            buffer.end_transaction_at(now, cx);
  237        });
  238
  239        assert_eq!(editor.text(cx), "ab2cde6");
  240        assert_eq!(editor.selections.ranges(cx), vec![3..3]);
  241
  242        // Last transaction happened past the group interval in a different editor.
  243        // Undo it individually and don't restore selections.
  244        editor.undo(&Undo, window, cx);
  245        assert_eq!(editor.text(cx), "12cde6");
  246        assert_eq!(editor.selections.ranges(cx), vec![2..2]);
  247
  248        // First two transactions happened within the group interval in this editor.
  249        // Undo them together and restore selections.
  250        editor.undo(&Undo, window, cx);
  251        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  252        assert_eq!(editor.text(cx), "123456");
  253        assert_eq!(editor.selections.ranges(cx), vec![0..0]);
  254
  255        // Redo the first two transactions together.
  256        editor.redo(&Redo, window, cx);
  257        assert_eq!(editor.text(cx), "12cde6");
  258        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  259
  260        // Redo the last transaction on its own.
  261        editor.redo(&Redo, window, cx);
  262        assert_eq!(editor.text(cx), "ab2cde6");
  263        assert_eq!(editor.selections.ranges(cx), vec![6..6]);
  264
  265        // Test empty transactions.
  266        editor.start_transaction_at(now, window, cx);
  267        editor.end_transaction_at(now, cx);
  268        editor.undo(&Undo, window, cx);
  269        assert_eq!(editor.text(cx), "12cde6");
  270    });
  271}
  272
  273#[gpui::test]
  274fn test_ime_composition(cx: &mut TestAppContext) {
  275    init_test(cx, |_| {});
  276
  277    let buffer = cx.new(|cx| {
  278        let mut buffer = language::Buffer::local("abcde", cx);
  279        // Ensure automatic grouping doesn't occur.
  280        buffer.set_group_interval(Duration::ZERO);
  281        buffer
  282    });
  283
  284    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  285    cx.add_window(|window, cx| {
  286        let mut editor = build_editor(buffer.clone(), window, cx);
  287
  288        // Start a new IME composition.
  289        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  290        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
  291        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  292        assert_eq!(editor.text(cx), "äbcde");
  293        assert_eq!(
  294            editor.marked_text_ranges(cx),
  295            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  296        );
  297
  298        // Finalize IME composition.
  299        editor.replace_text_in_range(None, "ā", window, cx);
  300        assert_eq!(editor.text(cx), "ābcde");
  301        assert_eq!(editor.marked_text_ranges(cx), None);
  302
  303        // IME composition edits are grouped and are undone/redone at once.
  304        editor.undo(&Default::default(), window, cx);
  305        assert_eq!(editor.text(cx), "abcde");
  306        assert_eq!(editor.marked_text_ranges(cx), None);
  307        editor.redo(&Default::default(), window, cx);
  308        assert_eq!(editor.text(cx), "ābcde");
  309        assert_eq!(editor.marked_text_ranges(cx), None);
  310
  311        // Start a new IME composition.
  312        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  313        assert_eq!(
  314            editor.marked_text_ranges(cx),
  315            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  316        );
  317
  318        // Undoing during an IME composition cancels it.
  319        editor.undo(&Default::default(), window, cx);
  320        assert_eq!(editor.text(cx), "ābcde");
  321        assert_eq!(editor.marked_text_ranges(cx), None);
  322
  323        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  324        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  325        assert_eq!(editor.text(cx), "ābcdè");
  326        assert_eq!(
  327            editor.marked_text_ranges(cx),
  328            Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
  329        );
  330
  331        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  332        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  333        assert_eq!(editor.text(cx), "ābcdę");
  334        assert_eq!(editor.marked_text_ranges(cx), None);
  335
  336        // Start a new IME composition with multiple cursors.
  337        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  338            s.select_ranges([
  339                OffsetUtf16(1)..OffsetUtf16(1),
  340                OffsetUtf16(3)..OffsetUtf16(3),
  341                OffsetUtf16(5)..OffsetUtf16(5),
  342            ])
  343        });
  344        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  345        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  346        assert_eq!(
  347            editor.marked_text_ranges(cx),
  348            Some(vec![
  349                OffsetUtf16(0)..OffsetUtf16(3),
  350                OffsetUtf16(4)..OffsetUtf16(7),
  351                OffsetUtf16(8)..OffsetUtf16(11)
  352            ])
  353        );
  354
  355        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  356        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  357        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  358        assert_eq!(
  359            editor.marked_text_ranges(cx),
  360            Some(vec![
  361                OffsetUtf16(1)..OffsetUtf16(2),
  362                OffsetUtf16(5)..OffsetUtf16(6),
  363                OffsetUtf16(9)..OffsetUtf16(10)
  364            ])
  365        );
  366
  367        // Finalize IME composition with multiple cursors.
  368        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  369        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  370        assert_eq!(editor.marked_text_ranges(cx), None);
  371
  372        editor
  373    });
  374}
  375
  376#[gpui::test]
  377fn test_selection_with_mouse(cx: &mut TestAppContext) {
  378    init_test(cx, |_| {});
  379
  380    let editor = cx.add_window(|window, cx| {
  381        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  382        build_editor(buffer, window, cx)
  383    });
  384
  385    _ = editor.update(cx, |editor, window, cx| {
  386        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  387    });
  388    assert_eq!(
  389        editor
  390            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  391            .unwrap(),
  392        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  393    );
  394
  395    _ = editor.update(cx, |editor, window, cx| {
  396        editor.update_selection(
  397            DisplayPoint::new(DisplayRow(3), 3),
  398            0,
  399            gpui::Point::<f32>::default(),
  400            window,
  401            cx,
  402        );
  403    });
  404
  405    assert_eq!(
  406        editor
  407            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  408            .unwrap(),
  409        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  410    );
  411
  412    _ = editor.update(cx, |editor, window, cx| {
  413        editor.update_selection(
  414            DisplayPoint::new(DisplayRow(1), 1),
  415            0,
  416            gpui::Point::<f32>::default(),
  417            window,
  418            cx,
  419        );
  420    });
  421
  422    assert_eq!(
  423        editor
  424            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  425            .unwrap(),
  426        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  427    );
  428
  429    _ = editor.update(cx, |editor, window, cx| {
  430        editor.end_selection(window, cx);
  431        editor.update_selection(
  432            DisplayPoint::new(DisplayRow(3), 3),
  433            0,
  434            gpui::Point::<f32>::default(),
  435            window,
  436            cx,
  437        );
  438    });
  439
  440    assert_eq!(
  441        editor
  442            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  443            .unwrap(),
  444        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  445    );
  446
  447    _ = editor.update(cx, |editor, window, cx| {
  448        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  449        editor.update_selection(
  450            DisplayPoint::new(DisplayRow(0), 0),
  451            0,
  452            gpui::Point::<f32>::default(),
  453            window,
  454            cx,
  455        );
  456    });
  457
  458    assert_eq!(
  459        editor
  460            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  461            .unwrap(),
  462        [
  463            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  464            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  465        ]
  466    );
  467
  468    _ = editor.update(cx, |editor, window, cx| {
  469        editor.end_selection(window, cx);
  470    });
  471
  472    assert_eq!(
  473        editor
  474            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  475            .unwrap(),
  476        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  477    );
  478}
  479
  480#[gpui::test]
  481fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  482    init_test(cx, |_| {});
  483
  484    let editor = cx.add_window(|window, cx| {
  485        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  486        build_editor(buffer, window, cx)
  487    });
  488
  489    _ = editor.update(cx, |editor, window, cx| {
  490        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  491    });
  492
  493    _ = editor.update(cx, |editor, window, cx| {
  494        editor.end_selection(window, cx);
  495    });
  496
  497    _ = editor.update(cx, |editor, window, cx| {
  498        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  499    });
  500
  501    _ = editor.update(cx, |editor, window, cx| {
  502        editor.end_selection(window, cx);
  503    });
  504
  505    assert_eq!(
  506        editor
  507            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  508            .unwrap(),
  509        [
  510            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  511            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  512        ]
  513    );
  514
  515    _ = editor.update(cx, |editor, window, cx| {
  516        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  517    });
  518
  519    _ = editor.update(cx, |editor, window, cx| {
  520        editor.end_selection(window, cx);
  521    });
  522
  523    assert_eq!(
  524        editor
  525            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  526            .unwrap(),
  527        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  528    );
  529}
  530
  531#[gpui::test]
  532fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  533    init_test(cx, |_| {});
  534
  535    let editor = cx.add_window(|window, cx| {
  536        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  537        build_editor(buffer, window, cx)
  538    });
  539
  540    _ = editor.update(cx, |editor, window, cx| {
  541        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  542        assert_eq!(
  543            editor.selections.display_ranges(cx),
  544            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  545        );
  546    });
  547
  548    _ = editor.update(cx, |editor, window, cx| {
  549        editor.update_selection(
  550            DisplayPoint::new(DisplayRow(3), 3),
  551            0,
  552            gpui::Point::<f32>::default(),
  553            window,
  554            cx,
  555        );
  556        assert_eq!(
  557            editor.selections.display_ranges(cx),
  558            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  559        );
  560    });
  561
  562    _ = editor.update(cx, |editor, window, cx| {
  563        editor.cancel(&Cancel, window, cx);
  564        editor.update_selection(
  565            DisplayPoint::new(DisplayRow(1), 1),
  566            0,
  567            gpui::Point::<f32>::default(),
  568            window,
  569            cx,
  570        );
  571        assert_eq!(
  572            editor.selections.display_ranges(cx),
  573            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  574        );
  575    });
  576}
  577
  578#[gpui::test]
  579fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  580    init_test(cx, |_| {});
  581
  582    let editor = cx.add_window(|window, cx| {
  583        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  584        build_editor(buffer, window, cx)
  585    });
  586
  587    _ = editor.update(cx, |editor, window, cx| {
  588        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  589        assert_eq!(
  590            editor.selections.display_ranges(cx),
  591            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  592        );
  593
  594        editor.move_down(&Default::default(), window, cx);
  595        assert_eq!(
  596            editor.selections.display_ranges(cx),
  597            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  598        );
  599
  600        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  601        assert_eq!(
  602            editor.selections.display_ranges(cx),
  603            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  604        );
  605
  606        editor.move_up(&Default::default(), window, cx);
  607        assert_eq!(
  608            editor.selections.display_ranges(cx),
  609            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  610        );
  611    });
  612}
  613
  614#[gpui::test]
  615fn test_clone(cx: &mut TestAppContext) {
  616    init_test(cx, |_| {});
  617
  618    let (text, selection_ranges) = marked_text_ranges(
  619        indoc! {"
  620            one
  621            two
  622            threeˇ
  623            four
  624            fiveˇ
  625        "},
  626        true,
  627    );
  628
  629    let editor = cx.add_window(|window, cx| {
  630        let buffer = MultiBuffer::build_simple(&text, cx);
  631        build_editor(buffer, window, cx)
  632    });
  633
  634    _ = editor.update(cx, |editor, window, cx| {
  635        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  636            s.select_ranges(selection_ranges.clone())
  637        });
  638        editor.fold_creases(
  639            vec![
  640                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  641                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  642            ],
  643            true,
  644            window,
  645            cx,
  646        );
  647    });
  648
  649    let cloned_editor = editor
  650        .update(cx, |editor, _, cx| {
  651            cx.open_window(Default::default(), |window, cx| {
  652                cx.new(|cx| editor.clone(window, cx))
  653            })
  654        })
  655        .unwrap()
  656        .unwrap();
  657
  658    let snapshot = editor
  659        .update(cx, |e, window, cx| e.snapshot(window, cx))
  660        .unwrap();
  661    let cloned_snapshot = cloned_editor
  662        .update(cx, |e, window, cx| e.snapshot(window, cx))
  663        .unwrap();
  664
  665    assert_eq!(
  666        cloned_editor
  667            .update(cx, |e, _, cx| e.display_text(cx))
  668            .unwrap(),
  669        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  670    );
  671    assert_eq!(
  672        cloned_snapshot
  673            .folds_in_range(0..text.len())
  674            .collect::<Vec<_>>(),
  675        snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
  676    );
  677    assert_set_eq!(
  678        cloned_editor
  679            .update(cx, |editor, _, cx| editor.selections.ranges::<Point>(cx))
  680            .unwrap(),
  681        editor
  682            .update(cx, |editor, _, cx| editor.selections.ranges(cx))
  683            .unwrap()
  684    );
  685    assert_set_eq!(
  686        cloned_editor
  687            .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
  688            .unwrap(),
  689        editor
  690            .update(cx, |e, _, cx| e.selections.display_ranges(cx))
  691            .unwrap()
  692    );
  693}
  694
  695#[gpui::test]
  696async fn test_navigation_history(cx: &mut TestAppContext) {
  697    init_test(cx, |_| {});
  698
  699    use workspace::item::Item;
  700
  701    let fs = FakeFs::new(cx.executor());
  702    let project = Project::test(fs, [], cx).await;
  703    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  704    let pane = workspace
  705        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  706        .unwrap();
  707
  708    _ = workspace.update(cx, |_v, window, cx| {
  709        cx.new(|cx| {
  710            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  711            let mut editor = build_editor(buffer.clone(), window, cx);
  712            let handle = cx.entity();
  713            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  714
  715            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  716                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  717            }
  718
  719            // Move the cursor a small distance.
  720            // Nothing is added to the navigation history.
  721            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  722                s.select_display_ranges([
  723                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  724                ])
  725            });
  726            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  727                s.select_display_ranges([
  728                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  729                ])
  730            });
  731            assert!(pop_history(&mut editor, cx).is_none());
  732
  733            // Move the cursor a large distance.
  734            // The history can jump back to the previous position.
  735            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  736                s.select_display_ranges([
  737                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  738                ])
  739            });
  740            let nav_entry = pop_history(&mut editor, cx).unwrap();
  741            editor.navigate(nav_entry.data.unwrap(), window, cx);
  742            assert_eq!(nav_entry.item.id(), cx.entity_id());
  743            assert_eq!(
  744                editor.selections.display_ranges(cx),
  745                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  746            );
  747            assert!(pop_history(&mut editor, cx).is_none());
  748
  749            // Move the cursor a small distance via the mouse.
  750            // Nothing is added to the navigation history.
  751            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  752            editor.end_selection(window, cx);
  753            assert_eq!(
  754                editor.selections.display_ranges(cx),
  755                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  756            );
  757            assert!(pop_history(&mut editor, cx).is_none());
  758
  759            // Move the cursor a large distance via the mouse.
  760            // The history can jump back to the previous position.
  761            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  762            editor.end_selection(window, cx);
  763            assert_eq!(
  764                editor.selections.display_ranges(cx),
  765                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  766            );
  767            let nav_entry = pop_history(&mut editor, cx).unwrap();
  768            editor.navigate(nav_entry.data.unwrap(), window, cx);
  769            assert_eq!(nav_entry.item.id(), cx.entity_id());
  770            assert_eq!(
  771                editor.selections.display_ranges(cx),
  772                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  773            );
  774            assert!(pop_history(&mut editor, cx).is_none());
  775
  776            // Set scroll position to check later
  777            editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), window, cx);
  778            let original_scroll_position = editor.scroll_manager.anchor();
  779
  780            // Jump to the end of the document and adjust scroll
  781            editor.move_to_end(&MoveToEnd, window, cx);
  782            editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), window, cx);
  783            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  784
  785            let nav_entry = pop_history(&mut editor, cx).unwrap();
  786            editor.navigate(nav_entry.data.unwrap(), window, cx);
  787            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  788
  789            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  790            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  791            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  792            let invalid_point = Point::new(9999, 0);
  793            editor.navigate(
  794                Box::new(NavigationData {
  795                    cursor_anchor: invalid_anchor,
  796                    cursor_position: invalid_point,
  797                    scroll_anchor: ScrollAnchor {
  798                        anchor: invalid_anchor,
  799                        offset: Default::default(),
  800                    },
  801                    scroll_top_row: invalid_point.row,
  802                }),
  803                window,
  804                cx,
  805            );
  806            assert_eq!(
  807                editor.selections.display_ranges(cx),
  808                &[editor.max_point(cx)..editor.max_point(cx)]
  809            );
  810            assert_eq!(
  811                editor.scroll_position(cx),
  812                gpui::Point::new(0., editor.max_point(cx).row().as_f32())
  813            );
  814
  815            editor
  816        })
  817    });
  818}
  819
  820#[gpui::test]
  821fn test_cancel(cx: &mut TestAppContext) {
  822    init_test(cx, |_| {});
  823
  824    let editor = cx.add_window(|window, cx| {
  825        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  826        build_editor(buffer, window, cx)
  827    });
  828
  829    _ = editor.update(cx, |editor, window, cx| {
  830        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
  831        editor.update_selection(
  832            DisplayPoint::new(DisplayRow(1), 1),
  833            0,
  834            gpui::Point::<f32>::default(),
  835            window,
  836            cx,
  837        );
  838        editor.end_selection(window, cx);
  839
  840        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
  841        editor.update_selection(
  842            DisplayPoint::new(DisplayRow(0), 3),
  843            0,
  844            gpui::Point::<f32>::default(),
  845            window,
  846            cx,
  847        );
  848        editor.end_selection(window, cx);
  849        assert_eq!(
  850            editor.selections.display_ranges(cx),
  851            [
  852                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
  853                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
  854            ]
  855        );
  856    });
  857
  858    _ = editor.update(cx, |editor, window, cx| {
  859        editor.cancel(&Cancel, window, cx);
  860        assert_eq!(
  861            editor.selections.display_ranges(cx),
  862            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
  863        );
  864    });
  865
  866    _ = editor.update(cx, |editor, window, cx| {
  867        editor.cancel(&Cancel, window, cx);
  868        assert_eq!(
  869            editor.selections.display_ranges(cx),
  870            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
  871        );
  872    });
  873}
  874
  875#[gpui::test]
  876fn test_fold_action(cx: &mut TestAppContext) {
  877    init_test(cx, |_| {});
  878
  879    let editor = cx.add_window(|window, cx| {
  880        let buffer = MultiBuffer::build_simple(
  881            &"
  882                impl Foo {
  883                    // Hello!
  884
  885                    fn a() {
  886                        1
  887                    }
  888
  889                    fn b() {
  890                        2
  891                    }
  892
  893                    fn c() {
  894                        3
  895                    }
  896                }
  897            "
  898            .unindent(),
  899            cx,
  900        );
  901        build_editor(buffer.clone(), window, cx)
  902    });
  903
  904    _ = editor.update(cx, |editor, window, cx| {
  905        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  906            s.select_display_ranges([
  907                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
  908            ]);
  909        });
  910        editor.fold(&Fold, window, cx);
  911        assert_eq!(
  912            editor.display_text(cx),
  913            "
  914                impl Foo {
  915                    // Hello!
  916
  917                    fn a() {
  918                        1
  919                    }
  920
  921                    fn b() {⋯
  922                    }
  923
  924                    fn c() {⋯
  925                    }
  926                }
  927            "
  928            .unindent(),
  929        );
  930
  931        editor.fold(&Fold, window, cx);
  932        assert_eq!(
  933            editor.display_text(cx),
  934            "
  935                impl Foo {⋯
  936                }
  937            "
  938            .unindent(),
  939        );
  940
  941        editor.unfold_lines(&UnfoldLines, window, cx);
  942        assert_eq!(
  943            editor.display_text(cx),
  944            "
  945                impl Foo {
  946                    // Hello!
  947
  948                    fn a() {
  949                        1
  950                    }
  951
  952                    fn b() {⋯
  953                    }
  954
  955                    fn c() {⋯
  956                    }
  957                }
  958            "
  959            .unindent(),
  960        );
  961
  962        editor.unfold_lines(&UnfoldLines, window, cx);
  963        assert_eq!(
  964            editor.display_text(cx),
  965            editor.buffer.read(cx).read(cx).text()
  966        );
  967    });
  968}
  969
  970#[gpui::test]
  971fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
  972    init_test(cx, |_| {});
  973
  974    let editor = cx.add_window(|window, cx| {
  975        let buffer = MultiBuffer::build_simple(
  976            &"
  977                class Foo:
  978                    # Hello!
  979
  980                    def a():
  981                        print(1)
  982
  983                    def b():
  984                        print(2)
  985
  986                    def c():
  987                        print(3)
  988            "
  989            .unindent(),
  990            cx,
  991        );
  992        build_editor(buffer.clone(), window, cx)
  993    });
  994
  995    _ = editor.update(cx, |editor, window, cx| {
  996        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  997            s.select_display_ranges([
  998                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
  999            ]);
 1000        });
 1001        editor.fold(&Fold, window, cx);
 1002        assert_eq!(
 1003            editor.display_text(cx),
 1004            "
 1005                class Foo:
 1006                    # Hello!
 1007
 1008                    def a():
 1009                        print(1)
 1010
 1011                    def b():⋯
 1012
 1013                    def c():⋯
 1014            "
 1015            .unindent(),
 1016        );
 1017
 1018        editor.fold(&Fold, window, cx);
 1019        assert_eq!(
 1020            editor.display_text(cx),
 1021            "
 1022                class Foo:⋯
 1023            "
 1024            .unindent(),
 1025        );
 1026
 1027        editor.unfold_lines(&UnfoldLines, window, cx);
 1028        assert_eq!(
 1029            editor.display_text(cx),
 1030            "
 1031                class Foo:
 1032                    # Hello!
 1033
 1034                    def a():
 1035                        print(1)
 1036
 1037                    def b():⋯
 1038
 1039                    def c():⋯
 1040            "
 1041            .unindent(),
 1042        );
 1043
 1044        editor.unfold_lines(&UnfoldLines, window, cx);
 1045        assert_eq!(
 1046            editor.display_text(cx),
 1047            editor.buffer.read(cx).read(cx).text()
 1048        );
 1049    });
 1050}
 1051
 1052#[gpui::test]
 1053fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1054    init_test(cx, |_| {});
 1055
 1056    let editor = cx.add_window(|window, cx| {
 1057        let buffer = MultiBuffer::build_simple(
 1058            &"
 1059                class Foo:
 1060                    # Hello!
 1061
 1062                    def a():
 1063                        print(1)
 1064
 1065                    def b():
 1066                        print(2)
 1067
 1068
 1069                    def c():
 1070                        print(3)
 1071
 1072
 1073            "
 1074            .unindent(),
 1075            cx,
 1076        );
 1077        build_editor(buffer.clone(), window, cx)
 1078    });
 1079
 1080    _ = editor.update(cx, |editor, window, cx| {
 1081        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1082            s.select_display_ranges([
 1083                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1084            ]);
 1085        });
 1086        editor.fold(&Fold, window, cx);
 1087        assert_eq!(
 1088            editor.display_text(cx),
 1089            "
 1090                class Foo:
 1091                    # Hello!
 1092
 1093                    def a():
 1094                        print(1)
 1095
 1096                    def b():⋯
 1097
 1098
 1099                    def c():⋯
 1100
 1101
 1102            "
 1103            .unindent(),
 1104        );
 1105
 1106        editor.fold(&Fold, window, cx);
 1107        assert_eq!(
 1108            editor.display_text(cx),
 1109            "
 1110                class Foo:⋯
 1111
 1112
 1113            "
 1114            .unindent(),
 1115        );
 1116
 1117        editor.unfold_lines(&UnfoldLines, window, cx);
 1118        assert_eq!(
 1119            editor.display_text(cx),
 1120            "
 1121                class Foo:
 1122                    # Hello!
 1123
 1124                    def a():
 1125                        print(1)
 1126
 1127                    def b():⋯
 1128
 1129
 1130                    def c():⋯
 1131
 1132
 1133            "
 1134            .unindent(),
 1135        );
 1136
 1137        editor.unfold_lines(&UnfoldLines, window, cx);
 1138        assert_eq!(
 1139            editor.display_text(cx),
 1140            editor.buffer.read(cx).read(cx).text()
 1141        );
 1142    });
 1143}
 1144
 1145#[gpui::test]
 1146fn test_fold_at_level(cx: &mut TestAppContext) {
 1147    init_test(cx, |_| {});
 1148
 1149    let editor = cx.add_window(|window, cx| {
 1150        let buffer = MultiBuffer::build_simple(
 1151            &"
 1152                class Foo:
 1153                    # Hello!
 1154
 1155                    def a():
 1156                        print(1)
 1157
 1158                    def b():
 1159                        print(2)
 1160
 1161
 1162                class Bar:
 1163                    # World!
 1164
 1165                    def a():
 1166                        print(1)
 1167
 1168                    def b():
 1169                        print(2)
 1170
 1171
 1172            "
 1173            .unindent(),
 1174            cx,
 1175        );
 1176        build_editor(buffer.clone(), window, cx)
 1177    });
 1178
 1179    _ = editor.update(cx, |editor, window, cx| {
 1180        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1181        assert_eq!(
 1182            editor.display_text(cx),
 1183            "
 1184                class Foo:
 1185                    # Hello!
 1186
 1187                    def a():⋯
 1188
 1189                    def b():⋯
 1190
 1191
 1192                class Bar:
 1193                    # World!
 1194
 1195                    def a():⋯
 1196
 1197                    def b():⋯
 1198
 1199
 1200            "
 1201            .unindent(),
 1202        );
 1203
 1204        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1205        assert_eq!(
 1206            editor.display_text(cx),
 1207            "
 1208                class Foo:⋯
 1209
 1210
 1211                class Bar:⋯
 1212
 1213
 1214            "
 1215            .unindent(),
 1216        );
 1217
 1218        editor.unfold_all(&UnfoldAll, window, cx);
 1219        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1220        assert_eq!(
 1221            editor.display_text(cx),
 1222            "
 1223                class Foo:
 1224                    # Hello!
 1225
 1226                    def a():
 1227                        print(1)
 1228
 1229                    def b():
 1230                        print(2)
 1231
 1232
 1233                class Bar:
 1234                    # World!
 1235
 1236                    def a():
 1237                        print(1)
 1238
 1239                    def b():
 1240                        print(2)
 1241
 1242
 1243            "
 1244            .unindent(),
 1245        );
 1246
 1247        assert_eq!(
 1248            editor.display_text(cx),
 1249            editor.buffer.read(cx).read(cx).text()
 1250        );
 1251    });
 1252}
 1253
 1254#[gpui::test]
 1255fn test_move_cursor(cx: &mut TestAppContext) {
 1256    init_test(cx, |_| {});
 1257
 1258    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1259    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1260
 1261    buffer.update(cx, |buffer, cx| {
 1262        buffer.edit(
 1263            vec![
 1264                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1265                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1266            ],
 1267            None,
 1268            cx,
 1269        );
 1270    });
 1271    _ = editor.update(cx, |editor, window, cx| {
 1272        assert_eq!(
 1273            editor.selections.display_ranges(cx),
 1274            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1275        );
 1276
 1277        editor.move_down(&MoveDown, window, cx);
 1278        assert_eq!(
 1279            editor.selections.display_ranges(cx),
 1280            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1281        );
 1282
 1283        editor.move_right(&MoveRight, window, cx);
 1284        assert_eq!(
 1285            editor.selections.display_ranges(cx),
 1286            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1287        );
 1288
 1289        editor.move_left(&MoveLeft, window, cx);
 1290        assert_eq!(
 1291            editor.selections.display_ranges(cx),
 1292            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1293        );
 1294
 1295        editor.move_up(&MoveUp, window, cx);
 1296        assert_eq!(
 1297            editor.selections.display_ranges(cx),
 1298            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1299        );
 1300
 1301        editor.move_to_end(&MoveToEnd, window, cx);
 1302        assert_eq!(
 1303            editor.selections.display_ranges(cx),
 1304            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1305        );
 1306
 1307        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1308        assert_eq!(
 1309            editor.selections.display_ranges(cx),
 1310            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1311        );
 1312
 1313        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1314            s.select_display_ranges([
 1315                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1316            ]);
 1317        });
 1318        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1319        assert_eq!(
 1320            editor.selections.display_ranges(cx),
 1321            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1322        );
 1323
 1324        editor.select_to_end(&SelectToEnd, window, cx);
 1325        assert_eq!(
 1326            editor.selections.display_ranges(cx),
 1327            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1328        );
 1329    });
 1330}
 1331
 1332#[gpui::test]
 1333fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1334    init_test(cx, |_| {});
 1335
 1336    let editor = cx.add_window(|window, cx| {
 1337        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1338        build_editor(buffer.clone(), window, cx)
 1339    });
 1340
 1341    assert_eq!('🟥'.len_utf8(), 4);
 1342    assert_eq!('α'.len_utf8(), 2);
 1343
 1344    _ = editor.update(cx, |editor, window, cx| {
 1345        editor.fold_creases(
 1346            vec![
 1347                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1348                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1349                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1350            ],
 1351            true,
 1352            window,
 1353            cx,
 1354        );
 1355        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1356
 1357        editor.move_right(&MoveRight, window, cx);
 1358        assert_eq!(
 1359            editor.selections.display_ranges(cx),
 1360            &[empty_range(0, "🟥".len())]
 1361        );
 1362        editor.move_right(&MoveRight, window, cx);
 1363        assert_eq!(
 1364            editor.selections.display_ranges(cx),
 1365            &[empty_range(0, "🟥🟧".len())]
 1366        );
 1367        editor.move_right(&MoveRight, window, cx);
 1368        assert_eq!(
 1369            editor.selections.display_ranges(cx),
 1370            &[empty_range(0, "🟥🟧⋯".len())]
 1371        );
 1372
 1373        editor.move_down(&MoveDown, window, cx);
 1374        assert_eq!(
 1375            editor.selections.display_ranges(cx),
 1376            &[empty_range(1, "ab⋯e".len())]
 1377        );
 1378        editor.move_left(&MoveLeft, window, cx);
 1379        assert_eq!(
 1380            editor.selections.display_ranges(cx),
 1381            &[empty_range(1, "ab⋯".len())]
 1382        );
 1383        editor.move_left(&MoveLeft, window, cx);
 1384        assert_eq!(
 1385            editor.selections.display_ranges(cx),
 1386            &[empty_range(1, "ab".len())]
 1387        );
 1388        editor.move_left(&MoveLeft, window, cx);
 1389        assert_eq!(
 1390            editor.selections.display_ranges(cx),
 1391            &[empty_range(1, "a".len())]
 1392        );
 1393
 1394        editor.move_down(&MoveDown, window, cx);
 1395        assert_eq!(
 1396            editor.selections.display_ranges(cx),
 1397            &[empty_range(2, "α".len())]
 1398        );
 1399        editor.move_right(&MoveRight, window, cx);
 1400        assert_eq!(
 1401            editor.selections.display_ranges(cx),
 1402            &[empty_range(2, "αβ".len())]
 1403        );
 1404        editor.move_right(&MoveRight, window, cx);
 1405        assert_eq!(
 1406            editor.selections.display_ranges(cx),
 1407            &[empty_range(2, "αβ⋯".len())]
 1408        );
 1409        editor.move_right(&MoveRight, window, cx);
 1410        assert_eq!(
 1411            editor.selections.display_ranges(cx),
 1412            &[empty_range(2, "αβ⋯ε".len())]
 1413        );
 1414
 1415        editor.move_up(&MoveUp, window, cx);
 1416        assert_eq!(
 1417            editor.selections.display_ranges(cx),
 1418            &[empty_range(1, "ab⋯e".len())]
 1419        );
 1420        editor.move_down(&MoveDown, window, cx);
 1421        assert_eq!(
 1422            editor.selections.display_ranges(cx),
 1423            &[empty_range(2, "αβ⋯ε".len())]
 1424        );
 1425        editor.move_up(&MoveUp, window, cx);
 1426        assert_eq!(
 1427            editor.selections.display_ranges(cx),
 1428            &[empty_range(1, "ab⋯e".len())]
 1429        );
 1430
 1431        editor.move_up(&MoveUp, window, cx);
 1432        assert_eq!(
 1433            editor.selections.display_ranges(cx),
 1434            &[empty_range(0, "🟥🟧".len())]
 1435        );
 1436        editor.move_left(&MoveLeft, window, cx);
 1437        assert_eq!(
 1438            editor.selections.display_ranges(cx),
 1439            &[empty_range(0, "🟥".len())]
 1440        );
 1441        editor.move_left(&MoveLeft, window, cx);
 1442        assert_eq!(
 1443            editor.selections.display_ranges(cx),
 1444            &[empty_range(0, "".len())]
 1445        );
 1446    });
 1447}
 1448
 1449#[gpui::test]
 1450fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1451    init_test(cx, |_| {});
 1452
 1453    let editor = cx.add_window(|window, cx| {
 1454        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1455        build_editor(buffer.clone(), window, cx)
 1456    });
 1457    _ = editor.update(cx, |editor, window, cx| {
 1458        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1459            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1460        });
 1461
 1462        // moving above start of document should move selection to start of document,
 1463        // but the next move down should still be at the original goal_x
 1464        editor.move_up(&MoveUp, window, cx);
 1465        assert_eq!(
 1466            editor.selections.display_ranges(cx),
 1467            &[empty_range(0, "".len())]
 1468        );
 1469
 1470        editor.move_down(&MoveDown, window, cx);
 1471        assert_eq!(
 1472            editor.selections.display_ranges(cx),
 1473            &[empty_range(1, "abcd".len())]
 1474        );
 1475
 1476        editor.move_down(&MoveDown, window, cx);
 1477        assert_eq!(
 1478            editor.selections.display_ranges(cx),
 1479            &[empty_range(2, "αβγ".len())]
 1480        );
 1481
 1482        editor.move_down(&MoveDown, window, cx);
 1483        assert_eq!(
 1484            editor.selections.display_ranges(cx),
 1485            &[empty_range(3, "abcd".len())]
 1486        );
 1487
 1488        editor.move_down(&MoveDown, window, cx);
 1489        assert_eq!(
 1490            editor.selections.display_ranges(cx),
 1491            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1492        );
 1493
 1494        // moving past end of document should not change goal_x
 1495        editor.move_down(&MoveDown, window, cx);
 1496        assert_eq!(
 1497            editor.selections.display_ranges(cx),
 1498            &[empty_range(5, "".len())]
 1499        );
 1500
 1501        editor.move_down(&MoveDown, window, cx);
 1502        assert_eq!(
 1503            editor.selections.display_ranges(cx),
 1504            &[empty_range(5, "".len())]
 1505        );
 1506
 1507        editor.move_up(&MoveUp, window, cx);
 1508        assert_eq!(
 1509            editor.selections.display_ranges(cx),
 1510            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1511        );
 1512
 1513        editor.move_up(&MoveUp, window, cx);
 1514        assert_eq!(
 1515            editor.selections.display_ranges(cx),
 1516            &[empty_range(3, "abcd".len())]
 1517        );
 1518
 1519        editor.move_up(&MoveUp, window, cx);
 1520        assert_eq!(
 1521            editor.selections.display_ranges(cx),
 1522            &[empty_range(2, "αβγ".len())]
 1523        );
 1524    });
 1525}
 1526
 1527#[gpui::test]
 1528fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1529    init_test(cx, |_| {});
 1530    let move_to_beg = MoveToBeginningOfLine {
 1531        stop_at_soft_wraps: true,
 1532        stop_at_indent: true,
 1533    };
 1534
 1535    let delete_to_beg = DeleteToBeginningOfLine {
 1536        stop_at_indent: false,
 1537    };
 1538
 1539    let move_to_end = MoveToEndOfLine {
 1540        stop_at_soft_wraps: true,
 1541    };
 1542
 1543    let editor = cx.add_window(|window, cx| {
 1544        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1545        build_editor(buffer, window, cx)
 1546    });
 1547    _ = editor.update(cx, |editor, window, cx| {
 1548        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1549            s.select_display_ranges([
 1550                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1551                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1552            ]);
 1553        });
 1554    });
 1555
 1556    _ = editor.update(cx, |editor, window, cx| {
 1557        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1558        assert_eq!(
 1559            editor.selections.display_ranges(cx),
 1560            &[
 1561                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1562                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1563            ]
 1564        );
 1565    });
 1566
 1567    _ = editor.update(cx, |editor, window, cx| {
 1568        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1569        assert_eq!(
 1570            editor.selections.display_ranges(cx),
 1571            &[
 1572                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1573                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1574            ]
 1575        );
 1576    });
 1577
 1578    _ = editor.update(cx, |editor, window, cx| {
 1579        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1580        assert_eq!(
 1581            editor.selections.display_ranges(cx),
 1582            &[
 1583                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1584                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1585            ]
 1586        );
 1587    });
 1588
 1589    _ = editor.update(cx, |editor, window, cx| {
 1590        editor.move_to_end_of_line(&move_to_end, window, cx);
 1591        assert_eq!(
 1592            editor.selections.display_ranges(cx),
 1593            &[
 1594                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1595                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1596            ]
 1597        );
 1598    });
 1599
 1600    // Moving to the end of line again is a no-op.
 1601    _ = editor.update(cx, |editor, window, cx| {
 1602        editor.move_to_end_of_line(&move_to_end, window, cx);
 1603        assert_eq!(
 1604            editor.selections.display_ranges(cx),
 1605            &[
 1606                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1607                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1608            ]
 1609        );
 1610    });
 1611
 1612    _ = editor.update(cx, |editor, window, cx| {
 1613        editor.move_left(&MoveLeft, window, cx);
 1614        editor.select_to_beginning_of_line(
 1615            &SelectToBeginningOfLine {
 1616                stop_at_soft_wraps: true,
 1617                stop_at_indent: true,
 1618            },
 1619            window,
 1620            cx,
 1621        );
 1622        assert_eq!(
 1623            editor.selections.display_ranges(cx),
 1624            &[
 1625                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1626                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1627            ]
 1628        );
 1629    });
 1630
 1631    _ = editor.update(cx, |editor, window, cx| {
 1632        editor.select_to_beginning_of_line(
 1633            &SelectToBeginningOfLine {
 1634                stop_at_soft_wraps: true,
 1635                stop_at_indent: true,
 1636            },
 1637            window,
 1638            cx,
 1639        );
 1640        assert_eq!(
 1641            editor.selections.display_ranges(cx),
 1642            &[
 1643                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1644                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1645            ]
 1646        );
 1647    });
 1648
 1649    _ = editor.update(cx, |editor, window, cx| {
 1650        editor.select_to_beginning_of_line(
 1651            &SelectToBeginningOfLine {
 1652                stop_at_soft_wraps: true,
 1653                stop_at_indent: true,
 1654            },
 1655            window,
 1656            cx,
 1657        );
 1658        assert_eq!(
 1659            editor.selections.display_ranges(cx),
 1660            &[
 1661                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1662                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1663            ]
 1664        );
 1665    });
 1666
 1667    _ = editor.update(cx, |editor, window, cx| {
 1668        editor.select_to_end_of_line(
 1669            &SelectToEndOfLine {
 1670                stop_at_soft_wraps: true,
 1671            },
 1672            window,
 1673            cx,
 1674        );
 1675        assert_eq!(
 1676            editor.selections.display_ranges(cx),
 1677            &[
 1678                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1679                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1680            ]
 1681        );
 1682    });
 1683
 1684    _ = editor.update(cx, |editor, window, cx| {
 1685        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1686        assert_eq!(editor.display_text(cx), "ab\n  de");
 1687        assert_eq!(
 1688            editor.selections.display_ranges(cx),
 1689            &[
 1690                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1691                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1692            ]
 1693        );
 1694    });
 1695
 1696    _ = editor.update(cx, |editor, window, cx| {
 1697        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1698        assert_eq!(editor.display_text(cx), "\n");
 1699        assert_eq!(
 1700            editor.selections.display_ranges(cx),
 1701            &[
 1702                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1703                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1704            ]
 1705        );
 1706    });
 1707}
 1708
 1709#[gpui::test]
 1710fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1711    init_test(cx, |_| {});
 1712    let move_to_beg = MoveToBeginningOfLine {
 1713        stop_at_soft_wraps: false,
 1714        stop_at_indent: false,
 1715    };
 1716
 1717    let move_to_end = MoveToEndOfLine {
 1718        stop_at_soft_wraps: false,
 1719    };
 1720
 1721    let editor = cx.add_window(|window, cx| {
 1722        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1723        build_editor(buffer, window, cx)
 1724    });
 1725
 1726    _ = editor.update(cx, |editor, window, cx| {
 1727        editor.set_wrap_width(Some(140.0.into()), cx);
 1728
 1729        // We expect the following lines after wrapping
 1730        // ```
 1731        // thequickbrownfox
 1732        // jumpedoverthelazydo
 1733        // gs
 1734        // ```
 1735        // The final `gs` was soft-wrapped onto a new line.
 1736        assert_eq!(
 1737            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1738            editor.display_text(cx),
 1739        );
 1740
 1741        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1742        // Start the cursor at the `k` on the first line
 1743        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1744            s.select_display_ranges([
 1745                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1746            ]);
 1747        });
 1748
 1749        // Moving to the beginning of the line should put us at the beginning of the line.
 1750        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1751        assert_eq!(
 1752            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1753            editor.selections.display_ranges(cx)
 1754        );
 1755
 1756        // Moving to the end of the line should put us at the end of the line.
 1757        editor.move_to_end_of_line(&move_to_end, window, cx);
 1758        assert_eq!(
 1759            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1760            editor.selections.display_ranges(cx)
 1761        );
 1762
 1763        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1764        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1765        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1766            s.select_display_ranges([
 1767                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1768            ]);
 1769        });
 1770
 1771        // Moving to the beginning of the line should put us at the start of the second line of
 1772        // display text, i.e., the `j`.
 1773        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1774        assert_eq!(
 1775            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1776            editor.selections.display_ranges(cx)
 1777        );
 1778
 1779        // Moving to the beginning of the line again should be a no-op.
 1780        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1781        assert_eq!(
 1782            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1783            editor.selections.display_ranges(cx)
 1784        );
 1785
 1786        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1787        // next display line.
 1788        editor.move_to_end_of_line(&move_to_end, window, cx);
 1789        assert_eq!(
 1790            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1791            editor.selections.display_ranges(cx)
 1792        );
 1793
 1794        // Moving to the end of the line again should be a no-op.
 1795        editor.move_to_end_of_line(&move_to_end, window, cx);
 1796        assert_eq!(
 1797            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1798            editor.selections.display_ranges(cx)
 1799        );
 1800    });
 1801}
 1802
 1803#[gpui::test]
 1804fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1805    init_test(cx, |_| {});
 1806
 1807    let move_to_beg = MoveToBeginningOfLine {
 1808        stop_at_soft_wraps: true,
 1809        stop_at_indent: true,
 1810    };
 1811
 1812    let select_to_beg = SelectToBeginningOfLine {
 1813        stop_at_soft_wraps: true,
 1814        stop_at_indent: true,
 1815    };
 1816
 1817    let delete_to_beg = DeleteToBeginningOfLine {
 1818        stop_at_indent: true,
 1819    };
 1820
 1821    let move_to_end = MoveToEndOfLine {
 1822        stop_at_soft_wraps: false,
 1823    };
 1824
 1825    let editor = cx.add_window(|window, cx| {
 1826        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1827        build_editor(buffer, window, cx)
 1828    });
 1829
 1830    _ = editor.update(cx, |editor, window, cx| {
 1831        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1832            s.select_display_ranges([
 1833                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1834                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1835            ]);
 1836        });
 1837
 1838        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 1839        // and the second cursor at the first non-whitespace character in the line.
 1840        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1841        assert_eq!(
 1842            editor.selections.display_ranges(cx),
 1843            &[
 1844                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1845                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1846            ]
 1847        );
 1848
 1849        // Moving to the beginning of the line again should be a no-op for the first cursor,
 1850        // and should move the second cursor to the beginning of the line.
 1851        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1852        assert_eq!(
 1853            editor.selections.display_ranges(cx),
 1854            &[
 1855                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1856                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1857            ]
 1858        );
 1859
 1860        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 1861        // and should move the second cursor back to the first non-whitespace character in the line.
 1862        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1863        assert_eq!(
 1864            editor.selections.display_ranges(cx),
 1865            &[
 1866                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1867                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1868            ]
 1869        );
 1870
 1871        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 1872        // and to the first non-whitespace character in the line for the second cursor.
 1873        editor.move_to_end_of_line(&move_to_end, window, cx);
 1874        editor.move_left(&MoveLeft, window, cx);
 1875        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1876        assert_eq!(
 1877            editor.selections.display_ranges(cx),
 1878            &[
 1879                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1880                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1881            ]
 1882        );
 1883
 1884        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 1885        // and should select to the beginning of the line for the second cursor.
 1886        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1887        assert_eq!(
 1888            editor.selections.display_ranges(cx),
 1889            &[
 1890                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1891                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1892            ]
 1893        );
 1894
 1895        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 1896        // and should delete to the first non-whitespace character in the line for the second cursor.
 1897        editor.move_to_end_of_line(&move_to_end, window, cx);
 1898        editor.move_left(&MoveLeft, window, cx);
 1899        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1900        assert_eq!(editor.text(cx), "c\n  f");
 1901    });
 1902}
 1903
 1904#[gpui::test]
 1905fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 1906    init_test(cx, |_| {});
 1907
 1908    let editor = cx.add_window(|window, cx| {
 1909        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 1910        build_editor(buffer, window, cx)
 1911    });
 1912    _ = editor.update(cx, |editor, window, cx| {
 1913        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1914            s.select_display_ranges([
 1915                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 1916                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 1917            ])
 1918        });
 1919        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1920        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 1921
 1922        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1923        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 1924
 1925        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1926        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1927
 1928        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1929        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1930
 1931        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1932        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 1933
 1934        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1935        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1936
 1937        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1938        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 1939
 1940        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1941        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1942
 1943        editor.move_right(&MoveRight, window, cx);
 1944        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1945        assert_selection_ranges(
 1946            "use std::«ˇs»tr::{foo, bar}\n«ˇ\n»  {baz.qux()}",
 1947            editor,
 1948            cx,
 1949        );
 1950
 1951        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1952        assert_selection_ranges(
 1953            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 1954            editor,
 1955            cx,
 1956        );
 1957
 1958        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 1959        assert_selection_ranges(
 1960            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 1961            editor,
 1962            cx,
 1963        );
 1964    });
 1965}
 1966
 1967#[gpui::test]
 1968fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 1969    init_test(cx, |_| {});
 1970
 1971    let editor = cx.add_window(|window, cx| {
 1972        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 1973        build_editor(buffer, window, cx)
 1974    });
 1975
 1976    _ = editor.update(cx, |editor, window, cx| {
 1977        editor.set_wrap_width(Some(140.0.into()), cx);
 1978        assert_eq!(
 1979            editor.display_text(cx),
 1980            "use one::{\n    two::three::\n    four::five\n};"
 1981        );
 1982
 1983        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1984            s.select_display_ranges([
 1985                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 1986            ]);
 1987        });
 1988
 1989        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1990        assert_eq!(
 1991            editor.selections.display_ranges(cx),
 1992            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 1993        );
 1994
 1995        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1996        assert_eq!(
 1997            editor.selections.display_ranges(cx),
 1998            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 1999        );
 2000
 2001        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2002        assert_eq!(
 2003            editor.selections.display_ranges(cx),
 2004            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2005        );
 2006
 2007        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2008        assert_eq!(
 2009            editor.selections.display_ranges(cx),
 2010            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2011        );
 2012
 2013        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2014        assert_eq!(
 2015            editor.selections.display_ranges(cx),
 2016            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2017        );
 2018
 2019        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2020        assert_eq!(
 2021            editor.selections.display_ranges(cx),
 2022            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2023        );
 2024    });
 2025}
 2026
 2027#[gpui::test]
 2028async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2029    init_test(cx, |_| {});
 2030    let mut cx = EditorTestContext::new(cx).await;
 2031
 2032    let line_height = cx.editor(|editor, window, _| {
 2033        editor
 2034            .style()
 2035            .unwrap()
 2036            .text
 2037            .line_height_in_pixels(window.rem_size())
 2038    });
 2039    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2040
 2041    cx.set_state(
 2042        &r#"ˇone
 2043        two
 2044
 2045        three
 2046        fourˇ
 2047        five
 2048
 2049        six"#
 2050            .unindent(),
 2051    );
 2052
 2053    cx.update_editor(|editor, window, cx| {
 2054        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2055    });
 2056    cx.assert_editor_state(
 2057        &r#"one
 2058        two
 2059        ˇ
 2060        three
 2061        four
 2062        five
 2063        ˇ
 2064        six"#
 2065            .unindent(),
 2066    );
 2067
 2068    cx.update_editor(|editor, window, cx| {
 2069        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2070    });
 2071    cx.assert_editor_state(
 2072        &r#"one
 2073        two
 2074
 2075        three
 2076        four
 2077        five
 2078        ˇ
 2079        sixˇ"#
 2080            .unindent(),
 2081    );
 2082
 2083    cx.update_editor(|editor, window, cx| {
 2084        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2085    });
 2086    cx.assert_editor_state(
 2087        &r#"one
 2088        two
 2089
 2090        three
 2091        four
 2092        five
 2093
 2094        sixˇ"#
 2095            .unindent(),
 2096    );
 2097
 2098    cx.update_editor(|editor, window, cx| {
 2099        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2100    });
 2101    cx.assert_editor_state(
 2102        &r#"one
 2103        two
 2104
 2105        three
 2106        four
 2107        five
 2108        ˇ
 2109        six"#
 2110            .unindent(),
 2111    );
 2112
 2113    cx.update_editor(|editor, window, cx| {
 2114        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2115    });
 2116    cx.assert_editor_state(
 2117        &r#"one
 2118        two
 2119        ˇ
 2120        three
 2121        four
 2122        five
 2123
 2124        six"#
 2125            .unindent(),
 2126    );
 2127
 2128    cx.update_editor(|editor, window, cx| {
 2129        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2130    });
 2131    cx.assert_editor_state(
 2132        &r#"ˇone
 2133        two
 2134
 2135        three
 2136        four
 2137        five
 2138
 2139        six"#
 2140            .unindent(),
 2141    );
 2142}
 2143
 2144#[gpui::test]
 2145async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2146    init_test(cx, |_| {});
 2147    let mut cx = EditorTestContext::new(cx).await;
 2148    let line_height = cx.editor(|editor, window, _| {
 2149        editor
 2150            .style()
 2151            .unwrap()
 2152            .text
 2153            .line_height_in_pixels(window.rem_size())
 2154    });
 2155    let window = cx.window;
 2156    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2157
 2158    cx.set_state(
 2159        r#"ˇone
 2160        two
 2161        three
 2162        four
 2163        five
 2164        six
 2165        seven
 2166        eight
 2167        nine
 2168        ten
 2169        "#,
 2170    );
 2171
 2172    cx.update_editor(|editor, window, cx| {
 2173        assert_eq!(
 2174            editor.snapshot(window, cx).scroll_position(),
 2175            gpui::Point::new(0., 0.)
 2176        );
 2177        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2178        assert_eq!(
 2179            editor.snapshot(window, cx).scroll_position(),
 2180            gpui::Point::new(0., 3.)
 2181        );
 2182        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2183        assert_eq!(
 2184            editor.snapshot(window, cx).scroll_position(),
 2185            gpui::Point::new(0., 6.)
 2186        );
 2187        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2188        assert_eq!(
 2189            editor.snapshot(window, cx).scroll_position(),
 2190            gpui::Point::new(0., 3.)
 2191        );
 2192
 2193        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2194        assert_eq!(
 2195            editor.snapshot(window, cx).scroll_position(),
 2196            gpui::Point::new(0., 1.)
 2197        );
 2198        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2199        assert_eq!(
 2200            editor.snapshot(window, cx).scroll_position(),
 2201            gpui::Point::new(0., 3.)
 2202        );
 2203    });
 2204}
 2205
 2206#[gpui::test]
 2207async fn test_autoscroll(cx: &mut TestAppContext) {
 2208    init_test(cx, |_| {});
 2209    let mut cx = EditorTestContext::new(cx).await;
 2210
 2211    let line_height = cx.update_editor(|editor, window, cx| {
 2212        editor.set_vertical_scroll_margin(2, cx);
 2213        editor
 2214            .style()
 2215            .unwrap()
 2216            .text
 2217            .line_height_in_pixels(window.rem_size())
 2218    });
 2219    let window = cx.window;
 2220    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2221
 2222    cx.set_state(
 2223        r#"ˇone
 2224            two
 2225            three
 2226            four
 2227            five
 2228            six
 2229            seven
 2230            eight
 2231            nine
 2232            ten
 2233        "#,
 2234    );
 2235    cx.update_editor(|editor, window, cx| {
 2236        assert_eq!(
 2237            editor.snapshot(window, cx).scroll_position(),
 2238            gpui::Point::new(0., 0.0)
 2239        );
 2240    });
 2241
 2242    // Add a cursor below the visible area. Since both cursors cannot fit
 2243    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2244    // allows the vertical scroll margin below that cursor.
 2245    cx.update_editor(|editor, window, cx| {
 2246        editor.change_selections(Default::default(), window, cx, |selections| {
 2247            selections.select_ranges([
 2248                Point::new(0, 0)..Point::new(0, 0),
 2249                Point::new(6, 0)..Point::new(6, 0),
 2250            ]);
 2251        })
 2252    });
 2253    cx.update_editor(|editor, window, cx| {
 2254        assert_eq!(
 2255            editor.snapshot(window, cx).scroll_position(),
 2256            gpui::Point::new(0., 3.0)
 2257        );
 2258    });
 2259
 2260    // Move down. The editor cursor scrolls down to track the newest cursor.
 2261    cx.update_editor(|editor, window, cx| {
 2262        editor.move_down(&Default::default(), window, cx);
 2263    });
 2264    cx.update_editor(|editor, window, cx| {
 2265        assert_eq!(
 2266            editor.snapshot(window, cx).scroll_position(),
 2267            gpui::Point::new(0., 4.0)
 2268        );
 2269    });
 2270
 2271    // Add a cursor above the visible area. Since both cursors fit on screen,
 2272    // the editor scrolls to show both.
 2273    cx.update_editor(|editor, window, cx| {
 2274        editor.change_selections(Default::default(), window, cx, |selections| {
 2275            selections.select_ranges([
 2276                Point::new(1, 0)..Point::new(1, 0),
 2277                Point::new(6, 0)..Point::new(6, 0),
 2278            ]);
 2279        })
 2280    });
 2281    cx.update_editor(|editor, window, cx| {
 2282        assert_eq!(
 2283            editor.snapshot(window, cx).scroll_position(),
 2284            gpui::Point::new(0., 1.0)
 2285        );
 2286    });
 2287}
 2288
 2289#[gpui::test]
 2290async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2291    init_test(cx, |_| {});
 2292    let mut cx = EditorTestContext::new(cx).await;
 2293
 2294    let line_height = cx.editor(|editor, window, _cx| {
 2295        editor
 2296            .style()
 2297            .unwrap()
 2298            .text
 2299            .line_height_in_pixels(window.rem_size())
 2300    });
 2301    let window = cx.window;
 2302    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2303    cx.set_state(
 2304        &r#"
 2305        ˇone
 2306        two
 2307        threeˇ
 2308        four
 2309        five
 2310        six
 2311        seven
 2312        eight
 2313        nine
 2314        ten
 2315        "#
 2316        .unindent(),
 2317    );
 2318
 2319    cx.update_editor(|editor, window, cx| {
 2320        editor.move_page_down(&MovePageDown::default(), window, cx)
 2321    });
 2322    cx.assert_editor_state(
 2323        &r#"
 2324        one
 2325        two
 2326        three
 2327        ˇfour
 2328        five
 2329        sixˇ
 2330        seven
 2331        eight
 2332        nine
 2333        ten
 2334        "#
 2335        .unindent(),
 2336    );
 2337
 2338    cx.update_editor(|editor, window, cx| {
 2339        editor.move_page_down(&MovePageDown::default(), window, cx)
 2340    });
 2341    cx.assert_editor_state(
 2342        &r#"
 2343        one
 2344        two
 2345        three
 2346        four
 2347        five
 2348        six
 2349        ˇseven
 2350        eight
 2351        nineˇ
 2352        ten
 2353        "#
 2354        .unindent(),
 2355    );
 2356
 2357    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2358    cx.assert_editor_state(
 2359        &r#"
 2360        one
 2361        two
 2362        three
 2363        ˇfour
 2364        five
 2365        sixˇ
 2366        seven
 2367        eight
 2368        nine
 2369        ten
 2370        "#
 2371        .unindent(),
 2372    );
 2373
 2374    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2375    cx.assert_editor_state(
 2376        &r#"
 2377        ˇone
 2378        two
 2379        threeˇ
 2380        four
 2381        five
 2382        six
 2383        seven
 2384        eight
 2385        nine
 2386        ten
 2387        "#
 2388        .unindent(),
 2389    );
 2390
 2391    // Test select collapsing
 2392    cx.update_editor(|editor, window, cx| {
 2393        editor.move_page_down(&MovePageDown::default(), window, cx);
 2394        editor.move_page_down(&MovePageDown::default(), window, cx);
 2395        editor.move_page_down(&MovePageDown::default(), window, cx);
 2396    });
 2397    cx.assert_editor_state(
 2398        &r#"
 2399        one
 2400        two
 2401        three
 2402        four
 2403        five
 2404        six
 2405        seven
 2406        eight
 2407        nine
 2408        ˇten
 2409        ˇ"#
 2410        .unindent(),
 2411    );
 2412}
 2413
 2414#[gpui::test]
 2415async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2416    init_test(cx, |_| {});
 2417    let mut cx = EditorTestContext::new(cx).await;
 2418    cx.set_state("one «two threeˇ» four");
 2419    cx.update_editor(|editor, window, cx| {
 2420        editor.delete_to_beginning_of_line(
 2421            &DeleteToBeginningOfLine {
 2422                stop_at_indent: false,
 2423            },
 2424            window,
 2425            cx,
 2426        );
 2427        assert_eq!(editor.text(cx), " four");
 2428    });
 2429}
 2430
 2431#[gpui::test]
 2432fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2433    init_test(cx, |_| {});
 2434
 2435    let editor = cx.add_window(|window, cx| {
 2436        let buffer = MultiBuffer::build_simple("one two three four", cx);
 2437        build_editor(buffer.clone(), window, cx)
 2438    });
 2439
 2440    _ = editor.update(cx, |editor, window, cx| {
 2441        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2442            s.select_display_ranges([
 2443                // an empty selection - the preceding word fragment is deleted
 2444                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2445                // characters selected - they are deleted
 2446                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
 2447            ])
 2448        });
 2449        editor.delete_to_previous_word_start(
 2450            &DeleteToPreviousWordStart {
 2451                ignore_newlines: false,
 2452            },
 2453            window,
 2454            cx,
 2455        );
 2456        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e two te four");
 2457    });
 2458
 2459    _ = editor.update(cx, |editor, window, cx| {
 2460        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2461            s.select_display_ranges([
 2462                // an empty selection - the following word fragment is deleted
 2463                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 2464                // characters selected - they are deleted
 2465                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
 2466            ])
 2467        });
 2468        editor.delete_to_next_word_end(
 2469            &DeleteToNextWordEnd {
 2470                ignore_newlines: false,
 2471            },
 2472            window,
 2473            cx,
 2474        );
 2475        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e t te our");
 2476    });
 2477}
 2478
 2479#[gpui::test]
 2480fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2481    init_test(cx, |_| {});
 2482
 2483    let editor = cx.add_window(|window, cx| {
 2484        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 2485        build_editor(buffer.clone(), window, cx)
 2486    });
 2487    let del_to_prev_word_start = DeleteToPreviousWordStart {
 2488        ignore_newlines: false,
 2489    };
 2490    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 2491        ignore_newlines: true,
 2492    };
 2493
 2494    _ = editor.update(cx, |editor, window, cx| {
 2495        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2496            s.select_display_ranges([
 2497                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 2498            ])
 2499        });
 2500        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2501        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 2502        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2503        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 2504        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2505        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 2506        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2507        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 2508        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2509        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 2510        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2511        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2512    });
 2513}
 2514
 2515#[gpui::test]
 2516fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 2517    init_test(cx, |_| {});
 2518
 2519    let editor = cx.add_window(|window, cx| {
 2520        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 2521        build_editor(buffer.clone(), window, cx)
 2522    });
 2523    let del_to_next_word_end = DeleteToNextWordEnd {
 2524        ignore_newlines: false,
 2525    };
 2526    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 2527        ignore_newlines: true,
 2528    };
 2529
 2530    _ = editor.update(cx, |editor, window, cx| {
 2531        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2532            s.select_display_ranges([
 2533                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 2534            ])
 2535        });
 2536        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2537        assert_eq!(
 2538            editor.buffer.read(cx).read(cx).text(),
 2539            "one\n   two\nthree\n   four"
 2540        );
 2541        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2542        assert_eq!(
 2543            editor.buffer.read(cx).read(cx).text(),
 2544            "\n   two\nthree\n   four"
 2545        );
 2546        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2547        assert_eq!(
 2548            editor.buffer.read(cx).read(cx).text(),
 2549            "two\nthree\n   four"
 2550        );
 2551        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2552        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 2553        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2554        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 2555        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2556        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2557    });
 2558}
 2559
 2560#[gpui::test]
 2561fn test_newline(cx: &mut TestAppContext) {
 2562    init_test(cx, |_| {});
 2563
 2564    let editor = cx.add_window(|window, cx| {
 2565        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 2566        build_editor(buffer.clone(), window, cx)
 2567    });
 2568
 2569    _ = editor.update(cx, |editor, window, cx| {
 2570        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2571            s.select_display_ranges([
 2572                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2573                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2574                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 2575            ])
 2576        });
 2577
 2578        editor.newline(&Newline, window, cx);
 2579        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 2580    });
 2581}
 2582
 2583#[gpui::test]
 2584fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 2585    init_test(cx, |_| {});
 2586
 2587    let editor = cx.add_window(|window, cx| {
 2588        let buffer = MultiBuffer::build_simple(
 2589            "
 2590                a
 2591                b(
 2592                    X
 2593                )
 2594                c(
 2595                    X
 2596                )
 2597            "
 2598            .unindent()
 2599            .as_str(),
 2600            cx,
 2601        );
 2602        let mut editor = build_editor(buffer.clone(), window, cx);
 2603        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2604            s.select_ranges([
 2605                Point::new(2, 4)..Point::new(2, 5),
 2606                Point::new(5, 4)..Point::new(5, 5),
 2607            ])
 2608        });
 2609        editor
 2610    });
 2611
 2612    _ = editor.update(cx, |editor, window, cx| {
 2613        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 2614        editor.buffer.update(cx, |buffer, cx| {
 2615            buffer.edit(
 2616                [
 2617                    (Point::new(1, 2)..Point::new(3, 0), ""),
 2618                    (Point::new(4, 2)..Point::new(6, 0), ""),
 2619                ],
 2620                None,
 2621                cx,
 2622            );
 2623            assert_eq!(
 2624                buffer.read(cx).text(),
 2625                "
 2626                    a
 2627                    b()
 2628                    c()
 2629                "
 2630                .unindent()
 2631            );
 2632        });
 2633        assert_eq!(
 2634            editor.selections.ranges(cx),
 2635            &[
 2636                Point::new(1, 2)..Point::new(1, 2),
 2637                Point::new(2, 2)..Point::new(2, 2),
 2638            ],
 2639        );
 2640
 2641        editor.newline(&Newline, window, cx);
 2642        assert_eq!(
 2643            editor.text(cx),
 2644            "
 2645                a
 2646                b(
 2647                )
 2648                c(
 2649                )
 2650            "
 2651            .unindent()
 2652        );
 2653
 2654        // The selections are moved after the inserted newlines
 2655        assert_eq!(
 2656            editor.selections.ranges(cx),
 2657            &[
 2658                Point::new(2, 0)..Point::new(2, 0),
 2659                Point::new(4, 0)..Point::new(4, 0),
 2660            ],
 2661        );
 2662    });
 2663}
 2664
 2665#[gpui::test]
 2666async fn test_newline_above(cx: &mut TestAppContext) {
 2667    init_test(cx, |settings| {
 2668        settings.defaults.tab_size = NonZeroU32::new(4)
 2669    });
 2670
 2671    let language = Arc::new(
 2672        Language::new(
 2673            LanguageConfig::default(),
 2674            Some(tree_sitter_rust::LANGUAGE.into()),
 2675        )
 2676        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2677        .unwrap(),
 2678    );
 2679
 2680    let mut cx = EditorTestContext::new(cx).await;
 2681    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2682    cx.set_state(indoc! {"
 2683        const a: ˇA = (
 2684 2685                «const_functionˇ»(ˇ),
 2686                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2687 2688        ˇ);ˇ
 2689    "});
 2690
 2691    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 2692    cx.assert_editor_state(indoc! {"
 2693        ˇ
 2694        const a: A = (
 2695            ˇ
 2696            (
 2697                ˇ
 2698                ˇ
 2699                const_function(),
 2700                ˇ
 2701                ˇ
 2702                ˇ
 2703                ˇ
 2704                something_else,
 2705                ˇ
 2706            )
 2707            ˇ
 2708            ˇ
 2709        );
 2710    "});
 2711}
 2712
 2713#[gpui::test]
 2714async fn test_newline_below(cx: &mut TestAppContext) {
 2715    init_test(cx, |settings| {
 2716        settings.defaults.tab_size = NonZeroU32::new(4)
 2717    });
 2718
 2719    let language = Arc::new(
 2720        Language::new(
 2721            LanguageConfig::default(),
 2722            Some(tree_sitter_rust::LANGUAGE.into()),
 2723        )
 2724        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2725        .unwrap(),
 2726    );
 2727
 2728    let mut cx = EditorTestContext::new(cx).await;
 2729    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2730    cx.set_state(indoc! {"
 2731        const a: ˇA = (
 2732 2733                «const_functionˇ»(ˇ),
 2734                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2735 2736        ˇ);ˇ
 2737    "});
 2738
 2739    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 2740    cx.assert_editor_state(indoc! {"
 2741        const a: A = (
 2742            ˇ
 2743            (
 2744                ˇ
 2745                const_function(),
 2746                ˇ
 2747                ˇ
 2748                something_else,
 2749                ˇ
 2750                ˇ
 2751                ˇ
 2752                ˇ
 2753            )
 2754            ˇ
 2755        );
 2756        ˇ
 2757        ˇ
 2758    "});
 2759}
 2760
 2761#[gpui::test]
 2762async fn test_newline_comments(cx: &mut TestAppContext) {
 2763    init_test(cx, |settings| {
 2764        settings.defaults.tab_size = NonZeroU32::new(4)
 2765    });
 2766
 2767    let language = Arc::new(Language::new(
 2768        LanguageConfig {
 2769            line_comments: vec!["// ".into()],
 2770            ..LanguageConfig::default()
 2771        },
 2772        None,
 2773    ));
 2774    {
 2775        let mut cx = EditorTestContext::new(cx).await;
 2776        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2777        cx.set_state(indoc! {"
 2778        // Fooˇ
 2779    "});
 2780
 2781        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2782        cx.assert_editor_state(indoc! {"
 2783        // Foo
 2784        // ˇ
 2785    "});
 2786        // Ensure that we add comment prefix when existing line contains space
 2787        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2788        cx.assert_editor_state(
 2789            indoc! {"
 2790        // Foo
 2791        //s
 2792        // ˇ
 2793    "}
 2794            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2795            .as_str(),
 2796        );
 2797        // Ensure that we add comment prefix when existing line does not contain space
 2798        cx.set_state(indoc! {"
 2799        // Foo
 2800        //ˇ
 2801    "});
 2802        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2803        cx.assert_editor_state(indoc! {"
 2804        // Foo
 2805        //
 2806        // ˇ
 2807    "});
 2808        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 2809        cx.set_state(indoc! {"
 2810        ˇ// Foo
 2811    "});
 2812        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2813        cx.assert_editor_state(indoc! {"
 2814
 2815        ˇ// Foo
 2816    "});
 2817    }
 2818    // Ensure that comment continuations can be disabled.
 2819    update_test_language_settings(cx, |settings| {
 2820        settings.defaults.extend_comment_on_newline = Some(false);
 2821    });
 2822    let mut cx = EditorTestContext::new(cx).await;
 2823    cx.set_state(indoc! {"
 2824        // Fooˇ
 2825    "});
 2826    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2827    cx.assert_editor_state(indoc! {"
 2828        // Foo
 2829        ˇ
 2830    "});
 2831}
 2832
 2833#[gpui::test]
 2834async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 2835    init_test(cx, |settings| {
 2836        settings.defaults.tab_size = NonZeroU32::new(4)
 2837    });
 2838
 2839    let language = Arc::new(Language::new(
 2840        LanguageConfig {
 2841            line_comments: vec!["// ".into(), "/// ".into()],
 2842            ..LanguageConfig::default()
 2843        },
 2844        None,
 2845    ));
 2846    {
 2847        let mut cx = EditorTestContext::new(cx).await;
 2848        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2849        cx.set_state(indoc! {"
 2850        //ˇ
 2851    "});
 2852        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2853        cx.assert_editor_state(indoc! {"
 2854        //
 2855        // ˇ
 2856    "});
 2857
 2858        cx.set_state(indoc! {"
 2859        ///ˇ
 2860    "});
 2861        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2862        cx.assert_editor_state(indoc! {"
 2863        ///
 2864        /// ˇ
 2865    "});
 2866    }
 2867}
 2868
 2869#[gpui::test]
 2870async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 2871    init_test(cx, |settings| {
 2872        settings.defaults.tab_size = NonZeroU32::new(4)
 2873    });
 2874
 2875    let language = Arc::new(
 2876        Language::new(
 2877            LanguageConfig {
 2878                documentation: Some(language::DocumentationConfig {
 2879                    start: "/**".into(),
 2880                    end: "*/".into(),
 2881                    prefix: "* ".into(),
 2882                    tab_size: NonZeroU32::new(1).unwrap(),
 2883                }),
 2884
 2885                ..LanguageConfig::default()
 2886            },
 2887            Some(tree_sitter_rust::LANGUAGE.into()),
 2888        )
 2889        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 2890        .unwrap(),
 2891    );
 2892
 2893    {
 2894        let mut cx = EditorTestContext::new(cx).await;
 2895        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2896        cx.set_state(indoc! {"
 2897        /**ˇ
 2898    "});
 2899
 2900        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2901        cx.assert_editor_state(indoc! {"
 2902        /**
 2903         * ˇ
 2904    "});
 2905        // Ensure that if cursor is before the comment start,
 2906        // we do not actually insert a comment prefix.
 2907        cx.set_state(indoc! {"
 2908        ˇ/**
 2909    "});
 2910        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2911        cx.assert_editor_state(indoc! {"
 2912
 2913        ˇ/**
 2914    "});
 2915        // Ensure that if cursor is between it doesn't add comment prefix.
 2916        cx.set_state(indoc! {"
 2917        /*ˇ*
 2918    "});
 2919        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2920        cx.assert_editor_state(indoc! {"
 2921        /*
 2922        ˇ*
 2923    "});
 2924        // Ensure that if suffix exists on same line after cursor it adds new line.
 2925        cx.set_state(indoc! {"
 2926        /**ˇ*/
 2927    "});
 2928        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2929        cx.assert_editor_state(indoc! {"
 2930        /**
 2931         * ˇ
 2932         */
 2933    "});
 2934        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2935        cx.set_state(indoc! {"
 2936        /**ˇ */
 2937    "});
 2938        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2939        cx.assert_editor_state(indoc! {"
 2940        /**
 2941         * ˇ
 2942         */
 2943    "});
 2944        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2945        cx.set_state(indoc! {"
 2946        /** ˇ*/
 2947    "});
 2948        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2949        cx.assert_editor_state(
 2950            indoc! {"
 2951        /**s
 2952         * ˇ
 2953         */
 2954    "}
 2955            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2956            .as_str(),
 2957        );
 2958        // Ensure that delimiter space is preserved when newline on already
 2959        // spaced delimiter.
 2960        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2961        cx.assert_editor_state(
 2962            indoc! {"
 2963        /**s
 2964         *s
 2965         * ˇ
 2966         */
 2967    "}
 2968            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2969            .as_str(),
 2970        );
 2971        // Ensure that delimiter space is preserved when space is not
 2972        // on existing delimiter.
 2973        cx.set_state(indoc! {"
 2974        /**
 2975 2976         */
 2977    "});
 2978        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2979        cx.assert_editor_state(indoc! {"
 2980        /**
 2981         *
 2982         * ˇ
 2983         */
 2984    "});
 2985        // Ensure that if suffix exists on same line after cursor it
 2986        // doesn't add extra new line if prefix is not on same line.
 2987        cx.set_state(indoc! {"
 2988        /**
 2989        ˇ*/
 2990    "});
 2991        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2992        cx.assert_editor_state(indoc! {"
 2993        /**
 2994
 2995        ˇ*/
 2996    "});
 2997        // Ensure that it detects suffix after existing prefix.
 2998        cx.set_state(indoc! {"
 2999        /**ˇ/
 3000    "});
 3001        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3002        cx.assert_editor_state(indoc! {"
 3003        /**
 3004        ˇ/
 3005    "});
 3006        // Ensure that if suffix exists on same line before
 3007        // cursor it does not add comment prefix.
 3008        cx.set_state(indoc! {"
 3009        /** */ˇ
 3010    "});
 3011        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3012        cx.assert_editor_state(indoc! {"
 3013        /** */
 3014        ˇ
 3015    "});
 3016        // Ensure that if suffix exists on same line before
 3017        // cursor it does not add comment prefix.
 3018        cx.set_state(indoc! {"
 3019        /**
 3020         *
 3021         */ˇ
 3022    "});
 3023        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3024        cx.assert_editor_state(indoc! {"
 3025        /**
 3026         *
 3027         */
 3028         ˇ
 3029    "});
 3030
 3031        // Ensure that inline comment followed by code
 3032        // doesn't add comment prefix on newline
 3033        cx.set_state(indoc! {"
 3034        /** */ textˇ
 3035    "});
 3036        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3037        cx.assert_editor_state(indoc! {"
 3038        /** */ text
 3039        ˇ
 3040    "});
 3041
 3042        // Ensure that text after comment end tag
 3043        // doesn't add comment prefix on newline
 3044        cx.set_state(indoc! {"
 3045        /**
 3046         *
 3047         */ˇtext
 3048    "});
 3049        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3050        cx.assert_editor_state(indoc! {"
 3051        /**
 3052         *
 3053         */
 3054         ˇtext
 3055    "});
 3056
 3057        // Ensure if not comment block it doesn't
 3058        // add comment prefix on newline
 3059        cx.set_state(indoc! {"
 3060        * textˇ
 3061    "});
 3062        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3063        cx.assert_editor_state(indoc! {"
 3064        * text
 3065        ˇ
 3066    "});
 3067    }
 3068    // Ensure that comment continuations can be disabled.
 3069    update_test_language_settings(cx, |settings| {
 3070        settings.defaults.extend_comment_on_newline = Some(false);
 3071    });
 3072    let mut cx = EditorTestContext::new(cx).await;
 3073    cx.set_state(indoc! {"
 3074        /**ˇ
 3075    "});
 3076    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3077    cx.assert_editor_state(indoc! {"
 3078        /**
 3079        ˇ
 3080    "});
 3081}
 3082
 3083#[gpui::test]
 3084fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3085    init_test(cx, |_| {});
 3086
 3087    let editor = cx.add_window(|window, cx| {
 3088        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3089        let mut editor = build_editor(buffer.clone(), window, cx);
 3090        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3091            s.select_ranges([3..4, 11..12, 19..20])
 3092        });
 3093        editor
 3094    });
 3095
 3096    _ = editor.update(cx, |editor, window, cx| {
 3097        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3098        editor.buffer.update(cx, |buffer, cx| {
 3099            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 3100            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3101        });
 3102        assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
 3103
 3104        editor.insert("Z", window, cx);
 3105        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3106
 3107        // The selections are moved after the inserted characters
 3108        assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
 3109    });
 3110}
 3111
 3112#[gpui::test]
 3113async fn test_tab(cx: &mut TestAppContext) {
 3114    init_test(cx, |settings| {
 3115        settings.defaults.tab_size = NonZeroU32::new(3)
 3116    });
 3117
 3118    let mut cx = EditorTestContext::new(cx).await;
 3119    cx.set_state(indoc! {"
 3120        ˇabˇc
 3121        ˇ🏀ˇ🏀ˇefg
 3122 3123    "});
 3124    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3125    cx.assert_editor_state(indoc! {"
 3126           ˇab ˇc
 3127           ˇ🏀  ˇ🏀  ˇefg
 3128        d  ˇ
 3129    "});
 3130
 3131    cx.set_state(indoc! {"
 3132        a
 3133        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3134    "});
 3135    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3136    cx.assert_editor_state(indoc! {"
 3137        a
 3138           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3139    "});
 3140}
 3141
 3142#[gpui::test]
 3143async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3144    init_test(cx, |_| {});
 3145
 3146    let mut cx = EditorTestContext::new(cx).await;
 3147    let language = Arc::new(
 3148        Language::new(
 3149            LanguageConfig::default(),
 3150            Some(tree_sitter_rust::LANGUAGE.into()),
 3151        )
 3152        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3153        .unwrap(),
 3154    );
 3155    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3156
 3157    // test when all cursors are not at suggested indent
 3158    // then simply move to their suggested indent location
 3159    cx.set_state(indoc! {"
 3160        const a: B = (
 3161            c(
 3162        ˇ
 3163        ˇ    )
 3164        );
 3165    "});
 3166    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3167    cx.assert_editor_state(indoc! {"
 3168        const a: B = (
 3169            c(
 3170                ˇ
 3171            ˇ)
 3172        );
 3173    "});
 3174
 3175    // test cursor already at suggested indent not moving when
 3176    // other cursors are yet to reach their suggested indents
 3177    cx.set_state(indoc! {"
 3178        ˇ
 3179        const a: B = (
 3180            c(
 3181                d(
 3182        ˇ
 3183                )
 3184        ˇ
 3185        ˇ    )
 3186        );
 3187    "});
 3188    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3189    cx.assert_editor_state(indoc! {"
 3190        ˇ
 3191        const a: B = (
 3192            c(
 3193                d(
 3194                    ˇ
 3195                )
 3196                ˇ
 3197            ˇ)
 3198        );
 3199    "});
 3200    // test when all cursors are at suggested indent then tab is inserted
 3201    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3202    cx.assert_editor_state(indoc! {"
 3203            ˇ
 3204        const a: B = (
 3205            c(
 3206                d(
 3207                        ˇ
 3208                )
 3209                    ˇ
 3210                ˇ)
 3211        );
 3212    "});
 3213
 3214    // test when current indent is less than suggested indent,
 3215    // we adjust line to match suggested indent and move cursor to it
 3216    //
 3217    // when no other cursor is at word boundary, all of them should move
 3218    cx.set_state(indoc! {"
 3219        const a: B = (
 3220            c(
 3221                d(
 3222        ˇ
 3223        ˇ   )
 3224        ˇ   )
 3225        );
 3226    "});
 3227    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3228    cx.assert_editor_state(indoc! {"
 3229        const a: B = (
 3230            c(
 3231                d(
 3232                    ˇ
 3233                ˇ)
 3234            ˇ)
 3235        );
 3236    "});
 3237
 3238    // test when current indent is less than suggested indent,
 3239    // we adjust line to match suggested indent and move cursor to it
 3240    //
 3241    // when some other cursor is at word boundary, it should not move
 3242    cx.set_state(indoc! {"
 3243        const a: B = (
 3244            c(
 3245                d(
 3246        ˇ
 3247        ˇ   )
 3248           ˇ)
 3249        );
 3250    "});
 3251    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3252    cx.assert_editor_state(indoc! {"
 3253        const a: B = (
 3254            c(
 3255                d(
 3256                    ˇ
 3257                ˇ)
 3258            ˇ)
 3259        );
 3260    "});
 3261
 3262    // test when current indent is more than suggested indent,
 3263    // we just move cursor to current indent instead of suggested indent
 3264    //
 3265    // when no other cursor is at word boundary, all of them should move
 3266    cx.set_state(indoc! {"
 3267        const a: B = (
 3268            c(
 3269                d(
 3270        ˇ
 3271        ˇ                )
 3272        ˇ   )
 3273        );
 3274    "});
 3275    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3276    cx.assert_editor_state(indoc! {"
 3277        const a: B = (
 3278            c(
 3279                d(
 3280                    ˇ
 3281                        ˇ)
 3282            ˇ)
 3283        );
 3284    "});
 3285    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3286    cx.assert_editor_state(indoc! {"
 3287        const a: B = (
 3288            c(
 3289                d(
 3290                        ˇ
 3291                            ˇ)
 3292                ˇ)
 3293        );
 3294    "});
 3295
 3296    // test when current indent is more than suggested indent,
 3297    // we just move cursor to current indent instead of suggested indent
 3298    //
 3299    // when some other cursor is at word boundary, it doesn't move
 3300    cx.set_state(indoc! {"
 3301        const a: B = (
 3302            c(
 3303                d(
 3304        ˇ
 3305        ˇ                )
 3306            ˇ)
 3307        );
 3308    "});
 3309    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3310    cx.assert_editor_state(indoc! {"
 3311        const a: B = (
 3312            c(
 3313                d(
 3314                    ˇ
 3315                        ˇ)
 3316            ˇ)
 3317        );
 3318    "});
 3319
 3320    // handle auto-indent when there are multiple cursors on the same line
 3321    cx.set_state(indoc! {"
 3322        const a: B = (
 3323            c(
 3324        ˇ    ˇ
 3325        ˇ    )
 3326        );
 3327    "});
 3328    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3329    cx.assert_editor_state(indoc! {"
 3330        const a: B = (
 3331            c(
 3332                ˇ
 3333            ˇ)
 3334        );
 3335    "});
 3336}
 3337
 3338#[gpui::test]
 3339async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 3340    init_test(cx, |settings| {
 3341        settings.defaults.tab_size = NonZeroU32::new(3)
 3342    });
 3343
 3344    let mut cx = EditorTestContext::new(cx).await;
 3345    cx.set_state(indoc! {"
 3346         ˇ
 3347        \t ˇ
 3348        \t  ˇ
 3349        \t   ˇ
 3350         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 3351    "});
 3352
 3353    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3354    cx.assert_editor_state(indoc! {"
 3355           ˇ
 3356        \t   ˇ
 3357        \t   ˇ
 3358        \t      ˇ
 3359         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 3360    "});
 3361}
 3362
 3363#[gpui::test]
 3364async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 3365    init_test(cx, |settings| {
 3366        settings.defaults.tab_size = NonZeroU32::new(4)
 3367    });
 3368
 3369    let language = Arc::new(
 3370        Language::new(
 3371            LanguageConfig::default(),
 3372            Some(tree_sitter_rust::LANGUAGE.into()),
 3373        )
 3374        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 3375        .unwrap(),
 3376    );
 3377
 3378    let mut cx = EditorTestContext::new(cx).await;
 3379    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3380    cx.set_state(indoc! {"
 3381        fn a() {
 3382            if b {
 3383        \t ˇc
 3384            }
 3385        }
 3386    "});
 3387
 3388    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3389    cx.assert_editor_state(indoc! {"
 3390        fn a() {
 3391            if b {
 3392                ˇc
 3393            }
 3394        }
 3395    "});
 3396}
 3397
 3398#[gpui::test]
 3399async fn test_indent_outdent(cx: &mut TestAppContext) {
 3400    init_test(cx, |settings| {
 3401        settings.defaults.tab_size = NonZeroU32::new(4);
 3402    });
 3403
 3404    let mut cx = EditorTestContext::new(cx).await;
 3405
 3406    cx.set_state(indoc! {"
 3407          «oneˇ» «twoˇ»
 3408        three
 3409         four
 3410    "});
 3411    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3412    cx.assert_editor_state(indoc! {"
 3413            «oneˇ» «twoˇ»
 3414        three
 3415         four
 3416    "});
 3417
 3418    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3419    cx.assert_editor_state(indoc! {"
 3420        «oneˇ» «twoˇ»
 3421        three
 3422         four
 3423    "});
 3424
 3425    // select across line ending
 3426    cx.set_state(indoc! {"
 3427        one two
 3428        t«hree
 3429        ˇ» four
 3430    "});
 3431    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3432    cx.assert_editor_state(indoc! {"
 3433        one two
 3434            t«hree
 3435        ˇ» four
 3436    "});
 3437
 3438    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3439    cx.assert_editor_state(indoc! {"
 3440        one two
 3441        t«hree
 3442        ˇ» four
 3443    "});
 3444
 3445    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3446    cx.set_state(indoc! {"
 3447        one two
 3448        ˇthree
 3449            four
 3450    "});
 3451    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3452    cx.assert_editor_state(indoc! {"
 3453        one two
 3454            ˇthree
 3455            four
 3456    "});
 3457
 3458    cx.set_state(indoc! {"
 3459        one two
 3460        ˇ    three
 3461            four
 3462    "});
 3463    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3464    cx.assert_editor_state(indoc! {"
 3465        one two
 3466        ˇthree
 3467            four
 3468    "});
 3469}
 3470
 3471#[gpui::test]
 3472async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 3473    init_test(cx, |settings| {
 3474        settings.defaults.hard_tabs = Some(true);
 3475    });
 3476
 3477    let mut cx = EditorTestContext::new(cx).await;
 3478
 3479    // select two ranges on one line
 3480    cx.set_state(indoc! {"
 3481        «oneˇ» «twoˇ»
 3482        three
 3483        four
 3484    "});
 3485    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3486    cx.assert_editor_state(indoc! {"
 3487        \t«oneˇ» «twoˇ»
 3488        three
 3489        four
 3490    "});
 3491    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3492    cx.assert_editor_state(indoc! {"
 3493        \t\t«oneˇ» «twoˇ»
 3494        three
 3495        four
 3496    "});
 3497    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3498    cx.assert_editor_state(indoc! {"
 3499        \t«oneˇ» «twoˇ»
 3500        three
 3501        four
 3502    "});
 3503    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3504    cx.assert_editor_state(indoc! {"
 3505        «oneˇ» «twoˇ»
 3506        three
 3507        four
 3508    "});
 3509
 3510    // select across a line ending
 3511    cx.set_state(indoc! {"
 3512        one two
 3513        t«hree
 3514        ˇ»four
 3515    "});
 3516    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3517    cx.assert_editor_state(indoc! {"
 3518        one two
 3519        \tt«hree
 3520        ˇ»four
 3521    "});
 3522    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3523    cx.assert_editor_state(indoc! {"
 3524        one two
 3525        \t\tt«hree
 3526        ˇ»four
 3527    "});
 3528    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3529    cx.assert_editor_state(indoc! {"
 3530        one two
 3531        \tt«hree
 3532        ˇ»four
 3533    "});
 3534    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3535    cx.assert_editor_state(indoc! {"
 3536        one two
 3537        t«hree
 3538        ˇ»four
 3539    "});
 3540
 3541    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3542    cx.set_state(indoc! {"
 3543        one two
 3544        ˇthree
 3545        four
 3546    "});
 3547    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3548    cx.assert_editor_state(indoc! {"
 3549        one two
 3550        ˇthree
 3551        four
 3552    "});
 3553    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3554    cx.assert_editor_state(indoc! {"
 3555        one two
 3556        \tˇthree
 3557        four
 3558    "});
 3559    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3560    cx.assert_editor_state(indoc! {"
 3561        one two
 3562        ˇthree
 3563        four
 3564    "});
 3565}
 3566
 3567#[gpui::test]
 3568fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 3569    init_test(cx, |settings| {
 3570        settings.languages.0.extend([
 3571            (
 3572                "TOML".into(),
 3573                LanguageSettingsContent {
 3574                    tab_size: NonZeroU32::new(2),
 3575                    ..Default::default()
 3576                },
 3577            ),
 3578            (
 3579                "Rust".into(),
 3580                LanguageSettingsContent {
 3581                    tab_size: NonZeroU32::new(4),
 3582                    ..Default::default()
 3583                },
 3584            ),
 3585        ]);
 3586    });
 3587
 3588    let toml_language = Arc::new(Language::new(
 3589        LanguageConfig {
 3590            name: "TOML".into(),
 3591            ..Default::default()
 3592        },
 3593        None,
 3594    ));
 3595    let rust_language = Arc::new(Language::new(
 3596        LanguageConfig {
 3597            name: "Rust".into(),
 3598            ..Default::default()
 3599        },
 3600        None,
 3601    ));
 3602
 3603    let toml_buffer =
 3604        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 3605    let rust_buffer =
 3606        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 3607    let multibuffer = cx.new(|cx| {
 3608        let mut multibuffer = MultiBuffer::new(ReadWrite);
 3609        multibuffer.push_excerpts(
 3610            toml_buffer.clone(),
 3611            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 3612            cx,
 3613        );
 3614        multibuffer.push_excerpts(
 3615            rust_buffer.clone(),
 3616            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 3617            cx,
 3618        );
 3619        multibuffer
 3620    });
 3621
 3622    cx.add_window(|window, cx| {
 3623        let mut editor = build_editor(multibuffer, window, cx);
 3624
 3625        assert_eq!(
 3626            editor.text(cx),
 3627            indoc! {"
 3628                a = 1
 3629                b = 2
 3630
 3631                const c: usize = 3;
 3632            "}
 3633        );
 3634
 3635        select_ranges(
 3636            &mut editor,
 3637            indoc! {"
 3638                «aˇ» = 1
 3639                b = 2
 3640
 3641                «const c:ˇ» usize = 3;
 3642            "},
 3643            window,
 3644            cx,
 3645        );
 3646
 3647        editor.tab(&Tab, window, cx);
 3648        assert_text_with_selections(
 3649            &mut editor,
 3650            indoc! {"
 3651                  «aˇ» = 1
 3652                b = 2
 3653
 3654                    «const c:ˇ» usize = 3;
 3655            "},
 3656            cx,
 3657        );
 3658        editor.backtab(&Backtab, window, cx);
 3659        assert_text_with_selections(
 3660            &mut editor,
 3661            indoc! {"
 3662                «aˇ» = 1
 3663                b = 2
 3664
 3665                «const c:ˇ» usize = 3;
 3666            "},
 3667            cx,
 3668        );
 3669
 3670        editor
 3671    });
 3672}
 3673
 3674#[gpui::test]
 3675async fn test_backspace(cx: &mut TestAppContext) {
 3676    init_test(cx, |_| {});
 3677
 3678    let mut cx = EditorTestContext::new(cx).await;
 3679
 3680    // Basic backspace
 3681    cx.set_state(indoc! {"
 3682        onˇe two three
 3683        fou«rˇ» five six
 3684        seven «ˇeight nine
 3685        »ten
 3686    "});
 3687    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3688    cx.assert_editor_state(indoc! {"
 3689        oˇe two three
 3690        fouˇ five six
 3691        seven ˇten
 3692    "});
 3693
 3694    // Test backspace inside and around indents
 3695    cx.set_state(indoc! {"
 3696        zero
 3697            ˇone
 3698                ˇtwo
 3699            ˇ ˇ ˇ  three
 3700        ˇ  ˇ  four
 3701    "});
 3702    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3703    cx.assert_editor_state(indoc! {"
 3704        zero
 3705        ˇone
 3706            ˇtwo
 3707        ˇ  threeˇ  four
 3708    "});
 3709}
 3710
 3711#[gpui::test]
 3712async fn test_delete(cx: &mut TestAppContext) {
 3713    init_test(cx, |_| {});
 3714
 3715    let mut cx = EditorTestContext::new(cx).await;
 3716    cx.set_state(indoc! {"
 3717        onˇe two three
 3718        fou«rˇ» five six
 3719        seven «ˇeight nine
 3720        »ten
 3721    "});
 3722    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 3723    cx.assert_editor_state(indoc! {"
 3724        onˇ two three
 3725        fouˇ five six
 3726        seven ˇten
 3727    "});
 3728}
 3729
 3730#[gpui::test]
 3731fn test_delete_line(cx: &mut TestAppContext) {
 3732    init_test(cx, |_| {});
 3733
 3734    let editor = cx.add_window(|window, cx| {
 3735        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3736        build_editor(buffer, window, cx)
 3737    });
 3738    _ = editor.update(cx, |editor, window, cx| {
 3739        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3740            s.select_display_ranges([
 3741                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 3742                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 3743                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 3744            ])
 3745        });
 3746        editor.delete_line(&DeleteLine, window, cx);
 3747        assert_eq!(editor.display_text(cx), "ghi");
 3748        assert_eq!(
 3749            editor.selections.display_ranges(cx),
 3750            vec![
 3751                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 3752                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
 3753            ]
 3754        );
 3755    });
 3756
 3757    let editor = cx.add_window(|window, cx| {
 3758        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3759        build_editor(buffer, window, cx)
 3760    });
 3761    _ = editor.update(cx, |editor, window, cx| {
 3762        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3763            s.select_display_ranges([
 3764                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 3765            ])
 3766        });
 3767        editor.delete_line(&DeleteLine, window, cx);
 3768        assert_eq!(editor.display_text(cx), "ghi\n");
 3769        assert_eq!(
 3770            editor.selections.display_ranges(cx),
 3771            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 3772        );
 3773    });
 3774}
 3775
 3776#[gpui::test]
 3777fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 3778    init_test(cx, |_| {});
 3779
 3780    cx.add_window(|window, cx| {
 3781        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3782        let mut editor = build_editor(buffer.clone(), window, cx);
 3783        let buffer = buffer.read(cx).as_singleton().unwrap();
 3784
 3785        assert_eq!(
 3786            editor.selections.ranges::<Point>(cx),
 3787            &[Point::new(0, 0)..Point::new(0, 0)]
 3788        );
 3789
 3790        // When on single line, replace newline at end by space
 3791        editor.join_lines(&JoinLines, window, cx);
 3792        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3793        assert_eq!(
 3794            editor.selections.ranges::<Point>(cx),
 3795            &[Point::new(0, 3)..Point::new(0, 3)]
 3796        );
 3797
 3798        // When multiple lines are selected, remove newlines that are spanned by the selection
 3799        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3800            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 3801        });
 3802        editor.join_lines(&JoinLines, window, cx);
 3803        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 3804        assert_eq!(
 3805            editor.selections.ranges::<Point>(cx),
 3806            &[Point::new(0, 11)..Point::new(0, 11)]
 3807        );
 3808
 3809        // Undo should be transactional
 3810        editor.undo(&Undo, window, cx);
 3811        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3812        assert_eq!(
 3813            editor.selections.ranges::<Point>(cx),
 3814            &[Point::new(0, 5)..Point::new(2, 2)]
 3815        );
 3816
 3817        // When joining an empty line don't insert a space
 3818        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3819            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 3820        });
 3821        editor.join_lines(&JoinLines, window, cx);
 3822        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 3823        assert_eq!(
 3824            editor.selections.ranges::<Point>(cx),
 3825            [Point::new(2, 3)..Point::new(2, 3)]
 3826        );
 3827
 3828        // We can remove trailing newlines
 3829        editor.join_lines(&JoinLines, window, cx);
 3830        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3831        assert_eq!(
 3832            editor.selections.ranges::<Point>(cx),
 3833            [Point::new(2, 3)..Point::new(2, 3)]
 3834        );
 3835
 3836        // We don't blow up on the last line
 3837        editor.join_lines(&JoinLines, window, cx);
 3838        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3839        assert_eq!(
 3840            editor.selections.ranges::<Point>(cx),
 3841            [Point::new(2, 3)..Point::new(2, 3)]
 3842        );
 3843
 3844        // reset to test indentation
 3845        editor.buffer.update(cx, |buffer, cx| {
 3846            buffer.edit(
 3847                [
 3848                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 3849                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 3850                ],
 3851                None,
 3852                cx,
 3853            )
 3854        });
 3855
 3856        // We remove any leading spaces
 3857        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 3858        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3859            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 3860        });
 3861        editor.join_lines(&JoinLines, window, cx);
 3862        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 3863
 3864        // We don't insert a space for a line containing only spaces
 3865        editor.join_lines(&JoinLines, window, cx);
 3866        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 3867
 3868        // We ignore any leading tabs
 3869        editor.join_lines(&JoinLines, window, cx);
 3870        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 3871
 3872        editor
 3873    });
 3874}
 3875
 3876#[gpui::test]
 3877fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 3878    init_test(cx, |_| {});
 3879
 3880    cx.add_window(|window, cx| {
 3881        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3882        let mut editor = build_editor(buffer.clone(), window, cx);
 3883        let buffer = buffer.read(cx).as_singleton().unwrap();
 3884
 3885        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3886            s.select_ranges([
 3887                Point::new(0, 2)..Point::new(1, 1),
 3888                Point::new(1, 2)..Point::new(1, 2),
 3889                Point::new(3, 1)..Point::new(3, 2),
 3890            ])
 3891        });
 3892
 3893        editor.join_lines(&JoinLines, window, cx);
 3894        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 3895
 3896        assert_eq!(
 3897            editor.selections.ranges::<Point>(cx),
 3898            [
 3899                Point::new(0, 7)..Point::new(0, 7),
 3900                Point::new(1, 3)..Point::new(1, 3)
 3901            ]
 3902        );
 3903        editor
 3904    });
 3905}
 3906
 3907#[gpui::test]
 3908async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 3909    init_test(cx, |_| {});
 3910
 3911    let mut cx = EditorTestContext::new(cx).await;
 3912
 3913    let diff_base = r#"
 3914        Line 0
 3915        Line 1
 3916        Line 2
 3917        Line 3
 3918        "#
 3919    .unindent();
 3920
 3921    cx.set_state(
 3922        &r#"
 3923        ˇLine 0
 3924        Line 1
 3925        Line 2
 3926        Line 3
 3927        "#
 3928        .unindent(),
 3929    );
 3930
 3931    cx.set_head_text(&diff_base);
 3932    executor.run_until_parked();
 3933
 3934    // Join lines
 3935    cx.update_editor(|editor, window, cx| {
 3936        editor.join_lines(&JoinLines, window, cx);
 3937    });
 3938    executor.run_until_parked();
 3939
 3940    cx.assert_editor_state(
 3941        &r#"
 3942        Line 0ˇ Line 1
 3943        Line 2
 3944        Line 3
 3945        "#
 3946        .unindent(),
 3947    );
 3948    // Join again
 3949    cx.update_editor(|editor, window, cx| {
 3950        editor.join_lines(&JoinLines, window, cx);
 3951    });
 3952    executor.run_until_parked();
 3953
 3954    cx.assert_editor_state(
 3955        &r#"
 3956        Line 0 Line 1ˇ Line 2
 3957        Line 3
 3958        "#
 3959        .unindent(),
 3960    );
 3961}
 3962
 3963#[gpui::test]
 3964async fn test_custom_newlines_cause_no_false_positive_diffs(
 3965    executor: BackgroundExecutor,
 3966    cx: &mut TestAppContext,
 3967) {
 3968    init_test(cx, |_| {});
 3969    let mut cx = EditorTestContext::new(cx).await;
 3970    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 3971    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 3972    executor.run_until_parked();
 3973
 3974    cx.update_editor(|editor, window, cx| {
 3975        let snapshot = editor.snapshot(window, cx);
 3976        assert_eq!(
 3977            snapshot
 3978                .buffer_snapshot
 3979                .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
 3980                .collect::<Vec<_>>(),
 3981            Vec::new(),
 3982            "Should not have any diffs for files with custom newlines"
 3983        );
 3984    });
 3985}
 3986
 3987#[gpui::test]
 3988async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 3989    init_test(cx, |_| {});
 3990
 3991    let mut cx = EditorTestContext::new(cx).await;
 3992
 3993    // Test sort_lines_case_insensitive()
 3994    cx.set_state(indoc! {"
 3995        «z
 3996        y
 3997        x
 3998        Z
 3999        Y
 4000        Xˇ»
 4001    "});
 4002    cx.update_editor(|e, window, cx| {
 4003        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4004    });
 4005    cx.assert_editor_state(indoc! {"
 4006        «x
 4007        X
 4008        y
 4009        Y
 4010        z
 4011        Zˇ»
 4012    "});
 4013
 4014    // Test reverse_lines()
 4015    cx.set_state(indoc! {"
 4016        «5
 4017        4
 4018        3
 4019        2
 4020        1ˇ»
 4021    "});
 4022    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4023    cx.assert_editor_state(indoc! {"
 4024        «1
 4025        2
 4026        3
 4027        4
 4028        5ˇ»
 4029    "});
 4030
 4031    // Skip testing shuffle_line()
 4032
 4033    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4034    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4035
 4036    // Don't manipulate when cursor is on single line, but expand the selection
 4037    cx.set_state(indoc! {"
 4038        ddˇdd
 4039        ccc
 4040        bb
 4041        a
 4042    "});
 4043    cx.update_editor(|e, window, cx| {
 4044        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4045    });
 4046    cx.assert_editor_state(indoc! {"
 4047        «ddddˇ»
 4048        ccc
 4049        bb
 4050        a
 4051    "});
 4052
 4053    // Basic manipulate case
 4054    // Start selection moves to column 0
 4055    // End of selection shrinks to fit shorter line
 4056    cx.set_state(indoc! {"
 4057        dd«d
 4058        ccc
 4059        bb
 4060        aaaaaˇ»
 4061    "});
 4062    cx.update_editor(|e, window, cx| {
 4063        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4064    });
 4065    cx.assert_editor_state(indoc! {"
 4066        «aaaaa
 4067        bb
 4068        ccc
 4069        dddˇ»
 4070    "});
 4071
 4072    // Manipulate case with newlines
 4073    cx.set_state(indoc! {"
 4074        dd«d
 4075        ccc
 4076
 4077        bb
 4078        aaaaa
 4079
 4080        ˇ»
 4081    "});
 4082    cx.update_editor(|e, window, cx| {
 4083        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4084    });
 4085    cx.assert_editor_state(indoc! {"
 4086        «
 4087
 4088        aaaaa
 4089        bb
 4090        ccc
 4091        dddˇ»
 4092
 4093    "});
 4094
 4095    // Adding new line
 4096    cx.set_state(indoc! {"
 4097        aa«a
 4098        bbˇ»b
 4099    "});
 4100    cx.update_editor(|e, window, cx| {
 4101        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4102    });
 4103    cx.assert_editor_state(indoc! {"
 4104        «aaa
 4105        bbb
 4106        added_lineˇ»
 4107    "});
 4108
 4109    // Removing line
 4110    cx.set_state(indoc! {"
 4111        aa«a
 4112        bbbˇ»
 4113    "});
 4114    cx.update_editor(|e, window, cx| {
 4115        e.manipulate_immutable_lines(window, cx, |lines| {
 4116            lines.pop();
 4117        })
 4118    });
 4119    cx.assert_editor_state(indoc! {"
 4120        «aaaˇ»
 4121    "});
 4122
 4123    // Removing all lines
 4124    cx.set_state(indoc! {"
 4125        aa«a
 4126        bbbˇ»
 4127    "});
 4128    cx.update_editor(|e, window, cx| {
 4129        e.manipulate_immutable_lines(window, cx, |lines| {
 4130            lines.drain(..);
 4131        })
 4132    });
 4133    cx.assert_editor_state(indoc! {"
 4134        ˇ
 4135    "});
 4136}
 4137
 4138#[gpui::test]
 4139async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4140    init_test(cx, |_| {});
 4141
 4142    let mut cx = EditorTestContext::new(cx).await;
 4143
 4144    // Consider continuous selection as single selection
 4145    cx.set_state(indoc! {"
 4146        Aaa«aa
 4147        cˇ»c«c
 4148        bb
 4149        aaaˇ»aa
 4150    "});
 4151    cx.update_editor(|e, window, cx| {
 4152        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4153    });
 4154    cx.assert_editor_state(indoc! {"
 4155        «Aaaaa
 4156        ccc
 4157        bb
 4158        aaaaaˇ»
 4159    "});
 4160
 4161    cx.set_state(indoc! {"
 4162        Aaa«aa
 4163        cˇ»c«c
 4164        bb
 4165        aaaˇ»aa
 4166    "});
 4167    cx.update_editor(|e, window, cx| {
 4168        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4169    });
 4170    cx.assert_editor_state(indoc! {"
 4171        «Aaaaa
 4172        ccc
 4173        bbˇ»
 4174    "});
 4175
 4176    // Consider non continuous selection as distinct dedup operations
 4177    cx.set_state(indoc! {"
 4178        «aaaaa
 4179        bb
 4180        aaaaa
 4181        aaaaaˇ»
 4182
 4183        aaa«aaˇ»
 4184    "});
 4185    cx.update_editor(|e, window, cx| {
 4186        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4187    });
 4188    cx.assert_editor_state(indoc! {"
 4189        «aaaaa
 4190        bbˇ»
 4191
 4192        «aaaaaˇ»
 4193    "});
 4194}
 4195
 4196#[gpui::test]
 4197async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4198    init_test(cx, |_| {});
 4199
 4200    let mut cx = EditorTestContext::new(cx).await;
 4201
 4202    cx.set_state(indoc! {"
 4203        «Aaa
 4204        aAa
 4205        Aaaˇ»
 4206    "});
 4207    cx.update_editor(|e, window, cx| {
 4208        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4209    });
 4210    cx.assert_editor_state(indoc! {"
 4211        «Aaa
 4212        aAaˇ»
 4213    "});
 4214
 4215    cx.set_state(indoc! {"
 4216        «Aaa
 4217        aAa
 4218        aaAˇ»
 4219    "});
 4220    cx.update_editor(|e, window, cx| {
 4221        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4222    });
 4223    cx.assert_editor_state(indoc! {"
 4224        «Aaaˇ»
 4225    "});
 4226}
 4227
 4228#[gpui::test]
 4229async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 4230    init_test(cx, |_| {});
 4231
 4232    let mut cx = EditorTestContext::new(cx).await;
 4233
 4234    // Manipulate with multiple selections on a single line
 4235    cx.set_state(indoc! {"
 4236        dd«dd
 4237        cˇ»c«c
 4238        bb
 4239        aaaˇ»aa
 4240    "});
 4241    cx.update_editor(|e, window, cx| {
 4242        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4243    });
 4244    cx.assert_editor_state(indoc! {"
 4245        «aaaaa
 4246        bb
 4247        ccc
 4248        ddddˇ»
 4249    "});
 4250
 4251    // Manipulate with multiple disjoin selections
 4252    cx.set_state(indoc! {"
 4253 4254        4
 4255        3
 4256        2
 4257        1ˇ»
 4258
 4259        dd«dd
 4260        ccc
 4261        bb
 4262        aaaˇ»aa
 4263    "});
 4264    cx.update_editor(|e, window, cx| {
 4265        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4266    });
 4267    cx.assert_editor_state(indoc! {"
 4268        «1
 4269        2
 4270        3
 4271        4
 4272        5ˇ»
 4273
 4274        «aaaaa
 4275        bb
 4276        ccc
 4277        ddddˇ»
 4278    "});
 4279
 4280    // Adding lines on each selection
 4281    cx.set_state(indoc! {"
 4282 4283        1ˇ»
 4284
 4285        bb«bb
 4286        aaaˇ»aa
 4287    "});
 4288    cx.update_editor(|e, window, cx| {
 4289        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 4290    });
 4291    cx.assert_editor_state(indoc! {"
 4292        «2
 4293        1
 4294        added lineˇ»
 4295
 4296        «bbbb
 4297        aaaaa
 4298        added lineˇ»
 4299    "});
 4300
 4301    // Removing lines on each selection
 4302    cx.set_state(indoc! {"
 4303 4304        1ˇ»
 4305
 4306        bb«bb
 4307        aaaˇ»aa
 4308    "});
 4309    cx.update_editor(|e, window, cx| {
 4310        e.manipulate_immutable_lines(window, cx, |lines| {
 4311            lines.pop();
 4312        })
 4313    });
 4314    cx.assert_editor_state(indoc! {"
 4315        «2ˇ»
 4316
 4317        «bbbbˇ»
 4318    "});
 4319}
 4320
 4321#[gpui::test]
 4322async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 4323    init_test(cx, |settings| {
 4324        settings.defaults.tab_size = NonZeroU32::new(3)
 4325    });
 4326
 4327    let mut cx = EditorTestContext::new(cx).await;
 4328
 4329    // MULTI SELECTION
 4330    // Ln.1 "«" tests empty lines
 4331    // Ln.9 tests just leading whitespace
 4332    cx.set_state(indoc! {"
 4333        «
 4334        abc                 // No indentationˇ»
 4335        «\tabc              // 1 tabˇ»
 4336        \t\tabc «      ˇ»   // 2 tabs
 4337        \t ab«c             // Tab followed by space
 4338         \tabc              // Space followed by tab (3 spaces should be the result)
 4339        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4340           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 4341        \t
 4342        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4343    "});
 4344    cx.update_editor(|e, window, cx| {
 4345        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4346    });
 4347    cx.assert_editor_state(
 4348        indoc! {"
 4349            «
 4350            abc                 // No indentation
 4351               abc              // 1 tab
 4352                  abc          // 2 tabs
 4353                abc             // Tab followed by space
 4354               abc              // Space followed by tab (3 spaces should be the result)
 4355                           abc   // Mixed indentation (tab conversion depends on the column)
 4356               abc         // Already space indented
 4357               ·
 4358               abc\tdef          // Only the leading tab is manipulatedˇ»
 4359        "}
 4360        .replace("·", "")
 4361        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4362    );
 4363
 4364    // Test on just a few lines, the others should remain unchanged
 4365    // Only lines (3, 5, 10, 11) should change
 4366    cx.set_state(
 4367        indoc! {"
 4368            ·
 4369            abc                 // No indentation
 4370            \tabcˇ               // 1 tab
 4371            \t\tabc             // 2 tabs
 4372            \t abcˇ              // Tab followed by space
 4373             \tabc              // Space followed by tab (3 spaces should be the result)
 4374            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4375               abc              // Already space indented
 4376            «\t
 4377            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4378        "}
 4379        .replace("·", "")
 4380        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4381    );
 4382    cx.update_editor(|e, window, cx| {
 4383        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4384    });
 4385    cx.assert_editor_state(
 4386        indoc! {"
 4387            ·
 4388            abc                 // No indentation
 4389            «   abc               // 1 tabˇ»
 4390            \t\tabc             // 2 tabs
 4391            «    abc              // Tab followed by spaceˇ»
 4392             \tabc              // Space followed by tab (3 spaces should be the result)
 4393            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4394               abc              // Already space indented
 4395            «   ·
 4396               abc\tdef          // Only the leading tab is manipulatedˇ»
 4397        "}
 4398        .replace("·", "")
 4399        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4400    );
 4401
 4402    // SINGLE SELECTION
 4403    // Ln.1 "«" tests empty lines
 4404    // Ln.9 tests just leading whitespace
 4405    cx.set_state(indoc! {"
 4406        «
 4407        abc                 // No indentation
 4408        \tabc               // 1 tab
 4409        \t\tabc             // 2 tabs
 4410        \t abc              // Tab followed by space
 4411         \tabc              // Space followed by tab (3 spaces should be the result)
 4412        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4413           abc              // Already space indented
 4414        \t
 4415        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4416    "});
 4417    cx.update_editor(|e, window, cx| {
 4418        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4419    });
 4420    cx.assert_editor_state(
 4421        indoc! {"
 4422            «
 4423            abc                 // No indentation
 4424               abc               // 1 tab
 4425                  abc             // 2 tabs
 4426                abc              // Tab followed by space
 4427               abc              // Space followed by tab (3 spaces should be the result)
 4428                           abc   // Mixed indentation (tab conversion depends on the column)
 4429               abc              // Already space indented
 4430               ·
 4431               abc\tdef          // Only the leading tab is manipulatedˇ»
 4432        "}
 4433        .replace("·", "")
 4434        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4435    );
 4436}
 4437
 4438#[gpui::test]
 4439async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 4440    init_test(cx, |settings| {
 4441        settings.defaults.tab_size = NonZeroU32::new(3)
 4442    });
 4443
 4444    let mut cx = EditorTestContext::new(cx).await;
 4445
 4446    // MULTI SELECTION
 4447    // Ln.1 "«" tests empty lines
 4448    // Ln.11 tests just leading whitespace
 4449    cx.set_state(indoc! {"
 4450        «
 4451        abˇ»ˇc                 // No indentation
 4452         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 4453          abc  «             // 2 spaces (< 3 so dont convert)
 4454           abc              // 3 spaces (convert)
 4455             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 4456        «\tˇ»\t«\tˇ»abc           // Already tab indented
 4457        «\t abc              // Tab followed by space
 4458         \tabc              // Space followed by tab (should be consumed due to tab)
 4459        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4460           \tˇ»  «\t
 4461           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 4462    "});
 4463    cx.update_editor(|e, window, cx| {
 4464        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4465    });
 4466    cx.assert_editor_state(indoc! {"
 4467        «
 4468        abc                 // No indentation
 4469         abc                // 1 space (< 3 so dont convert)
 4470          abc               // 2 spaces (< 3 so dont convert)
 4471        \tabc              // 3 spaces (convert)
 4472        \t  abc            // 5 spaces (1 tab + 2 spaces)
 4473        \t\t\tabc           // Already tab indented
 4474        \t abc              // Tab followed by space
 4475        \tabc              // Space followed by tab (should be consumed due to tab)
 4476        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4477        \t\t\t
 4478        \tabc   \t         // Only the leading spaces should be convertedˇ»
 4479    "});
 4480
 4481    // Test on just a few lines, the other should remain unchanged
 4482    // Only lines (4, 8, 11, 12) should change
 4483    cx.set_state(
 4484        indoc! {"
 4485            ·
 4486            abc                 // No indentation
 4487             abc                // 1 space (< 3 so dont convert)
 4488              abc               // 2 spaces (< 3 so dont convert)
 4489            «   abc              // 3 spaces (convert)ˇ»
 4490                 abc            // 5 spaces (1 tab + 2 spaces)
 4491            \t\t\tabc           // Already tab indented
 4492            \t abc              // Tab followed by space
 4493             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 4494               \t\t  \tabc      // Mixed indentation
 4495            \t \t  \t   \tabc   // Mixed indentation
 4496               \t  \tˇ
 4497            «   abc   \t         // Only the leading spaces should be convertedˇ»
 4498        "}
 4499        .replace("·", "")
 4500        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4501    );
 4502    cx.update_editor(|e, window, cx| {
 4503        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4504    });
 4505    cx.assert_editor_state(
 4506        indoc! {"
 4507            ·
 4508            abc                 // No indentation
 4509             abc                // 1 space (< 3 so dont convert)
 4510              abc               // 2 spaces (< 3 so dont convert)
 4511            «\tabc              // 3 spaces (convert)ˇ»
 4512                 abc            // 5 spaces (1 tab + 2 spaces)
 4513            \t\t\tabc           // Already tab indented
 4514            \t abc              // Tab followed by space
 4515            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 4516               \t\t  \tabc      // Mixed indentation
 4517            \t \t  \t   \tabc   // Mixed indentation
 4518            «\t\t\t
 4519            \tabc   \t         // Only the leading spaces should be convertedˇ»
 4520        "}
 4521        .replace("·", "")
 4522        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4523    );
 4524
 4525    // SINGLE SELECTION
 4526    // Ln.1 "«" tests empty lines
 4527    // Ln.11 tests just leading whitespace
 4528    cx.set_state(indoc! {"
 4529        «
 4530        abc                 // No indentation
 4531         abc                // 1 space (< 3 so dont convert)
 4532          abc               // 2 spaces (< 3 so dont convert)
 4533           abc              // 3 spaces (convert)
 4534             abc            // 5 spaces (1 tab + 2 spaces)
 4535        \t\t\tabc           // Already tab indented
 4536        \t abc              // Tab followed by space
 4537         \tabc              // Space followed by tab (should be consumed due to tab)
 4538        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4539           \t  \t
 4540           abc   \t         // Only the leading spaces should be convertedˇ»
 4541    "});
 4542    cx.update_editor(|e, window, cx| {
 4543        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4544    });
 4545    cx.assert_editor_state(indoc! {"
 4546        «
 4547        abc                 // No indentation
 4548         abc                // 1 space (< 3 so dont convert)
 4549          abc               // 2 spaces (< 3 so dont convert)
 4550        \tabc              // 3 spaces (convert)
 4551        \t  abc            // 5 spaces (1 tab + 2 spaces)
 4552        \t\t\tabc           // Already tab indented
 4553        \t abc              // Tab followed by space
 4554        \tabc              // Space followed by tab (should be consumed due to tab)
 4555        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4556        \t\t\t
 4557        \tabc   \t         // Only the leading spaces should be convertedˇ»
 4558    "});
 4559}
 4560
 4561#[gpui::test]
 4562async fn test_toggle_case(cx: &mut TestAppContext) {
 4563    init_test(cx, |_| {});
 4564
 4565    let mut cx = EditorTestContext::new(cx).await;
 4566
 4567    // If all lower case -> upper case
 4568    cx.set_state(indoc! {"
 4569        «hello worldˇ»
 4570    "});
 4571    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4572    cx.assert_editor_state(indoc! {"
 4573        «HELLO WORLDˇ»
 4574    "});
 4575
 4576    // If all upper case -> lower case
 4577    cx.set_state(indoc! {"
 4578        «HELLO WORLDˇ»
 4579    "});
 4580    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4581    cx.assert_editor_state(indoc! {"
 4582        «hello worldˇ»
 4583    "});
 4584
 4585    // If any upper case characters are identified -> lower case
 4586    // This matches JetBrains IDEs
 4587    cx.set_state(indoc! {"
 4588        «hEllo worldˇ»
 4589    "});
 4590    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4591    cx.assert_editor_state(indoc! {"
 4592        «hello worldˇ»
 4593    "});
 4594}
 4595
 4596#[gpui::test]
 4597async fn test_manipulate_text(cx: &mut TestAppContext) {
 4598    init_test(cx, |_| {});
 4599
 4600    let mut cx = EditorTestContext::new(cx).await;
 4601
 4602    // Test convert_to_upper_case()
 4603    cx.set_state(indoc! {"
 4604        «hello worldˇ»
 4605    "});
 4606    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4607    cx.assert_editor_state(indoc! {"
 4608        «HELLO WORLDˇ»
 4609    "});
 4610
 4611    // Test convert_to_lower_case()
 4612    cx.set_state(indoc! {"
 4613        «HELLO WORLDˇ»
 4614    "});
 4615    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 4616    cx.assert_editor_state(indoc! {"
 4617        «hello worldˇ»
 4618    "});
 4619
 4620    // Test multiple line, single selection case
 4621    cx.set_state(indoc! {"
 4622        «The quick brown
 4623        fox jumps over
 4624        the lazy dogˇ»
 4625    "});
 4626    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 4627    cx.assert_editor_state(indoc! {"
 4628        «The Quick Brown
 4629        Fox Jumps Over
 4630        The Lazy Dogˇ»
 4631    "});
 4632
 4633    // Test multiple line, single selection case
 4634    cx.set_state(indoc! {"
 4635        «The quick brown
 4636        fox jumps over
 4637        the lazy dogˇ»
 4638    "});
 4639    cx.update_editor(|e, window, cx| {
 4640        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 4641    });
 4642    cx.assert_editor_state(indoc! {"
 4643        «TheQuickBrown
 4644        FoxJumpsOver
 4645        TheLazyDogˇ»
 4646    "});
 4647
 4648    // From here on out, test more complex cases of manipulate_text()
 4649
 4650    // Test no selection case - should affect words cursors are in
 4651    // Cursor at beginning, middle, and end of word
 4652    cx.set_state(indoc! {"
 4653        ˇhello big beauˇtiful worldˇ
 4654    "});
 4655    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4656    cx.assert_editor_state(indoc! {"
 4657        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 4658    "});
 4659
 4660    // Test multiple selections on a single line and across multiple lines
 4661    cx.set_state(indoc! {"
 4662        «Theˇ» quick «brown
 4663        foxˇ» jumps «overˇ»
 4664        the «lazyˇ» dog
 4665    "});
 4666    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4667    cx.assert_editor_state(indoc! {"
 4668        «THEˇ» quick «BROWN
 4669        FOXˇ» jumps «OVERˇ»
 4670        the «LAZYˇ» dog
 4671    "});
 4672
 4673    // Test case where text length grows
 4674    cx.set_state(indoc! {"
 4675        «tschüߡ»
 4676    "});
 4677    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4678    cx.assert_editor_state(indoc! {"
 4679        «TSCHÜSSˇ»
 4680    "});
 4681
 4682    // Test to make sure we don't crash when text shrinks
 4683    cx.set_state(indoc! {"
 4684        aaa_bbbˇ
 4685    "});
 4686    cx.update_editor(|e, window, cx| {
 4687        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4688    });
 4689    cx.assert_editor_state(indoc! {"
 4690        «aaaBbbˇ»
 4691    "});
 4692
 4693    // Test to make sure we all aware of the fact that each word can grow and shrink
 4694    // Final selections should be aware of this fact
 4695    cx.set_state(indoc! {"
 4696        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 4697    "});
 4698    cx.update_editor(|e, window, cx| {
 4699        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4700    });
 4701    cx.assert_editor_state(indoc! {"
 4702        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 4703    "});
 4704
 4705    cx.set_state(indoc! {"
 4706        «hElLo, WoRld!ˇ»
 4707    "});
 4708    cx.update_editor(|e, window, cx| {
 4709        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 4710    });
 4711    cx.assert_editor_state(indoc! {"
 4712        «HeLlO, wOrLD!ˇ»
 4713    "});
 4714}
 4715
 4716#[gpui::test]
 4717fn test_duplicate_line(cx: &mut TestAppContext) {
 4718    init_test(cx, |_| {});
 4719
 4720    let editor = cx.add_window(|window, cx| {
 4721        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4722        build_editor(buffer, window, cx)
 4723    });
 4724    _ = editor.update(cx, |editor, window, cx| {
 4725        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4726            s.select_display_ranges([
 4727                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4728                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4729                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4730                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4731            ])
 4732        });
 4733        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4734        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4735        assert_eq!(
 4736            editor.selections.display_ranges(cx),
 4737            vec![
 4738                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4739                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 4740                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4741                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4742            ]
 4743        );
 4744    });
 4745
 4746    let editor = cx.add_window(|window, cx| {
 4747        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4748        build_editor(buffer, window, cx)
 4749    });
 4750    _ = editor.update(cx, |editor, window, cx| {
 4751        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4752            s.select_display_ranges([
 4753                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4754                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4755            ])
 4756        });
 4757        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4758        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4759        assert_eq!(
 4760            editor.selections.display_ranges(cx),
 4761            vec![
 4762                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 4763                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 4764            ]
 4765        );
 4766    });
 4767
 4768    // With `move_upwards` the selections stay in place, except for
 4769    // the lines inserted above them
 4770    let editor = cx.add_window(|window, cx| {
 4771        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4772        build_editor(buffer, window, cx)
 4773    });
 4774    _ = editor.update(cx, |editor, window, cx| {
 4775        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4776            s.select_display_ranges([
 4777                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4778                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4779                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4780                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4781            ])
 4782        });
 4783        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4784        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4785        assert_eq!(
 4786            editor.selections.display_ranges(cx),
 4787            vec![
 4788                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4789                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4790                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 4791                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4792            ]
 4793        );
 4794    });
 4795
 4796    let editor = cx.add_window(|window, cx| {
 4797        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4798        build_editor(buffer, window, cx)
 4799    });
 4800    _ = editor.update(cx, |editor, window, cx| {
 4801        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4802            s.select_display_ranges([
 4803                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4804                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4805            ])
 4806        });
 4807        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4808        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4809        assert_eq!(
 4810            editor.selections.display_ranges(cx),
 4811            vec![
 4812                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4813                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4814            ]
 4815        );
 4816    });
 4817
 4818    let editor = cx.add_window(|window, cx| {
 4819        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4820        build_editor(buffer, window, cx)
 4821    });
 4822    _ = editor.update(cx, |editor, window, cx| {
 4823        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4824            s.select_display_ranges([
 4825                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4826                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4827            ])
 4828        });
 4829        editor.duplicate_selection(&DuplicateSelection, window, cx);
 4830        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 4831        assert_eq!(
 4832            editor.selections.display_ranges(cx),
 4833            vec![
 4834                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4835                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 4836            ]
 4837        );
 4838    });
 4839}
 4840
 4841#[gpui::test]
 4842fn test_move_line_up_down(cx: &mut TestAppContext) {
 4843    init_test(cx, |_| {});
 4844
 4845    let editor = cx.add_window(|window, cx| {
 4846        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 4847        build_editor(buffer, window, cx)
 4848    });
 4849    _ = editor.update(cx, |editor, window, cx| {
 4850        editor.fold_creases(
 4851            vec![
 4852                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 4853                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 4854                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 4855            ],
 4856            true,
 4857            window,
 4858            cx,
 4859        );
 4860        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4861            s.select_display_ranges([
 4862                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4863                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4864                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4865                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 4866            ])
 4867        });
 4868        assert_eq!(
 4869            editor.display_text(cx),
 4870            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 4871        );
 4872
 4873        editor.move_line_up(&MoveLineUp, window, cx);
 4874        assert_eq!(
 4875            editor.display_text(cx),
 4876            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 4877        );
 4878        assert_eq!(
 4879            editor.selections.display_ranges(cx),
 4880            vec![
 4881                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4882                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4883                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 4884                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 4885            ]
 4886        );
 4887    });
 4888
 4889    _ = editor.update(cx, |editor, window, cx| {
 4890        editor.move_line_down(&MoveLineDown, window, cx);
 4891        assert_eq!(
 4892            editor.display_text(cx),
 4893            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 4894        );
 4895        assert_eq!(
 4896            editor.selections.display_ranges(cx),
 4897            vec![
 4898                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4899                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4900                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4901                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 4902            ]
 4903        );
 4904    });
 4905
 4906    _ = editor.update(cx, |editor, window, cx| {
 4907        editor.move_line_down(&MoveLineDown, window, cx);
 4908        assert_eq!(
 4909            editor.display_text(cx),
 4910            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 4911        );
 4912        assert_eq!(
 4913            editor.selections.display_ranges(cx),
 4914            vec![
 4915                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4916                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4917                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4918                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 4919            ]
 4920        );
 4921    });
 4922
 4923    _ = editor.update(cx, |editor, window, cx| {
 4924        editor.move_line_up(&MoveLineUp, window, cx);
 4925        assert_eq!(
 4926            editor.display_text(cx),
 4927            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 4928        );
 4929        assert_eq!(
 4930            editor.selections.display_ranges(cx),
 4931            vec![
 4932                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4933                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4934                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 4935                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 4936            ]
 4937        );
 4938    });
 4939}
 4940
 4941#[gpui::test]
 4942fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 4943    init_test(cx, |_| {});
 4944
 4945    let editor = cx.add_window(|window, cx| {
 4946        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 4947        build_editor(buffer, window, cx)
 4948    });
 4949    _ = editor.update(cx, |editor, window, cx| {
 4950        let snapshot = editor.buffer.read(cx).snapshot(cx);
 4951        editor.insert_blocks(
 4952            [BlockProperties {
 4953                style: BlockStyle::Fixed,
 4954                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 4955                height: Some(1),
 4956                render: Arc::new(|_| div().into_any()),
 4957                priority: 0,
 4958                render_in_minimap: true,
 4959            }],
 4960            Some(Autoscroll::fit()),
 4961            cx,
 4962        );
 4963        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4964            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 4965        });
 4966        editor.move_line_down(&MoveLineDown, window, cx);
 4967    });
 4968}
 4969
 4970#[gpui::test]
 4971async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 4972    init_test(cx, |_| {});
 4973
 4974    let mut cx = EditorTestContext::new(cx).await;
 4975    cx.set_state(
 4976        &"
 4977            ˇzero
 4978            one
 4979            two
 4980            three
 4981            four
 4982            five
 4983        "
 4984        .unindent(),
 4985    );
 4986
 4987    // Create a four-line block that replaces three lines of text.
 4988    cx.update_editor(|editor, window, cx| {
 4989        let snapshot = editor.snapshot(window, cx);
 4990        let snapshot = &snapshot.buffer_snapshot;
 4991        let placement = BlockPlacement::Replace(
 4992            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 4993        );
 4994        editor.insert_blocks(
 4995            [BlockProperties {
 4996                placement,
 4997                height: Some(4),
 4998                style: BlockStyle::Sticky,
 4999                render: Arc::new(|_| gpui::div().into_any_element()),
 5000                priority: 0,
 5001                render_in_minimap: true,
 5002            }],
 5003            None,
 5004            cx,
 5005        );
 5006    });
 5007
 5008    // Move down so that the cursor touches the block.
 5009    cx.update_editor(|editor, window, cx| {
 5010        editor.move_down(&Default::default(), window, cx);
 5011    });
 5012    cx.assert_editor_state(
 5013        &"
 5014            zero
 5015            «one
 5016            two
 5017            threeˇ»
 5018            four
 5019            five
 5020        "
 5021        .unindent(),
 5022    );
 5023
 5024    // Move down past the block.
 5025    cx.update_editor(|editor, window, cx| {
 5026        editor.move_down(&Default::default(), window, cx);
 5027    });
 5028    cx.assert_editor_state(
 5029        &"
 5030            zero
 5031            one
 5032            two
 5033            three
 5034            ˇfour
 5035            five
 5036        "
 5037        .unindent(),
 5038    );
 5039}
 5040
 5041#[gpui::test]
 5042fn test_transpose(cx: &mut TestAppContext) {
 5043    init_test(cx, |_| {});
 5044
 5045    _ = cx.add_window(|window, cx| {
 5046        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 5047        editor.set_style(EditorStyle::default(), window, cx);
 5048        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5049            s.select_ranges([1..1])
 5050        });
 5051        editor.transpose(&Default::default(), window, cx);
 5052        assert_eq!(editor.text(cx), "bac");
 5053        assert_eq!(editor.selections.ranges(cx), [2..2]);
 5054
 5055        editor.transpose(&Default::default(), window, cx);
 5056        assert_eq!(editor.text(cx), "bca");
 5057        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5058
 5059        editor.transpose(&Default::default(), window, cx);
 5060        assert_eq!(editor.text(cx), "bac");
 5061        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5062
 5063        editor
 5064    });
 5065
 5066    _ = cx.add_window(|window, cx| {
 5067        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5068        editor.set_style(EditorStyle::default(), window, cx);
 5069        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5070            s.select_ranges([3..3])
 5071        });
 5072        editor.transpose(&Default::default(), window, cx);
 5073        assert_eq!(editor.text(cx), "acb\nde");
 5074        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5075
 5076        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5077            s.select_ranges([4..4])
 5078        });
 5079        editor.transpose(&Default::default(), window, cx);
 5080        assert_eq!(editor.text(cx), "acbd\ne");
 5081        assert_eq!(editor.selections.ranges(cx), [5..5]);
 5082
 5083        editor.transpose(&Default::default(), window, cx);
 5084        assert_eq!(editor.text(cx), "acbde\n");
 5085        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5086
 5087        editor.transpose(&Default::default(), window, cx);
 5088        assert_eq!(editor.text(cx), "acbd\ne");
 5089        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5090
 5091        editor
 5092    });
 5093
 5094    _ = cx.add_window(|window, cx| {
 5095        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5096        editor.set_style(EditorStyle::default(), window, cx);
 5097        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5098            s.select_ranges([1..1, 2..2, 4..4])
 5099        });
 5100        editor.transpose(&Default::default(), window, cx);
 5101        assert_eq!(editor.text(cx), "bacd\ne");
 5102        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 5103
 5104        editor.transpose(&Default::default(), window, cx);
 5105        assert_eq!(editor.text(cx), "bcade\n");
 5106        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 5107
 5108        editor.transpose(&Default::default(), window, cx);
 5109        assert_eq!(editor.text(cx), "bcda\ne");
 5110        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5111
 5112        editor.transpose(&Default::default(), window, cx);
 5113        assert_eq!(editor.text(cx), "bcade\n");
 5114        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5115
 5116        editor.transpose(&Default::default(), window, cx);
 5117        assert_eq!(editor.text(cx), "bcaed\n");
 5118        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 5119
 5120        editor
 5121    });
 5122
 5123    _ = cx.add_window(|window, cx| {
 5124        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 5125        editor.set_style(EditorStyle::default(), window, cx);
 5126        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5127            s.select_ranges([4..4])
 5128        });
 5129        editor.transpose(&Default::default(), window, cx);
 5130        assert_eq!(editor.text(cx), "🏀🍐✋");
 5131        assert_eq!(editor.selections.ranges(cx), [8..8]);
 5132
 5133        editor.transpose(&Default::default(), window, cx);
 5134        assert_eq!(editor.text(cx), "🏀✋🍐");
 5135        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5136
 5137        editor.transpose(&Default::default(), window, cx);
 5138        assert_eq!(editor.text(cx), "🏀🍐✋");
 5139        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5140
 5141        editor
 5142    });
 5143}
 5144
 5145#[gpui::test]
 5146async fn test_rewrap(cx: &mut TestAppContext) {
 5147    init_test(cx, |settings| {
 5148        settings.languages.0.extend([
 5149            (
 5150                "Markdown".into(),
 5151                LanguageSettingsContent {
 5152                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5153                    preferred_line_length: Some(40),
 5154                    ..Default::default()
 5155                },
 5156            ),
 5157            (
 5158                "Plain Text".into(),
 5159                LanguageSettingsContent {
 5160                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5161                    preferred_line_length: Some(40),
 5162                    ..Default::default()
 5163                },
 5164            ),
 5165            (
 5166                "C++".into(),
 5167                LanguageSettingsContent {
 5168                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5169                    preferred_line_length: Some(40),
 5170                    ..Default::default()
 5171                },
 5172            ),
 5173            (
 5174                "Python".into(),
 5175                LanguageSettingsContent {
 5176                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5177                    preferred_line_length: Some(40),
 5178                    ..Default::default()
 5179                },
 5180            ),
 5181            (
 5182                "Rust".into(),
 5183                LanguageSettingsContent {
 5184                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5185                    preferred_line_length: Some(40),
 5186                    ..Default::default()
 5187                },
 5188            ),
 5189        ])
 5190    });
 5191
 5192    let mut cx = EditorTestContext::new(cx).await;
 5193
 5194    let cpp_language = Arc::new(Language::new(
 5195        LanguageConfig {
 5196            name: "C++".into(),
 5197            line_comments: vec!["// ".into()],
 5198            ..LanguageConfig::default()
 5199        },
 5200        None,
 5201    ));
 5202    let python_language = Arc::new(Language::new(
 5203        LanguageConfig {
 5204            name: "Python".into(),
 5205            line_comments: vec!["# ".into()],
 5206            ..LanguageConfig::default()
 5207        },
 5208        None,
 5209    ));
 5210    let markdown_language = Arc::new(Language::new(
 5211        LanguageConfig {
 5212            name: "Markdown".into(),
 5213            ..LanguageConfig::default()
 5214        },
 5215        None,
 5216    ));
 5217    let rust_language = Arc::new(Language::new(
 5218        LanguageConfig {
 5219            name: "Rust".into(),
 5220            line_comments: vec!["// ".into(), "/// ".into()],
 5221            ..LanguageConfig::default()
 5222        },
 5223        Some(tree_sitter_rust::LANGUAGE.into()),
 5224    ));
 5225
 5226    let plaintext_language = Arc::new(Language::new(
 5227        LanguageConfig {
 5228            name: "Plain Text".into(),
 5229            ..LanguageConfig::default()
 5230        },
 5231        None,
 5232    ));
 5233
 5234    // Test basic rewrapping of a long line with a cursor
 5235    assert_rewrap(
 5236        indoc! {"
 5237            // ˇThis is a long comment that needs to be wrapped.
 5238        "},
 5239        indoc! {"
 5240            // ˇThis is a long comment that needs to
 5241            // be wrapped.
 5242        "},
 5243        cpp_language.clone(),
 5244        &mut cx,
 5245    );
 5246
 5247    // Test rewrapping a full selection
 5248    assert_rewrap(
 5249        indoc! {"
 5250            «// This selected long comment needs to be wrapped.ˇ»"
 5251        },
 5252        indoc! {"
 5253            «// This selected long comment needs to
 5254            // be wrapped.ˇ»"
 5255        },
 5256        cpp_language.clone(),
 5257        &mut cx,
 5258    );
 5259
 5260    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 5261    assert_rewrap(
 5262        indoc! {"
 5263            // ˇThis is the first line.
 5264            // Thisˇ is the second line.
 5265            // This is the thirdˇ line, all part of one paragraph.
 5266         "},
 5267        indoc! {"
 5268            // ˇThis is the first line. Thisˇ is the
 5269            // second line. This is the thirdˇ line,
 5270            // all part of one paragraph.
 5271         "},
 5272        cpp_language.clone(),
 5273        &mut cx,
 5274    );
 5275
 5276    // Test multiple cursors in different paragraphs trigger separate rewraps
 5277    assert_rewrap(
 5278        indoc! {"
 5279            // ˇThis is the first paragraph, first line.
 5280            // ˇThis is the first paragraph, second line.
 5281
 5282            // ˇThis is the second paragraph, first line.
 5283            // ˇThis is the second paragraph, second line.
 5284        "},
 5285        indoc! {"
 5286            // ˇThis is the first paragraph, first
 5287            // line. ˇThis is the first paragraph,
 5288            // second line.
 5289
 5290            // ˇThis is the second paragraph, first
 5291            // line. ˇThis is the second paragraph,
 5292            // second line.
 5293        "},
 5294        cpp_language.clone(),
 5295        &mut cx,
 5296    );
 5297
 5298    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 5299    assert_rewrap(
 5300        indoc! {"
 5301            «// A regular long long comment to be wrapped.
 5302            /// A documentation long comment to be wrapped.ˇ»
 5303          "},
 5304        indoc! {"
 5305            «// A regular long long comment to be
 5306            // wrapped.
 5307            /// A documentation long comment to be
 5308            /// wrapped.ˇ»
 5309          "},
 5310        rust_language.clone(),
 5311        &mut cx,
 5312    );
 5313
 5314    // Test that change in indentation level trigger seperate rewraps
 5315    assert_rewrap(
 5316        indoc! {"
 5317            fn foo() {
 5318                «// This is a long comment at the base indent.
 5319                    // This is a long comment at the next indent.ˇ»
 5320            }
 5321        "},
 5322        indoc! {"
 5323            fn foo() {
 5324                «// This is a long comment at the
 5325                // base indent.
 5326                    // This is a long comment at the
 5327                    // next indent.ˇ»
 5328            }
 5329        "},
 5330        rust_language.clone(),
 5331        &mut cx,
 5332    );
 5333
 5334    // Test that different comment prefix characters (e.g., '#') are handled correctly
 5335    assert_rewrap(
 5336        indoc! {"
 5337            # ˇThis is a long comment using a pound sign.
 5338        "},
 5339        indoc! {"
 5340            # ˇThis is a long comment using a pound
 5341            # sign.
 5342        "},
 5343        python_language.clone(),
 5344        &mut cx,
 5345    );
 5346
 5347    // Test rewrapping only affects comments, not code even when selected
 5348    assert_rewrap(
 5349        indoc! {"
 5350            «/// This doc comment is long and should be wrapped.
 5351            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 5352        "},
 5353        indoc! {"
 5354            «/// This doc comment is long and should
 5355            /// be wrapped.
 5356            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 5357        "},
 5358        rust_language.clone(),
 5359        &mut cx,
 5360    );
 5361
 5362    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 5363    assert_rewrap(
 5364        indoc! {"
 5365            # Header
 5366
 5367            A long long long line of markdown text to wrap.ˇ
 5368         "},
 5369        indoc! {"
 5370            # Header
 5371
 5372            A long long long line of markdown text
 5373            to wrap.ˇ
 5374         "},
 5375        markdown_language,
 5376        &mut cx,
 5377    );
 5378
 5379    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 5380    assert_rewrap(
 5381        indoc! {"
 5382            ˇThis is a very long line of plain text that will be wrapped.
 5383        "},
 5384        indoc! {"
 5385            ˇThis is a very long line of plain text
 5386            that will be wrapped.
 5387        "},
 5388        plaintext_language.clone(),
 5389        &mut cx,
 5390    );
 5391
 5392    // Test that non-commented code acts as a paragraph boundary within a selection
 5393    assert_rewrap(
 5394        indoc! {"
 5395               «// This is the first long comment block to be wrapped.
 5396               fn my_func(a: u32);
 5397               // This is the second long comment block to be wrapped.ˇ»
 5398           "},
 5399        indoc! {"
 5400               «// This is the first long comment block
 5401               // to be wrapped.
 5402               fn my_func(a: u32);
 5403               // This is the second long comment block
 5404               // to be wrapped.ˇ»
 5405           "},
 5406        rust_language.clone(),
 5407        &mut cx,
 5408    );
 5409
 5410    // Test rewrapping multiple selections, including ones with blank lines or tabs
 5411    assert_rewrap(
 5412        indoc! {"
 5413            «ˇThis is a very long line that will be wrapped.
 5414
 5415            This is another paragraph in the same selection.»
 5416
 5417            «\tThis is a very long indented line that will be wrapped.ˇ»
 5418         "},
 5419        indoc! {"
 5420            «ˇThis is a very long line that will be
 5421            wrapped.
 5422
 5423            This is another paragraph in the same
 5424            selection.»
 5425
 5426            «\tThis is a very long indented line
 5427            \tthat will be wrapped.ˇ»
 5428         "},
 5429        plaintext_language.clone(),
 5430        &mut cx,
 5431    );
 5432
 5433    // Test that an empty comment line acts as a paragraph boundary
 5434    assert_rewrap(
 5435        indoc! {"
 5436            // ˇThis is a long comment that will be wrapped.
 5437            //
 5438            // And this is another long comment that will also be wrapped.ˇ
 5439         "},
 5440        indoc! {"
 5441            // ˇThis is a long comment that will be
 5442            // wrapped.
 5443            //
 5444            // And this is another long comment that
 5445            // will also be wrapped.ˇ
 5446         "},
 5447        cpp_language,
 5448        &mut cx,
 5449    );
 5450
 5451    #[track_caller]
 5452    fn assert_rewrap(
 5453        unwrapped_text: &str,
 5454        wrapped_text: &str,
 5455        language: Arc<Language>,
 5456        cx: &mut EditorTestContext,
 5457    ) {
 5458        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 5459        cx.set_state(unwrapped_text);
 5460        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 5461        cx.assert_editor_state(wrapped_text);
 5462    }
 5463}
 5464
 5465#[gpui::test]
 5466async fn test_hard_wrap(cx: &mut TestAppContext) {
 5467    init_test(cx, |_| {});
 5468    let mut cx = EditorTestContext::new(cx).await;
 5469
 5470    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 5471    cx.update_editor(|editor, _, cx| {
 5472        editor.set_hard_wrap(Some(14), cx);
 5473    });
 5474
 5475    cx.set_state(indoc!(
 5476        "
 5477        one two three ˇ
 5478        "
 5479    ));
 5480    cx.simulate_input("four");
 5481    cx.run_until_parked();
 5482
 5483    cx.assert_editor_state(indoc!(
 5484        "
 5485        one two three
 5486        fourˇ
 5487        "
 5488    ));
 5489
 5490    cx.update_editor(|editor, window, cx| {
 5491        editor.newline(&Default::default(), window, cx);
 5492    });
 5493    cx.run_until_parked();
 5494    cx.assert_editor_state(indoc!(
 5495        "
 5496        one two three
 5497        four
 5498        ˇ
 5499        "
 5500    ));
 5501
 5502    cx.simulate_input("five");
 5503    cx.run_until_parked();
 5504    cx.assert_editor_state(indoc!(
 5505        "
 5506        one two three
 5507        four
 5508        fiveˇ
 5509        "
 5510    ));
 5511
 5512    cx.update_editor(|editor, window, cx| {
 5513        editor.newline(&Default::default(), window, cx);
 5514    });
 5515    cx.run_until_parked();
 5516    cx.simulate_input("# ");
 5517    cx.run_until_parked();
 5518    cx.assert_editor_state(indoc!(
 5519        "
 5520        one two three
 5521        four
 5522        five
 5523        # ˇ
 5524        "
 5525    ));
 5526
 5527    cx.update_editor(|editor, window, cx| {
 5528        editor.newline(&Default::default(), window, cx);
 5529    });
 5530    cx.run_until_parked();
 5531    cx.assert_editor_state(indoc!(
 5532        "
 5533        one two three
 5534        four
 5535        five
 5536        #\x20
 5537 5538        "
 5539    ));
 5540
 5541    cx.simulate_input(" 6");
 5542    cx.run_until_parked();
 5543    cx.assert_editor_state(indoc!(
 5544        "
 5545        one two three
 5546        four
 5547        five
 5548        #
 5549        # 6ˇ
 5550        "
 5551    ));
 5552}
 5553
 5554#[gpui::test]
 5555async fn test_clipboard(cx: &mut TestAppContext) {
 5556    init_test(cx, |_| {});
 5557
 5558    let mut cx = EditorTestContext::new(cx).await;
 5559
 5560    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 5561    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5562    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 5563
 5564    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 5565    cx.set_state("two ˇfour ˇsix ˇ");
 5566    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5567    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 5568
 5569    // Paste again but with only two cursors. Since the number of cursors doesn't
 5570    // match the number of slices in the clipboard, the entire clipboard text
 5571    // is pasted at each cursor.
 5572    cx.set_state("ˇtwo one✅ four three six five ˇ");
 5573    cx.update_editor(|e, window, cx| {
 5574        e.handle_input("( ", window, cx);
 5575        e.paste(&Paste, window, cx);
 5576        e.handle_input(") ", window, cx);
 5577    });
 5578    cx.assert_editor_state(
 5579        &([
 5580            "( one✅ ",
 5581            "three ",
 5582            "five ) ˇtwo one✅ four three six five ( one✅ ",
 5583            "three ",
 5584            "five ) ˇ",
 5585        ]
 5586        .join("\n")),
 5587    );
 5588
 5589    // Cut with three selections, one of which is full-line.
 5590    cx.set_state(indoc! {"
 5591        1«2ˇ»3
 5592        4ˇ567
 5593        «8ˇ»9"});
 5594    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5595    cx.assert_editor_state(indoc! {"
 5596        1ˇ3
 5597        ˇ9"});
 5598
 5599    // Paste with three selections, noticing how the copied selection that was full-line
 5600    // gets inserted before the second cursor.
 5601    cx.set_state(indoc! {"
 5602        1ˇ3
 5603 5604        «oˇ»ne"});
 5605    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5606    cx.assert_editor_state(indoc! {"
 5607        12ˇ3
 5608        4567
 5609 5610        8ˇne"});
 5611
 5612    // Copy with a single cursor only, which writes the whole line into the clipboard.
 5613    cx.set_state(indoc! {"
 5614        The quick brown
 5615        fox juˇmps over
 5616        the lazy dog"});
 5617    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5618    assert_eq!(
 5619        cx.read_from_clipboard()
 5620            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5621        Some("fox jumps over\n".to_string())
 5622    );
 5623
 5624    // Paste with three selections, noticing how the copied full-line selection is inserted
 5625    // before the empty selections but replaces the selection that is non-empty.
 5626    cx.set_state(indoc! {"
 5627        Tˇhe quick brown
 5628        «foˇ»x jumps over
 5629        tˇhe lazy dog"});
 5630    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5631    cx.assert_editor_state(indoc! {"
 5632        fox jumps over
 5633        Tˇhe quick brown
 5634        fox jumps over
 5635        ˇx jumps over
 5636        fox jumps over
 5637        tˇhe lazy dog"});
 5638}
 5639
 5640#[gpui::test]
 5641async fn test_copy_trim(cx: &mut TestAppContext) {
 5642    init_test(cx, |_| {});
 5643
 5644    let mut cx = EditorTestContext::new(cx).await;
 5645    cx.set_state(
 5646        r#"            «for selection in selections.iter() {
 5647            let mut start = selection.start;
 5648            let mut end = selection.end;
 5649            let is_entire_line = selection.is_empty();
 5650            if is_entire_line {
 5651                start = Point::new(start.row, 0);ˇ»
 5652                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5653            }
 5654        "#,
 5655    );
 5656    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5657    assert_eq!(
 5658        cx.read_from_clipboard()
 5659            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5660        Some(
 5661            "for selection in selections.iter() {
 5662            let mut start = selection.start;
 5663            let mut end = selection.end;
 5664            let is_entire_line = selection.is_empty();
 5665            if is_entire_line {
 5666                start = Point::new(start.row, 0);"
 5667                .to_string()
 5668        ),
 5669        "Regular copying preserves all indentation selected",
 5670    );
 5671    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5672    assert_eq!(
 5673        cx.read_from_clipboard()
 5674            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5675        Some(
 5676            "for selection in selections.iter() {
 5677let mut start = selection.start;
 5678let mut end = selection.end;
 5679let is_entire_line = selection.is_empty();
 5680if is_entire_line {
 5681    start = Point::new(start.row, 0);"
 5682                .to_string()
 5683        ),
 5684        "Copying with stripping should strip all leading whitespaces"
 5685    );
 5686
 5687    cx.set_state(
 5688        r#"       «     for selection in selections.iter() {
 5689            let mut start = selection.start;
 5690            let mut end = selection.end;
 5691            let is_entire_line = selection.is_empty();
 5692            if is_entire_line {
 5693                start = Point::new(start.row, 0);ˇ»
 5694                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5695            }
 5696        "#,
 5697    );
 5698    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5699    assert_eq!(
 5700        cx.read_from_clipboard()
 5701            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5702        Some(
 5703            "     for selection in selections.iter() {
 5704            let mut start = selection.start;
 5705            let mut end = selection.end;
 5706            let is_entire_line = selection.is_empty();
 5707            if is_entire_line {
 5708                start = Point::new(start.row, 0);"
 5709                .to_string()
 5710        ),
 5711        "Regular copying preserves all indentation selected",
 5712    );
 5713    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5714    assert_eq!(
 5715        cx.read_from_clipboard()
 5716            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5717        Some(
 5718            "for selection in selections.iter() {
 5719let mut start = selection.start;
 5720let mut end = selection.end;
 5721let is_entire_line = selection.is_empty();
 5722if is_entire_line {
 5723    start = Point::new(start.row, 0);"
 5724                .to_string()
 5725        ),
 5726        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 5727    );
 5728
 5729    cx.set_state(
 5730        r#"       «ˇ     for selection in selections.iter() {
 5731            let mut start = selection.start;
 5732            let mut end = selection.end;
 5733            let is_entire_line = selection.is_empty();
 5734            if is_entire_line {
 5735                start = Point::new(start.row, 0);»
 5736                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5737            }
 5738        "#,
 5739    );
 5740    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5741    assert_eq!(
 5742        cx.read_from_clipboard()
 5743            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5744        Some(
 5745            "     for selection in selections.iter() {
 5746            let mut start = selection.start;
 5747            let mut end = selection.end;
 5748            let is_entire_line = selection.is_empty();
 5749            if is_entire_line {
 5750                start = Point::new(start.row, 0);"
 5751                .to_string()
 5752        ),
 5753        "Regular copying for reverse selection works the same",
 5754    );
 5755    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5756    assert_eq!(
 5757        cx.read_from_clipboard()
 5758            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5759        Some(
 5760            "for selection in selections.iter() {
 5761let mut start = selection.start;
 5762let mut end = selection.end;
 5763let is_entire_line = selection.is_empty();
 5764if is_entire_line {
 5765    start = Point::new(start.row, 0);"
 5766                .to_string()
 5767        ),
 5768        "Copying with stripping for reverse selection works the same"
 5769    );
 5770
 5771    cx.set_state(
 5772        r#"            for selection «in selections.iter() {
 5773            let mut start = selection.start;
 5774            let mut end = selection.end;
 5775            let is_entire_line = selection.is_empty();
 5776            if is_entire_line {
 5777                start = Point::new(start.row, 0);ˇ»
 5778                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5779            }
 5780        "#,
 5781    );
 5782    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5783    assert_eq!(
 5784        cx.read_from_clipboard()
 5785            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5786        Some(
 5787            "in selections.iter() {
 5788            let mut start = selection.start;
 5789            let mut end = selection.end;
 5790            let is_entire_line = selection.is_empty();
 5791            if is_entire_line {
 5792                start = Point::new(start.row, 0);"
 5793                .to_string()
 5794        ),
 5795        "When selecting past the indent, the copying works as usual",
 5796    );
 5797    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5798    assert_eq!(
 5799        cx.read_from_clipboard()
 5800            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5801        Some(
 5802            "in selections.iter() {
 5803            let mut start = selection.start;
 5804            let mut end = selection.end;
 5805            let is_entire_line = selection.is_empty();
 5806            if is_entire_line {
 5807                start = Point::new(start.row, 0);"
 5808                .to_string()
 5809        ),
 5810        "When selecting past the indent, nothing is trimmed"
 5811    );
 5812
 5813    cx.set_state(
 5814        r#"            «for selection in selections.iter() {
 5815            let mut start = selection.start;
 5816
 5817            let mut end = selection.end;
 5818            let is_entire_line = selection.is_empty();
 5819            if is_entire_line {
 5820                start = Point::new(start.row, 0);
 5821ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5822            }
 5823        "#,
 5824    );
 5825    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5826    assert_eq!(
 5827        cx.read_from_clipboard()
 5828            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5829        Some(
 5830            "for selection in selections.iter() {
 5831let mut start = selection.start;
 5832
 5833let mut end = selection.end;
 5834let is_entire_line = selection.is_empty();
 5835if is_entire_line {
 5836    start = Point::new(start.row, 0);
 5837"
 5838            .to_string()
 5839        ),
 5840        "Copying with stripping should ignore empty lines"
 5841    );
 5842}
 5843
 5844#[gpui::test]
 5845async fn test_paste_multiline(cx: &mut TestAppContext) {
 5846    init_test(cx, |_| {});
 5847
 5848    let mut cx = EditorTestContext::new(cx).await;
 5849    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 5850
 5851    // Cut an indented block, without the leading whitespace.
 5852    cx.set_state(indoc! {"
 5853        const a: B = (
 5854            c(),
 5855            «d(
 5856                e,
 5857                f
 5858            )ˇ»
 5859        );
 5860    "});
 5861    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5862    cx.assert_editor_state(indoc! {"
 5863        const a: B = (
 5864            c(),
 5865            ˇ
 5866        );
 5867    "});
 5868
 5869    // Paste it at the same position.
 5870    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5871    cx.assert_editor_state(indoc! {"
 5872        const a: B = (
 5873            c(),
 5874            d(
 5875                e,
 5876                f
 5877 5878        );
 5879    "});
 5880
 5881    // Paste it at a line with a lower indent level.
 5882    cx.set_state(indoc! {"
 5883        ˇ
 5884        const a: B = (
 5885            c(),
 5886        );
 5887    "});
 5888    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5889    cx.assert_editor_state(indoc! {"
 5890        d(
 5891            e,
 5892            f
 5893 5894        const a: B = (
 5895            c(),
 5896        );
 5897    "});
 5898
 5899    // Cut an indented block, with the leading whitespace.
 5900    cx.set_state(indoc! {"
 5901        const a: B = (
 5902            c(),
 5903        «    d(
 5904                e,
 5905                f
 5906            )
 5907        ˇ»);
 5908    "});
 5909    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5910    cx.assert_editor_state(indoc! {"
 5911        const a: B = (
 5912            c(),
 5913        ˇ);
 5914    "});
 5915
 5916    // Paste it at the same position.
 5917    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5918    cx.assert_editor_state(indoc! {"
 5919        const a: B = (
 5920            c(),
 5921            d(
 5922                e,
 5923                f
 5924            )
 5925        ˇ);
 5926    "});
 5927
 5928    // Paste it at a line with a higher indent level.
 5929    cx.set_state(indoc! {"
 5930        const a: B = (
 5931            c(),
 5932            d(
 5933                e,
 5934 5935            )
 5936        );
 5937    "});
 5938    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5939    cx.assert_editor_state(indoc! {"
 5940        const a: B = (
 5941            c(),
 5942            d(
 5943                e,
 5944                f    d(
 5945                    e,
 5946                    f
 5947                )
 5948        ˇ
 5949            )
 5950        );
 5951    "});
 5952
 5953    // Copy an indented block, starting mid-line
 5954    cx.set_state(indoc! {"
 5955        const a: B = (
 5956            c(),
 5957            somethin«g(
 5958                e,
 5959                f
 5960            )ˇ»
 5961        );
 5962    "});
 5963    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5964
 5965    // Paste it on a line with a lower indent level
 5966    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 5967    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5968    cx.assert_editor_state(indoc! {"
 5969        const a: B = (
 5970            c(),
 5971            something(
 5972                e,
 5973                f
 5974            )
 5975        );
 5976        g(
 5977            e,
 5978            f
 5979"});
 5980}
 5981
 5982#[gpui::test]
 5983async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 5984    init_test(cx, |_| {});
 5985
 5986    cx.write_to_clipboard(ClipboardItem::new_string(
 5987        "    d(\n        e\n    );\n".into(),
 5988    ));
 5989
 5990    let mut cx = EditorTestContext::new(cx).await;
 5991    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 5992
 5993    cx.set_state(indoc! {"
 5994        fn a() {
 5995            b();
 5996            if c() {
 5997                ˇ
 5998            }
 5999        }
 6000    "});
 6001
 6002    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6003    cx.assert_editor_state(indoc! {"
 6004        fn a() {
 6005            b();
 6006            if c() {
 6007                d(
 6008                    e
 6009                );
 6010        ˇ
 6011            }
 6012        }
 6013    "});
 6014
 6015    cx.set_state(indoc! {"
 6016        fn a() {
 6017            b();
 6018            ˇ
 6019        }
 6020    "});
 6021
 6022    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6023    cx.assert_editor_state(indoc! {"
 6024        fn a() {
 6025            b();
 6026            d(
 6027                e
 6028            );
 6029        ˇ
 6030        }
 6031    "});
 6032}
 6033
 6034#[gpui::test]
 6035fn test_select_all(cx: &mut TestAppContext) {
 6036    init_test(cx, |_| {});
 6037
 6038    let editor = cx.add_window(|window, cx| {
 6039        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 6040        build_editor(buffer, window, cx)
 6041    });
 6042    _ = editor.update(cx, |editor, window, cx| {
 6043        editor.select_all(&SelectAll, window, cx);
 6044        assert_eq!(
 6045            editor.selections.display_ranges(cx),
 6046            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 6047        );
 6048    });
 6049}
 6050
 6051#[gpui::test]
 6052fn test_select_line(cx: &mut TestAppContext) {
 6053    init_test(cx, |_| {});
 6054
 6055    let editor = cx.add_window(|window, cx| {
 6056        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 6057        build_editor(buffer, window, cx)
 6058    });
 6059    _ = editor.update(cx, |editor, window, cx| {
 6060        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6061            s.select_display_ranges([
 6062                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6063                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6064                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6065                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 6066            ])
 6067        });
 6068        editor.select_line(&SelectLine, window, cx);
 6069        assert_eq!(
 6070            editor.selections.display_ranges(cx),
 6071            vec![
 6072                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 6073                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 6074            ]
 6075        );
 6076    });
 6077
 6078    _ = editor.update(cx, |editor, window, cx| {
 6079        editor.select_line(&SelectLine, window, cx);
 6080        assert_eq!(
 6081            editor.selections.display_ranges(cx),
 6082            vec![
 6083                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 6084                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 6085            ]
 6086        );
 6087    });
 6088
 6089    _ = editor.update(cx, |editor, window, cx| {
 6090        editor.select_line(&SelectLine, window, cx);
 6091        assert_eq!(
 6092            editor.selections.display_ranges(cx),
 6093            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 6094        );
 6095    });
 6096}
 6097
 6098#[gpui::test]
 6099async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 6100    init_test(cx, |_| {});
 6101    let mut cx = EditorTestContext::new(cx).await;
 6102
 6103    #[track_caller]
 6104    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 6105        cx.set_state(initial_state);
 6106        cx.update_editor(|e, window, cx| {
 6107            e.split_selection_into_lines(&SplitSelectionIntoLines, window, cx)
 6108        });
 6109        cx.assert_editor_state(expected_state);
 6110    }
 6111
 6112    // Selection starts and ends at the middle of lines, left-to-right
 6113    test(
 6114        &mut cx,
 6115        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 6116        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 6117    );
 6118    // Same thing, right-to-left
 6119    test(
 6120        &mut cx,
 6121        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 6122        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 6123    );
 6124
 6125    // Whole buffer, left-to-right, last line *doesn't* end with newline
 6126    test(
 6127        &mut cx,
 6128        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 6129        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 6130    );
 6131    // Same thing, right-to-left
 6132    test(
 6133        &mut cx,
 6134        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 6135        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 6136    );
 6137
 6138    // Whole buffer, left-to-right, last line ends with newline
 6139    test(
 6140        &mut cx,
 6141        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 6142        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 6143    );
 6144    // Same thing, right-to-left
 6145    test(
 6146        &mut cx,
 6147        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 6148        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 6149    );
 6150
 6151    // Starts at the end of a line, ends at the start of another
 6152    test(
 6153        &mut cx,
 6154        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 6155        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 6156    );
 6157}
 6158
 6159#[gpui::test]
 6160async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 6161    init_test(cx, |_| {});
 6162
 6163    let editor = cx.add_window(|window, cx| {
 6164        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 6165        build_editor(buffer, window, cx)
 6166    });
 6167
 6168    // setup
 6169    _ = editor.update(cx, |editor, window, cx| {
 6170        editor.fold_creases(
 6171            vec![
 6172                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 6173                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 6174                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 6175            ],
 6176            true,
 6177            window,
 6178            cx,
 6179        );
 6180        assert_eq!(
 6181            editor.display_text(cx),
 6182            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 6183        );
 6184    });
 6185
 6186    _ = editor.update(cx, |editor, window, cx| {
 6187        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6188            s.select_display_ranges([
 6189                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6190                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6191                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6192                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 6193            ])
 6194        });
 6195        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 6196        assert_eq!(
 6197            editor.display_text(cx),
 6198            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 6199        );
 6200    });
 6201    EditorTestContext::for_editor(editor, cx)
 6202        .await
 6203        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 6204
 6205    _ = editor.update(cx, |editor, window, cx| {
 6206        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6207            s.select_display_ranges([
 6208                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 6209            ])
 6210        });
 6211        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 6212        assert_eq!(
 6213            editor.display_text(cx),
 6214            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 6215        );
 6216        assert_eq!(
 6217            editor.selections.display_ranges(cx),
 6218            [
 6219                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 6220                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 6221                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 6222                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 6223                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 6224                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 6225                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 6226            ]
 6227        );
 6228    });
 6229    EditorTestContext::for_editor(editor, cx)
 6230        .await
 6231        .assert_editor_state(
 6232            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 6233        );
 6234}
 6235
 6236#[gpui::test]
 6237async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 6238    init_test(cx, |_| {});
 6239
 6240    let mut cx = EditorTestContext::new(cx).await;
 6241
 6242    cx.set_state(indoc!(
 6243        r#"abc
 6244           defˇghi
 6245
 6246           jk
 6247           nlmo
 6248           "#
 6249    ));
 6250
 6251    cx.update_editor(|editor, window, cx| {
 6252        editor.add_selection_above(&Default::default(), window, cx);
 6253    });
 6254
 6255    cx.assert_editor_state(indoc!(
 6256        r#"abcˇ
 6257           defˇghi
 6258
 6259           jk
 6260           nlmo
 6261           "#
 6262    ));
 6263
 6264    cx.update_editor(|editor, window, cx| {
 6265        editor.add_selection_above(&Default::default(), window, cx);
 6266    });
 6267
 6268    cx.assert_editor_state(indoc!(
 6269        r#"abcˇ
 6270            defˇghi
 6271
 6272            jk
 6273            nlmo
 6274            "#
 6275    ));
 6276
 6277    cx.update_editor(|editor, window, cx| {
 6278        editor.add_selection_below(&Default::default(), window, cx);
 6279    });
 6280
 6281    cx.assert_editor_state(indoc!(
 6282        r#"abc
 6283           defˇghi
 6284
 6285           jk
 6286           nlmo
 6287           "#
 6288    ));
 6289
 6290    cx.update_editor(|editor, window, cx| {
 6291        editor.undo_selection(&Default::default(), window, cx);
 6292    });
 6293
 6294    cx.assert_editor_state(indoc!(
 6295        r#"abcˇ
 6296           defˇghi
 6297
 6298           jk
 6299           nlmo
 6300           "#
 6301    ));
 6302
 6303    cx.update_editor(|editor, window, cx| {
 6304        editor.redo_selection(&Default::default(), window, cx);
 6305    });
 6306
 6307    cx.assert_editor_state(indoc!(
 6308        r#"abc
 6309           defˇghi
 6310
 6311           jk
 6312           nlmo
 6313           "#
 6314    ));
 6315
 6316    cx.update_editor(|editor, window, cx| {
 6317        editor.add_selection_below(&Default::default(), window, cx);
 6318    });
 6319
 6320    cx.assert_editor_state(indoc!(
 6321        r#"abc
 6322           defˇghi
 6323           ˇ
 6324           jk
 6325           nlmo
 6326           "#
 6327    ));
 6328
 6329    cx.update_editor(|editor, window, cx| {
 6330        editor.add_selection_below(&Default::default(), window, cx);
 6331    });
 6332
 6333    cx.assert_editor_state(indoc!(
 6334        r#"abc
 6335           defˇghi
 6336           ˇ
 6337           jkˇ
 6338           nlmo
 6339           "#
 6340    ));
 6341
 6342    cx.update_editor(|editor, window, cx| {
 6343        editor.add_selection_below(&Default::default(), window, cx);
 6344    });
 6345
 6346    cx.assert_editor_state(indoc!(
 6347        r#"abc
 6348           defˇghi
 6349           ˇ
 6350           jkˇ
 6351           nlmˇo
 6352           "#
 6353    ));
 6354
 6355    cx.update_editor(|editor, window, cx| {
 6356        editor.add_selection_below(&Default::default(), window, cx);
 6357    });
 6358
 6359    cx.assert_editor_state(indoc!(
 6360        r#"abc
 6361           defˇghi
 6362           ˇ
 6363           jkˇ
 6364           nlmˇo
 6365           ˇ"#
 6366    ));
 6367
 6368    // change selections
 6369    cx.set_state(indoc!(
 6370        r#"abc
 6371           def«ˇg»hi
 6372
 6373           jk
 6374           nlmo
 6375           "#
 6376    ));
 6377
 6378    cx.update_editor(|editor, window, cx| {
 6379        editor.add_selection_below(&Default::default(), window, cx);
 6380    });
 6381
 6382    cx.assert_editor_state(indoc!(
 6383        r#"abc
 6384           def«ˇg»hi
 6385
 6386           jk
 6387           nlm«ˇo»
 6388           "#
 6389    ));
 6390
 6391    cx.update_editor(|editor, window, cx| {
 6392        editor.add_selection_below(&Default::default(), window, cx);
 6393    });
 6394
 6395    cx.assert_editor_state(indoc!(
 6396        r#"abc
 6397           def«ˇg»hi
 6398
 6399           jk
 6400           nlm«ˇo»
 6401           "#
 6402    ));
 6403
 6404    cx.update_editor(|editor, window, cx| {
 6405        editor.add_selection_above(&Default::default(), window, cx);
 6406    });
 6407
 6408    cx.assert_editor_state(indoc!(
 6409        r#"abc
 6410           def«ˇg»hi
 6411
 6412           jk
 6413           nlmo
 6414           "#
 6415    ));
 6416
 6417    cx.update_editor(|editor, window, cx| {
 6418        editor.add_selection_above(&Default::default(), window, cx);
 6419    });
 6420
 6421    cx.assert_editor_state(indoc!(
 6422        r#"abc
 6423           def«ˇg»hi
 6424
 6425           jk
 6426           nlmo
 6427           "#
 6428    ));
 6429
 6430    // Change selections again
 6431    cx.set_state(indoc!(
 6432        r#"a«bc
 6433           defgˇ»hi
 6434
 6435           jk
 6436           nlmo
 6437           "#
 6438    ));
 6439
 6440    cx.update_editor(|editor, window, cx| {
 6441        editor.add_selection_below(&Default::default(), window, cx);
 6442    });
 6443
 6444    cx.assert_editor_state(indoc!(
 6445        r#"a«bcˇ»
 6446           d«efgˇ»hi
 6447
 6448           j«kˇ»
 6449           nlmo
 6450           "#
 6451    ));
 6452
 6453    cx.update_editor(|editor, window, cx| {
 6454        editor.add_selection_below(&Default::default(), window, cx);
 6455    });
 6456    cx.assert_editor_state(indoc!(
 6457        r#"a«bcˇ»
 6458           d«efgˇ»hi
 6459
 6460           j«kˇ»
 6461           n«lmoˇ»
 6462           "#
 6463    ));
 6464    cx.update_editor(|editor, window, cx| {
 6465        editor.add_selection_above(&Default::default(), window, cx);
 6466    });
 6467
 6468    cx.assert_editor_state(indoc!(
 6469        r#"a«bcˇ»
 6470           d«efgˇ»hi
 6471
 6472           j«kˇ»
 6473           nlmo
 6474           "#
 6475    ));
 6476
 6477    // Change selections again
 6478    cx.set_state(indoc!(
 6479        r#"abc
 6480           d«ˇefghi
 6481
 6482           jk
 6483           nlm»o
 6484           "#
 6485    ));
 6486
 6487    cx.update_editor(|editor, window, cx| {
 6488        editor.add_selection_above(&Default::default(), window, cx);
 6489    });
 6490
 6491    cx.assert_editor_state(indoc!(
 6492        r#"a«ˇbc»
 6493           d«ˇef»ghi
 6494
 6495           j«ˇk»
 6496           n«ˇlm»o
 6497           "#
 6498    ));
 6499
 6500    cx.update_editor(|editor, window, cx| {
 6501        editor.add_selection_below(&Default::default(), window, cx);
 6502    });
 6503
 6504    cx.assert_editor_state(indoc!(
 6505        r#"abc
 6506           d«ˇef»ghi
 6507
 6508           j«ˇk»
 6509           n«ˇlm»o
 6510           "#
 6511    ));
 6512}
 6513
 6514#[gpui::test]
 6515async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 6516    init_test(cx, |_| {});
 6517    let mut cx = EditorTestContext::new(cx).await;
 6518
 6519    cx.set_state(indoc!(
 6520        r#"line onˇe
 6521           liˇne two
 6522           line three
 6523           line four"#
 6524    ));
 6525
 6526    cx.update_editor(|editor, window, cx| {
 6527        editor.add_selection_below(&Default::default(), window, cx);
 6528    });
 6529
 6530    // test multiple cursors expand in the same direction
 6531    cx.assert_editor_state(indoc!(
 6532        r#"line onˇe
 6533           liˇne twˇo
 6534           liˇne three
 6535           line four"#
 6536    ));
 6537
 6538    cx.update_editor(|editor, window, cx| {
 6539        editor.add_selection_below(&Default::default(), window, cx);
 6540    });
 6541
 6542    cx.update_editor(|editor, window, cx| {
 6543        editor.add_selection_below(&Default::default(), window, cx);
 6544    });
 6545
 6546    // test multiple cursors expand below overflow
 6547    cx.assert_editor_state(indoc!(
 6548        r#"line onˇe
 6549           liˇne twˇo
 6550           liˇne thˇree
 6551           liˇne foˇur"#
 6552    ));
 6553
 6554    cx.update_editor(|editor, window, cx| {
 6555        editor.add_selection_above(&Default::default(), window, cx);
 6556    });
 6557
 6558    // test multiple cursors retrieves back correctly
 6559    cx.assert_editor_state(indoc!(
 6560        r#"line onˇe
 6561           liˇne twˇo
 6562           liˇne thˇree
 6563           line four"#
 6564    ));
 6565
 6566    cx.update_editor(|editor, window, cx| {
 6567        editor.add_selection_above(&Default::default(), window, cx);
 6568    });
 6569
 6570    cx.update_editor(|editor, window, cx| {
 6571        editor.add_selection_above(&Default::default(), window, cx);
 6572    });
 6573
 6574    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 6575    cx.assert_editor_state(indoc!(
 6576        r#"liˇne onˇe
 6577           liˇne two
 6578           line three
 6579           line four"#
 6580    ));
 6581
 6582    cx.update_editor(|editor, window, cx| {
 6583        editor.undo_selection(&Default::default(), window, cx);
 6584    });
 6585
 6586    // test undo
 6587    cx.assert_editor_state(indoc!(
 6588        r#"line onˇe
 6589           liˇne twˇo
 6590           line three
 6591           line four"#
 6592    ));
 6593
 6594    cx.update_editor(|editor, window, cx| {
 6595        editor.redo_selection(&Default::default(), window, cx);
 6596    });
 6597
 6598    // test redo
 6599    cx.assert_editor_state(indoc!(
 6600        r#"liˇne onˇe
 6601           liˇne two
 6602           line three
 6603           line four"#
 6604    ));
 6605
 6606    cx.set_state(indoc!(
 6607        r#"abcd
 6608           ef«ghˇ»
 6609           ijkl
 6610           «mˇ»nop"#
 6611    ));
 6612
 6613    cx.update_editor(|editor, window, cx| {
 6614        editor.add_selection_above(&Default::default(), window, cx);
 6615    });
 6616
 6617    // test multiple selections expand in the same direction
 6618    cx.assert_editor_state(indoc!(
 6619        r#"ab«cdˇ»
 6620           ef«ghˇ»
 6621           «iˇ»jkl
 6622           «mˇ»nop"#
 6623    ));
 6624
 6625    cx.update_editor(|editor, window, cx| {
 6626        editor.add_selection_above(&Default::default(), window, cx);
 6627    });
 6628
 6629    // test multiple selection upward overflow
 6630    cx.assert_editor_state(indoc!(
 6631        r#"ab«cdˇ»
 6632           «eˇ»f«ghˇ»
 6633           «iˇ»jkl
 6634           «mˇ»nop"#
 6635    ));
 6636
 6637    cx.update_editor(|editor, window, cx| {
 6638        editor.add_selection_below(&Default::default(), window, cx);
 6639    });
 6640
 6641    // test multiple selection retrieves back correctly
 6642    cx.assert_editor_state(indoc!(
 6643        r#"abcd
 6644           ef«ghˇ»
 6645           «iˇ»jkl
 6646           «mˇ»nop"#
 6647    ));
 6648
 6649    cx.update_editor(|editor, window, cx| {
 6650        editor.add_selection_below(&Default::default(), window, cx);
 6651    });
 6652
 6653    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 6654    cx.assert_editor_state(indoc!(
 6655        r#"abcd
 6656           ef«ghˇ»
 6657           ij«klˇ»
 6658           «mˇ»nop"#
 6659    ));
 6660
 6661    cx.update_editor(|editor, window, cx| {
 6662        editor.undo_selection(&Default::default(), window, cx);
 6663    });
 6664
 6665    // test undo
 6666    cx.assert_editor_state(indoc!(
 6667        r#"abcd
 6668           ef«ghˇ»
 6669           «iˇ»jkl
 6670           «mˇ»nop"#
 6671    ));
 6672
 6673    cx.update_editor(|editor, window, cx| {
 6674        editor.redo_selection(&Default::default(), window, cx);
 6675    });
 6676
 6677    // test redo
 6678    cx.assert_editor_state(indoc!(
 6679        r#"abcd
 6680           ef«ghˇ»
 6681           ij«klˇ»
 6682           «mˇ»nop"#
 6683    ));
 6684}
 6685
 6686#[gpui::test]
 6687async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 6688    init_test(cx, |_| {});
 6689    let mut cx = EditorTestContext::new(cx).await;
 6690
 6691    cx.set_state(indoc!(
 6692        r#"line onˇe
 6693           liˇne two
 6694           line three
 6695           line four"#
 6696    ));
 6697
 6698    cx.update_editor(|editor, window, cx| {
 6699        editor.add_selection_below(&Default::default(), window, cx);
 6700        editor.add_selection_below(&Default::default(), window, cx);
 6701        editor.add_selection_below(&Default::default(), window, cx);
 6702    });
 6703
 6704    // initial state with two multi cursor groups
 6705    cx.assert_editor_state(indoc!(
 6706        r#"line onˇe
 6707           liˇne twˇo
 6708           liˇne thˇree
 6709           liˇne foˇur"#
 6710    ));
 6711
 6712    // add single cursor in middle - simulate opt click
 6713    cx.update_editor(|editor, window, cx| {
 6714        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 6715        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 6716        editor.end_selection(window, cx);
 6717    });
 6718
 6719    cx.assert_editor_state(indoc!(
 6720        r#"line onˇe
 6721           liˇne twˇo
 6722           liˇneˇ thˇree
 6723           liˇne foˇur"#
 6724    ));
 6725
 6726    cx.update_editor(|editor, window, cx| {
 6727        editor.add_selection_above(&Default::default(), window, cx);
 6728    });
 6729
 6730    // test new added selection expands above and existing selection shrinks
 6731    cx.assert_editor_state(indoc!(
 6732        r#"line onˇe
 6733           liˇneˇ twˇo
 6734           liˇneˇ thˇree
 6735           line four"#
 6736    ));
 6737
 6738    cx.update_editor(|editor, window, cx| {
 6739        editor.add_selection_above(&Default::default(), window, cx);
 6740    });
 6741
 6742    // test new added selection expands above and existing selection shrinks
 6743    cx.assert_editor_state(indoc!(
 6744        r#"lineˇ onˇe
 6745           liˇneˇ twˇo
 6746           lineˇ three
 6747           line four"#
 6748    ));
 6749
 6750    // intial state with two selection groups
 6751    cx.set_state(indoc!(
 6752        r#"abcd
 6753           ef«ghˇ»
 6754           ijkl
 6755           «mˇ»nop"#
 6756    ));
 6757
 6758    cx.update_editor(|editor, window, cx| {
 6759        editor.add_selection_above(&Default::default(), window, cx);
 6760        editor.add_selection_above(&Default::default(), window, cx);
 6761    });
 6762
 6763    cx.assert_editor_state(indoc!(
 6764        r#"ab«cdˇ»
 6765           «eˇ»f«ghˇ»
 6766           «iˇ»jkl
 6767           «mˇ»nop"#
 6768    ));
 6769
 6770    // add single selection in middle - simulate opt drag
 6771    cx.update_editor(|editor, window, cx| {
 6772        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 6773        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 6774        editor.update_selection(
 6775            DisplayPoint::new(DisplayRow(2), 4),
 6776            0,
 6777            gpui::Point::<f32>::default(),
 6778            window,
 6779            cx,
 6780        );
 6781        editor.end_selection(window, cx);
 6782    });
 6783
 6784    cx.assert_editor_state(indoc!(
 6785        r#"ab«cdˇ»
 6786           «eˇ»f«ghˇ»
 6787           «iˇ»jk«lˇ»
 6788           «mˇ»nop"#
 6789    ));
 6790
 6791    cx.update_editor(|editor, window, cx| {
 6792        editor.add_selection_below(&Default::default(), window, cx);
 6793    });
 6794
 6795    // test new added selection expands below, others shrinks from above
 6796    cx.assert_editor_state(indoc!(
 6797        r#"abcd
 6798           ef«ghˇ»
 6799           «iˇ»jk«lˇ»
 6800           «mˇ»no«pˇ»"#
 6801    ));
 6802}
 6803
 6804#[gpui::test]
 6805async fn test_select_next(cx: &mut TestAppContext) {
 6806    init_test(cx, |_| {});
 6807
 6808    let mut cx = EditorTestContext::new(cx).await;
 6809    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 6810
 6811    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6812        .unwrap();
 6813    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6814
 6815    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6816        .unwrap();
 6817    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 6818
 6819    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 6820    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6821
 6822    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 6823    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 6824
 6825    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6826        .unwrap();
 6827    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6828
 6829    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6830        .unwrap();
 6831    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6832
 6833    // Test selection direction should be preserved
 6834    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 6835
 6836    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6837        .unwrap();
 6838    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 6839}
 6840
 6841#[gpui::test]
 6842async fn test_select_all_matches(cx: &mut TestAppContext) {
 6843    init_test(cx, |_| {});
 6844
 6845    let mut cx = EditorTestContext::new(cx).await;
 6846
 6847    // Test caret-only selections
 6848    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 6849    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6850        .unwrap();
 6851    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6852
 6853    // Test left-to-right selections
 6854    cx.set_state("abc\n«abcˇ»\nabc");
 6855    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6856        .unwrap();
 6857    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 6858
 6859    // Test right-to-left selections
 6860    cx.set_state("abc\n«ˇabc»\nabc");
 6861    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6862        .unwrap();
 6863    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 6864
 6865    // Test selecting whitespace with caret selection
 6866    cx.set_state("abc\nˇ   abc\nabc");
 6867    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6868        .unwrap();
 6869    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 6870
 6871    // Test selecting whitespace with left-to-right selection
 6872    cx.set_state("abc\n«ˇ  »abc\nabc");
 6873    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6874        .unwrap();
 6875    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 6876
 6877    // Test no matches with right-to-left selection
 6878    cx.set_state("abc\n«  ˇ»abc\nabc");
 6879    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6880        .unwrap();
 6881    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 6882
 6883    // Test with a single word and clip_at_line_ends=true (#29823)
 6884    cx.set_state("aˇbc");
 6885    cx.update_editor(|e, window, cx| {
 6886        e.set_clip_at_line_ends(true, cx);
 6887        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 6888        e.set_clip_at_line_ends(false, cx);
 6889    });
 6890    cx.assert_editor_state("«abcˇ»");
 6891}
 6892
 6893#[gpui::test]
 6894async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 6895    init_test(cx, |_| {});
 6896
 6897    let mut cx = EditorTestContext::new(cx).await;
 6898
 6899    let large_body_1 = "\nd".repeat(200);
 6900    let large_body_2 = "\ne".repeat(200);
 6901
 6902    cx.set_state(&format!(
 6903        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 6904    ));
 6905    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 6906        let scroll_position = editor.scroll_position(cx);
 6907        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 6908        scroll_position
 6909    });
 6910
 6911    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6912        .unwrap();
 6913    cx.assert_editor_state(&format!(
 6914        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 6915    ));
 6916    let scroll_position_after_selection =
 6917        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 6918    assert_eq!(
 6919        initial_scroll_position, scroll_position_after_selection,
 6920        "Scroll position should not change after selecting all matches"
 6921    );
 6922}
 6923
 6924#[gpui::test]
 6925async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 6926    init_test(cx, |_| {});
 6927
 6928    let mut cx = EditorLspTestContext::new_rust(
 6929        lsp::ServerCapabilities {
 6930            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 6931            ..Default::default()
 6932        },
 6933        cx,
 6934    )
 6935    .await;
 6936
 6937    cx.set_state(indoc! {"
 6938        line 1
 6939        line 2
 6940        linˇe 3
 6941        line 4
 6942        line 5
 6943    "});
 6944
 6945    // Make an edit
 6946    cx.update_editor(|editor, window, cx| {
 6947        editor.handle_input("X", window, cx);
 6948    });
 6949
 6950    // Move cursor to a different position
 6951    cx.update_editor(|editor, window, cx| {
 6952        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6953            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 6954        });
 6955    });
 6956
 6957    cx.assert_editor_state(indoc! {"
 6958        line 1
 6959        line 2
 6960        linXe 3
 6961        line 4
 6962        liˇne 5
 6963    "});
 6964
 6965    cx.lsp
 6966        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 6967            Ok(Some(vec![lsp::TextEdit::new(
 6968                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 6969                "PREFIX ".to_string(),
 6970            )]))
 6971        });
 6972
 6973    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 6974        .unwrap()
 6975        .await
 6976        .unwrap();
 6977
 6978    cx.assert_editor_state(indoc! {"
 6979        PREFIX line 1
 6980        line 2
 6981        linXe 3
 6982        line 4
 6983        liˇne 5
 6984    "});
 6985
 6986    // Undo formatting
 6987    cx.update_editor(|editor, window, cx| {
 6988        editor.undo(&Default::default(), window, cx);
 6989    });
 6990
 6991    // Verify cursor moved back to position after edit
 6992    cx.assert_editor_state(indoc! {"
 6993        line 1
 6994        line 2
 6995        linXˇe 3
 6996        line 4
 6997        line 5
 6998    "});
 6999}
 7000
 7001#[gpui::test]
 7002async fn test_undo_inline_completion_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 7003    init_test(cx, |_| {});
 7004
 7005    let mut cx = EditorTestContext::new(cx).await;
 7006
 7007    let provider = cx.new(|_| FakeInlineCompletionProvider::default());
 7008    cx.update_editor(|editor, window, cx| {
 7009        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 7010    });
 7011
 7012    cx.set_state(indoc! {"
 7013        line 1
 7014        line 2
 7015        linˇe 3
 7016        line 4
 7017        line 5
 7018        line 6
 7019        line 7
 7020        line 8
 7021        line 9
 7022        line 10
 7023    "});
 7024
 7025    let snapshot = cx.buffer_snapshot();
 7026    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 7027
 7028    cx.update(|_, cx| {
 7029        provider.update(cx, |provider, _| {
 7030            provider.set_inline_completion(Some(inline_completion::InlineCompletion {
 7031                id: None,
 7032                edits: vec![(edit_position..edit_position, "X".into())],
 7033                edit_preview: None,
 7034            }))
 7035        })
 7036    });
 7037
 7038    cx.update_editor(|editor, window, cx| editor.update_visible_inline_completion(window, cx));
 7039    cx.update_editor(|editor, window, cx| {
 7040        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 7041    });
 7042
 7043    cx.assert_editor_state(indoc! {"
 7044        line 1
 7045        line 2
 7046        lineXˇ 3
 7047        line 4
 7048        line 5
 7049        line 6
 7050        line 7
 7051        line 8
 7052        line 9
 7053        line 10
 7054    "});
 7055
 7056    cx.update_editor(|editor, window, cx| {
 7057        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7058            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 7059        });
 7060    });
 7061
 7062    cx.assert_editor_state(indoc! {"
 7063        line 1
 7064        line 2
 7065        lineX 3
 7066        line 4
 7067        line 5
 7068        line 6
 7069        line 7
 7070        line 8
 7071        line 9
 7072        liˇne 10
 7073    "});
 7074
 7075    cx.update_editor(|editor, window, cx| {
 7076        editor.undo(&Default::default(), window, cx);
 7077    });
 7078
 7079    cx.assert_editor_state(indoc! {"
 7080        line 1
 7081        line 2
 7082        lineˇ 3
 7083        line 4
 7084        line 5
 7085        line 6
 7086        line 7
 7087        line 8
 7088        line 9
 7089        line 10
 7090    "});
 7091}
 7092
 7093#[gpui::test]
 7094async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 7095    init_test(cx, |_| {});
 7096
 7097    let mut cx = EditorTestContext::new(cx).await;
 7098    cx.set_state(
 7099        r#"let foo = 2;
 7100lˇet foo = 2;
 7101let fooˇ = 2;
 7102let foo = 2;
 7103let foo = ˇ2;"#,
 7104    );
 7105
 7106    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7107        .unwrap();
 7108    cx.assert_editor_state(
 7109        r#"let foo = 2;
 7110«letˇ» foo = 2;
 7111let «fooˇ» = 2;
 7112let foo = 2;
 7113let foo = «2ˇ»;"#,
 7114    );
 7115
 7116    // noop for multiple selections with different contents
 7117    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7118        .unwrap();
 7119    cx.assert_editor_state(
 7120        r#"let foo = 2;
 7121«letˇ» foo = 2;
 7122let «fooˇ» = 2;
 7123let foo = 2;
 7124let foo = «2ˇ»;"#,
 7125    );
 7126
 7127    // Test last selection direction should be preserved
 7128    cx.set_state(
 7129        r#"let foo = 2;
 7130let foo = 2;
 7131let «fooˇ» = 2;
 7132let «ˇfoo» = 2;
 7133let foo = 2;"#,
 7134    );
 7135
 7136    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7137        .unwrap();
 7138    cx.assert_editor_state(
 7139        r#"let foo = 2;
 7140let foo = 2;
 7141let «fooˇ» = 2;
 7142let «ˇfoo» = 2;
 7143let «ˇfoo» = 2;"#,
 7144    );
 7145}
 7146
 7147#[gpui::test]
 7148async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 7149    init_test(cx, |_| {});
 7150
 7151    let mut cx =
 7152        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 7153
 7154    cx.assert_editor_state(indoc! {"
 7155        ˇbbb
 7156        ccc
 7157
 7158        bbb
 7159        ccc
 7160        "});
 7161    cx.dispatch_action(SelectPrevious::default());
 7162    cx.assert_editor_state(indoc! {"
 7163                «bbbˇ»
 7164                ccc
 7165
 7166                bbb
 7167                ccc
 7168                "});
 7169    cx.dispatch_action(SelectPrevious::default());
 7170    cx.assert_editor_state(indoc! {"
 7171                «bbbˇ»
 7172                ccc
 7173
 7174                «bbbˇ»
 7175                ccc
 7176                "});
 7177}
 7178
 7179#[gpui::test]
 7180async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 7181    init_test(cx, |_| {});
 7182
 7183    let mut cx = EditorTestContext::new(cx).await;
 7184    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7185
 7186    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7187        .unwrap();
 7188    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7189
 7190    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7191        .unwrap();
 7192    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 7193
 7194    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7195    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7196
 7197    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7198    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 7199
 7200    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7201        .unwrap();
 7202    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 7203
 7204    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7205        .unwrap();
 7206    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7207}
 7208
 7209#[gpui::test]
 7210async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 7211    init_test(cx, |_| {});
 7212
 7213    let mut cx = EditorTestContext::new(cx).await;
 7214    cx.set_state("");
 7215
 7216    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7217        .unwrap();
 7218    cx.assert_editor_state("«aˇ»");
 7219    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7220        .unwrap();
 7221    cx.assert_editor_state("«aˇ»");
 7222}
 7223
 7224#[gpui::test]
 7225async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 7226    init_test(cx, |_| {});
 7227
 7228    let mut cx = EditorTestContext::new(cx).await;
 7229    cx.set_state(
 7230        r#"let foo = 2;
 7231lˇet foo = 2;
 7232let fooˇ = 2;
 7233let foo = 2;
 7234let foo = ˇ2;"#,
 7235    );
 7236
 7237    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7238        .unwrap();
 7239    cx.assert_editor_state(
 7240        r#"let foo = 2;
 7241«letˇ» foo = 2;
 7242let «fooˇ» = 2;
 7243let foo = 2;
 7244let foo = «2ˇ»;"#,
 7245    );
 7246
 7247    // noop for multiple selections with different contents
 7248    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7249        .unwrap();
 7250    cx.assert_editor_state(
 7251        r#"let foo = 2;
 7252«letˇ» foo = 2;
 7253let «fooˇ» = 2;
 7254let foo = 2;
 7255let foo = «2ˇ»;"#,
 7256    );
 7257}
 7258
 7259#[gpui::test]
 7260async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 7261    init_test(cx, |_| {});
 7262
 7263    let mut cx = EditorTestContext::new(cx).await;
 7264    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 7265
 7266    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7267        .unwrap();
 7268    // selection direction is preserved
 7269    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 7270
 7271    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7272        .unwrap();
 7273    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7274
 7275    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7276    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 7277
 7278    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7279    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7280
 7281    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7282        .unwrap();
 7283    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 7284
 7285    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7286        .unwrap();
 7287    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 7288}
 7289
 7290#[gpui::test]
 7291async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 7292    init_test(cx, |_| {});
 7293
 7294    let language = Arc::new(Language::new(
 7295        LanguageConfig::default(),
 7296        Some(tree_sitter_rust::LANGUAGE.into()),
 7297    ));
 7298
 7299    let text = r#"
 7300        use mod1::mod2::{mod3, mod4};
 7301
 7302        fn fn_1(param1: bool, param2: &str) {
 7303            let var1 = "text";
 7304        }
 7305    "#
 7306    .unindent();
 7307
 7308    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7309    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7310    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7311
 7312    editor
 7313        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7314        .await;
 7315
 7316    editor.update_in(cx, |editor, window, cx| {
 7317        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7318            s.select_display_ranges([
 7319                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 7320                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 7321                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 7322            ]);
 7323        });
 7324        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7325    });
 7326    editor.update(cx, |editor, cx| {
 7327        assert_text_with_selections(
 7328            editor,
 7329            indoc! {r#"
 7330                use mod1::mod2::{mod3, «mod4ˇ»};
 7331
 7332                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7333                    let var1 = "«ˇtext»";
 7334                }
 7335            "#},
 7336            cx,
 7337        );
 7338    });
 7339
 7340    editor.update_in(cx, |editor, window, cx| {
 7341        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7342    });
 7343    editor.update(cx, |editor, cx| {
 7344        assert_text_with_selections(
 7345            editor,
 7346            indoc! {r#"
 7347                use mod1::mod2::«{mod3, mod4}ˇ»;
 7348
 7349                «ˇfn fn_1(param1: bool, param2: &str) {
 7350                    let var1 = "text";
 7351 7352            "#},
 7353            cx,
 7354        );
 7355    });
 7356
 7357    editor.update_in(cx, |editor, window, cx| {
 7358        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7359    });
 7360    assert_eq!(
 7361        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7362        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7363    );
 7364
 7365    // Trying to expand the selected syntax node one more time has no effect.
 7366    editor.update_in(cx, |editor, window, cx| {
 7367        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7368    });
 7369    assert_eq!(
 7370        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7371        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7372    );
 7373
 7374    editor.update_in(cx, |editor, window, cx| {
 7375        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7376    });
 7377    editor.update(cx, |editor, cx| {
 7378        assert_text_with_selections(
 7379            editor,
 7380            indoc! {r#"
 7381                use mod1::mod2::«{mod3, mod4}ˇ»;
 7382
 7383                «ˇfn fn_1(param1: bool, param2: &str) {
 7384                    let var1 = "text";
 7385 7386            "#},
 7387            cx,
 7388        );
 7389    });
 7390
 7391    editor.update_in(cx, |editor, window, cx| {
 7392        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7393    });
 7394    editor.update(cx, |editor, cx| {
 7395        assert_text_with_selections(
 7396            editor,
 7397            indoc! {r#"
 7398                use mod1::mod2::{mod3, «mod4ˇ»};
 7399
 7400                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7401                    let var1 = "«ˇtext»";
 7402                }
 7403            "#},
 7404            cx,
 7405        );
 7406    });
 7407
 7408    editor.update_in(cx, |editor, window, cx| {
 7409        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7410    });
 7411    editor.update(cx, |editor, cx| {
 7412        assert_text_with_selections(
 7413            editor,
 7414            indoc! {r#"
 7415                use mod1::mod2::{mod3, mo«ˇ»d4};
 7416
 7417                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7418                    let var1 = "te«ˇ»xt";
 7419                }
 7420            "#},
 7421            cx,
 7422        );
 7423    });
 7424
 7425    // Trying to shrink the selected syntax node one more time has no effect.
 7426    editor.update_in(cx, |editor, window, cx| {
 7427        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7428    });
 7429    editor.update_in(cx, |editor, _, cx| {
 7430        assert_text_with_selections(
 7431            editor,
 7432            indoc! {r#"
 7433                use mod1::mod2::{mod3, mo«ˇ»d4};
 7434
 7435                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7436                    let var1 = "te«ˇ»xt";
 7437                }
 7438            "#},
 7439            cx,
 7440        );
 7441    });
 7442
 7443    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 7444    // a fold.
 7445    editor.update_in(cx, |editor, window, cx| {
 7446        editor.fold_creases(
 7447            vec![
 7448                Crease::simple(
 7449                    Point::new(0, 21)..Point::new(0, 24),
 7450                    FoldPlaceholder::test(),
 7451                ),
 7452                Crease::simple(
 7453                    Point::new(3, 20)..Point::new(3, 22),
 7454                    FoldPlaceholder::test(),
 7455                ),
 7456            ],
 7457            true,
 7458            window,
 7459            cx,
 7460        );
 7461        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7462    });
 7463    editor.update(cx, |editor, cx| {
 7464        assert_text_with_selections(
 7465            editor,
 7466            indoc! {r#"
 7467                use mod1::mod2::«{mod3, mod4}ˇ»;
 7468
 7469                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7470                    let var1 = "«ˇtext»";
 7471                }
 7472            "#},
 7473            cx,
 7474        );
 7475    });
 7476}
 7477
 7478#[gpui::test]
 7479async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 7480    init_test(cx, |_| {});
 7481
 7482    let language = Arc::new(Language::new(
 7483        LanguageConfig::default(),
 7484        Some(tree_sitter_rust::LANGUAGE.into()),
 7485    ));
 7486
 7487    let text = "let a = 2;";
 7488
 7489    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7490    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7491    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7492
 7493    editor
 7494        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7495        .await;
 7496
 7497    // Test case 1: Cursor at end of word
 7498    editor.update_in(cx, |editor, window, cx| {
 7499        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7500            s.select_display_ranges([
 7501                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 7502            ]);
 7503        });
 7504    });
 7505    editor.update(cx, |editor, cx| {
 7506        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 7507    });
 7508    editor.update_in(cx, |editor, window, cx| {
 7509        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7510    });
 7511    editor.update(cx, |editor, cx| {
 7512        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 7513    });
 7514    editor.update_in(cx, |editor, window, cx| {
 7515        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7516    });
 7517    editor.update(cx, |editor, cx| {
 7518        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7519    });
 7520
 7521    // Test case 2: Cursor at end of statement
 7522    editor.update_in(cx, |editor, window, cx| {
 7523        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7524            s.select_display_ranges([
 7525                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 7526            ]);
 7527        });
 7528    });
 7529    editor.update(cx, |editor, cx| {
 7530        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 7531    });
 7532    editor.update_in(cx, |editor, window, cx| {
 7533        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7534    });
 7535    editor.update(cx, |editor, cx| {
 7536        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7537    });
 7538}
 7539
 7540#[gpui::test]
 7541async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 7542    init_test(cx, |_| {});
 7543
 7544    let language = Arc::new(Language::new(
 7545        LanguageConfig::default(),
 7546        Some(tree_sitter_rust::LANGUAGE.into()),
 7547    ));
 7548
 7549    let text = r#"
 7550        use mod1::mod2::{mod3, mod4};
 7551
 7552        fn fn_1(param1: bool, param2: &str) {
 7553            let var1 = "hello world";
 7554        }
 7555    "#
 7556    .unindent();
 7557
 7558    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7559    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7560    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7561
 7562    editor
 7563        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7564        .await;
 7565
 7566    // Test 1: Cursor on a letter of a string word
 7567    editor.update_in(cx, |editor, window, cx| {
 7568        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7569            s.select_display_ranges([
 7570                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 7571            ]);
 7572        });
 7573    });
 7574    editor.update_in(cx, |editor, window, cx| {
 7575        assert_text_with_selections(
 7576            editor,
 7577            indoc! {r#"
 7578                use mod1::mod2::{mod3, mod4};
 7579
 7580                fn fn_1(param1: bool, param2: &str) {
 7581                    let var1 = "hˇello world";
 7582                }
 7583            "#},
 7584            cx,
 7585        );
 7586        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7587        assert_text_with_selections(
 7588            editor,
 7589            indoc! {r#"
 7590                use mod1::mod2::{mod3, mod4};
 7591
 7592                fn fn_1(param1: bool, param2: &str) {
 7593                    let var1 = "«ˇhello» world";
 7594                }
 7595            "#},
 7596            cx,
 7597        );
 7598    });
 7599
 7600    // Test 2: Partial selection within a word
 7601    editor.update_in(cx, |editor, window, cx| {
 7602        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7603            s.select_display_ranges([
 7604                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 7605            ]);
 7606        });
 7607    });
 7608    editor.update_in(cx, |editor, window, cx| {
 7609        assert_text_with_selections(
 7610            editor,
 7611            indoc! {r#"
 7612                use mod1::mod2::{mod3, mod4};
 7613
 7614                fn fn_1(param1: bool, param2: &str) {
 7615                    let var1 = "h«elˇ»lo world";
 7616                }
 7617            "#},
 7618            cx,
 7619        );
 7620        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7621        assert_text_with_selections(
 7622            editor,
 7623            indoc! {r#"
 7624                use mod1::mod2::{mod3, mod4};
 7625
 7626                fn fn_1(param1: bool, param2: &str) {
 7627                    let var1 = "«ˇhello» world";
 7628                }
 7629            "#},
 7630            cx,
 7631        );
 7632    });
 7633
 7634    // Test 3: Complete word already selected
 7635    editor.update_in(cx, |editor, window, cx| {
 7636        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7637            s.select_display_ranges([
 7638                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 7639            ]);
 7640        });
 7641    });
 7642    editor.update_in(cx, |editor, window, cx| {
 7643        assert_text_with_selections(
 7644            editor,
 7645            indoc! {r#"
 7646                use mod1::mod2::{mod3, mod4};
 7647
 7648                fn fn_1(param1: bool, param2: &str) {
 7649                    let var1 = "«helloˇ» world";
 7650                }
 7651            "#},
 7652            cx,
 7653        );
 7654        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7655        assert_text_with_selections(
 7656            editor,
 7657            indoc! {r#"
 7658                use mod1::mod2::{mod3, mod4};
 7659
 7660                fn fn_1(param1: bool, param2: &str) {
 7661                    let var1 = "«hello worldˇ»";
 7662                }
 7663            "#},
 7664            cx,
 7665        );
 7666    });
 7667
 7668    // Test 4: Selection spanning across words
 7669    editor.update_in(cx, |editor, window, cx| {
 7670        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7671            s.select_display_ranges([
 7672                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 7673            ]);
 7674        });
 7675    });
 7676    editor.update_in(cx, |editor, window, cx| {
 7677        assert_text_with_selections(
 7678            editor,
 7679            indoc! {r#"
 7680                use mod1::mod2::{mod3, mod4};
 7681
 7682                fn fn_1(param1: bool, param2: &str) {
 7683                    let var1 = "hel«lo woˇ»rld";
 7684                }
 7685            "#},
 7686            cx,
 7687        );
 7688        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7689        assert_text_with_selections(
 7690            editor,
 7691            indoc! {r#"
 7692                use mod1::mod2::{mod3, mod4};
 7693
 7694                fn fn_1(param1: bool, param2: &str) {
 7695                    let var1 = "«ˇhello world»";
 7696                }
 7697            "#},
 7698            cx,
 7699        );
 7700    });
 7701
 7702    // Test 5: Expansion beyond string
 7703    editor.update_in(cx, |editor, window, cx| {
 7704        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7705        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7706        assert_text_with_selections(
 7707            editor,
 7708            indoc! {r#"
 7709                use mod1::mod2::{mod3, mod4};
 7710
 7711                fn fn_1(param1: bool, param2: &str) {
 7712                    «ˇlet var1 = "hello world";»
 7713                }
 7714            "#},
 7715            cx,
 7716        );
 7717    });
 7718}
 7719
 7720#[gpui::test]
 7721async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 7722    init_test(cx, |_| {});
 7723
 7724    let base_text = r#"
 7725        impl A {
 7726            // this is an uncommitted comment
 7727
 7728            fn b() {
 7729                c();
 7730            }
 7731
 7732            // this is another uncommitted comment
 7733
 7734            fn d() {
 7735                // e
 7736                // f
 7737            }
 7738        }
 7739
 7740        fn g() {
 7741            // h
 7742        }
 7743    "#
 7744    .unindent();
 7745
 7746    let text = r#"
 7747        ˇimpl A {
 7748
 7749            fn b() {
 7750                c();
 7751            }
 7752
 7753            fn d() {
 7754                // e
 7755                // f
 7756            }
 7757        }
 7758
 7759        fn g() {
 7760            // h
 7761        }
 7762    "#
 7763    .unindent();
 7764
 7765    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 7766    cx.set_state(&text);
 7767    cx.set_head_text(&base_text);
 7768    cx.update_editor(|editor, window, cx| {
 7769        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 7770    });
 7771
 7772    cx.assert_state_with_diff(
 7773        "
 7774        ˇimpl A {
 7775      -     // this is an uncommitted comment
 7776
 7777            fn b() {
 7778                c();
 7779            }
 7780
 7781      -     // this is another uncommitted comment
 7782      -
 7783            fn d() {
 7784                // e
 7785                // f
 7786            }
 7787        }
 7788
 7789        fn g() {
 7790            // h
 7791        }
 7792    "
 7793        .unindent(),
 7794    );
 7795
 7796    let expected_display_text = "
 7797        impl A {
 7798            // this is an uncommitted comment
 7799
 7800            fn b() {
 7801 7802            }
 7803
 7804            // this is another uncommitted comment
 7805
 7806            fn d() {
 7807 7808            }
 7809        }
 7810
 7811        fn g() {
 7812 7813        }
 7814        "
 7815    .unindent();
 7816
 7817    cx.update_editor(|editor, window, cx| {
 7818        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 7819        assert_eq!(editor.display_text(cx), expected_display_text);
 7820    });
 7821}
 7822
 7823#[gpui::test]
 7824async fn test_autoindent(cx: &mut TestAppContext) {
 7825    init_test(cx, |_| {});
 7826
 7827    let language = Arc::new(
 7828        Language::new(
 7829            LanguageConfig {
 7830                brackets: BracketPairConfig {
 7831                    pairs: vec![
 7832                        BracketPair {
 7833                            start: "{".to_string(),
 7834                            end: "}".to_string(),
 7835                            close: false,
 7836                            surround: false,
 7837                            newline: true,
 7838                        },
 7839                        BracketPair {
 7840                            start: "(".to_string(),
 7841                            end: ")".to_string(),
 7842                            close: false,
 7843                            surround: false,
 7844                            newline: true,
 7845                        },
 7846                    ],
 7847                    ..Default::default()
 7848                },
 7849                ..Default::default()
 7850            },
 7851            Some(tree_sitter_rust::LANGUAGE.into()),
 7852        )
 7853        .with_indents_query(
 7854            r#"
 7855                (_ "(" ")" @end) @indent
 7856                (_ "{" "}" @end) @indent
 7857            "#,
 7858        )
 7859        .unwrap(),
 7860    );
 7861
 7862    let text = "fn a() {}";
 7863
 7864    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7865    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7866    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7867    editor
 7868        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7869        .await;
 7870
 7871    editor.update_in(cx, |editor, window, cx| {
 7872        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7873            s.select_ranges([5..5, 8..8, 9..9])
 7874        });
 7875        editor.newline(&Newline, window, cx);
 7876        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 7877        assert_eq!(
 7878            editor.selections.ranges(cx),
 7879            &[
 7880                Point::new(1, 4)..Point::new(1, 4),
 7881                Point::new(3, 4)..Point::new(3, 4),
 7882                Point::new(5, 0)..Point::new(5, 0)
 7883            ]
 7884        );
 7885    });
 7886}
 7887
 7888#[gpui::test]
 7889async fn test_autoindent_selections(cx: &mut TestAppContext) {
 7890    init_test(cx, |_| {});
 7891
 7892    {
 7893        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 7894        cx.set_state(indoc! {"
 7895            impl A {
 7896
 7897                fn b() {}
 7898
 7899            «fn c() {
 7900
 7901            }ˇ»
 7902            }
 7903        "});
 7904
 7905        cx.update_editor(|editor, window, cx| {
 7906            editor.autoindent(&Default::default(), window, cx);
 7907        });
 7908
 7909        cx.assert_editor_state(indoc! {"
 7910            impl A {
 7911
 7912                fn b() {}
 7913
 7914                «fn c() {
 7915
 7916                }ˇ»
 7917            }
 7918        "});
 7919    }
 7920
 7921    {
 7922        let mut cx = EditorTestContext::new_multibuffer(
 7923            cx,
 7924            [indoc! { "
 7925                impl A {
 7926                «
 7927                // a
 7928                fn b(){}
 7929                »
 7930                «
 7931                    }
 7932                    fn c(){}
 7933                »
 7934            "}],
 7935        );
 7936
 7937        let buffer = cx.update_editor(|editor, _, cx| {
 7938            let buffer = editor.buffer().update(cx, |buffer, _| {
 7939                buffer.all_buffers().iter().next().unwrap().clone()
 7940            });
 7941            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7942            buffer
 7943        });
 7944
 7945        cx.run_until_parked();
 7946        cx.update_editor(|editor, window, cx| {
 7947            editor.select_all(&Default::default(), window, cx);
 7948            editor.autoindent(&Default::default(), window, cx)
 7949        });
 7950        cx.run_until_parked();
 7951
 7952        cx.update(|_, cx| {
 7953            assert_eq!(
 7954                buffer.read(cx).text(),
 7955                indoc! { "
 7956                    impl A {
 7957
 7958                        // a
 7959                        fn b(){}
 7960
 7961
 7962                    }
 7963                    fn c(){}
 7964
 7965                " }
 7966            )
 7967        });
 7968    }
 7969}
 7970
 7971#[gpui::test]
 7972async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 7973    init_test(cx, |_| {});
 7974
 7975    let mut cx = EditorTestContext::new(cx).await;
 7976
 7977    let language = Arc::new(Language::new(
 7978        LanguageConfig {
 7979            brackets: BracketPairConfig {
 7980                pairs: vec![
 7981                    BracketPair {
 7982                        start: "{".to_string(),
 7983                        end: "}".to_string(),
 7984                        close: true,
 7985                        surround: true,
 7986                        newline: true,
 7987                    },
 7988                    BracketPair {
 7989                        start: "(".to_string(),
 7990                        end: ")".to_string(),
 7991                        close: true,
 7992                        surround: true,
 7993                        newline: true,
 7994                    },
 7995                    BracketPair {
 7996                        start: "/*".to_string(),
 7997                        end: " */".to_string(),
 7998                        close: true,
 7999                        surround: true,
 8000                        newline: true,
 8001                    },
 8002                    BracketPair {
 8003                        start: "[".to_string(),
 8004                        end: "]".to_string(),
 8005                        close: false,
 8006                        surround: false,
 8007                        newline: true,
 8008                    },
 8009                    BracketPair {
 8010                        start: "\"".to_string(),
 8011                        end: "\"".to_string(),
 8012                        close: true,
 8013                        surround: true,
 8014                        newline: false,
 8015                    },
 8016                    BracketPair {
 8017                        start: "<".to_string(),
 8018                        end: ">".to_string(),
 8019                        close: false,
 8020                        surround: true,
 8021                        newline: true,
 8022                    },
 8023                ],
 8024                ..Default::default()
 8025            },
 8026            autoclose_before: "})]".to_string(),
 8027            ..Default::default()
 8028        },
 8029        Some(tree_sitter_rust::LANGUAGE.into()),
 8030    ));
 8031
 8032    cx.language_registry().add(language.clone());
 8033    cx.update_buffer(|buffer, cx| {
 8034        buffer.set_language(Some(language), cx);
 8035    });
 8036
 8037    cx.set_state(
 8038        &r#"
 8039            🏀ˇ
 8040            εˇ
 8041            ❤️ˇ
 8042        "#
 8043        .unindent(),
 8044    );
 8045
 8046    // autoclose multiple nested brackets at multiple cursors
 8047    cx.update_editor(|editor, window, cx| {
 8048        editor.handle_input("{", window, cx);
 8049        editor.handle_input("{", window, cx);
 8050        editor.handle_input("{", window, cx);
 8051    });
 8052    cx.assert_editor_state(
 8053        &"
 8054            🏀{{{ˇ}}}
 8055            ε{{{ˇ}}}
 8056            ❤️{{{ˇ}}}
 8057        "
 8058        .unindent(),
 8059    );
 8060
 8061    // insert a different closing bracket
 8062    cx.update_editor(|editor, window, cx| {
 8063        editor.handle_input(")", window, cx);
 8064    });
 8065    cx.assert_editor_state(
 8066        &"
 8067            🏀{{{)ˇ}}}
 8068            ε{{{)ˇ}}}
 8069            ❤️{{{)ˇ}}}
 8070        "
 8071        .unindent(),
 8072    );
 8073
 8074    // skip over the auto-closed brackets when typing a closing bracket
 8075    cx.update_editor(|editor, window, cx| {
 8076        editor.move_right(&MoveRight, window, cx);
 8077        editor.handle_input("}", window, cx);
 8078        editor.handle_input("}", window, cx);
 8079        editor.handle_input("}", window, cx);
 8080    });
 8081    cx.assert_editor_state(
 8082        &"
 8083            🏀{{{)}}}}ˇ
 8084            ε{{{)}}}}ˇ
 8085            ❤️{{{)}}}}ˇ
 8086        "
 8087        .unindent(),
 8088    );
 8089
 8090    // autoclose multi-character pairs
 8091    cx.set_state(
 8092        &"
 8093            ˇ
 8094            ˇ
 8095        "
 8096        .unindent(),
 8097    );
 8098    cx.update_editor(|editor, window, cx| {
 8099        editor.handle_input("/", window, cx);
 8100        editor.handle_input("*", window, cx);
 8101    });
 8102    cx.assert_editor_state(
 8103        &"
 8104            /*ˇ */
 8105            /*ˇ */
 8106        "
 8107        .unindent(),
 8108    );
 8109
 8110    // one cursor autocloses a multi-character pair, one cursor
 8111    // does not autoclose.
 8112    cx.set_state(
 8113        &"
 8114 8115            ˇ
 8116        "
 8117        .unindent(),
 8118    );
 8119    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 8120    cx.assert_editor_state(
 8121        &"
 8122            /*ˇ */
 8123 8124        "
 8125        .unindent(),
 8126    );
 8127
 8128    // Don't autoclose if the next character isn't whitespace and isn't
 8129    // listed in the language's "autoclose_before" section.
 8130    cx.set_state("ˇa b");
 8131    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8132    cx.assert_editor_state("{ˇa b");
 8133
 8134    // Don't autoclose if `close` is false for the bracket pair
 8135    cx.set_state("ˇ");
 8136    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 8137    cx.assert_editor_state("");
 8138
 8139    // Surround with brackets if text is selected
 8140    cx.set_state("«aˇ» b");
 8141    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8142    cx.assert_editor_state("{«aˇ»} b");
 8143
 8144    // Autoclose when not immediately after a word character
 8145    cx.set_state("a ˇ");
 8146    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8147    cx.assert_editor_state("a \"ˇ\"");
 8148
 8149    // Autoclose pair where the start and end characters are the same
 8150    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8151    cx.assert_editor_state("a \"\"ˇ");
 8152
 8153    // Don't autoclose when immediately after a word character
 8154    cx.set_state("");
 8155    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8156    cx.assert_editor_state("a\"ˇ");
 8157
 8158    // Do autoclose when after a non-word character
 8159    cx.set_state("");
 8160    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8161    cx.assert_editor_state("{\"ˇ\"");
 8162
 8163    // Non identical pairs autoclose regardless of preceding character
 8164    cx.set_state("");
 8165    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8166    cx.assert_editor_state("a{ˇ}");
 8167
 8168    // Don't autoclose pair if autoclose is disabled
 8169    cx.set_state("ˇ");
 8170    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 8171    cx.assert_editor_state("");
 8172
 8173    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 8174    cx.set_state("«aˇ» b");
 8175    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 8176    cx.assert_editor_state("<«aˇ»> b");
 8177}
 8178
 8179#[gpui::test]
 8180async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 8181    init_test(cx, |settings| {
 8182        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 8183    });
 8184
 8185    let mut cx = EditorTestContext::new(cx).await;
 8186
 8187    let language = Arc::new(Language::new(
 8188        LanguageConfig {
 8189            brackets: BracketPairConfig {
 8190                pairs: vec![
 8191                    BracketPair {
 8192                        start: "{".to_string(),
 8193                        end: "}".to_string(),
 8194                        close: true,
 8195                        surround: true,
 8196                        newline: true,
 8197                    },
 8198                    BracketPair {
 8199                        start: "(".to_string(),
 8200                        end: ")".to_string(),
 8201                        close: true,
 8202                        surround: true,
 8203                        newline: true,
 8204                    },
 8205                    BracketPair {
 8206                        start: "[".to_string(),
 8207                        end: "]".to_string(),
 8208                        close: false,
 8209                        surround: false,
 8210                        newline: true,
 8211                    },
 8212                ],
 8213                ..Default::default()
 8214            },
 8215            autoclose_before: "})]".to_string(),
 8216            ..Default::default()
 8217        },
 8218        Some(tree_sitter_rust::LANGUAGE.into()),
 8219    ));
 8220
 8221    cx.language_registry().add(language.clone());
 8222    cx.update_buffer(|buffer, cx| {
 8223        buffer.set_language(Some(language), cx);
 8224    });
 8225
 8226    cx.set_state(
 8227        &"
 8228            ˇ
 8229            ˇ
 8230            ˇ
 8231        "
 8232        .unindent(),
 8233    );
 8234
 8235    // ensure only matching closing brackets are skipped over
 8236    cx.update_editor(|editor, window, cx| {
 8237        editor.handle_input("}", window, cx);
 8238        editor.move_left(&MoveLeft, window, cx);
 8239        editor.handle_input(")", window, cx);
 8240        editor.move_left(&MoveLeft, window, cx);
 8241    });
 8242    cx.assert_editor_state(
 8243        &"
 8244            ˇ)}
 8245            ˇ)}
 8246            ˇ)}
 8247        "
 8248        .unindent(),
 8249    );
 8250
 8251    // skip-over closing brackets at multiple cursors
 8252    cx.update_editor(|editor, window, cx| {
 8253        editor.handle_input(")", window, cx);
 8254        editor.handle_input("}", window, cx);
 8255    });
 8256    cx.assert_editor_state(
 8257        &"
 8258            )}ˇ
 8259            )}ˇ
 8260            )}ˇ
 8261        "
 8262        .unindent(),
 8263    );
 8264
 8265    // ignore non-close brackets
 8266    cx.update_editor(|editor, window, cx| {
 8267        editor.handle_input("]", window, cx);
 8268        editor.move_left(&MoveLeft, window, cx);
 8269        editor.handle_input("]", window, cx);
 8270    });
 8271    cx.assert_editor_state(
 8272        &"
 8273            )}]ˇ]
 8274            )}]ˇ]
 8275            )}]ˇ]
 8276        "
 8277        .unindent(),
 8278    );
 8279}
 8280
 8281#[gpui::test]
 8282async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
 8283    init_test(cx, |_| {});
 8284
 8285    let mut cx = EditorTestContext::new(cx).await;
 8286
 8287    let html_language = Arc::new(
 8288        Language::new(
 8289            LanguageConfig {
 8290                name: "HTML".into(),
 8291                brackets: BracketPairConfig {
 8292                    pairs: vec![
 8293                        BracketPair {
 8294                            start: "<".into(),
 8295                            end: ">".into(),
 8296                            close: true,
 8297                            ..Default::default()
 8298                        },
 8299                        BracketPair {
 8300                            start: "{".into(),
 8301                            end: "}".into(),
 8302                            close: true,
 8303                            ..Default::default()
 8304                        },
 8305                        BracketPair {
 8306                            start: "(".into(),
 8307                            end: ")".into(),
 8308                            close: true,
 8309                            ..Default::default()
 8310                        },
 8311                    ],
 8312                    ..Default::default()
 8313                },
 8314                autoclose_before: "})]>".into(),
 8315                ..Default::default()
 8316            },
 8317            Some(tree_sitter_html::LANGUAGE.into()),
 8318        )
 8319        .with_injection_query(
 8320            r#"
 8321            (script_element
 8322                (raw_text) @injection.content
 8323                (#set! injection.language "javascript"))
 8324            "#,
 8325        )
 8326        .unwrap(),
 8327    );
 8328
 8329    let javascript_language = Arc::new(Language::new(
 8330        LanguageConfig {
 8331            name: "JavaScript".into(),
 8332            brackets: BracketPairConfig {
 8333                pairs: vec![
 8334                    BracketPair {
 8335                        start: "/*".into(),
 8336                        end: " */".into(),
 8337                        close: true,
 8338                        ..Default::default()
 8339                    },
 8340                    BracketPair {
 8341                        start: "{".into(),
 8342                        end: "}".into(),
 8343                        close: true,
 8344                        ..Default::default()
 8345                    },
 8346                    BracketPair {
 8347                        start: "(".into(),
 8348                        end: ")".into(),
 8349                        close: true,
 8350                        ..Default::default()
 8351                    },
 8352                ],
 8353                ..Default::default()
 8354            },
 8355            autoclose_before: "})]>".into(),
 8356            ..Default::default()
 8357        },
 8358        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 8359    ));
 8360
 8361    cx.language_registry().add(html_language.clone());
 8362    cx.language_registry().add(javascript_language.clone());
 8363
 8364    cx.update_buffer(|buffer, cx| {
 8365        buffer.set_language(Some(html_language), cx);
 8366    });
 8367
 8368    cx.set_state(
 8369        &r#"
 8370            <body>ˇ
 8371                <script>
 8372                    var x = 1;ˇ
 8373                </script>
 8374            </body>ˇ
 8375        "#
 8376        .unindent(),
 8377    );
 8378
 8379    // Precondition: different languages are active at different locations.
 8380    cx.update_editor(|editor, window, cx| {
 8381        let snapshot = editor.snapshot(window, cx);
 8382        let cursors = editor.selections.ranges::<usize>(cx);
 8383        let languages = cursors
 8384            .iter()
 8385            .map(|c| snapshot.language_at(c.start).unwrap().name())
 8386            .collect::<Vec<_>>();
 8387        assert_eq!(
 8388            languages,
 8389            &["HTML".into(), "JavaScript".into(), "HTML".into()]
 8390        );
 8391    });
 8392
 8393    // Angle brackets autoclose in HTML, but not JavaScript.
 8394    cx.update_editor(|editor, window, cx| {
 8395        editor.handle_input("<", window, cx);
 8396        editor.handle_input("a", window, cx);
 8397    });
 8398    cx.assert_editor_state(
 8399        &r#"
 8400            <body><aˇ>
 8401                <script>
 8402                    var x = 1;<aˇ
 8403                </script>
 8404            </body><aˇ>
 8405        "#
 8406        .unindent(),
 8407    );
 8408
 8409    // Curly braces and parens autoclose in both HTML and JavaScript.
 8410    cx.update_editor(|editor, window, cx| {
 8411        editor.handle_input(" b=", window, cx);
 8412        editor.handle_input("{", window, cx);
 8413        editor.handle_input("c", window, cx);
 8414        editor.handle_input("(", window, cx);
 8415    });
 8416    cx.assert_editor_state(
 8417        &r#"
 8418            <body><a b={c(ˇ)}>
 8419                <script>
 8420                    var x = 1;<a b={c(ˇ)}
 8421                </script>
 8422            </body><a b={c(ˇ)}>
 8423        "#
 8424        .unindent(),
 8425    );
 8426
 8427    // Brackets that were already autoclosed are skipped.
 8428    cx.update_editor(|editor, window, cx| {
 8429        editor.handle_input(")", window, cx);
 8430        editor.handle_input("d", window, cx);
 8431        editor.handle_input("}", window, cx);
 8432    });
 8433    cx.assert_editor_state(
 8434        &r#"
 8435            <body><a b={c()d}ˇ>
 8436                <script>
 8437                    var x = 1;<a b={c()d}ˇ
 8438                </script>
 8439            </body><a b={c()d}ˇ>
 8440        "#
 8441        .unindent(),
 8442    );
 8443    cx.update_editor(|editor, window, cx| {
 8444        editor.handle_input(">", window, cx);
 8445    });
 8446    cx.assert_editor_state(
 8447        &r#"
 8448            <body><a b={c()d}>ˇ
 8449                <script>
 8450                    var x = 1;<a b={c()d}>ˇ
 8451                </script>
 8452            </body><a b={c()d}>ˇ
 8453        "#
 8454        .unindent(),
 8455    );
 8456
 8457    // Reset
 8458    cx.set_state(
 8459        &r#"
 8460            <body>ˇ
 8461                <script>
 8462                    var x = 1;ˇ
 8463                </script>
 8464            </body>ˇ
 8465        "#
 8466        .unindent(),
 8467    );
 8468
 8469    cx.update_editor(|editor, window, cx| {
 8470        editor.handle_input("<", window, cx);
 8471    });
 8472    cx.assert_editor_state(
 8473        &r#"
 8474            <body><ˇ>
 8475                <script>
 8476                    var x = 1;<ˇ
 8477                </script>
 8478            </body><ˇ>
 8479        "#
 8480        .unindent(),
 8481    );
 8482
 8483    // When backspacing, the closing angle brackets are removed.
 8484    cx.update_editor(|editor, window, cx| {
 8485        editor.backspace(&Backspace, window, cx);
 8486    });
 8487    cx.assert_editor_state(
 8488        &r#"
 8489            <body>ˇ
 8490                <script>
 8491                    var x = 1;ˇ
 8492                </script>
 8493            </body>ˇ
 8494        "#
 8495        .unindent(),
 8496    );
 8497
 8498    // Block comments autoclose in JavaScript, but not HTML.
 8499    cx.update_editor(|editor, window, cx| {
 8500        editor.handle_input("/", window, cx);
 8501        editor.handle_input("*", window, cx);
 8502    });
 8503    cx.assert_editor_state(
 8504        &r#"
 8505            <body>/*ˇ
 8506                <script>
 8507                    var x = 1;/*ˇ */
 8508                </script>
 8509            </body>/*ˇ
 8510        "#
 8511        .unindent(),
 8512    );
 8513}
 8514
 8515#[gpui::test]
 8516async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
 8517    init_test(cx, |_| {});
 8518
 8519    let mut cx = EditorTestContext::new(cx).await;
 8520
 8521    let rust_language = Arc::new(
 8522        Language::new(
 8523            LanguageConfig {
 8524                name: "Rust".into(),
 8525                brackets: serde_json::from_value(json!([
 8526                    { "start": "{", "end": "}", "close": true, "newline": true },
 8527                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
 8528                ]))
 8529                .unwrap(),
 8530                autoclose_before: "})]>".into(),
 8531                ..Default::default()
 8532            },
 8533            Some(tree_sitter_rust::LANGUAGE.into()),
 8534        )
 8535        .with_override_query("(string_literal) @string")
 8536        .unwrap(),
 8537    );
 8538
 8539    cx.language_registry().add(rust_language.clone());
 8540    cx.update_buffer(|buffer, cx| {
 8541        buffer.set_language(Some(rust_language), cx);
 8542    });
 8543
 8544    cx.set_state(
 8545        &r#"
 8546            let x = ˇ
 8547        "#
 8548        .unindent(),
 8549    );
 8550
 8551    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
 8552    cx.update_editor(|editor, window, cx| {
 8553        editor.handle_input("\"", window, cx);
 8554    });
 8555    cx.assert_editor_state(
 8556        &r#"
 8557            let x = "ˇ"
 8558        "#
 8559        .unindent(),
 8560    );
 8561
 8562    // Inserting another quotation mark. The cursor moves across the existing
 8563    // automatically-inserted quotation mark.
 8564    cx.update_editor(|editor, window, cx| {
 8565        editor.handle_input("\"", window, cx);
 8566    });
 8567    cx.assert_editor_state(
 8568        &r#"
 8569            let x = ""ˇ
 8570        "#
 8571        .unindent(),
 8572    );
 8573
 8574    // Reset
 8575    cx.set_state(
 8576        &r#"
 8577            let x = ˇ
 8578        "#
 8579        .unindent(),
 8580    );
 8581
 8582    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
 8583    cx.update_editor(|editor, window, cx| {
 8584        editor.handle_input("\"", window, cx);
 8585        editor.handle_input(" ", window, cx);
 8586        editor.move_left(&Default::default(), window, cx);
 8587        editor.handle_input("\\", window, cx);
 8588        editor.handle_input("\"", window, cx);
 8589    });
 8590    cx.assert_editor_state(
 8591        &r#"
 8592            let x = "\"ˇ "
 8593        "#
 8594        .unindent(),
 8595    );
 8596
 8597    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
 8598    // mark. Nothing is inserted.
 8599    cx.update_editor(|editor, window, cx| {
 8600        editor.move_right(&Default::default(), window, cx);
 8601        editor.handle_input("\"", window, cx);
 8602    });
 8603    cx.assert_editor_state(
 8604        &r#"
 8605            let x = "\" "ˇ
 8606        "#
 8607        .unindent(),
 8608    );
 8609}
 8610
 8611#[gpui::test]
 8612async fn test_surround_with_pair(cx: &mut TestAppContext) {
 8613    init_test(cx, |_| {});
 8614
 8615    let language = Arc::new(Language::new(
 8616        LanguageConfig {
 8617            brackets: BracketPairConfig {
 8618                pairs: vec![
 8619                    BracketPair {
 8620                        start: "{".to_string(),
 8621                        end: "}".to_string(),
 8622                        close: true,
 8623                        surround: true,
 8624                        newline: true,
 8625                    },
 8626                    BracketPair {
 8627                        start: "/* ".to_string(),
 8628                        end: "*/".to_string(),
 8629                        close: true,
 8630                        surround: true,
 8631                        ..Default::default()
 8632                    },
 8633                ],
 8634                ..Default::default()
 8635            },
 8636            ..Default::default()
 8637        },
 8638        Some(tree_sitter_rust::LANGUAGE.into()),
 8639    ));
 8640
 8641    let text = r#"
 8642        a
 8643        b
 8644        c
 8645    "#
 8646    .unindent();
 8647
 8648    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8649    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8650    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8651    editor
 8652        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8653        .await;
 8654
 8655    editor.update_in(cx, |editor, window, cx| {
 8656        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8657            s.select_display_ranges([
 8658                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8659                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8660                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
 8661            ])
 8662        });
 8663
 8664        editor.handle_input("{", window, cx);
 8665        editor.handle_input("{", window, cx);
 8666        editor.handle_input("{", window, cx);
 8667        assert_eq!(
 8668            editor.text(cx),
 8669            "
 8670                {{{a}}}
 8671                {{{b}}}
 8672                {{{c}}}
 8673            "
 8674            .unindent()
 8675        );
 8676        assert_eq!(
 8677            editor.selections.display_ranges(cx),
 8678            [
 8679                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
 8680                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
 8681                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
 8682            ]
 8683        );
 8684
 8685        editor.undo(&Undo, window, cx);
 8686        editor.undo(&Undo, window, cx);
 8687        editor.undo(&Undo, window, cx);
 8688        assert_eq!(
 8689            editor.text(cx),
 8690            "
 8691                a
 8692                b
 8693                c
 8694            "
 8695            .unindent()
 8696        );
 8697        assert_eq!(
 8698            editor.selections.display_ranges(cx),
 8699            [
 8700                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8701                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8702                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 8703            ]
 8704        );
 8705
 8706        // Ensure inserting the first character of a multi-byte bracket pair
 8707        // doesn't surround the selections with the bracket.
 8708        editor.handle_input("/", window, cx);
 8709        assert_eq!(
 8710            editor.text(cx),
 8711            "
 8712                /
 8713                /
 8714                /
 8715            "
 8716            .unindent()
 8717        );
 8718        assert_eq!(
 8719            editor.selections.display_ranges(cx),
 8720            [
 8721                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 8722                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 8723                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 8724            ]
 8725        );
 8726
 8727        editor.undo(&Undo, window, cx);
 8728        assert_eq!(
 8729            editor.text(cx),
 8730            "
 8731                a
 8732                b
 8733                c
 8734            "
 8735            .unindent()
 8736        );
 8737        assert_eq!(
 8738            editor.selections.display_ranges(cx),
 8739            [
 8740                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8741                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8742                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 8743            ]
 8744        );
 8745
 8746        // Ensure inserting the last character of a multi-byte bracket pair
 8747        // doesn't surround the selections with the bracket.
 8748        editor.handle_input("*", window, cx);
 8749        assert_eq!(
 8750            editor.text(cx),
 8751            "
 8752                *
 8753                *
 8754                *
 8755            "
 8756            .unindent()
 8757        );
 8758        assert_eq!(
 8759            editor.selections.display_ranges(cx),
 8760            [
 8761                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 8762                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 8763                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 8764            ]
 8765        );
 8766    });
 8767}
 8768
 8769#[gpui::test]
 8770async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
 8771    init_test(cx, |_| {});
 8772
 8773    let language = Arc::new(Language::new(
 8774        LanguageConfig {
 8775            brackets: BracketPairConfig {
 8776                pairs: vec![BracketPair {
 8777                    start: "{".to_string(),
 8778                    end: "}".to_string(),
 8779                    close: true,
 8780                    surround: true,
 8781                    newline: true,
 8782                }],
 8783                ..Default::default()
 8784            },
 8785            autoclose_before: "}".to_string(),
 8786            ..Default::default()
 8787        },
 8788        Some(tree_sitter_rust::LANGUAGE.into()),
 8789    ));
 8790
 8791    let text = r#"
 8792        a
 8793        b
 8794        c
 8795    "#
 8796    .unindent();
 8797
 8798    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8799    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8800    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8801    editor
 8802        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8803        .await;
 8804
 8805    editor.update_in(cx, |editor, window, cx| {
 8806        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8807            s.select_ranges([
 8808                Point::new(0, 1)..Point::new(0, 1),
 8809                Point::new(1, 1)..Point::new(1, 1),
 8810                Point::new(2, 1)..Point::new(2, 1),
 8811            ])
 8812        });
 8813
 8814        editor.handle_input("{", window, cx);
 8815        editor.handle_input("{", window, cx);
 8816        editor.handle_input("_", window, cx);
 8817        assert_eq!(
 8818            editor.text(cx),
 8819            "
 8820                a{{_}}
 8821                b{{_}}
 8822                c{{_}}
 8823            "
 8824            .unindent()
 8825        );
 8826        assert_eq!(
 8827            editor.selections.ranges::<Point>(cx),
 8828            [
 8829                Point::new(0, 4)..Point::new(0, 4),
 8830                Point::new(1, 4)..Point::new(1, 4),
 8831                Point::new(2, 4)..Point::new(2, 4)
 8832            ]
 8833        );
 8834
 8835        editor.backspace(&Default::default(), window, cx);
 8836        editor.backspace(&Default::default(), window, cx);
 8837        assert_eq!(
 8838            editor.text(cx),
 8839            "
 8840                a{}
 8841                b{}
 8842                c{}
 8843            "
 8844            .unindent()
 8845        );
 8846        assert_eq!(
 8847            editor.selections.ranges::<Point>(cx),
 8848            [
 8849                Point::new(0, 2)..Point::new(0, 2),
 8850                Point::new(1, 2)..Point::new(1, 2),
 8851                Point::new(2, 2)..Point::new(2, 2)
 8852            ]
 8853        );
 8854
 8855        editor.delete_to_previous_word_start(&Default::default(), window, cx);
 8856        assert_eq!(
 8857            editor.text(cx),
 8858            "
 8859                a
 8860                b
 8861                c
 8862            "
 8863            .unindent()
 8864        );
 8865        assert_eq!(
 8866            editor.selections.ranges::<Point>(cx),
 8867            [
 8868                Point::new(0, 1)..Point::new(0, 1),
 8869                Point::new(1, 1)..Point::new(1, 1),
 8870                Point::new(2, 1)..Point::new(2, 1)
 8871            ]
 8872        );
 8873    });
 8874}
 8875
 8876#[gpui::test]
 8877async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
 8878    init_test(cx, |settings| {
 8879        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 8880    });
 8881
 8882    let mut cx = EditorTestContext::new(cx).await;
 8883
 8884    let language = Arc::new(Language::new(
 8885        LanguageConfig {
 8886            brackets: BracketPairConfig {
 8887                pairs: vec![
 8888                    BracketPair {
 8889                        start: "{".to_string(),
 8890                        end: "}".to_string(),
 8891                        close: true,
 8892                        surround: true,
 8893                        newline: true,
 8894                    },
 8895                    BracketPair {
 8896                        start: "(".to_string(),
 8897                        end: ")".to_string(),
 8898                        close: true,
 8899                        surround: true,
 8900                        newline: true,
 8901                    },
 8902                    BracketPair {
 8903                        start: "[".to_string(),
 8904                        end: "]".to_string(),
 8905                        close: false,
 8906                        surround: true,
 8907                        newline: true,
 8908                    },
 8909                ],
 8910                ..Default::default()
 8911            },
 8912            autoclose_before: "})]".to_string(),
 8913            ..Default::default()
 8914        },
 8915        Some(tree_sitter_rust::LANGUAGE.into()),
 8916    ));
 8917
 8918    cx.language_registry().add(language.clone());
 8919    cx.update_buffer(|buffer, cx| {
 8920        buffer.set_language(Some(language), cx);
 8921    });
 8922
 8923    cx.set_state(
 8924        &"
 8925            {(ˇ)}
 8926            [[ˇ]]
 8927            {(ˇ)}
 8928        "
 8929        .unindent(),
 8930    );
 8931
 8932    cx.update_editor(|editor, window, cx| {
 8933        editor.backspace(&Default::default(), window, cx);
 8934        editor.backspace(&Default::default(), window, cx);
 8935    });
 8936
 8937    cx.assert_editor_state(
 8938        &"
 8939            ˇ
 8940            ˇ]]
 8941            ˇ
 8942        "
 8943        .unindent(),
 8944    );
 8945
 8946    cx.update_editor(|editor, window, cx| {
 8947        editor.handle_input("{", window, cx);
 8948        editor.handle_input("{", window, cx);
 8949        editor.move_right(&MoveRight, window, cx);
 8950        editor.move_right(&MoveRight, window, cx);
 8951        editor.move_left(&MoveLeft, window, cx);
 8952        editor.move_left(&MoveLeft, window, cx);
 8953        editor.backspace(&Default::default(), window, cx);
 8954    });
 8955
 8956    cx.assert_editor_state(
 8957        &"
 8958            {ˇ}
 8959            {ˇ}]]
 8960            {ˇ}
 8961        "
 8962        .unindent(),
 8963    );
 8964
 8965    cx.update_editor(|editor, window, cx| {
 8966        editor.backspace(&Default::default(), window, cx);
 8967    });
 8968
 8969    cx.assert_editor_state(
 8970        &"
 8971            ˇ
 8972            ˇ]]
 8973            ˇ
 8974        "
 8975        .unindent(),
 8976    );
 8977}
 8978
 8979#[gpui::test]
 8980async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
 8981    init_test(cx, |_| {});
 8982
 8983    let language = Arc::new(Language::new(
 8984        LanguageConfig::default(),
 8985        Some(tree_sitter_rust::LANGUAGE.into()),
 8986    ));
 8987
 8988    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
 8989    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8990    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8991    editor
 8992        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8993        .await;
 8994
 8995    editor.update_in(cx, |editor, window, cx| {
 8996        editor.set_auto_replace_emoji_shortcode(true);
 8997
 8998        editor.handle_input("Hello ", window, cx);
 8999        editor.handle_input(":wave", window, cx);
 9000        assert_eq!(editor.text(cx), "Hello :wave".unindent());
 9001
 9002        editor.handle_input(":", window, cx);
 9003        assert_eq!(editor.text(cx), "Hello 👋".unindent());
 9004
 9005        editor.handle_input(" :smile", window, cx);
 9006        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
 9007
 9008        editor.handle_input(":", window, cx);
 9009        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
 9010
 9011        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
 9012        editor.handle_input(":wave", window, cx);
 9013        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
 9014
 9015        editor.handle_input(":", window, cx);
 9016        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
 9017
 9018        editor.handle_input(":1", window, cx);
 9019        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
 9020
 9021        editor.handle_input(":", window, cx);
 9022        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
 9023
 9024        // Ensure shortcode does not get replaced when it is part of a word
 9025        editor.handle_input(" Test:wave", window, cx);
 9026        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
 9027
 9028        editor.handle_input(":", window, cx);
 9029        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
 9030
 9031        editor.set_auto_replace_emoji_shortcode(false);
 9032
 9033        // Ensure shortcode does not get replaced when auto replace is off
 9034        editor.handle_input(" :wave", window, cx);
 9035        assert_eq!(
 9036            editor.text(cx),
 9037            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
 9038        );
 9039
 9040        editor.handle_input(":", window, cx);
 9041        assert_eq!(
 9042            editor.text(cx),
 9043            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
 9044        );
 9045    });
 9046}
 9047
 9048#[gpui::test]
 9049async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
 9050    init_test(cx, |_| {});
 9051
 9052    let (text, insertion_ranges) = marked_text_ranges(
 9053        indoc! {"
 9054            ˇ
 9055        "},
 9056        false,
 9057    );
 9058
 9059    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
 9060    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9061
 9062    _ = editor.update_in(cx, |editor, window, cx| {
 9063        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
 9064
 9065        editor
 9066            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9067            .unwrap();
 9068
 9069        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
 9070            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
 9071            assert_eq!(editor.text(cx), expected_text);
 9072            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
 9073        }
 9074
 9075        assert(
 9076            editor,
 9077            cx,
 9078            indoc! {"
 9079            type «» =•
 9080            "},
 9081        );
 9082
 9083        assert!(editor.context_menu_visible(), "There should be a matches");
 9084    });
 9085}
 9086
 9087#[gpui::test]
 9088async fn test_snippets(cx: &mut TestAppContext) {
 9089    init_test(cx, |_| {});
 9090
 9091    let mut cx = EditorTestContext::new(cx).await;
 9092
 9093    cx.set_state(indoc! {"
 9094        a.ˇ b
 9095        a.ˇ b
 9096        a.ˇ b
 9097    "});
 9098
 9099    cx.update_editor(|editor, window, cx| {
 9100        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
 9101        let insertion_ranges = editor
 9102            .selections
 9103            .all(cx)
 9104            .iter()
 9105            .map(|s| s.range().clone())
 9106            .collect::<Vec<_>>();
 9107        editor
 9108            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9109            .unwrap();
 9110    });
 9111
 9112    cx.assert_editor_state(indoc! {"
 9113        a.f(«oneˇ», two, «threeˇ») b
 9114        a.f(«oneˇ», two, «threeˇ») b
 9115        a.f(«oneˇ», two, «threeˇ») b
 9116    "});
 9117
 9118    // Can't move earlier than the first tab stop
 9119    cx.update_editor(|editor, window, cx| {
 9120        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 9121    });
 9122    cx.assert_editor_state(indoc! {"
 9123        a.f(«oneˇ», two, «threeˇ») b
 9124        a.f(«oneˇ», two, «threeˇ») b
 9125        a.f(«oneˇ», two, «threeˇ») b
 9126    "});
 9127
 9128    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9129    cx.assert_editor_state(indoc! {"
 9130        a.f(one, «twoˇ», three) b
 9131        a.f(one, «twoˇ», three) b
 9132        a.f(one, «twoˇ», three) b
 9133    "});
 9134
 9135    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
 9136    cx.assert_editor_state(indoc! {"
 9137        a.f(«oneˇ», two, «threeˇ») b
 9138        a.f(«oneˇ», two, «threeˇ») b
 9139        a.f(«oneˇ», two, «threeˇ») b
 9140    "});
 9141
 9142    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9143    cx.assert_editor_state(indoc! {"
 9144        a.f(one, «twoˇ», three) b
 9145        a.f(one, «twoˇ», three) b
 9146        a.f(one, «twoˇ», three) b
 9147    "});
 9148    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9149    cx.assert_editor_state(indoc! {"
 9150        a.f(one, two, three)ˇ b
 9151        a.f(one, two, three)ˇ b
 9152        a.f(one, two, three)ˇ b
 9153    "});
 9154
 9155    // As soon as the last tab stop is reached, snippet state is gone
 9156    cx.update_editor(|editor, window, cx| {
 9157        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 9158    });
 9159    cx.assert_editor_state(indoc! {"
 9160        a.f(one, two, three)ˇ b
 9161        a.f(one, two, three)ˇ b
 9162        a.f(one, two, three)ˇ b
 9163    "});
 9164}
 9165
 9166#[gpui::test]
 9167async fn test_snippet_indentation(cx: &mut TestAppContext) {
 9168    init_test(cx, |_| {});
 9169
 9170    let mut cx = EditorTestContext::new(cx).await;
 9171
 9172    cx.update_editor(|editor, window, cx| {
 9173        let snippet = Snippet::parse(indoc! {"
 9174            /*
 9175             * Multiline comment with leading indentation
 9176             *
 9177             * $1
 9178             */
 9179            $0"})
 9180        .unwrap();
 9181        let insertion_ranges = editor
 9182            .selections
 9183            .all(cx)
 9184            .iter()
 9185            .map(|s| s.range().clone())
 9186            .collect::<Vec<_>>();
 9187        editor
 9188            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9189            .unwrap();
 9190    });
 9191
 9192    cx.assert_editor_state(indoc! {"
 9193        /*
 9194         * Multiline comment with leading indentation
 9195         *
 9196         * ˇ
 9197         */
 9198    "});
 9199
 9200    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9201    cx.assert_editor_state(indoc! {"
 9202        /*
 9203         * Multiline comment with leading indentation
 9204         *
 9205         *•
 9206         */
 9207        ˇ"});
 9208}
 9209
 9210#[gpui::test]
 9211async fn test_document_format_during_save(cx: &mut TestAppContext) {
 9212    init_test(cx, |_| {});
 9213
 9214    let fs = FakeFs::new(cx.executor());
 9215    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9216
 9217    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
 9218
 9219    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9220    language_registry.add(rust_lang());
 9221    let mut fake_servers = language_registry.register_fake_lsp(
 9222        "Rust",
 9223        FakeLspAdapter {
 9224            capabilities: lsp::ServerCapabilities {
 9225                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9226                ..Default::default()
 9227            },
 9228            ..Default::default()
 9229        },
 9230    );
 9231
 9232    let buffer = project
 9233        .update(cx, |project, cx| {
 9234            project.open_local_buffer(path!("/file.rs"), cx)
 9235        })
 9236        .await
 9237        .unwrap();
 9238
 9239    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9240    let (editor, cx) = cx.add_window_view(|window, cx| {
 9241        build_editor_with_project(project.clone(), buffer, window, cx)
 9242    });
 9243    editor.update_in(cx, |editor, window, cx| {
 9244        editor.set_text("one\ntwo\nthree\n", window, cx)
 9245    });
 9246    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9247
 9248    cx.executor().start_waiting();
 9249    let fake_server = fake_servers.next().await.unwrap();
 9250
 9251    {
 9252        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9253            move |params, _| async move {
 9254                assert_eq!(
 9255                    params.text_document.uri,
 9256                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9257                );
 9258                assert_eq!(params.options.tab_size, 4);
 9259                Ok(Some(vec![lsp::TextEdit::new(
 9260                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9261                    ", ".to_string(),
 9262                )]))
 9263            },
 9264        );
 9265        let save = editor
 9266            .update_in(cx, |editor, window, cx| {
 9267                editor.save(
 9268                    SaveOptions {
 9269                        format: true,
 9270                        autosave: false,
 9271                    },
 9272                    project.clone(),
 9273                    window,
 9274                    cx,
 9275                )
 9276            })
 9277            .unwrap();
 9278        cx.executor().start_waiting();
 9279        save.await;
 9280
 9281        assert_eq!(
 9282            editor.update(cx, |editor, cx| editor.text(cx)),
 9283            "one, two\nthree\n"
 9284        );
 9285        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9286    }
 9287
 9288    {
 9289        editor.update_in(cx, |editor, window, cx| {
 9290            editor.set_text("one\ntwo\nthree\n", window, cx)
 9291        });
 9292        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9293
 9294        // Ensure we can still save even if formatting hangs.
 9295        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9296            move |params, _| async move {
 9297                assert_eq!(
 9298                    params.text_document.uri,
 9299                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9300                );
 9301                futures::future::pending::<()>().await;
 9302                unreachable!()
 9303            },
 9304        );
 9305        let save = editor
 9306            .update_in(cx, |editor, window, cx| {
 9307                editor.save(
 9308                    SaveOptions {
 9309                        format: true,
 9310                        autosave: false,
 9311                    },
 9312                    project.clone(),
 9313                    window,
 9314                    cx,
 9315                )
 9316            })
 9317            .unwrap();
 9318        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9319        cx.executor().start_waiting();
 9320        save.await;
 9321        assert_eq!(
 9322            editor.update(cx, |editor, cx| editor.text(cx)),
 9323            "one\ntwo\nthree\n"
 9324        );
 9325    }
 9326
 9327    // Set rust language override and assert overridden tabsize is sent to language server
 9328    update_test_language_settings(cx, |settings| {
 9329        settings.languages.0.insert(
 9330            "Rust".into(),
 9331            LanguageSettingsContent {
 9332                tab_size: NonZeroU32::new(8),
 9333                ..Default::default()
 9334            },
 9335        );
 9336    });
 9337
 9338    {
 9339        editor.update_in(cx, |editor, window, cx| {
 9340            editor.set_text("somehting_new\n", window, cx)
 9341        });
 9342        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9343        let _formatting_request_signal = fake_server
 9344            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9345                assert_eq!(
 9346                    params.text_document.uri,
 9347                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9348                );
 9349                assert_eq!(params.options.tab_size, 8);
 9350                Ok(Some(vec![]))
 9351            });
 9352        let save = editor
 9353            .update_in(cx, |editor, window, cx| {
 9354                editor.save(
 9355                    SaveOptions {
 9356                        format: true,
 9357                        autosave: false,
 9358                    },
 9359                    project.clone(),
 9360                    window,
 9361                    cx,
 9362                )
 9363            })
 9364            .unwrap();
 9365        cx.executor().start_waiting();
 9366        save.await;
 9367    }
 9368}
 9369
 9370#[gpui::test]
 9371async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
 9372    init_test(cx, |_| {});
 9373
 9374    let cols = 4;
 9375    let rows = 10;
 9376    let sample_text_1 = sample_text(rows, cols, 'a');
 9377    assert_eq!(
 9378        sample_text_1,
 9379        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
 9380    );
 9381    let sample_text_2 = sample_text(rows, cols, 'l');
 9382    assert_eq!(
 9383        sample_text_2,
 9384        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
 9385    );
 9386    let sample_text_3 = sample_text(rows, cols, 'v');
 9387    assert_eq!(
 9388        sample_text_3,
 9389        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
 9390    );
 9391
 9392    let fs = FakeFs::new(cx.executor());
 9393    fs.insert_tree(
 9394        path!("/a"),
 9395        json!({
 9396            "main.rs": sample_text_1,
 9397            "other.rs": sample_text_2,
 9398            "lib.rs": sample_text_3,
 9399        }),
 9400    )
 9401    .await;
 9402
 9403    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
 9404    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9405    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 9406
 9407    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9408    language_registry.add(rust_lang());
 9409    let mut fake_servers = language_registry.register_fake_lsp(
 9410        "Rust",
 9411        FakeLspAdapter {
 9412            capabilities: lsp::ServerCapabilities {
 9413                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9414                ..Default::default()
 9415            },
 9416            ..Default::default()
 9417        },
 9418    );
 9419
 9420    let worktree = project.update(cx, |project, cx| {
 9421        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
 9422        assert_eq!(worktrees.len(), 1);
 9423        worktrees.pop().unwrap()
 9424    });
 9425    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 9426
 9427    let buffer_1 = project
 9428        .update(cx, |project, cx| {
 9429            project.open_buffer((worktree_id, "main.rs"), cx)
 9430        })
 9431        .await
 9432        .unwrap();
 9433    let buffer_2 = project
 9434        .update(cx, |project, cx| {
 9435            project.open_buffer((worktree_id, "other.rs"), cx)
 9436        })
 9437        .await
 9438        .unwrap();
 9439    let buffer_3 = project
 9440        .update(cx, |project, cx| {
 9441            project.open_buffer((worktree_id, "lib.rs"), cx)
 9442        })
 9443        .await
 9444        .unwrap();
 9445
 9446    let multi_buffer = cx.new(|cx| {
 9447        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 9448        multi_buffer.push_excerpts(
 9449            buffer_1.clone(),
 9450            [
 9451                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9452                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9453                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9454            ],
 9455            cx,
 9456        );
 9457        multi_buffer.push_excerpts(
 9458            buffer_2.clone(),
 9459            [
 9460                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9461                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9462                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9463            ],
 9464            cx,
 9465        );
 9466        multi_buffer.push_excerpts(
 9467            buffer_3.clone(),
 9468            [
 9469                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9470                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9471                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9472            ],
 9473            cx,
 9474        );
 9475        multi_buffer
 9476    });
 9477    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
 9478        Editor::new(
 9479            EditorMode::full(),
 9480            multi_buffer,
 9481            Some(project.clone()),
 9482            window,
 9483            cx,
 9484        )
 9485    });
 9486
 9487    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 9488        editor.change_selections(
 9489            SelectionEffects::scroll(Autoscroll::Next),
 9490            window,
 9491            cx,
 9492            |s| s.select_ranges(Some(1..2)),
 9493        );
 9494        editor.insert("|one|two|three|", window, cx);
 9495    });
 9496    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 9497    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 9498        editor.change_selections(
 9499            SelectionEffects::scroll(Autoscroll::Next),
 9500            window,
 9501            cx,
 9502            |s| s.select_ranges(Some(60..70)),
 9503        );
 9504        editor.insert("|four|five|six|", window, cx);
 9505    });
 9506    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 9507
 9508    // First two buffers should be edited, but not the third one.
 9509    assert_eq!(
 9510        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 9511        "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}",
 9512    );
 9513    buffer_1.update(cx, |buffer, _| {
 9514        assert!(buffer.is_dirty());
 9515        assert_eq!(
 9516            buffer.text(),
 9517            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
 9518        )
 9519    });
 9520    buffer_2.update(cx, |buffer, _| {
 9521        assert!(buffer.is_dirty());
 9522        assert_eq!(
 9523            buffer.text(),
 9524            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
 9525        )
 9526    });
 9527    buffer_3.update(cx, |buffer, _| {
 9528        assert!(!buffer.is_dirty());
 9529        assert_eq!(buffer.text(), sample_text_3,)
 9530    });
 9531    cx.executor().run_until_parked();
 9532
 9533    cx.executor().start_waiting();
 9534    let save = multi_buffer_editor
 9535        .update_in(cx, |editor, window, cx| {
 9536            editor.save(
 9537                SaveOptions {
 9538                    format: true,
 9539                    autosave: false,
 9540                },
 9541                project.clone(),
 9542                window,
 9543                cx,
 9544            )
 9545        })
 9546        .unwrap();
 9547
 9548    let fake_server = fake_servers.next().await.unwrap();
 9549    fake_server
 9550        .server
 9551        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9552            Ok(Some(vec![lsp::TextEdit::new(
 9553                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9554                format!("[{} formatted]", params.text_document.uri),
 9555            )]))
 9556        })
 9557        .detach();
 9558    save.await;
 9559
 9560    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
 9561    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
 9562    assert_eq!(
 9563        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 9564        uri!(
 9565            "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}"
 9566        ),
 9567    );
 9568    buffer_1.update(cx, |buffer, _| {
 9569        assert!(!buffer.is_dirty());
 9570        assert_eq!(
 9571            buffer.text(),
 9572            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
 9573        )
 9574    });
 9575    buffer_2.update(cx, |buffer, _| {
 9576        assert!(!buffer.is_dirty());
 9577        assert_eq!(
 9578            buffer.text(),
 9579            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
 9580        )
 9581    });
 9582    buffer_3.update(cx, |buffer, _| {
 9583        assert!(!buffer.is_dirty());
 9584        assert_eq!(buffer.text(), sample_text_3,)
 9585    });
 9586}
 9587
 9588#[gpui::test]
 9589async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
 9590    init_test(cx, |_| {});
 9591
 9592    let fs = FakeFs::new(cx.executor());
 9593    fs.insert_tree(
 9594        path!("/dir"),
 9595        json!({
 9596            "file1.rs": "fn main() { println!(\"hello\"); }",
 9597            "file2.rs": "fn test() { println!(\"test\"); }",
 9598            "file3.rs": "fn other() { println!(\"other\"); }\n",
 9599        }),
 9600    )
 9601    .await;
 9602
 9603    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
 9604    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9605    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 9606
 9607    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9608    language_registry.add(rust_lang());
 9609
 9610    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
 9611    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 9612
 9613    // Open three buffers
 9614    let buffer_1 = project
 9615        .update(cx, |project, cx| {
 9616            project.open_buffer((worktree_id, "file1.rs"), cx)
 9617        })
 9618        .await
 9619        .unwrap();
 9620    let buffer_2 = project
 9621        .update(cx, |project, cx| {
 9622            project.open_buffer((worktree_id, "file2.rs"), cx)
 9623        })
 9624        .await
 9625        .unwrap();
 9626    let buffer_3 = project
 9627        .update(cx, |project, cx| {
 9628            project.open_buffer((worktree_id, "file3.rs"), cx)
 9629        })
 9630        .await
 9631        .unwrap();
 9632
 9633    // Create a multi-buffer with all three buffers
 9634    let multi_buffer = cx.new(|cx| {
 9635        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 9636        multi_buffer.push_excerpts(
 9637            buffer_1.clone(),
 9638            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9639            cx,
 9640        );
 9641        multi_buffer.push_excerpts(
 9642            buffer_2.clone(),
 9643            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9644            cx,
 9645        );
 9646        multi_buffer.push_excerpts(
 9647            buffer_3.clone(),
 9648            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9649            cx,
 9650        );
 9651        multi_buffer
 9652    });
 9653
 9654    let editor = cx.new_window_entity(|window, cx| {
 9655        Editor::new(
 9656            EditorMode::full(),
 9657            multi_buffer,
 9658            Some(project.clone()),
 9659            window,
 9660            cx,
 9661        )
 9662    });
 9663
 9664    // Edit only the first buffer
 9665    editor.update_in(cx, |editor, window, cx| {
 9666        editor.change_selections(
 9667            SelectionEffects::scroll(Autoscroll::Next),
 9668            window,
 9669            cx,
 9670            |s| s.select_ranges(Some(10..10)),
 9671        );
 9672        editor.insert("// edited", window, cx);
 9673    });
 9674
 9675    // Verify that only buffer 1 is dirty
 9676    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
 9677    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9678    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9679
 9680    // Get write counts after file creation (files were created with initial content)
 9681    // We expect each file to have been written once during creation
 9682    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
 9683    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
 9684    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
 9685
 9686    // Perform autosave
 9687    let save_task = editor.update_in(cx, |editor, window, cx| {
 9688        editor.save(
 9689            SaveOptions {
 9690                format: true,
 9691                autosave: true,
 9692            },
 9693            project.clone(),
 9694            window,
 9695            cx,
 9696        )
 9697    });
 9698    save_task.await.unwrap();
 9699
 9700    // Only the dirty buffer should have been saved
 9701    assert_eq!(
 9702        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
 9703        1,
 9704        "Buffer 1 was dirty, so it should have been written once during autosave"
 9705    );
 9706    assert_eq!(
 9707        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
 9708        0,
 9709        "Buffer 2 was clean, so it should not have been written during autosave"
 9710    );
 9711    assert_eq!(
 9712        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
 9713        0,
 9714        "Buffer 3 was clean, so it should not have been written during autosave"
 9715    );
 9716
 9717    // Verify buffer states after autosave
 9718    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9719    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9720    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9721
 9722    // Now perform a manual save (format = true)
 9723    let save_task = editor.update_in(cx, |editor, window, cx| {
 9724        editor.save(
 9725            SaveOptions {
 9726                format: true,
 9727                autosave: false,
 9728            },
 9729            project.clone(),
 9730            window,
 9731            cx,
 9732        )
 9733    });
 9734    save_task.await.unwrap();
 9735
 9736    // During manual save, clean buffers don't get written to disk
 9737    // They just get did_save called for language server notifications
 9738    assert_eq!(
 9739        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
 9740        1,
 9741        "Buffer 1 should only have been written once total (during autosave, not manual save)"
 9742    );
 9743    assert_eq!(
 9744        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
 9745        0,
 9746        "Buffer 2 should not have been written at all"
 9747    );
 9748    assert_eq!(
 9749        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
 9750        0,
 9751        "Buffer 3 should not have been written at all"
 9752    );
 9753}
 9754
 9755#[gpui::test]
 9756async fn test_range_format_during_save(cx: &mut TestAppContext) {
 9757    init_test(cx, |_| {});
 9758
 9759    let fs = FakeFs::new(cx.executor());
 9760    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9761
 9762    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 9763
 9764    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9765    language_registry.add(rust_lang());
 9766    let mut fake_servers = language_registry.register_fake_lsp(
 9767        "Rust",
 9768        FakeLspAdapter {
 9769            capabilities: lsp::ServerCapabilities {
 9770                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
 9771                ..Default::default()
 9772            },
 9773            ..Default::default()
 9774        },
 9775    );
 9776
 9777    let buffer = project
 9778        .update(cx, |project, cx| {
 9779            project.open_local_buffer(path!("/file.rs"), cx)
 9780        })
 9781        .await
 9782        .unwrap();
 9783
 9784    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9785    let (editor, cx) = cx.add_window_view(|window, cx| {
 9786        build_editor_with_project(project.clone(), buffer, window, cx)
 9787    });
 9788    editor.update_in(cx, |editor, window, cx| {
 9789        editor.set_text("one\ntwo\nthree\n", window, cx)
 9790    });
 9791    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9792
 9793    cx.executor().start_waiting();
 9794    let fake_server = fake_servers.next().await.unwrap();
 9795
 9796    let save = editor
 9797        .update_in(cx, |editor, window, cx| {
 9798            editor.save(
 9799                SaveOptions {
 9800                    format: true,
 9801                    autosave: false,
 9802                },
 9803                project.clone(),
 9804                window,
 9805                cx,
 9806            )
 9807        })
 9808        .unwrap();
 9809    fake_server
 9810        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
 9811            assert_eq!(
 9812                params.text_document.uri,
 9813                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9814            );
 9815            assert_eq!(params.options.tab_size, 4);
 9816            Ok(Some(vec![lsp::TextEdit::new(
 9817                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9818                ", ".to_string(),
 9819            )]))
 9820        })
 9821        .next()
 9822        .await;
 9823    cx.executor().start_waiting();
 9824    save.await;
 9825    assert_eq!(
 9826        editor.update(cx, |editor, cx| editor.text(cx)),
 9827        "one, two\nthree\n"
 9828    );
 9829    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9830
 9831    editor.update_in(cx, |editor, window, cx| {
 9832        editor.set_text("one\ntwo\nthree\n", window, cx)
 9833    });
 9834    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9835
 9836    // Ensure we can still save even if formatting hangs.
 9837    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
 9838        move |params, _| async move {
 9839            assert_eq!(
 9840                params.text_document.uri,
 9841                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9842            );
 9843            futures::future::pending::<()>().await;
 9844            unreachable!()
 9845        },
 9846    );
 9847    let save = editor
 9848        .update_in(cx, |editor, window, cx| {
 9849            editor.save(
 9850                SaveOptions {
 9851                    format: true,
 9852                    autosave: false,
 9853                },
 9854                project.clone(),
 9855                window,
 9856                cx,
 9857            )
 9858        })
 9859        .unwrap();
 9860    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9861    cx.executor().start_waiting();
 9862    save.await;
 9863    assert_eq!(
 9864        editor.update(cx, |editor, cx| editor.text(cx)),
 9865        "one\ntwo\nthree\n"
 9866    );
 9867    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9868
 9869    // For non-dirty buffer, no formatting request should be sent
 9870    let save = editor
 9871        .update_in(cx, |editor, window, cx| {
 9872            editor.save(
 9873                SaveOptions {
 9874                    format: false,
 9875                    autosave: false,
 9876                },
 9877                project.clone(),
 9878                window,
 9879                cx,
 9880            )
 9881        })
 9882        .unwrap();
 9883    let _pending_format_request = fake_server
 9884        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
 9885            panic!("Should not be invoked");
 9886        })
 9887        .next();
 9888    cx.executor().start_waiting();
 9889    save.await;
 9890
 9891    // Set Rust language override and assert overridden tabsize is sent to language server
 9892    update_test_language_settings(cx, |settings| {
 9893        settings.languages.0.insert(
 9894            "Rust".into(),
 9895            LanguageSettingsContent {
 9896                tab_size: NonZeroU32::new(8),
 9897                ..Default::default()
 9898            },
 9899        );
 9900    });
 9901
 9902    editor.update_in(cx, |editor, window, cx| {
 9903        editor.set_text("somehting_new\n", window, cx)
 9904    });
 9905    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9906    let save = editor
 9907        .update_in(cx, |editor, window, cx| {
 9908            editor.save(
 9909                SaveOptions {
 9910                    format: true,
 9911                    autosave: false,
 9912                },
 9913                project.clone(),
 9914                window,
 9915                cx,
 9916            )
 9917        })
 9918        .unwrap();
 9919    fake_server
 9920        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
 9921            assert_eq!(
 9922                params.text_document.uri,
 9923                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9924            );
 9925            assert_eq!(params.options.tab_size, 8);
 9926            Ok(Some(Vec::new()))
 9927        })
 9928        .next()
 9929        .await;
 9930    save.await;
 9931}
 9932
 9933#[gpui::test]
 9934async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
 9935    init_test(cx, |settings| {
 9936        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(vec![
 9937            Formatter::LanguageServer { name: None },
 9938        ]))
 9939    });
 9940
 9941    let fs = FakeFs::new(cx.executor());
 9942    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9943
 9944    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 9945
 9946    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9947    language_registry.add(Arc::new(Language::new(
 9948        LanguageConfig {
 9949            name: "Rust".into(),
 9950            matcher: LanguageMatcher {
 9951                path_suffixes: vec!["rs".to_string()],
 9952                ..Default::default()
 9953            },
 9954            ..LanguageConfig::default()
 9955        },
 9956        Some(tree_sitter_rust::LANGUAGE.into()),
 9957    )));
 9958    update_test_language_settings(cx, |settings| {
 9959        // Enable Prettier formatting for the same buffer, and ensure
 9960        // LSP is called instead of Prettier.
 9961        settings.defaults.prettier = Some(PrettierSettings {
 9962            allowed: true,
 9963            ..PrettierSettings::default()
 9964        });
 9965    });
 9966    let mut fake_servers = language_registry.register_fake_lsp(
 9967        "Rust",
 9968        FakeLspAdapter {
 9969            capabilities: lsp::ServerCapabilities {
 9970                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9971                ..Default::default()
 9972            },
 9973            ..Default::default()
 9974        },
 9975    );
 9976
 9977    let buffer = project
 9978        .update(cx, |project, cx| {
 9979            project.open_local_buffer(path!("/file.rs"), cx)
 9980        })
 9981        .await
 9982        .unwrap();
 9983
 9984    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9985    let (editor, cx) = cx.add_window_view(|window, cx| {
 9986        build_editor_with_project(project.clone(), buffer, window, cx)
 9987    });
 9988    editor.update_in(cx, |editor, window, cx| {
 9989        editor.set_text("one\ntwo\nthree\n", window, cx)
 9990    });
 9991
 9992    cx.executor().start_waiting();
 9993    let fake_server = fake_servers.next().await.unwrap();
 9994
 9995    let format = editor
 9996        .update_in(cx, |editor, window, cx| {
 9997            editor.perform_format(
 9998                project.clone(),
 9999                FormatTrigger::Manual,
10000                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10001                window,
10002                cx,
10003            )
10004        })
10005        .unwrap();
10006    fake_server
10007        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
10008            assert_eq!(
10009                params.text_document.uri,
10010                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10011            );
10012            assert_eq!(params.options.tab_size, 4);
10013            Ok(Some(vec![lsp::TextEdit::new(
10014                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10015                ", ".to_string(),
10016            )]))
10017        })
10018        .next()
10019        .await;
10020    cx.executor().start_waiting();
10021    format.await;
10022    assert_eq!(
10023        editor.update(cx, |editor, cx| editor.text(cx)),
10024        "one, two\nthree\n"
10025    );
10026
10027    editor.update_in(cx, |editor, window, cx| {
10028        editor.set_text("one\ntwo\nthree\n", window, cx)
10029    });
10030    // Ensure we don't lock if formatting hangs.
10031    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10032        move |params, _| async move {
10033            assert_eq!(
10034                params.text_document.uri,
10035                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10036            );
10037            futures::future::pending::<()>().await;
10038            unreachable!()
10039        },
10040    );
10041    let format = editor
10042        .update_in(cx, |editor, window, cx| {
10043            editor.perform_format(
10044                project,
10045                FormatTrigger::Manual,
10046                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10047                window,
10048                cx,
10049            )
10050        })
10051        .unwrap();
10052    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10053    cx.executor().start_waiting();
10054    format.await;
10055    assert_eq!(
10056        editor.update(cx, |editor, cx| editor.text(cx)),
10057        "one\ntwo\nthree\n"
10058    );
10059}
10060
10061#[gpui::test]
10062async fn test_multiple_formatters(cx: &mut TestAppContext) {
10063    init_test(cx, |settings| {
10064        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
10065        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(vec![
10066            Formatter::LanguageServer { name: None },
10067            Formatter::CodeActions(
10068                [
10069                    ("code-action-1".into(), true),
10070                    ("code-action-2".into(), true),
10071                ]
10072                .into_iter()
10073                .collect(),
10074            ),
10075        ]))
10076    });
10077
10078    let fs = FakeFs::new(cx.executor());
10079    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
10080        .await;
10081
10082    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10083    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10084    language_registry.add(rust_lang());
10085
10086    let mut fake_servers = language_registry.register_fake_lsp(
10087        "Rust",
10088        FakeLspAdapter {
10089            capabilities: lsp::ServerCapabilities {
10090                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10091                execute_command_provider: Some(lsp::ExecuteCommandOptions {
10092                    commands: vec!["the-command-for-code-action-1".into()],
10093                    ..Default::default()
10094                }),
10095                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10096                ..Default::default()
10097            },
10098            ..Default::default()
10099        },
10100    );
10101
10102    let buffer = project
10103        .update(cx, |project, cx| {
10104            project.open_local_buffer(path!("/file.rs"), cx)
10105        })
10106        .await
10107        .unwrap();
10108
10109    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10110    let (editor, cx) = cx.add_window_view(|window, cx| {
10111        build_editor_with_project(project.clone(), buffer, window, cx)
10112    });
10113
10114    cx.executor().start_waiting();
10115
10116    let fake_server = fake_servers.next().await.unwrap();
10117    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10118        move |_params, _| async move {
10119            Ok(Some(vec![lsp::TextEdit::new(
10120                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10121                "applied-formatting\n".to_string(),
10122            )]))
10123        },
10124    );
10125    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
10126        move |params, _| async move {
10127            assert_eq!(
10128                params.context.only,
10129                Some(vec!["code-action-1".into(), "code-action-2".into()])
10130            );
10131            let uri = lsp::Url::from_file_path(path!("/file.rs")).unwrap();
10132            Ok(Some(vec![
10133                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10134                    kind: Some("code-action-1".into()),
10135                    edit: Some(lsp::WorkspaceEdit::new(
10136                        [(
10137                            uri.clone(),
10138                            vec![lsp::TextEdit::new(
10139                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10140                                "applied-code-action-1-edit\n".to_string(),
10141                            )],
10142                        )]
10143                        .into_iter()
10144                        .collect(),
10145                    )),
10146                    command: Some(lsp::Command {
10147                        command: "the-command-for-code-action-1".into(),
10148                        ..Default::default()
10149                    }),
10150                    ..Default::default()
10151                }),
10152                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10153                    kind: Some("code-action-2".into()),
10154                    edit: Some(lsp::WorkspaceEdit::new(
10155                        [(
10156                            uri.clone(),
10157                            vec![lsp::TextEdit::new(
10158                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10159                                "applied-code-action-2-edit\n".to_string(),
10160                            )],
10161                        )]
10162                        .into_iter()
10163                        .collect(),
10164                    )),
10165                    ..Default::default()
10166                }),
10167            ]))
10168        },
10169    );
10170
10171    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
10172        move |params, _| async move { Ok(params) }
10173    });
10174
10175    let command_lock = Arc::new(futures::lock::Mutex::new(()));
10176    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
10177        let fake = fake_server.clone();
10178        let lock = command_lock.clone();
10179        move |params, _| {
10180            assert_eq!(params.command, "the-command-for-code-action-1");
10181            let fake = fake.clone();
10182            let lock = lock.clone();
10183            async move {
10184                lock.lock().await;
10185                fake.server
10186                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
10187                        label: None,
10188                        edit: lsp::WorkspaceEdit {
10189                            changes: Some(
10190                                [(
10191                                    lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
10192                                    vec![lsp::TextEdit {
10193                                        range: lsp::Range::new(
10194                                            lsp::Position::new(0, 0),
10195                                            lsp::Position::new(0, 0),
10196                                        ),
10197                                        new_text: "applied-code-action-1-command\n".into(),
10198                                    }],
10199                                )]
10200                                .into_iter()
10201                                .collect(),
10202                            ),
10203                            ..Default::default()
10204                        },
10205                    })
10206                    .await
10207                    .into_response()
10208                    .unwrap();
10209                Ok(Some(json!(null)))
10210            }
10211        }
10212    });
10213
10214    cx.executor().start_waiting();
10215    editor
10216        .update_in(cx, |editor, window, cx| {
10217            editor.perform_format(
10218                project.clone(),
10219                FormatTrigger::Manual,
10220                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10221                window,
10222                cx,
10223            )
10224        })
10225        .unwrap()
10226        .await;
10227    editor.update(cx, |editor, cx| {
10228        assert_eq!(
10229            editor.text(cx),
10230            r#"
10231                applied-code-action-2-edit
10232                applied-code-action-1-command
10233                applied-code-action-1-edit
10234                applied-formatting
10235                one
10236                two
10237                three
10238            "#
10239            .unindent()
10240        );
10241    });
10242
10243    editor.update_in(cx, |editor, window, cx| {
10244        editor.undo(&Default::default(), window, cx);
10245        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10246    });
10247
10248    // Perform a manual edit while waiting for an LSP command
10249    // that's being run as part of a formatting code action.
10250    let lock_guard = command_lock.lock().await;
10251    let format = editor
10252        .update_in(cx, |editor, window, cx| {
10253            editor.perform_format(
10254                project.clone(),
10255                FormatTrigger::Manual,
10256                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10257                window,
10258                cx,
10259            )
10260        })
10261        .unwrap();
10262    cx.run_until_parked();
10263    editor.update(cx, |editor, cx| {
10264        assert_eq!(
10265            editor.text(cx),
10266            r#"
10267                applied-code-action-1-edit
10268                applied-formatting
10269                one
10270                two
10271                three
10272            "#
10273            .unindent()
10274        );
10275
10276        editor.buffer.update(cx, |buffer, cx| {
10277            let ix = buffer.len(cx);
10278            buffer.edit([(ix..ix, "edited\n")], None, cx);
10279        });
10280    });
10281
10282    // Allow the LSP command to proceed. Because the buffer was edited,
10283    // the second code action will not be run.
10284    drop(lock_guard);
10285    format.await;
10286    editor.update_in(cx, |editor, window, cx| {
10287        assert_eq!(
10288            editor.text(cx),
10289            r#"
10290                applied-code-action-1-command
10291                applied-code-action-1-edit
10292                applied-formatting
10293                one
10294                two
10295                three
10296                edited
10297            "#
10298            .unindent()
10299        );
10300
10301        // The manual edit is undone first, because it is the last thing the user did
10302        // (even though the command completed afterwards).
10303        editor.undo(&Default::default(), window, cx);
10304        assert_eq!(
10305            editor.text(cx),
10306            r#"
10307                applied-code-action-1-command
10308                applied-code-action-1-edit
10309                applied-formatting
10310                one
10311                two
10312                three
10313            "#
10314            .unindent()
10315        );
10316
10317        // All the formatting (including the command, which completed after the manual edit)
10318        // is undone together.
10319        editor.undo(&Default::default(), window, cx);
10320        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10321    });
10322}
10323
10324#[gpui::test]
10325async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
10326    init_test(cx, |settings| {
10327        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(vec![
10328            Formatter::LanguageServer { name: None },
10329        ]))
10330    });
10331
10332    let fs = FakeFs::new(cx.executor());
10333    fs.insert_file(path!("/file.ts"), Default::default()).await;
10334
10335    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10336
10337    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10338    language_registry.add(Arc::new(Language::new(
10339        LanguageConfig {
10340            name: "TypeScript".into(),
10341            matcher: LanguageMatcher {
10342                path_suffixes: vec!["ts".to_string()],
10343                ..Default::default()
10344            },
10345            ..LanguageConfig::default()
10346        },
10347        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
10348    )));
10349    update_test_language_settings(cx, |settings| {
10350        settings.defaults.prettier = Some(PrettierSettings {
10351            allowed: true,
10352            ..PrettierSettings::default()
10353        });
10354    });
10355    let mut fake_servers = language_registry.register_fake_lsp(
10356        "TypeScript",
10357        FakeLspAdapter {
10358            capabilities: lsp::ServerCapabilities {
10359                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10360                ..Default::default()
10361            },
10362            ..Default::default()
10363        },
10364    );
10365
10366    let buffer = project
10367        .update(cx, |project, cx| {
10368            project.open_local_buffer(path!("/file.ts"), cx)
10369        })
10370        .await
10371        .unwrap();
10372
10373    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10374    let (editor, cx) = cx.add_window_view(|window, cx| {
10375        build_editor_with_project(project.clone(), buffer, window, cx)
10376    });
10377    editor.update_in(cx, |editor, window, cx| {
10378        editor.set_text(
10379            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
10380            window,
10381            cx,
10382        )
10383    });
10384
10385    cx.executor().start_waiting();
10386    let fake_server = fake_servers.next().await.unwrap();
10387
10388    let format = editor
10389        .update_in(cx, |editor, window, cx| {
10390            editor.perform_code_action_kind(
10391                project.clone(),
10392                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
10393                window,
10394                cx,
10395            )
10396        })
10397        .unwrap();
10398    fake_server
10399        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
10400            assert_eq!(
10401                params.text_document.uri,
10402                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
10403            );
10404            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
10405                lsp::CodeAction {
10406                    title: "Organize Imports".to_string(),
10407                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
10408                    edit: Some(lsp::WorkspaceEdit {
10409                        changes: Some(
10410                            [(
10411                                params.text_document.uri.clone(),
10412                                vec![lsp::TextEdit::new(
10413                                    lsp::Range::new(
10414                                        lsp::Position::new(1, 0),
10415                                        lsp::Position::new(2, 0),
10416                                    ),
10417                                    "".to_string(),
10418                                )],
10419                            )]
10420                            .into_iter()
10421                            .collect(),
10422                        ),
10423                        ..Default::default()
10424                    }),
10425                    ..Default::default()
10426                },
10427            )]))
10428        })
10429        .next()
10430        .await;
10431    cx.executor().start_waiting();
10432    format.await;
10433    assert_eq!(
10434        editor.update(cx, |editor, cx| editor.text(cx)),
10435        "import { a } from 'module';\n\nconst x = a;\n"
10436    );
10437
10438    editor.update_in(cx, |editor, window, cx| {
10439        editor.set_text(
10440            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
10441            window,
10442            cx,
10443        )
10444    });
10445    // Ensure we don't lock if code action hangs.
10446    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
10447        move |params, _| async move {
10448            assert_eq!(
10449                params.text_document.uri,
10450                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
10451            );
10452            futures::future::pending::<()>().await;
10453            unreachable!()
10454        },
10455    );
10456    let format = editor
10457        .update_in(cx, |editor, window, cx| {
10458            editor.perform_code_action_kind(
10459                project,
10460                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
10461                window,
10462                cx,
10463            )
10464        })
10465        .unwrap();
10466    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
10467    cx.executor().start_waiting();
10468    format.await;
10469    assert_eq!(
10470        editor.update(cx, |editor, cx| editor.text(cx)),
10471        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
10472    );
10473}
10474
10475#[gpui::test]
10476async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
10477    init_test(cx, |_| {});
10478
10479    let mut cx = EditorLspTestContext::new_rust(
10480        lsp::ServerCapabilities {
10481            document_formatting_provider: Some(lsp::OneOf::Left(true)),
10482            ..Default::default()
10483        },
10484        cx,
10485    )
10486    .await;
10487
10488    cx.set_state(indoc! {"
10489        one.twoˇ
10490    "});
10491
10492    // The format request takes a long time. When it completes, it inserts
10493    // a newline and an indent before the `.`
10494    cx.lsp
10495        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
10496            let executor = cx.background_executor().clone();
10497            async move {
10498                executor.timer(Duration::from_millis(100)).await;
10499                Ok(Some(vec![lsp::TextEdit {
10500                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
10501                    new_text: "\n    ".into(),
10502                }]))
10503            }
10504        });
10505
10506    // Submit a format request.
10507    let format_1 = cx
10508        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10509        .unwrap();
10510    cx.executor().run_until_parked();
10511
10512    // Submit a second format request.
10513    let format_2 = cx
10514        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10515        .unwrap();
10516    cx.executor().run_until_parked();
10517
10518    // Wait for both format requests to complete
10519    cx.executor().advance_clock(Duration::from_millis(200));
10520    cx.executor().start_waiting();
10521    format_1.await.unwrap();
10522    cx.executor().start_waiting();
10523    format_2.await.unwrap();
10524
10525    // The formatting edits only happens once.
10526    cx.assert_editor_state(indoc! {"
10527        one
10528            .twoˇ
10529    "});
10530}
10531
10532#[gpui::test]
10533async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
10534    init_test(cx, |settings| {
10535        settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
10536    });
10537
10538    let mut cx = EditorLspTestContext::new_rust(
10539        lsp::ServerCapabilities {
10540            document_formatting_provider: Some(lsp::OneOf::Left(true)),
10541            ..Default::default()
10542        },
10543        cx,
10544    )
10545    .await;
10546
10547    // Set up a buffer white some trailing whitespace and no trailing newline.
10548    cx.set_state(
10549        &[
10550            "one ",   //
10551            "twoˇ",   //
10552            "three ", //
10553            "four",   //
10554        ]
10555        .join("\n"),
10556    );
10557
10558    // Submit a format request.
10559    let format = cx
10560        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10561        .unwrap();
10562
10563    // Record which buffer changes have been sent to the language server
10564    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
10565    cx.lsp
10566        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
10567            let buffer_changes = buffer_changes.clone();
10568            move |params, _| {
10569                buffer_changes.lock().extend(
10570                    params
10571                        .content_changes
10572                        .into_iter()
10573                        .map(|e| (e.range.unwrap(), e.text)),
10574                );
10575            }
10576        });
10577
10578    // Handle formatting requests to the language server.
10579    cx.lsp
10580        .set_request_handler::<lsp::request::Formatting, _, _>({
10581            let buffer_changes = buffer_changes.clone();
10582            move |_, _| {
10583                // When formatting is requested, trailing whitespace has already been stripped,
10584                // and the trailing newline has already been added.
10585                assert_eq!(
10586                    &buffer_changes.lock()[1..],
10587                    &[
10588                        (
10589                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
10590                            "".into()
10591                        ),
10592                        (
10593                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
10594                            "".into()
10595                        ),
10596                        (
10597                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
10598                            "\n".into()
10599                        ),
10600                    ]
10601                );
10602
10603                // Insert blank lines between each line of the buffer.
10604                async move {
10605                    Ok(Some(vec![
10606                        lsp::TextEdit {
10607                            range: lsp::Range::new(
10608                                lsp::Position::new(1, 0),
10609                                lsp::Position::new(1, 0),
10610                            ),
10611                            new_text: "\n".into(),
10612                        },
10613                        lsp::TextEdit {
10614                            range: lsp::Range::new(
10615                                lsp::Position::new(2, 0),
10616                                lsp::Position::new(2, 0),
10617                            ),
10618                            new_text: "\n".into(),
10619                        },
10620                    ]))
10621                }
10622            }
10623        });
10624
10625    // After formatting the buffer, the trailing whitespace is stripped,
10626    // a newline is appended, and the edits provided by the language server
10627    // have been applied.
10628    format.await.unwrap();
10629    cx.assert_editor_state(
10630        &[
10631            "one",   //
10632            "",      //
10633            "twoˇ",  //
10634            "",      //
10635            "three", //
10636            "four",  //
10637            "",      //
10638        ]
10639        .join("\n"),
10640    );
10641
10642    // Undoing the formatting undoes the trailing whitespace removal, the
10643    // trailing newline, and the LSP edits.
10644    cx.update_buffer(|buffer, cx| buffer.undo(cx));
10645    cx.assert_editor_state(
10646        &[
10647            "one ",   //
10648            "twoˇ",   //
10649            "three ", //
10650            "four",   //
10651        ]
10652        .join("\n"),
10653    );
10654}
10655
10656#[gpui::test]
10657async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
10658    cx: &mut TestAppContext,
10659) {
10660    init_test(cx, |_| {});
10661
10662    cx.update(|cx| {
10663        cx.update_global::<SettingsStore, _>(|settings, cx| {
10664            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10665                settings.auto_signature_help = Some(true);
10666            });
10667        });
10668    });
10669
10670    let mut cx = EditorLspTestContext::new_rust(
10671        lsp::ServerCapabilities {
10672            signature_help_provider: Some(lsp::SignatureHelpOptions {
10673                ..Default::default()
10674            }),
10675            ..Default::default()
10676        },
10677        cx,
10678    )
10679    .await;
10680
10681    let language = Language::new(
10682        LanguageConfig {
10683            name: "Rust".into(),
10684            brackets: BracketPairConfig {
10685                pairs: vec![
10686                    BracketPair {
10687                        start: "{".to_string(),
10688                        end: "}".to_string(),
10689                        close: true,
10690                        surround: true,
10691                        newline: true,
10692                    },
10693                    BracketPair {
10694                        start: "(".to_string(),
10695                        end: ")".to_string(),
10696                        close: true,
10697                        surround: true,
10698                        newline: true,
10699                    },
10700                    BracketPair {
10701                        start: "/*".to_string(),
10702                        end: " */".to_string(),
10703                        close: true,
10704                        surround: true,
10705                        newline: true,
10706                    },
10707                    BracketPair {
10708                        start: "[".to_string(),
10709                        end: "]".to_string(),
10710                        close: false,
10711                        surround: false,
10712                        newline: true,
10713                    },
10714                    BracketPair {
10715                        start: "\"".to_string(),
10716                        end: "\"".to_string(),
10717                        close: true,
10718                        surround: true,
10719                        newline: false,
10720                    },
10721                    BracketPair {
10722                        start: "<".to_string(),
10723                        end: ">".to_string(),
10724                        close: false,
10725                        surround: true,
10726                        newline: true,
10727                    },
10728                ],
10729                ..Default::default()
10730            },
10731            autoclose_before: "})]".to_string(),
10732            ..Default::default()
10733        },
10734        Some(tree_sitter_rust::LANGUAGE.into()),
10735    );
10736    let language = Arc::new(language);
10737
10738    cx.language_registry().add(language.clone());
10739    cx.update_buffer(|buffer, cx| {
10740        buffer.set_language(Some(language), cx);
10741    });
10742
10743    cx.set_state(
10744        &r#"
10745            fn main() {
10746                sampleˇ
10747            }
10748        "#
10749        .unindent(),
10750    );
10751
10752    cx.update_editor(|editor, window, cx| {
10753        editor.handle_input("(", window, cx);
10754    });
10755    cx.assert_editor_state(
10756        &"
10757            fn main() {
10758                sample(ˇ)
10759            }
10760        "
10761        .unindent(),
10762    );
10763
10764    let mocked_response = lsp::SignatureHelp {
10765        signatures: vec![lsp::SignatureInformation {
10766            label: "fn sample(param1: u8, param2: u8)".to_string(),
10767            documentation: None,
10768            parameters: Some(vec![
10769                lsp::ParameterInformation {
10770                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10771                    documentation: None,
10772                },
10773                lsp::ParameterInformation {
10774                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10775                    documentation: None,
10776                },
10777            ]),
10778            active_parameter: None,
10779        }],
10780        active_signature: Some(0),
10781        active_parameter: Some(0),
10782    };
10783    handle_signature_help_request(&mut cx, mocked_response).await;
10784
10785    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10786        .await;
10787
10788    cx.editor(|editor, _, _| {
10789        let signature_help_state = editor.signature_help_state.popover().cloned();
10790        assert_eq!(
10791            signature_help_state.unwrap().label,
10792            "param1: u8, param2: u8"
10793        );
10794    });
10795}
10796
10797#[gpui::test]
10798async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
10799    init_test(cx, |_| {});
10800
10801    cx.update(|cx| {
10802        cx.update_global::<SettingsStore, _>(|settings, cx| {
10803            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10804                settings.auto_signature_help = Some(false);
10805                settings.show_signature_help_after_edits = Some(false);
10806            });
10807        });
10808    });
10809
10810    let mut cx = EditorLspTestContext::new_rust(
10811        lsp::ServerCapabilities {
10812            signature_help_provider: Some(lsp::SignatureHelpOptions {
10813                ..Default::default()
10814            }),
10815            ..Default::default()
10816        },
10817        cx,
10818    )
10819    .await;
10820
10821    let language = Language::new(
10822        LanguageConfig {
10823            name: "Rust".into(),
10824            brackets: BracketPairConfig {
10825                pairs: vec![
10826                    BracketPair {
10827                        start: "{".to_string(),
10828                        end: "}".to_string(),
10829                        close: true,
10830                        surround: true,
10831                        newline: true,
10832                    },
10833                    BracketPair {
10834                        start: "(".to_string(),
10835                        end: ")".to_string(),
10836                        close: true,
10837                        surround: true,
10838                        newline: true,
10839                    },
10840                    BracketPair {
10841                        start: "/*".to_string(),
10842                        end: " */".to_string(),
10843                        close: true,
10844                        surround: true,
10845                        newline: true,
10846                    },
10847                    BracketPair {
10848                        start: "[".to_string(),
10849                        end: "]".to_string(),
10850                        close: false,
10851                        surround: false,
10852                        newline: true,
10853                    },
10854                    BracketPair {
10855                        start: "\"".to_string(),
10856                        end: "\"".to_string(),
10857                        close: true,
10858                        surround: true,
10859                        newline: false,
10860                    },
10861                    BracketPair {
10862                        start: "<".to_string(),
10863                        end: ">".to_string(),
10864                        close: false,
10865                        surround: true,
10866                        newline: true,
10867                    },
10868                ],
10869                ..Default::default()
10870            },
10871            autoclose_before: "})]".to_string(),
10872            ..Default::default()
10873        },
10874        Some(tree_sitter_rust::LANGUAGE.into()),
10875    );
10876    let language = Arc::new(language);
10877
10878    cx.language_registry().add(language.clone());
10879    cx.update_buffer(|buffer, cx| {
10880        buffer.set_language(Some(language), cx);
10881    });
10882
10883    // Ensure that signature_help is not called when no signature help is enabled.
10884    cx.set_state(
10885        &r#"
10886            fn main() {
10887                sampleˇ
10888            }
10889        "#
10890        .unindent(),
10891    );
10892    cx.update_editor(|editor, window, cx| {
10893        editor.handle_input("(", window, cx);
10894    });
10895    cx.assert_editor_state(
10896        &"
10897            fn main() {
10898                sample(ˇ)
10899            }
10900        "
10901        .unindent(),
10902    );
10903    cx.editor(|editor, _, _| {
10904        assert!(editor.signature_help_state.task().is_none());
10905    });
10906
10907    let mocked_response = lsp::SignatureHelp {
10908        signatures: vec![lsp::SignatureInformation {
10909            label: "fn sample(param1: u8, param2: u8)".to_string(),
10910            documentation: None,
10911            parameters: Some(vec![
10912                lsp::ParameterInformation {
10913                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10914                    documentation: None,
10915                },
10916                lsp::ParameterInformation {
10917                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10918                    documentation: None,
10919                },
10920            ]),
10921            active_parameter: None,
10922        }],
10923        active_signature: Some(0),
10924        active_parameter: Some(0),
10925    };
10926
10927    // Ensure that signature_help is called when enabled afte edits
10928    cx.update(|_, cx| {
10929        cx.update_global::<SettingsStore, _>(|settings, cx| {
10930            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10931                settings.auto_signature_help = Some(false);
10932                settings.show_signature_help_after_edits = Some(true);
10933            });
10934        });
10935    });
10936    cx.set_state(
10937        &r#"
10938            fn main() {
10939                sampleˇ
10940            }
10941        "#
10942        .unindent(),
10943    );
10944    cx.update_editor(|editor, window, cx| {
10945        editor.handle_input("(", window, cx);
10946    });
10947    cx.assert_editor_state(
10948        &"
10949            fn main() {
10950                sample(ˇ)
10951            }
10952        "
10953        .unindent(),
10954    );
10955    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
10956    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10957        .await;
10958    cx.update_editor(|editor, _, _| {
10959        let signature_help_state = editor.signature_help_state.popover().cloned();
10960        assert!(signature_help_state.is_some());
10961        assert_eq!(
10962            signature_help_state.unwrap().label,
10963            "param1: u8, param2: u8"
10964        );
10965        editor.signature_help_state = SignatureHelpState::default();
10966    });
10967
10968    // Ensure that signature_help is called when auto signature help override is enabled
10969    cx.update(|_, cx| {
10970        cx.update_global::<SettingsStore, _>(|settings, cx| {
10971            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10972                settings.auto_signature_help = Some(true);
10973                settings.show_signature_help_after_edits = Some(false);
10974            });
10975        });
10976    });
10977    cx.set_state(
10978        &r#"
10979            fn main() {
10980                sampleˇ
10981            }
10982        "#
10983        .unindent(),
10984    );
10985    cx.update_editor(|editor, window, cx| {
10986        editor.handle_input("(", window, cx);
10987    });
10988    cx.assert_editor_state(
10989        &"
10990            fn main() {
10991                sample(ˇ)
10992            }
10993        "
10994        .unindent(),
10995    );
10996    handle_signature_help_request(&mut cx, mocked_response).await;
10997    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10998        .await;
10999    cx.editor(|editor, _, _| {
11000        let signature_help_state = editor.signature_help_state.popover().cloned();
11001        assert!(signature_help_state.is_some());
11002        assert_eq!(
11003            signature_help_state.unwrap().label,
11004            "param1: u8, param2: u8"
11005        );
11006    });
11007}
11008
11009#[gpui::test]
11010async fn test_signature_help(cx: &mut TestAppContext) {
11011    init_test(cx, |_| {});
11012    cx.update(|cx| {
11013        cx.update_global::<SettingsStore, _>(|settings, cx| {
11014            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11015                settings.auto_signature_help = Some(true);
11016            });
11017        });
11018    });
11019
11020    let mut cx = EditorLspTestContext::new_rust(
11021        lsp::ServerCapabilities {
11022            signature_help_provider: Some(lsp::SignatureHelpOptions {
11023                ..Default::default()
11024            }),
11025            ..Default::default()
11026        },
11027        cx,
11028    )
11029    .await;
11030
11031    // A test that directly calls `show_signature_help`
11032    cx.update_editor(|editor, window, cx| {
11033        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11034    });
11035
11036    let mocked_response = lsp::SignatureHelp {
11037        signatures: vec![lsp::SignatureInformation {
11038            label: "fn sample(param1: u8, param2: u8)".to_string(),
11039            documentation: None,
11040            parameters: Some(vec![
11041                lsp::ParameterInformation {
11042                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11043                    documentation: None,
11044                },
11045                lsp::ParameterInformation {
11046                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11047                    documentation: None,
11048                },
11049            ]),
11050            active_parameter: None,
11051        }],
11052        active_signature: Some(0),
11053        active_parameter: Some(0),
11054    };
11055    handle_signature_help_request(&mut cx, mocked_response).await;
11056
11057    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11058        .await;
11059
11060    cx.editor(|editor, _, _| {
11061        let signature_help_state = editor.signature_help_state.popover().cloned();
11062        assert!(signature_help_state.is_some());
11063        assert_eq!(
11064            signature_help_state.unwrap().label,
11065            "param1: u8, param2: u8"
11066        );
11067    });
11068
11069    // When exiting outside from inside the brackets, `signature_help` is closed.
11070    cx.set_state(indoc! {"
11071        fn main() {
11072            sample(ˇ);
11073        }
11074
11075        fn sample(param1: u8, param2: u8) {}
11076    "});
11077
11078    cx.update_editor(|editor, window, cx| {
11079        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11080            s.select_ranges([0..0])
11081        });
11082    });
11083
11084    let mocked_response = lsp::SignatureHelp {
11085        signatures: Vec::new(),
11086        active_signature: None,
11087        active_parameter: None,
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        assert!(!editor.signature_help_state.is_shown());
11096    });
11097
11098    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
11099    cx.set_state(indoc! {"
11100        fn main() {
11101            sample(ˇ);
11102        }
11103
11104        fn sample(param1: u8, param2: u8) {}
11105    "});
11106
11107    let mocked_response = lsp::SignatureHelp {
11108        signatures: vec![lsp::SignatureInformation {
11109            label: "fn sample(param1: u8, param2: u8)".to_string(),
11110            documentation: None,
11111            parameters: Some(vec![
11112                lsp::ParameterInformation {
11113                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11114                    documentation: None,
11115                },
11116                lsp::ParameterInformation {
11117                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11118                    documentation: None,
11119                },
11120            ]),
11121            active_parameter: None,
11122        }],
11123        active_signature: Some(0),
11124        active_parameter: Some(0),
11125    };
11126    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11127    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11128        .await;
11129    cx.editor(|editor, _, _| {
11130        assert!(editor.signature_help_state.is_shown());
11131    });
11132
11133    // Restore the popover with more parameter input
11134    cx.set_state(indoc! {"
11135        fn main() {
11136            sample(param1, param2ˇ);
11137        }
11138
11139        fn sample(param1: u8, param2: u8) {}
11140    "});
11141
11142    let mocked_response = lsp::SignatureHelp {
11143        signatures: vec![lsp::SignatureInformation {
11144            label: "fn sample(param1: u8, param2: u8)".to_string(),
11145            documentation: None,
11146            parameters: Some(vec![
11147                lsp::ParameterInformation {
11148                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11149                    documentation: None,
11150                },
11151                lsp::ParameterInformation {
11152                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11153                    documentation: None,
11154                },
11155            ]),
11156            active_parameter: None,
11157        }],
11158        active_signature: Some(0),
11159        active_parameter: Some(1),
11160    };
11161    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11162    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11163        .await;
11164
11165    // When selecting a range, the popover is gone.
11166    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
11167    cx.update_editor(|editor, window, cx| {
11168        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11169            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11170        })
11171    });
11172    cx.assert_editor_state(indoc! {"
11173        fn main() {
11174            sample(param1, «ˇparam2»);
11175        }
11176
11177        fn sample(param1: u8, param2: u8) {}
11178    "});
11179    cx.editor(|editor, _, _| {
11180        assert!(!editor.signature_help_state.is_shown());
11181    });
11182
11183    // When unselecting again, the popover is back if within the brackets.
11184    cx.update_editor(|editor, window, cx| {
11185        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11186            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11187        })
11188    });
11189    cx.assert_editor_state(indoc! {"
11190        fn main() {
11191            sample(param1, ˇparam2);
11192        }
11193
11194        fn sample(param1: u8, param2: u8) {}
11195    "});
11196    handle_signature_help_request(&mut cx, mocked_response).await;
11197    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11198        .await;
11199    cx.editor(|editor, _, _| {
11200        assert!(editor.signature_help_state.is_shown());
11201    });
11202
11203    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
11204    cx.update_editor(|editor, window, cx| {
11205        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11206            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
11207            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11208        })
11209    });
11210    cx.assert_editor_state(indoc! {"
11211        fn main() {
11212            sample(param1, ˇparam2);
11213        }
11214
11215        fn sample(param1: u8, param2: u8) {}
11216    "});
11217
11218    let mocked_response = lsp::SignatureHelp {
11219        signatures: vec![lsp::SignatureInformation {
11220            label: "fn sample(param1: u8, param2: u8)".to_string(),
11221            documentation: None,
11222            parameters: Some(vec![
11223                lsp::ParameterInformation {
11224                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11225                    documentation: None,
11226                },
11227                lsp::ParameterInformation {
11228                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11229                    documentation: None,
11230                },
11231            ]),
11232            active_parameter: None,
11233        }],
11234        active_signature: Some(0),
11235        active_parameter: Some(1),
11236    };
11237    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11238    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11239        .await;
11240    cx.update_editor(|editor, _, cx| {
11241        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
11242    });
11243    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11244        .await;
11245    cx.update_editor(|editor, window, cx| {
11246        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11247            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11248        })
11249    });
11250    cx.assert_editor_state(indoc! {"
11251        fn main() {
11252            sample(param1, «ˇparam2»);
11253        }
11254
11255        fn sample(param1: u8, param2: u8) {}
11256    "});
11257    cx.update_editor(|editor, window, cx| {
11258        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11259            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11260        })
11261    });
11262    cx.assert_editor_state(indoc! {"
11263        fn main() {
11264            sample(param1, ˇparam2);
11265        }
11266
11267        fn sample(param1: u8, param2: u8) {}
11268    "});
11269    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
11270        .await;
11271}
11272
11273#[gpui::test]
11274async fn test_completion_mode(cx: &mut TestAppContext) {
11275    init_test(cx, |_| {});
11276    let mut cx = EditorLspTestContext::new_rust(
11277        lsp::ServerCapabilities {
11278            completion_provider: Some(lsp::CompletionOptions {
11279                resolve_provider: Some(true),
11280                ..Default::default()
11281            }),
11282            ..Default::default()
11283        },
11284        cx,
11285    )
11286    .await;
11287
11288    struct Run {
11289        run_description: &'static str,
11290        initial_state: String,
11291        buffer_marked_text: String,
11292        completion_label: &'static str,
11293        completion_text: &'static str,
11294        expected_with_insert_mode: String,
11295        expected_with_replace_mode: String,
11296        expected_with_replace_subsequence_mode: String,
11297        expected_with_replace_suffix_mode: String,
11298    }
11299
11300    let runs = [
11301        Run {
11302            run_description: "Start of word matches completion text",
11303            initial_state: "before ediˇ after".into(),
11304            buffer_marked_text: "before <edi|> after".into(),
11305            completion_label: "editor",
11306            completion_text: "editor",
11307            expected_with_insert_mode: "before editorˇ after".into(),
11308            expected_with_replace_mode: "before editorˇ after".into(),
11309            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11310            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11311        },
11312        Run {
11313            run_description: "Accept same text at the middle of the word",
11314            initial_state: "before ediˇtor after".into(),
11315            buffer_marked_text: "before <edi|tor> after".into(),
11316            completion_label: "editor",
11317            completion_text: "editor",
11318            expected_with_insert_mode: "before editorˇtor after".into(),
11319            expected_with_replace_mode: "before editorˇ after".into(),
11320            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11321            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11322        },
11323        Run {
11324            run_description: "End of word matches completion text -- cursor at end",
11325            initial_state: "before torˇ after".into(),
11326            buffer_marked_text: "before <tor|> after".into(),
11327            completion_label: "editor",
11328            completion_text: "editor",
11329            expected_with_insert_mode: "before editorˇ after".into(),
11330            expected_with_replace_mode: "before editorˇ after".into(),
11331            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11332            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11333        },
11334        Run {
11335            run_description: "End of word matches completion text -- cursor at start",
11336            initial_state: "before ˇtor after".into(),
11337            buffer_marked_text: "before <|tor> after".into(),
11338            completion_label: "editor",
11339            completion_text: "editor",
11340            expected_with_insert_mode: "before editorˇtor after".into(),
11341            expected_with_replace_mode: "before editorˇ after".into(),
11342            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11343            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11344        },
11345        Run {
11346            run_description: "Prepend text containing whitespace",
11347            initial_state: "pˇfield: bool".into(),
11348            buffer_marked_text: "<p|field>: bool".into(),
11349            completion_label: "pub ",
11350            completion_text: "pub ",
11351            expected_with_insert_mode: "pub ˇfield: bool".into(),
11352            expected_with_replace_mode: "pub ˇ: bool".into(),
11353            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
11354            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
11355        },
11356        Run {
11357            run_description: "Add element to start of list",
11358            initial_state: "[element_ˇelement_2]".into(),
11359            buffer_marked_text: "[<element_|element_2>]".into(),
11360            completion_label: "element_1",
11361            completion_text: "element_1",
11362            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
11363            expected_with_replace_mode: "[element_1ˇ]".into(),
11364            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
11365            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
11366        },
11367        Run {
11368            run_description: "Add element to start of list -- first and second elements are equal",
11369            initial_state: "[elˇelement]".into(),
11370            buffer_marked_text: "[<el|element>]".into(),
11371            completion_label: "element",
11372            completion_text: "element",
11373            expected_with_insert_mode: "[elementˇelement]".into(),
11374            expected_with_replace_mode: "[elementˇ]".into(),
11375            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
11376            expected_with_replace_suffix_mode: "[elementˇ]".into(),
11377        },
11378        Run {
11379            run_description: "Ends with matching suffix",
11380            initial_state: "SubˇError".into(),
11381            buffer_marked_text: "<Sub|Error>".into(),
11382            completion_label: "SubscriptionError",
11383            completion_text: "SubscriptionError",
11384            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
11385            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11386            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11387            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
11388        },
11389        Run {
11390            run_description: "Suffix is a subsequence -- contiguous",
11391            initial_state: "SubˇErr".into(),
11392            buffer_marked_text: "<Sub|Err>".into(),
11393            completion_label: "SubscriptionError",
11394            completion_text: "SubscriptionError",
11395            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
11396            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11397            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11398            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
11399        },
11400        Run {
11401            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
11402            initial_state: "Suˇscrirr".into(),
11403            buffer_marked_text: "<Su|scrirr>".into(),
11404            completion_label: "SubscriptionError",
11405            completion_text: "SubscriptionError",
11406            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
11407            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11408            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11409            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
11410        },
11411        Run {
11412            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
11413            initial_state: "foo(indˇix)".into(),
11414            buffer_marked_text: "foo(<ind|ix>)".into(),
11415            completion_label: "node_index",
11416            completion_text: "node_index",
11417            expected_with_insert_mode: "foo(node_indexˇix)".into(),
11418            expected_with_replace_mode: "foo(node_indexˇ)".into(),
11419            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
11420            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
11421        },
11422        Run {
11423            run_description: "Replace range ends before cursor - should extend to cursor",
11424            initial_state: "before editˇo after".into(),
11425            buffer_marked_text: "before <{ed}>it|o after".into(),
11426            completion_label: "editor",
11427            completion_text: "editor",
11428            expected_with_insert_mode: "before editorˇo after".into(),
11429            expected_with_replace_mode: "before editorˇo after".into(),
11430            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
11431            expected_with_replace_suffix_mode: "before editorˇo after".into(),
11432        },
11433        Run {
11434            run_description: "Uses label for suffix matching",
11435            initial_state: "before ediˇtor after".into(),
11436            buffer_marked_text: "before <edi|tor> after".into(),
11437            completion_label: "editor",
11438            completion_text: "editor()",
11439            expected_with_insert_mode: "before editor()ˇtor after".into(),
11440            expected_with_replace_mode: "before editor()ˇ after".into(),
11441            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
11442            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
11443        },
11444        Run {
11445            run_description: "Case insensitive subsequence and suffix matching",
11446            initial_state: "before EDiˇtoR after".into(),
11447            buffer_marked_text: "before <EDi|toR> after".into(),
11448            completion_label: "editor",
11449            completion_text: "editor",
11450            expected_with_insert_mode: "before editorˇtoR after".into(),
11451            expected_with_replace_mode: "before editorˇ after".into(),
11452            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11453            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11454        },
11455    ];
11456
11457    for run in runs {
11458        let run_variations = [
11459            (LspInsertMode::Insert, run.expected_with_insert_mode),
11460            (LspInsertMode::Replace, run.expected_with_replace_mode),
11461            (
11462                LspInsertMode::ReplaceSubsequence,
11463                run.expected_with_replace_subsequence_mode,
11464            ),
11465            (
11466                LspInsertMode::ReplaceSuffix,
11467                run.expected_with_replace_suffix_mode,
11468            ),
11469        ];
11470
11471        for (lsp_insert_mode, expected_text) in run_variations {
11472            eprintln!(
11473                "run = {:?}, mode = {lsp_insert_mode:.?}",
11474                run.run_description,
11475            );
11476
11477            update_test_language_settings(&mut cx, |settings| {
11478                settings.defaults.completions = Some(CompletionSettings {
11479                    lsp_insert_mode,
11480                    words: WordsCompletionMode::Disabled,
11481                    lsp: true,
11482                    lsp_fetch_timeout_ms: 0,
11483                });
11484            });
11485
11486            cx.set_state(&run.initial_state);
11487            cx.update_editor(|editor, window, cx| {
11488                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11489            });
11490
11491            let counter = Arc::new(AtomicUsize::new(0));
11492            handle_completion_request_with_insert_and_replace(
11493                &mut cx,
11494                &run.buffer_marked_text,
11495                vec![(run.completion_label, run.completion_text)],
11496                counter.clone(),
11497            )
11498            .await;
11499            cx.condition(|editor, _| editor.context_menu_visible())
11500                .await;
11501            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11502
11503            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11504                editor
11505                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
11506                    .unwrap()
11507            });
11508            cx.assert_editor_state(&expected_text);
11509            handle_resolve_completion_request(&mut cx, None).await;
11510            apply_additional_edits.await.unwrap();
11511        }
11512    }
11513}
11514
11515#[gpui::test]
11516async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
11517    init_test(cx, |_| {});
11518    let mut cx = EditorLspTestContext::new_rust(
11519        lsp::ServerCapabilities {
11520            completion_provider: Some(lsp::CompletionOptions {
11521                resolve_provider: Some(true),
11522                ..Default::default()
11523            }),
11524            ..Default::default()
11525        },
11526        cx,
11527    )
11528    .await;
11529
11530    let initial_state = "SubˇError";
11531    let buffer_marked_text = "<Sub|Error>";
11532    let completion_text = "SubscriptionError";
11533    let expected_with_insert_mode = "SubscriptionErrorˇError";
11534    let expected_with_replace_mode = "SubscriptionErrorˇ";
11535
11536    update_test_language_settings(&mut cx, |settings| {
11537        settings.defaults.completions = Some(CompletionSettings {
11538            words: WordsCompletionMode::Disabled,
11539            // set the opposite here to ensure that the action is overriding the default behavior
11540            lsp_insert_mode: LspInsertMode::Insert,
11541            lsp: true,
11542            lsp_fetch_timeout_ms: 0,
11543        });
11544    });
11545
11546    cx.set_state(initial_state);
11547    cx.update_editor(|editor, window, cx| {
11548        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11549    });
11550
11551    let counter = Arc::new(AtomicUsize::new(0));
11552    handle_completion_request_with_insert_and_replace(
11553        &mut cx,
11554        &buffer_marked_text,
11555        vec![(completion_text, completion_text)],
11556        counter.clone(),
11557    )
11558    .await;
11559    cx.condition(|editor, _| editor.context_menu_visible())
11560        .await;
11561    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11562
11563    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11564        editor
11565            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11566            .unwrap()
11567    });
11568    cx.assert_editor_state(&expected_with_replace_mode);
11569    handle_resolve_completion_request(&mut cx, None).await;
11570    apply_additional_edits.await.unwrap();
11571
11572    update_test_language_settings(&mut cx, |settings| {
11573        settings.defaults.completions = Some(CompletionSettings {
11574            words: WordsCompletionMode::Disabled,
11575            // set the opposite here to ensure that the action is overriding the default behavior
11576            lsp_insert_mode: LspInsertMode::Replace,
11577            lsp: true,
11578            lsp_fetch_timeout_ms: 0,
11579        });
11580    });
11581
11582    cx.set_state(initial_state);
11583    cx.update_editor(|editor, window, cx| {
11584        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11585    });
11586    handle_completion_request_with_insert_and_replace(
11587        &mut cx,
11588        &buffer_marked_text,
11589        vec![(completion_text, completion_text)],
11590        counter.clone(),
11591    )
11592    .await;
11593    cx.condition(|editor, _| editor.context_menu_visible())
11594        .await;
11595    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
11596
11597    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11598        editor
11599            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
11600            .unwrap()
11601    });
11602    cx.assert_editor_state(&expected_with_insert_mode);
11603    handle_resolve_completion_request(&mut cx, None).await;
11604    apply_additional_edits.await.unwrap();
11605}
11606
11607#[gpui::test]
11608async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
11609    init_test(cx, |_| {});
11610    let mut cx = EditorLspTestContext::new_rust(
11611        lsp::ServerCapabilities {
11612            completion_provider: Some(lsp::CompletionOptions {
11613                resolve_provider: Some(true),
11614                ..Default::default()
11615            }),
11616            ..Default::default()
11617        },
11618        cx,
11619    )
11620    .await;
11621
11622    // scenario: surrounding text matches completion text
11623    let completion_text = "to_offset";
11624    let initial_state = indoc! {"
11625        1. buf.to_offˇsuffix
11626        2. buf.to_offˇsuf
11627        3. buf.to_offˇfix
11628        4. buf.to_offˇ
11629        5. into_offˇensive
11630        6. ˇsuffix
11631        7. let ˇ //
11632        8. aaˇzz
11633        9. buf.to_off«zzzzzˇ»suffix
11634        10. buf.«ˇzzzzz»suffix
11635        11. to_off«ˇzzzzz»
11636
11637        buf.to_offˇsuffix  // newest cursor
11638    "};
11639    let completion_marked_buffer = indoc! {"
11640        1. buf.to_offsuffix
11641        2. buf.to_offsuf
11642        3. buf.to_offfix
11643        4. buf.to_off
11644        5. into_offensive
11645        6. suffix
11646        7. let  //
11647        8. aazz
11648        9. buf.to_offzzzzzsuffix
11649        10. buf.zzzzzsuffix
11650        11. to_offzzzzz
11651
11652        buf.<to_off|suffix>  // newest cursor
11653    "};
11654    let expected = indoc! {"
11655        1. buf.to_offsetˇ
11656        2. buf.to_offsetˇsuf
11657        3. buf.to_offsetˇfix
11658        4. buf.to_offsetˇ
11659        5. into_offsetˇensive
11660        6. to_offsetˇsuffix
11661        7. let to_offsetˇ //
11662        8. aato_offsetˇzz
11663        9. buf.to_offsetˇ
11664        10. buf.to_offsetˇsuffix
11665        11. to_offsetˇ
11666
11667        buf.to_offsetˇ  // newest cursor
11668    "};
11669    cx.set_state(initial_state);
11670    cx.update_editor(|editor, window, cx| {
11671        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11672    });
11673    handle_completion_request_with_insert_and_replace(
11674        &mut cx,
11675        completion_marked_buffer,
11676        vec![(completion_text, completion_text)],
11677        Arc::new(AtomicUsize::new(0)),
11678    )
11679    .await;
11680    cx.condition(|editor, _| editor.context_menu_visible())
11681        .await;
11682    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11683        editor
11684            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11685            .unwrap()
11686    });
11687    cx.assert_editor_state(expected);
11688    handle_resolve_completion_request(&mut cx, None).await;
11689    apply_additional_edits.await.unwrap();
11690
11691    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
11692    let completion_text = "foo_and_bar";
11693    let initial_state = indoc! {"
11694        1. ooanbˇ
11695        2. zooanbˇ
11696        3. ooanbˇz
11697        4. zooanbˇz
11698        5. ooanˇ
11699        6. oanbˇ
11700
11701        ooanbˇ
11702    "};
11703    let completion_marked_buffer = indoc! {"
11704        1. ooanb
11705        2. zooanb
11706        3. ooanbz
11707        4. zooanbz
11708        5. ooan
11709        6. oanb
11710
11711        <ooanb|>
11712    "};
11713    let expected = indoc! {"
11714        1. foo_and_barˇ
11715        2. zfoo_and_barˇ
11716        3. foo_and_barˇz
11717        4. zfoo_and_barˇz
11718        5. ooanfoo_and_barˇ
11719        6. oanbfoo_and_barˇ
11720
11721        foo_and_barˇ
11722    "};
11723    cx.set_state(initial_state);
11724    cx.update_editor(|editor, window, cx| {
11725        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11726    });
11727    handle_completion_request_with_insert_and_replace(
11728        &mut cx,
11729        completion_marked_buffer,
11730        vec![(completion_text, completion_text)],
11731        Arc::new(AtomicUsize::new(0)),
11732    )
11733    .await;
11734    cx.condition(|editor, _| editor.context_menu_visible())
11735        .await;
11736    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11737        editor
11738            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11739            .unwrap()
11740    });
11741    cx.assert_editor_state(expected);
11742    handle_resolve_completion_request(&mut cx, None).await;
11743    apply_additional_edits.await.unwrap();
11744
11745    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
11746    // (expects the same as if it was inserted at the end)
11747    let completion_text = "foo_and_bar";
11748    let initial_state = indoc! {"
11749        1. ooˇanb
11750        2. zooˇanb
11751        3. ooˇanbz
11752        4. zooˇanbz
11753
11754        ooˇanb
11755    "};
11756    let completion_marked_buffer = indoc! {"
11757        1. ooanb
11758        2. zooanb
11759        3. ooanbz
11760        4. zooanbz
11761
11762        <oo|anb>
11763    "};
11764    let expected = indoc! {"
11765        1. foo_and_barˇ
11766        2. zfoo_and_barˇ
11767        3. foo_and_barˇz
11768        4. zfoo_and_barˇz
11769
11770        foo_and_barˇ
11771    "};
11772    cx.set_state(initial_state);
11773    cx.update_editor(|editor, window, cx| {
11774        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11775    });
11776    handle_completion_request_with_insert_and_replace(
11777        &mut cx,
11778        completion_marked_buffer,
11779        vec![(completion_text, completion_text)],
11780        Arc::new(AtomicUsize::new(0)),
11781    )
11782    .await;
11783    cx.condition(|editor, _| editor.context_menu_visible())
11784        .await;
11785    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11786        editor
11787            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11788            .unwrap()
11789    });
11790    cx.assert_editor_state(expected);
11791    handle_resolve_completion_request(&mut cx, None).await;
11792    apply_additional_edits.await.unwrap();
11793}
11794
11795// This used to crash
11796#[gpui::test]
11797async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
11798    init_test(cx, |_| {});
11799
11800    let buffer_text = indoc! {"
11801        fn main() {
11802            10.satu;
11803
11804            //
11805            // separate cursors so they open in different excerpts (manually reproducible)
11806            //
11807
11808            10.satu20;
11809        }
11810    "};
11811    let multibuffer_text_with_selections = indoc! {"
11812        fn main() {
11813            10.satuˇ;
11814
11815            //
11816
11817            //
11818
11819            10.satuˇ20;
11820        }
11821    "};
11822    let expected_multibuffer = indoc! {"
11823        fn main() {
11824            10.saturating_sub()ˇ;
11825
11826            //
11827
11828            //
11829
11830            10.saturating_sub()ˇ;
11831        }
11832    "};
11833
11834    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
11835    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
11836
11837    let fs = FakeFs::new(cx.executor());
11838    fs.insert_tree(
11839        path!("/a"),
11840        json!({
11841            "main.rs": buffer_text,
11842        }),
11843    )
11844    .await;
11845
11846    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11847    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11848    language_registry.add(rust_lang());
11849    let mut fake_servers = language_registry.register_fake_lsp(
11850        "Rust",
11851        FakeLspAdapter {
11852            capabilities: lsp::ServerCapabilities {
11853                completion_provider: Some(lsp::CompletionOptions {
11854                    resolve_provider: None,
11855                    ..lsp::CompletionOptions::default()
11856                }),
11857                ..lsp::ServerCapabilities::default()
11858            },
11859            ..FakeLspAdapter::default()
11860        },
11861    );
11862    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11863    let cx = &mut VisualTestContext::from_window(*workspace, cx);
11864    let buffer = project
11865        .update(cx, |project, cx| {
11866            project.open_local_buffer(path!("/a/main.rs"), cx)
11867        })
11868        .await
11869        .unwrap();
11870
11871    let multi_buffer = cx.new(|cx| {
11872        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
11873        multi_buffer.push_excerpts(
11874            buffer.clone(),
11875            [ExcerptRange::new(0..first_excerpt_end)],
11876            cx,
11877        );
11878        multi_buffer.push_excerpts(
11879            buffer.clone(),
11880            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
11881            cx,
11882        );
11883        multi_buffer
11884    });
11885
11886    let editor = workspace
11887        .update(cx, |_, window, cx| {
11888            cx.new(|cx| {
11889                Editor::new(
11890                    EditorMode::Full {
11891                        scale_ui_elements_with_buffer_font_size: false,
11892                        show_active_line_background: false,
11893                        sized_by_content: false,
11894                    },
11895                    multi_buffer.clone(),
11896                    Some(project.clone()),
11897                    window,
11898                    cx,
11899                )
11900            })
11901        })
11902        .unwrap();
11903
11904    let pane = workspace
11905        .update(cx, |workspace, _, _| workspace.active_pane().clone())
11906        .unwrap();
11907    pane.update_in(cx, |pane, window, cx| {
11908        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
11909    });
11910
11911    let fake_server = fake_servers.next().await.unwrap();
11912
11913    editor.update_in(cx, |editor, window, cx| {
11914        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11915            s.select_ranges([
11916                Point::new(1, 11)..Point::new(1, 11),
11917                Point::new(7, 11)..Point::new(7, 11),
11918            ])
11919        });
11920
11921        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
11922    });
11923
11924    editor.update_in(cx, |editor, window, cx| {
11925        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11926    });
11927
11928    fake_server
11929        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
11930            let completion_item = lsp::CompletionItem {
11931                label: "saturating_sub()".into(),
11932                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
11933                    lsp::InsertReplaceEdit {
11934                        new_text: "saturating_sub()".to_owned(),
11935                        insert: lsp::Range::new(
11936                            lsp::Position::new(7, 7),
11937                            lsp::Position::new(7, 11),
11938                        ),
11939                        replace: lsp::Range::new(
11940                            lsp::Position::new(7, 7),
11941                            lsp::Position::new(7, 13),
11942                        ),
11943                    },
11944                )),
11945                ..lsp::CompletionItem::default()
11946            };
11947
11948            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
11949        })
11950        .next()
11951        .await
11952        .unwrap();
11953
11954    cx.condition(&editor, |editor, _| editor.context_menu_visible())
11955        .await;
11956
11957    editor
11958        .update_in(cx, |editor, window, cx| {
11959            editor
11960                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11961                .unwrap()
11962        })
11963        .await
11964        .unwrap();
11965
11966    editor.update(cx, |editor, cx| {
11967        assert_text_with_selections(editor, expected_multibuffer, cx);
11968    })
11969}
11970
11971#[gpui::test]
11972async fn test_completion(cx: &mut TestAppContext) {
11973    init_test(cx, |_| {});
11974
11975    let mut cx = EditorLspTestContext::new_rust(
11976        lsp::ServerCapabilities {
11977            completion_provider: Some(lsp::CompletionOptions {
11978                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
11979                resolve_provider: Some(true),
11980                ..Default::default()
11981            }),
11982            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
11983            ..Default::default()
11984        },
11985        cx,
11986    )
11987    .await;
11988    let counter = Arc::new(AtomicUsize::new(0));
11989
11990    cx.set_state(indoc! {"
11991        oneˇ
11992        two
11993        three
11994    "});
11995    cx.simulate_keystroke(".");
11996    handle_completion_request(
11997        indoc! {"
11998            one.|<>
11999            two
12000            three
12001        "},
12002        vec!["first_completion", "second_completion"],
12003        true,
12004        counter.clone(),
12005        &mut cx,
12006    )
12007    .await;
12008    cx.condition(|editor, _| editor.context_menu_visible())
12009        .await;
12010    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12011
12012    let _handler = handle_signature_help_request(
12013        &mut cx,
12014        lsp::SignatureHelp {
12015            signatures: vec![lsp::SignatureInformation {
12016                label: "test signature".to_string(),
12017                documentation: None,
12018                parameters: Some(vec![lsp::ParameterInformation {
12019                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
12020                    documentation: None,
12021                }]),
12022                active_parameter: None,
12023            }],
12024            active_signature: None,
12025            active_parameter: None,
12026        },
12027    );
12028    cx.update_editor(|editor, window, cx| {
12029        assert!(
12030            !editor.signature_help_state.is_shown(),
12031            "No signature help was called for"
12032        );
12033        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12034    });
12035    cx.run_until_parked();
12036    cx.update_editor(|editor, _, _| {
12037        assert!(
12038            !editor.signature_help_state.is_shown(),
12039            "No signature help should be shown when completions menu is open"
12040        );
12041    });
12042
12043    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12044        editor.context_menu_next(&Default::default(), window, cx);
12045        editor
12046            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12047            .unwrap()
12048    });
12049    cx.assert_editor_state(indoc! {"
12050        one.second_completionˇ
12051        two
12052        three
12053    "});
12054
12055    handle_resolve_completion_request(
12056        &mut cx,
12057        Some(vec![
12058            (
12059                //This overlaps with the primary completion edit which is
12060                //misbehavior from the LSP spec, test that we filter it out
12061                indoc! {"
12062                    one.second_ˇcompletion
12063                    two
12064                    threeˇ
12065                "},
12066                "overlapping additional edit",
12067            ),
12068            (
12069                indoc! {"
12070                    one.second_completion
12071                    two
12072                    threeˇ
12073                "},
12074                "\nadditional edit",
12075            ),
12076        ]),
12077    )
12078    .await;
12079    apply_additional_edits.await.unwrap();
12080    cx.assert_editor_state(indoc! {"
12081        one.second_completionˇ
12082        two
12083        three
12084        additional edit
12085    "});
12086
12087    cx.set_state(indoc! {"
12088        one.second_completion
12089        twoˇ
12090        threeˇ
12091        additional edit
12092    "});
12093    cx.simulate_keystroke(" ");
12094    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12095    cx.simulate_keystroke("s");
12096    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12097
12098    cx.assert_editor_state(indoc! {"
12099        one.second_completion
12100        two sˇ
12101        three sˇ
12102        additional edit
12103    "});
12104    handle_completion_request(
12105        indoc! {"
12106            one.second_completion
12107            two s
12108            three <s|>
12109            additional edit
12110        "},
12111        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
12112        true,
12113        counter.clone(),
12114        &mut cx,
12115    )
12116    .await;
12117    cx.condition(|editor, _| editor.context_menu_visible())
12118        .await;
12119    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12120
12121    cx.simulate_keystroke("i");
12122
12123    handle_completion_request(
12124        indoc! {"
12125            one.second_completion
12126            two si
12127            three <si|>
12128            additional edit
12129        "},
12130        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
12131        true,
12132        counter.clone(),
12133        &mut cx,
12134    )
12135    .await;
12136    cx.condition(|editor, _| editor.context_menu_visible())
12137        .await;
12138    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12139
12140    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12141        editor
12142            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12143            .unwrap()
12144    });
12145    cx.assert_editor_state(indoc! {"
12146        one.second_completion
12147        two sixth_completionˇ
12148        three sixth_completionˇ
12149        additional edit
12150    "});
12151
12152    apply_additional_edits.await.unwrap();
12153
12154    update_test_language_settings(&mut cx, |settings| {
12155        settings.defaults.show_completions_on_input = Some(false);
12156    });
12157    cx.set_state("editorˇ");
12158    cx.simulate_keystroke(".");
12159    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12160    cx.simulate_keystrokes("c l o");
12161    cx.assert_editor_state("editor.cloˇ");
12162    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12163    cx.update_editor(|editor, window, cx| {
12164        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12165    });
12166    handle_completion_request(
12167        "editor.<clo|>",
12168        vec!["close", "clobber"],
12169        true,
12170        counter.clone(),
12171        &mut cx,
12172    )
12173    .await;
12174    cx.condition(|editor, _| editor.context_menu_visible())
12175        .await;
12176    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
12177
12178    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12179        editor
12180            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12181            .unwrap()
12182    });
12183    cx.assert_editor_state("editor.clobberˇ");
12184    handle_resolve_completion_request(&mut cx, None).await;
12185    apply_additional_edits.await.unwrap();
12186}
12187
12188#[gpui::test]
12189async fn test_completion_reuse(cx: &mut TestAppContext) {
12190    init_test(cx, |_| {});
12191
12192    let mut cx = EditorLspTestContext::new_rust(
12193        lsp::ServerCapabilities {
12194            completion_provider: Some(lsp::CompletionOptions {
12195                trigger_characters: Some(vec![".".to_string()]),
12196                ..Default::default()
12197            }),
12198            ..Default::default()
12199        },
12200        cx,
12201    )
12202    .await;
12203
12204    let counter = Arc::new(AtomicUsize::new(0));
12205    cx.set_state("objˇ");
12206    cx.simulate_keystroke(".");
12207
12208    // Initial completion request returns complete results
12209    let is_incomplete = false;
12210    handle_completion_request(
12211        "obj.|<>",
12212        vec!["a", "ab", "abc"],
12213        is_incomplete,
12214        counter.clone(),
12215        &mut cx,
12216    )
12217    .await;
12218    cx.run_until_parked();
12219    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12220    cx.assert_editor_state("obj.ˇ");
12221    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12222
12223    // Type "a" - filters existing completions
12224    cx.simulate_keystroke("a");
12225    cx.run_until_parked();
12226    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12227    cx.assert_editor_state("obj.aˇ");
12228    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12229
12230    // Type "b" - filters existing completions
12231    cx.simulate_keystroke("b");
12232    cx.run_until_parked();
12233    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12234    cx.assert_editor_state("obj.abˇ");
12235    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12236
12237    // Type "c" - filters existing completions
12238    cx.simulate_keystroke("c");
12239    cx.run_until_parked();
12240    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12241    cx.assert_editor_state("obj.abcˇ");
12242    check_displayed_completions(vec!["abc"], &mut cx);
12243
12244    // Backspace to delete "c" - filters existing completions
12245    cx.update_editor(|editor, window, cx| {
12246        editor.backspace(&Backspace, window, cx);
12247    });
12248    cx.run_until_parked();
12249    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12250    cx.assert_editor_state("obj.abˇ");
12251    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12252
12253    // Moving cursor to the left dismisses menu.
12254    cx.update_editor(|editor, window, cx| {
12255        editor.move_left(&MoveLeft, window, cx);
12256    });
12257    cx.run_until_parked();
12258    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12259    cx.assert_editor_state("obj.aˇb");
12260    cx.update_editor(|editor, _, _| {
12261        assert_eq!(editor.context_menu_visible(), false);
12262    });
12263
12264    // Type "b" - new request
12265    cx.simulate_keystroke("b");
12266    let is_incomplete = false;
12267    handle_completion_request(
12268        "obj.<ab|>a",
12269        vec!["ab", "abc"],
12270        is_incomplete,
12271        counter.clone(),
12272        &mut cx,
12273    )
12274    .await;
12275    cx.run_until_parked();
12276    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12277    cx.assert_editor_state("obj.abˇb");
12278    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12279
12280    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
12281    cx.update_editor(|editor, window, cx| {
12282        editor.backspace(&Backspace, window, cx);
12283    });
12284    let is_incomplete = false;
12285    handle_completion_request(
12286        "obj.<a|>b",
12287        vec!["a", "ab", "abc"],
12288        is_incomplete,
12289        counter.clone(),
12290        &mut cx,
12291    )
12292    .await;
12293    cx.run_until_parked();
12294    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12295    cx.assert_editor_state("obj.aˇb");
12296    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12297
12298    // Backspace to delete "a" - dismisses menu.
12299    cx.update_editor(|editor, window, cx| {
12300        editor.backspace(&Backspace, window, cx);
12301    });
12302    cx.run_until_parked();
12303    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12304    cx.assert_editor_state("obj.ˇb");
12305    cx.update_editor(|editor, _, _| {
12306        assert_eq!(editor.context_menu_visible(), false);
12307    });
12308}
12309
12310#[gpui::test]
12311async fn test_word_completion(cx: &mut TestAppContext) {
12312    let lsp_fetch_timeout_ms = 10;
12313    init_test(cx, |language_settings| {
12314        language_settings.defaults.completions = Some(CompletionSettings {
12315            words: WordsCompletionMode::Fallback,
12316            lsp: true,
12317            lsp_fetch_timeout_ms: 10,
12318            lsp_insert_mode: LspInsertMode::Insert,
12319        });
12320    });
12321
12322    let mut cx = EditorLspTestContext::new_rust(
12323        lsp::ServerCapabilities {
12324            completion_provider: Some(lsp::CompletionOptions {
12325                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12326                ..lsp::CompletionOptions::default()
12327            }),
12328            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12329            ..lsp::ServerCapabilities::default()
12330        },
12331        cx,
12332    )
12333    .await;
12334
12335    let throttle_completions = Arc::new(AtomicBool::new(false));
12336
12337    let lsp_throttle_completions = throttle_completions.clone();
12338    let _completion_requests_handler =
12339        cx.lsp
12340            .server
12341            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
12342                let lsp_throttle_completions = lsp_throttle_completions.clone();
12343                let cx = cx.clone();
12344                async move {
12345                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
12346                        cx.background_executor()
12347                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
12348                            .await;
12349                    }
12350                    Ok(Some(lsp::CompletionResponse::Array(vec![
12351                        lsp::CompletionItem {
12352                            label: "first".into(),
12353                            ..lsp::CompletionItem::default()
12354                        },
12355                        lsp::CompletionItem {
12356                            label: "last".into(),
12357                            ..lsp::CompletionItem::default()
12358                        },
12359                    ])))
12360                }
12361            });
12362
12363    cx.set_state(indoc! {"
12364        oneˇ
12365        two
12366        three
12367    "});
12368    cx.simulate_keystroke(".");
12369    cx.executor().run_until_parked();
12370    cx.condition(|editor, _| editor.context_menu_visible())
12371        .await;
12372    cx.update_editor(|editor, window, cx| {
12373        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12374        {
12375            assert_eq!(
12376                completion_menu_entries(&menu),
12377                &["first", "last"],
12378                "When LSP server is fast to reply, no fallback word completions are used"
12379            );
12380        } else {
12381            panic!("expected completion menu to be open");
12382        }
12383        editor.cancel(&Cancel, window, cx);
12384    });
12385    cx.executor().run_until_parked();
12386    cx.condition(|editor, _| !editor.context_menu_visible())
12387        .await;
12388
12389    throttle_completions.store(true, atomic::Ordering::Release);
12390    cx.simulate_keystroke(".");
12391    cx.executor()
12392        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
12393    cx.executor().run_until_parked();
12394    cx.condition(|editor, _| editor.context_menu_visible())
12395        .await;
12396    cx.update_editor(|editor, _, _| {
12397        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12398        {
12399            assert_eq!(completion_menu_entries(&menu), &["one", "three", "two"],
12400                "When LSP server is slow, document words can be shown instead, if configured accordingly");
12401        } else {
12402            panic!("expected completion menu to be open");
12403        }
12404    });
12405}
12406
12407#[gpui::test]
12408async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
12409    init_test(cx, |language_settings| {
12410        language_settings.defaults.completions = Some(CompletionSettings {
12411            words: WordsCompletionMode::Enabled,
12412            lsp: true,
12413            lsp_fetch_timeout_ms: 0,
12414            lsp_insert_mode: LspInsertMode::Insert,
12415        });
12416    });
12417
12418    let mut cx = EditorLspTestContext::new_rust(
12419        lsp::ServerCapabilities {
12420            completion_provider: Some(lsp::CompletionOptions {
12421                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12422                ..lsp::CompletionOptions::default()
12423            }),
12424            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12425            ..lsp::ServerCapabilities::default()
12426        },
12427        cx,
12428    )
12429    .await;
12430
12431    let _completion_requests_handler =
12432        cx.lsp
12433            .server
12434            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12435                Ok(Some(lsp::CompletionResponse::Array(vec![
12436                    lsp::CompletionItem {
12437                        label: "first".into(),
12438                        ..lsp::CompletionItem::default()
12439                    },
12440                    lsp::CompletionItem {
12441                        label: "last".into(),
12442                        ..lsp::CompletionItem::default()
12443                    },
12444                ])))
12445            });
12446
12447    cx.set_state(indoc! {"ˇ
12448        first
12449        last
12450        second
12451    "});
12452    cx.simulate_keystroke(".");
12453    cx.executor().run_until_parked();
12454    cx.condition(|editor, _| editor.context_menu_visible())
12455        .await;
12456    cx.update_editor(|editor, _, _| {
12457        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12458        {
12459            assert_eq!(
12460                completion_menu_entries(&menu),
12461                &["first", "last", "second"],
12462                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
12463            );
12464        } else {
12465            panic!("expected completion menu to be open");
12466        }
12467    });
12468}
12469
12470#[gpui::test]
12471async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
12472    init_test(cx, |language_settings| {
12473        language_settings.defaults.completions = Some(CompletionSettings {
12474            words: WordsCompletionMode::Disabled,
12475            lsp: true,
12476            lsp_fetch_timeout_ms: 0,
12477            lsp_insert_mode: LspInsertMode::Insert,
12478        });
12479    });
12480
12481    let mut cx = EditorLspTestContext::new_rust(
12482        lsp::ServerCapabilities {
12483            completion_provider: Some(lsp::CompletionOptions {
12484                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12485                ..lsp::CompletionOptions::default()
12486            }),
12487            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12488            ..lsp::ServerCapabilities::default()
12489        },
12490        cx,
12491    )
12492    .await;
12493
12494    let _completion_requests_handler =
12495        cx.lsp
12496            .server
12497            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12498                panic!("LSP completions should not be queried when dealing with word completions")
12499            });
12500
12501    cx.set_state(indoc! {"ˇ
12502        first
12503        last
12504        second
12505    "});
12506    cx.update_editor(|editor, window, cx| {
12507        editor.show_word_completions(&ShowWordCompletions, window, cx);
12508    });
12509    cx.executor().run_until_parked();
12510    cx.condition(|editor, _| editor.context_menu_visible())
12511        .await;
12512    cx.update_editor(|editor, _, _| {
12513        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12514        {
12515            assert_eq!(
12516                completion_menu_entries(&menu),
12517                &["first", "last", "second"],
12518                "`ShowWordCompletions` action should show word completions"
12519            );
12520        } else {
12521            panic!("expected completion menu to be open");
12522        }
12523    });
12524
12525    cx.simulate_keystroke("l");
12526    cx.executor().run_until_parked();
12527    cx.condition(|editor, _| editor.context_menu_visible())
12528        .await;
12529    cx.update_editor(|editor, _, _| {
12530        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12531        {
12532            assert_eq!(
12533                completion_menu_entries(&menu),
12534                &["last"],
12535                "After showing word completions, further editing should filter them and not query the LSP"
12536            );
12537        } else {
12538            panic!("expected completion menu to be open");
12539        }
12540    });
12541}
12542
12543#[gpui::test]
12544async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
12545    init_test(cx, |language_settings| {
12546        language_settings.defaults.completions = Some(CompletionSettings {
12547            words: WordsCompletionMode::Fallback,
12548            lsp: false,
12549            lsp_fetch_timeout_ms: 0,
12550            lsp_insert_mode: LspInsertMode::Insert,
12551        });
12552    });
12553
12554    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12555
12556    cx.set_state(indoc! {"ˇ
12557        0_usize
12558        let
12559        33
12560        4.5f32
12561    "});
12562    cx.update_editor(|editor, window, cx| {
12563        editor.show_completions(&ShowCompletions::default(), window, cx);
12564    });
12565    cx.executor().run_until_parked();
12566    cx.condition(|editor, _| editor.context_menu_visible())
12567        .await;
12568    cx.update_editor(|editor, window, cx| {
12569        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12570        {
12571            assert_eq!(
12572                completion_menu_entries(&menu),
12573                &["let"],
12574                "With no digits in the completion query, no digits should be in the word completions"
12575            );
12576        } else {
12577            panic!("expected completion menu to be open");
12578        }
12579        editor.cancel(&Cancel, window, cx);
12580    });
12581
12582    cx.set_state(indoc! {"12583        0_usize
12584        let
12585        3
12586        33.35f32
12587    "});
12588    cx.update_editor(|editor, window, cx| {
12589        editor.show_completions(&ShowCompletions::default(), window, cx);
12590    });
12591    cx.executor().run_until_parked();
12592    cx.condition(|editor, _| editor.context_menu_visible())
12593        .await;
12594    cx.update_editor(|editor, _, _| {
12595        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12596        {
12597            assert_eq!(completion_menu_entries(&menu), &["33", "35f32"], "The digit is in the completion query, \
12598                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
12599        } else {
12600            panic!("expected completion menu to be open");
12601        }
12602    });
12603}
12604
12605fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
12606    let position = || lsp::Position {
12607        line: params.text_document_position.position.line,
12608        character: params.text_document_position.position.character,
12609    };
12610    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12611        range: lsp::Range {
12612            start: position(),
12613            end: position(),
12614        },
12615        new_text: text.to_string(),
12616    }))
12617}
12618
12619#[gpui::test]
12620async fn test_multiline_completion(cx: &mut TestAppContext) {
12621    init_test(cx, |_| {});
12622
12623    let fs = FakeFs::new(cx.executor());
12624    fs.insert_tree(
12625        path!("/a"),
12626        json!({
12627            "main.ts": "a",
12628        }),
12629    )
12630    .await;
12631
12632    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12633    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12634    let typescript_language = Arc::new(Language::new(
12635        LanguageConfig {
12636            name: "TypeScript".into(),
12637            matcher: LanguageMatcher {
12638                path_suffixes: vec!["ts".to_string()],
12639                ..LanguageMatcher::default()
12640            },
12641            line_comments: vec!["// ".into()],
12642            ..LanguageConfig::default()
12643        },
12644        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12645    ));
12646    language_registry.add(typescript_language.clone());
12647    let mut fake_servers = language_registry.register_fake_lsp(
12648        "TypeScript",
12649        FakeLspAdapter {
12650            capabilities: lsp::ServerCapabilities {
12651                completion_provider: Some(lsp::CompletionOptions {
12652                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12653                    ..lsp::CompletionOptions::default()
12654                }),
12655                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12656                ..lsp::ServerCapabilities::default()
12657            },
12658            // Emulate vtsls label generation
12659            label_for_completion: Some(Box::new(|item, _| {
12660                let text = if let Some(description) = item
12661                    .label_details
12662                    .as_ref()
12663                    .and_then(|label_details| label_details.description.as_ref())
12664                {
12665                    format!("{} {}", item.label, description)
12666                } else if let Some(detail) = &item.detail {
12667                    format!("{} {}", item.label, detail)
12668                } else {
12669                    item.label.clone()
12670                };
12671                let len = text.len();
12672                Some(language::CodeLabel {
12673                    text,
12674                    runs: Vec::new(),
12675                    filter_range: 0..len,
12676                })
12677            })),
12678            ..FakeLspAdapter::default()
12679        },
12680    );
12681    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12682    let cx = &mut VisualTestContext::from_window(*workspace, cx);
12683    let worktree_id = workspace
12684        .update(cx, |workspace, _window, cx| {
12685            workspace.project().update(cx, |project, cx| {
12686                project.worktrees(cx).next().unwrap().read(cx).id()
12687            })
12688        })
12689        .unwrap();
12690    let _buffer = project
12691        .update(cx, |project, cx| {
12692            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
12693        })
12694        .await
12695        .unwrap();
12696    let editor = workspace
12697        .update(cx, |workspace, window, cx| {
12698            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
12699        })
12700        .unwrap()
12701        .await
12702        .unwrap()
12703        .downcast::<Editor>()
12704        .unwrap();
12705    let fake_server = fake_servers.next().await.unwrap();
12706
12707    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
12708    let multiline_label_2 = "a\nb\nc\n";
12709    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
12710    let multiline_description = "d\ne\nf\n";
12711    let multiline_detail_2 = "g\nh\ni\n";
12712
12713    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
12714        move |params, _| async move {
12715            Ok(Some(lsp::CompletionResponse::Array(vec![
12716                lsp::CompletionItem {
12717                    label: multiline_label.to_string(),
12718                    text_edit: gen_text_edit(&params, "new_text_1"),
12719                    ..lsp::CompletionItem::default()
12720                },
12721                lsp::CompletionItem {
12722                    label: "single line label 1".to_string(),
12723                    detail: Some(multiline_detail.to_string()),
12724                    text_edit: gen_text_edit(&params, "new_text_2"),
12725                    ..lsp::CompletionItem::default()
12726                },
12727                lsp::CompletionItem {
12728                    label: "single line label 2".to_string(),
12729                    label_details: Some(lsp::CompletionItemLabelDetails {
12730                        description: Some(multiline_description.to_string()),
12731                        detail: None,
12732                    }),
12733                    text_edit: gen_text_edit(&params, "new_text_2"),
12734                    ..lsp::CompletionItem::default()
12735                },
12736                lsp::CompletionItem {
12737                    label: multiline_label_2.to_string(),
12738                    detail: Some(multiline_detail_2.to_string()),
12739                    text_edit: gen_text_edit(&params, "new_text_3"),
12740                    ..lsp::CompletionItem::default()
12741                },
12742                lsp::CompletionItem {
12743                    label: "Label with many     spaces and \t but without newlines".to_string(),
12744                    detail: Some(
12745                        "Details with many     spaces and \t but without newlines".to_string(),
12746                    ),
12747                    text_edit: gen_text_edit(&params, "new_text_4"),
12748                    ..lsp::CompletionItem::default()
12749                },
12750            ])))
12751        },
12752    );
12753
12754    editor.update_in(cx, |editor, window, cx| {
12755        cx.focus_self(window);
12756        editor.move_to_end(&MoveToEnd, window, cx);
12757        editor.handle_input(".", window, cx);
12758    });
12759    cx.run_until_parked();
12760    completion_handle.next().await.unwrap();
12761
12762    editor.update(cx, |editor, _| {
12763        assert!(editor.context_menu_visible());
12764        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12765        {
12766            let completion_labels = menu
12767                .completions
12768                .borrow()
12769                .iter()
12770                .map(|c| c.label.text.clone())
12771                .collect::<Vec<_>>();
12772            assert_eq!(
12773                completion_labels,
12774                &[
12775                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
12776                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
12777                    "single line label 2 d e f ",
12778                    "a b c g h i ",
12779                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
12780                ],
12781                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
12782            );
12783
12784            for completion in menu
12785                .completions
12786                .borrow()
12787                .iter() {
12788                    assert_eq!(
12789                        completion.label.filter_range,
12790                        0..completion.label.text.len(),
12791                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
12792                    );
12793                }
12794        } else {
12795            panic!("expected completion menu to be open");
12796        }
12797    });
12798}
12799
12800#[gpui::test]
12801async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
12802    init_test(cx, |_| {});
12803    let mut cx = EditorLspTestContext::new_rust(
12804        lsp::ServerCapabilities {
12805            completion_provider: Some(lsp::CompletionOptions {
12806                trigger_characters: Some(vec![".".to_string()]),
12807                ..Default::default()
12808            }),
12809            ..Default::default()
12810        },
12811        cx,
12812    )
12813    .await;
12814    cx.lsp
12815        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
12816            Ok(Some(lsp::CompletionResponse::Array(vec![
12817                lsp::CompletionItem {
12818                    label: "first".into(),
12819                    ..Default::default()
12820                },
12821                lsp::CompletionItem {
12822                    label: "last".into(),
12823                    ..Default::default()
12824                },
12825            ])))
12826        });
12827    cx.set_state("variableˇ");
12828    cx.simulate_keystroke(".");
12829    cx.executor().run_until_parked();
12830
12831    cx.update_editor(|editor, _, _| {
12832        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12833        {
12834            assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
12835        } else {
12836            panic!("expected completion menu to be open");
12837        }
12838    });
12839
12840    cx.update_editor(|editor, window, cx| {
12841        editor.move_page_down(&MovePageDown::default(), window, cx);
12842        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12843        {
12844            assert!(
12845                menu.selected_item == 1,
12846                "expected PageDown to select the last item from the context menu"
12847            );
12848        } else {
12849            panic!("expected completion menu to stay open after PageDown");
12850        }
12851    });
12852
12853    cx.update_editor(|editor, window, cx| {
12854        editor.move_page_up(&MovePageUp::default(), window, cx);
12855        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12856        {
12857            assert!(
12858                menu.selected_item == 0,
12859                "expected PageUp to select the first item from the context menu"
12860            );
12861        } else {
12862            panic!("expected completion menu to stay open after PageUp");
12863        }
12864    });
12865}
12866
12867#[gpui::test]
12868async fn test_as_is_completions(cx: &mut TestAppContext) {
12869    init_test(cx, |_| {});
12870    let mut cx = EditorLspTestContext::new_rust(
12871        lsp::ServerCapabilities {
12872            completion_provider: Some(lsp::CompletionOptions {
12873                ..Default::default()
12874            }),
12875            ..Default::default()
12876        },
12877        cx,
12878    )
12879    .await;
12880    cx.lsp
12881        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
12882            Ok(Some(lsp::CompletionResponse::Array(vec![
12883                lsp::CompletionItem {
12884                    label: "unsafe".into(),
12885                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12886                        range: lsp::Range {
12887                            start: lsp::Position {
12888                                line: 1,
12889                                character: 2,
12890                            },
12891                            end: lsp::Position {
12892                                line: 1,
12893                                character: 3,
12894                            },
12895                        },
12896                        new_text: "unsafe".to_string(),
12897                    })),
12898                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
12899                    ..Default::default()
12900                },
12901            ])))
12902        });
12903    cx.set_state("fn a() {}\n");
12904    cx.executor().run_until_parked();
12905    cx.update_editor(|editor, window, cx| {
12906        editor.show_completions(
12907            &ShowCompletions {
12908                trigger: Some("\n".into()),
12909            },
12910            window,
12911            cx,
12912        );
12913    });
12914    cx.executor().run_until_parked();
12915
12916    cx.update_editor(|editor, window, cx| {
12917        editor.confirm_completion(&Default::default(), window, cx)
12918    });
12919    cx.executor().run_until_parked();
12920    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
12921}
12922
12923#[gpui::test]
12924async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
12925    init_test(cx, |_| {});
12926
12927    let mut cx = EditorLspTestContext::new_rust(
12928        lsp::ServerCapabilities {
12929            completion_provider: Some(lsp::CompletionOptions {
12930                trigger_characters: Some(vec![".".to_string()]),
12931                resolve_provider: Some(true),
12932                ..Default::default()
12933            }),
12934            ..Default::default()
12935        },
12936        cx,
12937    )
12938    .await;
12939
12940    cx.set_state("fn main() { let a = 2ˇ; }");
12941    cx.simulate_keystroke(".");
12942    let completion_item = lsp::CompletionItem {
12943        label: "Some".into(),
12944        kind: Some(lsp::CompletionItemKind::SNIPPET),
12945        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
12946        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
12947            kind: lsp::MarkupKind::Markdown,
12948            value: "```rust\nSome(2)\n```".to_string(),
12949        })),
12950        deprecated: Some(false),
12951        sort_text: Some("Some".to_string()),
12952        filter_text: Some("Some".to_string()),
12953        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
12954        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12955            range: lsp::Range {
12956                start: lsp::Position {
12957                    line: 0,
12958                    character: 22,
12959                },
12960                end: lsp::Position {
12961                    line: 0,
12962                    character: 22,
12963                },
12964            },
12965            new_text: "Some(2)".to_string(),
12966        })),
12967        additional_text_edits: Some(vec![lsp::TextEdit {
12968            range: lsp::Range {
12969                start: lsp::Position {
12970                    line: 0,
12971                    character: 20,
12972                },
12973                end: lsp::Position {
12974                    line: 0,
12975                    character: 22,
12976                },
12977            },
12978            new_text: "".to_string(),
12979        }]),
12980        ..Default::default()
12981    };
12982
12983    let closure_completion_item = completion_item.clone();
12984    let counter = Arc::new(AtomicUsize::new(0));
12985    let counter_clone = counter.clone();
12986    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
12987        let task_completion_item = closure_completion_item.clone();
12988        counter_clone.fetch_add(1, atomic::Ordering::Release);
12989        async move {
12990            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
12991                is_incomplete: true,
12992                item_defaults: None,
12993                items: vec![task_completion_item],
12994            })))
12995        }
12996    });
12997
12998    cx.condition(|editor, _| editor.context_menu_visible())
12999        .await;
13000    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
13001    assert!(request.next().await.is_some());
13002    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13003
13004    cx.simulate_keystrokes("S o m");
13005    cx.condition(|editor, _| editor.context_menu_visible())
13006        .await;
13007    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
13008    assert!(request.next().await.is_some());
13009    assert!(request.next().await.is_some());
13010    assert!(request.next().await.is_some());
13011    request.close();
13012    assert!(request.next().await.is_none());
13013    assert_eq!(
13014        counter.load(atomic::Ordering::Acquire),
13015        4,
13016        "With the completions menu open, only one LSP request should happen per input"
13017    );
13018}
13019
13020#[gpui::test]
13021async fn test_toggle_comment(cx: &mut TestAppContext) {
13022    init_test(cx, |_| {});
13023    let mut cx = EditorTestContext::new(cx).await;
13024    let language = Arc::new(Language::new(
13025        LanguageConfig {
13026            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
13027            ..Default::default()
13028        },
13029        Some(tree_sitter_rust::LANGUAGE.into()),
13030    ));
13031    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
13032
13033    // If multiple selections intersect a line, the line is only toggled once.
13034    cx.set_state(indoc! {"
13035        fn a() {
13036            «//b();
13037            ˇ»// «c();
13038            //ˇ»  d();
13039        }
13040    "});
13041
13042    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13043
13044    cx.assert_editor_state(indoc! {"
13045        fn a() {
13046            «b();
13047            c();
13048            ˇ» d();
13049        }
13050    "});
13051
13052    // The comment prefix is inserted at the same column for every line in a
13053    // selection.
13054    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13055
13056    cx.assert_editor_state(indoc! {"
13057        fn a() {
13058            // «b();
13059            // c();
13060            ˇ»//  d();
13061        }
13062    "});
13063
13064    // If a selection ends at the beginning of a line, that line is not toggled.
13065    cx.set_selections_state(indoc! {"
13066        fn a() {
13067            // b();
13068            «// c();
13069        ˇ»    //  d();
13070        }
13071    "});
13072
13073    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13074
13075    cx.assert_editor_state(indoc! {"
13076        fn a() {
13077            // b();
13078            «c();
13079        ˇ»    //  d();
13080        }
13081    "});
13082
13083    // If a selection span a single line and is empty, the line is toggled.
13084    cx.set_state(indoc! {"
13085        fn a() {
13086            a();
13087            b();
13088        ˇ
13089        }
13090    "});
13091
13092    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13093
13094    cx.assert_editor_state(indoc! {"
13095        fn a() {
13096            a();
13097            b();
13098        //•ˇ
13099        }
13100    "});
13101
13102    // If a selection span multiple lines, empty lines are not toggled.
13103    cx.set_state(indoc! {"
13104        fn a() {
13105            «a();
13106
13107            c();ˇ»
13108        }
13109    "});
13110
13111    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13112
13113    cx.assert_editor_state(indoc! {"
13114        fn a() {
13115            // «a();
13116
13117            // c();ˇ»
13118        }
13119    "});
13120
13121    // If a selection includes multiple comment prefixes, all lines are uncommented.
13122    cx.set_state(indoc! {"
13123        fn a() {
13124            «// a();
13125            /// b();
13126            //! c();ˇ»
13127        }
13128    "});
13129
13130    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13131
13132    cx.assert_editor_state(indoc! {"
13133        fn a() {
13134            «a();
13135            b();
13136            c();ˇ»
13137        }
13138    "});
13139}
13140
13141#[gpui::test]
13142async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
13143    init_test(cx, |_| {});
13144    let mut cx = EditorTestContext::new(cx).await;
13145    let language = Arc::new(Language::new(
13146        LanguageConfig {
13147            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
13148            ..Default::default()
13149        },
13150        Some(tree_sitter_rust::LANGUAGE.into()),
13151    ));
13152    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
13153
13154    let toggle_comments = &ToggleComments {
13155        advance_downwards: false,
13156        ignore_indent: true,
13157    };
13158
13159    // If multiple selections intersect a line, the line is only toggled once.
13160    cx.set_state(indoc! {"
13161        fn a() {
13162        //    «b();
13163        //    c();
13164        //    ˇ» d();
13165        }
13166    "});
13167
13168    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13169
13170    cx.assert_editor_state(indoc! {"
13171        fn a() {
13172            «b();
13173            c();
13174            ˇ» d();
13175        }
13176    "});
13177
13178    // The comment prefix is inserted at the beginning of each line
13179    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13180
13181    cx.assert_editor_state(indoc! {"
13182        fn a() {
13183        //    «b();
13184        //    c();
13185        //    ˇ» d();
13186        }
13187    "});
13188
13189    // If a selection ends at the beginning of a line, that line is not toggled.
13190    cx.set_selections_state(indoc! {"
13191        fn a() {
13192        //    b();
13193        //    «c();
13194        ˇ»//     d();
13195        }
13196    "});
13197
13198    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13199
13200    cx.assert_editor_state(indoc! {"
13201        fn a() {
13202        //    b();
13203            «c();
13204        ˇ»//     d();
13205        }
13206    "});
13207
13208    // If a selection span a single line and is empty, the line is toggled.
13209    cx.set_state(indoc! {"
13210        fn a() {
13211            a();
13212            b();
13213        ˇ
13214        }
13215    "});
13216
13217    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13218
13219    cx.assert_editor_state(indoc! {"
13220        fn a() {
13221            a();
13222            b();
13223        //ˇ
13224        }
13225    "});
13226
13227    // If a selection span multiple lines, empty lines are not toggled.
13228    cx.set_state(indoc! {"
13229        fn a() {
13230            «a();
13231
13232            c();ˇ»
13233        }
13234    "});
13235
13236    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13237
13238    cx.assert_editor_state(indoc! {"
13239        fn a() {
13240        //    «a();
13241
13242        //    c();ˇ»
13243        }
13244    "});
13245
13246    // If a selection includes multiple comment prefixes, all lines are uncommented.
13247    cx.set_state(indoc! {"
13248        fn a() {
13249        //    «a();
13250        ///    b();
13251        //!    c();ˇ»
13252        }
13253    "});
13254
13255    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13256
13257    cx.assert_editor_state(indoc! {"
13258        fn a() {
13259            «a();
13260            b();
13261            c();ˇ»
13262        }
13263    "});
13264}
13265
13266#[gpui::test]
13267async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
13268    init_test(cx, |_| {});
13269
13270    let language = Arc::new(Language::new(
13271        LanguageConfig {
13272            line_comments: vec!["// ".into()],
13273            ..Default::default()
13274        },
13275        Some(tree_sitter_rust::LANGUAGE.into()),
13276    ));
13277
13278    let mut cx = EditorTestContext::new(cx).await;
13279
13280    cx.language_registry().add(language.clone());
13281    cx.update_buffer(|buffer, cx| {
13282        buffer.set_language(Some(language), cx);
13283    });
13284
13285    let toggle_comments = &ToggleComments {
13286        advance_downwards: true,
13287        ignore_indent: false,
13288    };
13289
13290    // Single cursor on one line -> advance
13291    // Cursor moves horizontally 3 characters as well on non-blank line
13292    cx.set_state(indoc!(
13293        "fn a() {
13294             ˇdog();
13295             cat();
13296        }"
13297    ));
13298    cx.update_editor(|editor, window, cx| {
13299        editor.toggle_comments(toggle_comments, window, cx);
13300    });
13301    cx.assert_editor_state(indoc!(
13302        "fn a() {
13303             // dog();
13304             catˇ();
13305        }"
13306    ));
13307
13308    // Single selection on one line -> don't advance
13309    cx.set_state(indoc!(
13310        "fn a() {
13311             «dog()ˇ»;
13312             cat();
13313        }"
13314    ));
13315    cx.update_editor(|editor, window, cx| {
13316        editor.toggle_comments(toggle_comments, window, cx);
13317    });
13318    cx.assert_editor_state(indoc!(
13319        "fn a() {
13320             // «dog()ˇ»;
13321             cat();
13322        }"
13323    ));
13324
13325    // Multiple cursors on one line -> advance
13326    cx.set_state(indoc!(
13327        "fn a() {
13328             ˇdˇog();
13329             cat();
13330        }"
13331    ));
13332    cx.update_editor(|editor, window, cx| {
13333        editor.toggle_comments(toggle_comments, window, cx);
13334    });
13335    cx.assert_editor_state(indoc!(
13336        "fn a() {
13337             // dog();
13338             catˇ(ˇ);
13339        }"
13340    ));
13341
13342    // Multiple cursors on one line, with selection -> don't advance
13343    cx.set_state(indoc!(
13344        "fn a() {
13345             ˇdˇog«()ˇ»;
13346             cat();
13347        }"
13348    ));
13349    cx.update_editor(|editor, window, cx| {
13350        editor.toggle_comments(toggle_comments, window, cx);
13351    });
13352    cx.assert_editor_state(indoc!(
13353        "fn a() {
13354             // ˇdˇog«()ˇ»;
13355             cat();
13356        }"
13357    ));
13358
13359    // Single cursor on one line -> advance
13360    // Cursor moves to column 0 on blank line
13361    cx.set_state(indoc!(
13362        "fn a() {
13363             ˇdog();
13364
13365             cat();
13366        }"
13367    ));
13368    cx.update_editor(|editor, window, cx| {
13369        editor.toggle_comments(toggle_comments, window, cx);
13370    });
13371    cx.assert_editor_state(indoc!(
13372        "fn a() {
13373             // dog();
13374        ˇ
13375             cat();
13376        }"
13377    ));
13378
13379    // Single cursor on one line -> advance
13380    // Cursor starts and ends at column 0
13381    cx.set_state(indoc!(
13382        "fn a() {
13383         ˇ    dog();
13384             cat();
13385        }"
13386    ));
13387    cx.update_editor(|editor, window, cx| {
13388        editor.toggle_comments(toggle_comments, window, cx);
13389    });
13390    cx.assert_editor_state(indoc!(
13391        "fn a() {
13392             // dog();
13393         ˇ    cat();
13394        }"
13395    ));
13396}
13397
13398#[gpui::test]
13399async fn test_toggle_block_comment(cx: &mut TestAppContext) {
13400    init_test(cx, |_| {});
13401
13402    let mut cx = EditorTestContext::new(cx).await;
13403
13404    let html_language = Arc::new(
13405        Language::new(
13406            LanguageConfig {
13407                name: "HTML".into(),
13408                block_comment: Some(("<!-- ".into(), " -->".into())),
13409                ..Default::default()
13410            },
13411            Some(tree_sitter_html::LANGUAGE.into()),
13412        )
13413        .with_injection_query(
13414            r#"
13415            (script_element
13416                (raw_text) @injection.content
13417                (#set! injection.language "javascript"))
13418            "#,
13419        )
13420        .unwrap(),
13421    );
13422
13423    let javascript_language = Arc::new(Language::new(
13424        LanguageConfig {
13425            name: "JavaScript".into(),
13426            line_comments: vec!["// ".into()],
13427            ..Default::default()
13428        },
13429        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
13430    ));
13431
13432    cx.language_registry().add(html_language.clone());
13433    cx.language_registry().add(javascript_language.clone());
13434    cx.update_buffer(|buffer, cx| {
13435        buffer.set_language(Some(html_language), cx);
13436    });
13437
13438    // Toggle comments for empty selections
13439    cx.set_state(
13440        &r#"
13441            <p>A</p>ˇ
13442            <p>B</p>ˇ
13443            <p>C</p>ˇ
13444        "#
13445        .unindent(),
13446    );
13447    cx.update_editor(|editor, window, cx| {
13448        editor.toggle_comments(&ToggleComments::default(), window, cx)
13449    });
13450    cx.assert_editor_state(
13451        &r#"
13452            <!-- <p>A</p>ˇ -->
13453            <!-- <p>B</p>ˇ -->
13454            <!-- <p>C</p>ˇ -->
13455        "#
13456        .unindent(),
13457    );
13458    cx.update_editor(|editor, window, cx| {
13459        editor.toggle_comments(&ToggleComments::default(), window, cx)
13460    });
13461    cx.assert_editor_state(
13462        &r#"
13463            <p>A</p>ˇ
13464            <p>B</p>ˇ
13465            <p>C</p>ˇ
13466        "#
13467        .unindent(),
13468    );
13469
13470    // Toggle comments for mixture of empty and non-empty selections, where
13471    // multiple selections occupy a given line.
13472    cx.set_state(
13473        &r#"
13474            <p>A«</p>
13475            <p>ˇ»B</p>ˇ
13476            <p>C«</p>
13477            <p>ˇ»D</p>ˇ
13478        "#
13479        .unindent(),
13480    );
13481
13482    cx.update_editor(|editor, window, cx| {
13483        editor.toggle_comments(&ToggleComments::default(), window, cx)
13484    });
13485    cx.assert_editor_state(
13486        &r#"
13487            <!-- <p>A«</p>
13488            <p>ˇ»B</p>ˇ -->
13489            <!-- <p>C«</p>
13490            <p>ˇ»D</p>ˇ -->
13491        "#
13492        .unindent(),
13493    );
13494    cx.update_editor(|editor, window, cx| {
13495        editor.toggle_comments(&ToggleComments::default(), window, cx)
13496    });
13497    cx.assert_editor_state(
13498        &r#"
13499            <p>A«</p>
13500            <p>ˇ»B</p>ˇ
13501            <p>C«</p>
13502            <p>ˇ»D</p>ˇ
13503        "#
13504        .unindent(),
13505    );
13506
13507    // Toggle comments when different languages are active for different
13508    // selections.
13509    cx.set_state(
13510        &r#"
13511            ˇ<script>
13512                ˇvar x = new Y();
13513            ˇ</script>
13514        "#
13515        .unindent(),
13516    );
13517    cx.executor().run_until_parked();
13518    cx.update_editor(|editor, window, cx| {
13519        editor.toggle_comments(&ToggleComments::default(), window, cx)
13520    });
13521    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
13522    // Uncommenting and commenting from this position brings in even more wrong artifacts.
13523    cx.assert_editor_state(
13524        &r#"
13525            <!-- ˇ<script> -->
13526                // ˇvar x = new Y();
13527            <!-- ˇ</script> -->
13528        "#
13529        .unindent(),
13530    );
13531}
13532
13533#[gpui::test]
13534fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
13535    init_test(cx, |_| {});
13536
13537    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
13538    let multibuffer = cx.new(|cx| {
13539        let mut multibuffer = MultiBuffer::new(ReadWrite);
13540        multibuffer.push_excerpts(
13541            buffer.clone(),
13542            [
13543                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
13544                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
13545            ],
13546            cx,
13547        );
13548        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
13549        multibuffer
13550    });
13551
13552    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
13553    editor.update_in(cx, |editor, window, cx| {
13554        assert_eq!(editor.text(cx), "aaaa\nbbbb");
13555        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13556            s.select_ranges([
13557                Point::new(0, 0)..Point::new(0, 0),
13558                Point::new(1, 0)..Point::new(1, 0),
13559            ])
13560        });
13561
13562        editor.handle_input("X", window, cx);
13563        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
13564        assert_eq!(
13565            editor.selections.ranges(cx),
13566            [
13567                Point::new(0, 1)..Point::new(0, 1),
13568                Point::new(1, 1)..Point::new(1, 1),
13569            ]
13570        );
13571
13572        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
13573        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13574            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
13575        });
13576        editor.backspace(&Default::default(), window, cx);
13577        assert_eq!(editor.text(cx), "Xa\nbbb");
13578        assert_eq!(
13579            editor.selections.ranges(cx),
13580            [Point::new(1, 0)..Point::new(1, 0)]
13581        );
13582
13583        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13584            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
13585        });
13586        editor.backspace(&Default::default(), window, cx);
13587        assert_eq!(editor.text(cx), "X\nbb");
13588        assert_eq!(
13589            editor.selections.ranges(cx),
13590            [Point::new(0, 1)..Point::new(0, 1)]
13591        );
13592    });
13593}
13594
13595#[gpui::test]
13596fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
13597    init_test(cx, |_| {});
13598
13599    let markers = vec![('[', ']').into(), ('(', ')').into()];
13600    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
13601        indoc! {"
13602            [aaaa
13603            (bbbb]
13604            cccc)",
13605        },
13606        markers.clone(),
13607    );
13608    let excerpt_ranges = markers.into_iter().map(|marker| {
13609        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
13610        ExcerptRange::new(context.clone())
13611    });
13612    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
13613    let multibuffer = cx.new(|cx| {
13614        let mut multibuffer = MultiBuffer::new(ReadWrite);
13615        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
13616        multibuffer
13617    });
13618
13619    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
13620    editor.update_in(cx, |editor, window, cx| {
13621        let (expected_text, selection_ranges) = marked_text_ranges(
13622            indoc! {"
13623                aaaa
13624                bˇbbb
13625                bˇbbˇb
13626                cccc"
13627            },
13628            true,
13629        );
13630        assert_eq!(editor.text(cx), expected_text);
13631        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13632            s.select_ranges(selection_ranges)
13633        });
13634
13635        editor.handle_input("X", window, cx);
13636
13637        let (expected_text, expected_selections) = marked_text_ranges(
13638            indoc! {"
13639                aaaa
13640                bXˇbbXb
13641                bXˇbbXˇb
13642                cccc"
13643            },
13644            false,
13645        );
13646        assert_eq!(editor.text(cx), expected_text);
13647        assert_eq!(editor.selections.ranges(cx), expected_selections);
13648
13649        editor.newline(&Newline, window, cx);
13650        let (expected_text, expected_selections) = marked_text_ranges(
13651            indoc! {"
13652                aaaa
13653                bX
13654                ˇbbX
13655                b
13656                bX
13657                ˇbbX
13658                ˇb
13659                cccc"
13660            },
13661            false,
13662        );
13663        assert_eq!(editor.text(cx), expected_text);
13664        assert_eq!(editor.selections.ranges(cx), expected_selections);
13665    });
13666}
13667
13668#[gpui::test]
13669fn test_refresh_selections(cx: &mut TestAppContext) {
13670    init_test(cx, |_| {});
13671
13672    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
13673    let mut excerpt1_id = None;
13674    let multibuffer = cx.new(|cx| {
13675        let mut multibuffer = MultiBuffer::new(ReadWrite);
13676        excerpt1_id = multibuffer
13677            .push_excerpts(
13678                buffer.clone(),
13679                [
13680                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
13681                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
13682                ],
13683                cx,
13684            )
13685            .into_iter()
13686            .next();
13687        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
13688        multibuffer
13689    });
13690
13691    let editor = cx.add_window(|window, cx| {
13692        let mut editor = build_editor(multibuffer.clone(), window, cx);
13693        let snapshot = editor.snapshot(window, cx);
13694        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13695            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
13696        });
13697        editor.begin_selection(
13698            Point::new(2, 1).to_display_point(&snapshot),
13699            true,
13700            1,
13701            window,
13702            cx,
13703        );
13704        assert_eq!(
13705            editor.selections.ranges(cx),
13706            [
13707                Point::new(1, 3)..Point::new(1, 3),
13708                Point::new(2, 1)..Point::new(2, 1),
13709            ]
13710        );
13711        editor
13712    });
13713
13714    // Refreshing selections is a no-op when excerpts haven't changed.
13715    _ = editor.update(cx, |editor, window, cx| {
13716        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
13717        assert_eq!(
13718            editor.selections.ranges(cx),
13719            [
13720                Point::new(1, 3)..Point::new(1, 3),
13721                Point::new(2, 1)..Point::new(2, 1),
13722            ]
13723        );
13724    });
13725
13726    multibuffer.update(cx, |multibuffer, cx| {
13727        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
13728    });
13729    _ = editor.update(cx, |editor, window, cx| {
13730        // Removing an excerpt causes the first selection to become degenerate.
13731        assert_eq!(
13732            editor.selections.ranges(cx),
13733            [
13734                Point::new(0, 0)..Point::new(0, 0),
13735                Point::new(0, 1)..Point::new(0, 1)
13736            ]
13737        );
13738
13739        // Refreshing selections will relocate the first selection to the original buffer
13740        // location.
13741        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
13742        assert_eq!(
13743            editor.selections.ranges(cx),
13744            [
13745                Point::new(0, 1)..Point::new(0, 1),
13746                Point::new(0, 3)..Point::new(0, 3)
13747            ]
13748        );
13749        assert!(editor.selections.pending_anchor().is_some());
13750    });
13751}
13752
13753#[gpui::test]
13754fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
13755    init_test(cx, |_| {});
13756
13757    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
13758    let mut excerpt1_id = None;
13759    let multibuffer = cx.new(|cx| {
13760        let mut multibuffer = MultiBuffer::new(ReadWrite);
13761        excerpt1_id = multibuffer
13762            .push_excerpts(
13763                buffer.clone(),
13764                [
13765                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
13766                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
13767                ],
13768                cx,
13769            )
13770            .into_iter()
13771            .next();
13772        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
13773        multibuffer
13774    });
13775
13776    let editor = cx.add_window(|window, cx| {
13777        let mut editor = build_editor(multibuffer.clone(), window, cx);
13778        let snapshot = editor.snapshot(window, cx);
13779        editor.begin_selection(
13780            Point::new(1, 3).to_display_point(&snapshot),
13781            false,
13782            1,
13783            window,
13784            cx,
13785        );
13786        assert_eq!(
13787            editor.selections.ranges(cx),
13788            [Point::new(1, 3)..Point::new(1, 3)]
13789        );
13790        editor
13791    });
13792
13793    multibuffer.update(cx, |multibuffer, cx| {
13794        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
13795    });
13796    _ = editor.update(cx, |editor, window, cx| {
13797        assert_eq!(
13798            editor.selections.ranges(cx),
13799            [Point::new(0, 0)..Point::new(0, 0)]
13800        );
13801
13802        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
13803        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
13804        assert_eq!(
13805            editor.selections.ranges(cx),
13806            [Point::new(0, 3)..Point::new(0, 3)]
13807        );
13808        assert!(editor.selections.pending_anchor().is_some());
13809    });
13810}
13811
13812#[gpui::test]
13813async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
13814    init_test(cx, |_| {});
13815
13816    let language = Arc::new(
13817        Language::new(
13818            LanguageConfig {
13819                brackets: BracketPairConfig {
13820                    pairs: vec![
13821                        BracketPair {
13822                            start: "{".to_string(),
13823                            end: "}".to_string(),
13824                            close: true,
13825                            surround: true,
13826                            newline: true,
13827                        },
13828                        BracketPair {
13829                            start: "/* ".to_string(),
13830                            end: " */".to_string(),
13831                            close: true,
13832                            surround: true,
13833                            newline: true,
13834                        },
13835                    ],
13836                    ..Default::default()
13837                },
13838                ..Default::default()
13839            },
13840            Some(tree_sitter_rust::LANGUAGE.into()),
13841        )
13842        .with_indents_query("")
13843        .unwrap(),
13844    );
13845
13846    let text = concat!(
13847        "{   }\n",     //
13848        "  x\n",       //
13849        "  /*   */\n", //
13850        "x\n",         //
13851        "{{} }\n",     //
13852    );
13853
13854    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
13855    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
13856    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
13857    editor
13858        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
13859        .await;
13860
13861    editor.update_in(cx, |editor, window, cx| {
13862        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13863            s.select_display_ranges([
13864                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
13865                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
13866                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
13867            ])
13868        });
13869        editor.newline(&Newline, window, cx);
13870
13871        assert_eq!(
13872            editor.buffer().read(cx).read(cx).text(),
13873            concat!(
13874                "{ \n",    // Suppress rustfmt
13875                "\n",      //
13876                "}\n",     //
13877                "  x\n",   //
13878                "  /* \n", //
13879                "  \n",    //
13880                "  */\n",  //
13881                "x\n",     //
13882                "{{} \n",  //
13883                "}\n",     //
13884            )
13885        );
13886    });
13887}
13888
13889#[gpui::test]
13890fn test_highlighted_ranges(cx: &mut TestAppContext) {
13891    init_test(cx, |_| {});
13892
13893    let editor = cx.add_window(|window, cx| {
13894        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
13895        build_editor(buffer.clone(), window, cx)
13896    });
13897
13898    _ = editor.update(cx, |editor, window, cx| {
13899        struct Type1;
13900        struct Type2;
13901
13902        let buffer = editor.buffer.read(cx).snapshot(cx);
13903
13904        let anchor_range =
13905            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
13906
13907        editor.highlight_background::<Type1>(
13908            &[
13909                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
13910                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
13911                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
13912                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
13913            ],
13914            |_| Hsla::red(),
13915            cx,
13916        );
13917        editor.highlight_background::<Type2>(
13918            &[
13919                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
13920                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
13921                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
13922                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
13923            ],
13924            |_| Hsla::green(),
13925            cx,
13926        );
13927
13928        let snapshot = editor.snapshot(window, cx);
13929        let mut highlighted_ranges = editor.background_highlights_in_range(
13930            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
13931            &snapshot,
13932            cx.theme(),
13933        );
13934        // Enforce a consistent ordering based on color without relying on the ordering of the
13935        // highlight's `TypeId` which is non-executor.
13936        highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
13937        assert_eq!(
13938            highlighted_ranges,
13939            &[
13940                (
13941                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
13942                    Hsla::red(),
13943                ),
13944                (
13945                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
13946                    Hsla::red(),
13947                ),
13948                (
13949                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
13950                    Hsla::green(),
13951                ),
13952                (
13953                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
13954                    Hsla::green(),
13955                ),
13956            ]
13957        );
13958        assert_eq!(
13959            editor.background_highlights_in_range(
13960                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
13961                &snapshot,
13962                cx.theme(),
13963            ),
13964            &[(
13965                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
13966                Hsla::red(),
13967            )]
13968        );
13969    });
13970}
13971
13972#[gpui::test]
13973async fn test_following(cx: &mut TestAppContext) {
13974    init_test(cx, |_| {});
13975
13976    let fs = FakeFs::new(cx.executor());
13977    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
13978
13979    let buffer = project.update(cx, |project, cx| {
13980        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
13981        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
13982    });
13983    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
13984    let follower = cx.update(|cx| {
13985        cx.open_window(
13986            WindowOptions {
13987                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
13988                    gpui::Point::new(px(0.), px(0.)),
13989                    gpui::Point::new(px(10.), px(80.)),
13990                ))),
13991                ..Default::default()
13992            },
13993            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
13994        )
13995        .unwrap()
13996    });
13997
13998    let is_still_following = Rc::new(RefCell::new(true));
13999    let follower_edit_event_count = Rc::new(RefCell::new(0));
14000    let pending_update = Rc::new(RefCell::new(None));
14001    let leader_entity = leader.root(cx).unwrap();
14002    let follower_entity = follower.root(cx).unwrap();
14003    _ = follower.update(cx, {
14004        let update = pending_update.clone();
14005        let is_still_following = is_still_following.clone();
14006        let follower_edit_event_count = follower_edit_event_count.clone();
14007        |_, window, cx| {
14008            cx.subscribe_in(
14009                &leader_entity,
14010                window,
14011                move |_, leader, event, window, cx| {
14012                    leader.read(cx).add_event_to_update_proto(
14013                        event,
14014                        &mut update.borrow_mut(),
14015                        window,
14016                        cx,
14017                    );
14018                },
14019            )
14020            .detach();
14021
14022            cx.subscribe_in(
14023                &follower_entity,
14024                window,
14025                move |_, _, event: &EditorEvent, _window, _cx| {
14026                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
14027                        *is_still_following.borrow_mut() = false;
14028                    }
14029
14030                    if let EditorEvent::BufferEdited = event {
14031                        *follower_edit_event_count.borrow_mut() += 1;
14032                    }
14033                },
14034            )
14035            .detach();
14036        }
14037    });
14038
14039    // Update the selections only
14040    _ = leader.update(cx, |leader, window, cx| {
14041        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14042            s.select_ranges([1..1])
14043        });
14044    });
14045    follower
14046        .update(cx, |follower, window, cx| {
14047            follower.apply_update_proto(
14048                &project,
14049                pending_update.borrow_mut().take().unwrap(),
14050                window,
14051                cx,
14052            )
14053        })
14054        .unwrap()
14055        .await
14056        .unwrap();
14057    _ = follower.update(cx, |follower, _, cx| {
14058        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
14059    });
14060    assert!(*is_still_following.borrow());
14061    assert_eq!(*follower_edit_event_count.borrow(), 0);
14062
14063    // Update the scroll position only
14064    _ = leader.update(cx, |leader, window, cx| {
14065        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
14066    });
14067    follower
14068        .update(cx, |follower, window, cx| {
14069            follower.apply_update_proto(
14070                &project,
14071                pending_update.borrow_mut().take().unwrap(),
14072                window,
14073                cx,
14074            )
14075        })
14076        .unwrap()
14077        .await
14078        .unwrap();
14079    assert_eq!(
14080        follower
14081            .update(cx, |follower, _, cx| follower.scroll_position(cx))
14082            .unwrap(),
14083        gpui::Point::new(1.5, 3.5)
14084    );
14085    assert!(*is_still_following.borrow());
14086    assert_eq!(*follower_edit_event_count.borrow(), 0);
14087
14088    // Update the selections and scroll position. The follower's scroll position is updated
14089    // via autoscroll, not via the leader's exact scroll position.
14090    _ = leader.update(cx, |leader, window, cx| {
14091        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14092            s.select_ranges([0..0])
14093        });
14094        leader.request_autoscroll(Autoscroll::newest(), cx);
14095        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
14096    });
14097    follower
14098        .update(cx, |follower, window, cx| {
14099            follower.apply_update_proto(
14100                &project,
14101                pending_update.borrow_mut().take().unwrap(),
14102                window,
14103                cx,
14104            )
14105        })
14106        .unwrap()
14107        .await
14108        .unwrap();
14109    _ = follower.update(cx, |follower, _, cx| {
14110        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
14111        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
14112    });
14113    assert!(*is_still_following.borrow());
14114
14115    // Creating a pending selection that precedes another selection
14116    _ = leader.update(cx, |leader, window, cx| {
14117        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14118            s.select_ranges([1..1])
14119        });
14120        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
14121    });
14122    follower
14123        .update(cx, |follower, window, cx| {
14124            follower.apply_update_proto(
14125                &project,
14126                pending_update.borrow_mut().take().unwrap(),
14127                window,
14128                cx,
14129            )
14130        })
14131        .unwrap()
14132        .await
14133        .unwrap();
14134    _ = follower.update(cx, |follower, _, cx| {
14135        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
14136    });
14137    assert!(*is_still_following.borrow());
14138
14139    // Extend the pending selection so that it surrounds another selection
14140    _ = leader.update(cx, |leader, window, cx| {
14141        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
14142    });
14143    follower
14144        .update(cx, |follower, window, cx| {
14145            follower.apply_update_proto(
14146                &project,
14147                pending_update.borrow_mut().take().unwrap(),
14148                window,
14149                cx,
14150            )
14151        })
14152        .unwrap()
14153        .await
14154        .unwrap();
14155    _ = follower.update(cx, |follower, _, cx| {
14156        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
14157    });
14158
14159    // Scrolling locally breaks the follow
14160    _ = follower.update(cx, |follower, window, cx| {
14161        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
14162        follower.set_scroll_anchor(
14163            ScrollAnchor {
14164                anchor: top_anchor,
14165                offset: gpui::Point::new(0.0, 0.5),
14166            },
14167            window,
14168            cx,
14169        );
14170    });
14171    assert!(!(*is_still_following.borrow()));
14172}
14173
14174#[gpui::test]
14175async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
14176    init_test(cx, |_| {});
14177
14178    let fs = FakeFs::new(cx.executor());
14179    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
14180    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14181    let pane = workspace
14182        .update(cx, |workspace, _, _| workspace.active_pane().clone())
14183        .unwrap();
14184
14185    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14186
14187    let leader = pane.update_in(cx, |_, window, cx| {
14188        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
14189        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
14190    });
14191
14192    // Start following the editor when it has no excerpts.
14193    let mut state_message =
14194        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
14195    let workspace_entity = workspace.root(cx).unwrap();
14196    let follower_1 = cx
14197        .update_window(*workspace.deref(), |_, window, cx| {
14198            Editor::from_state_proto(
14199                workspace_entity,
14200                ViewId {
14201                    creator: CollaboratorId::PeerId(PeerId::default()),
14202                    id: 0,
14203                },
14204                &mut state_message,
14205                window,
14206                cx,
14207            )
14208        })
14209        .unwrap()
14210        .unwrap()
14211        .await
14212        .unwrap();
14213
14214    let update_message = Rc::new(RefCell::new(None));
14215    follower_1.update_in(cx, {
14216        let update = update_message.clone();
14217        |_, window, cx| {
14218            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
14219                leader.read(cx).add_event_to_update_proto(
14220                    event,
14221                    &mut update.borrow_mut(),
14222                    window,
14223                    cx,
14224                );
14225            })
14226            .detach();
14227        }
14228    });
14229
14230    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
14231        (
14232            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
14233            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
14234        )
14235    });
14236
14237    // Insert some excerpts.
14238    leader.update(cx, |leader, cx| {
14239        leader.buffer.update(cx, |multibuffer, cx| {
14240            multibuffer.set_excerpts_for_path(
14241                PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
14242                buffer_1.clone(),
14243                vec![
14244                    Point::row_range(0..3),
14245                    Point::row_range(1..6),
14246                    Point::row_range(12..15),
14247                ],
14248                0,
14249                cx,
14250            );
14251            multibuffer.set_excerpts_for_path(
14252                PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
14253                buffer_2.clone(),
14254                vec![Point::row_range(0..6), Point::row_range(8..12)],
14255                0,
14256                cx,
14257            );
14258        });
14259    });
14260
14261    // Apply the update of adding the excerpts.
14262    follower_1
14263        .update_in(cx, |follower, window, cx| {
14264            follower.apply_update_proto(
14265                &project,
14266                update_message.borrow().clone().unwrap(),
14267                window,
14268                cx,
14269            )
14270        })
14271        .await
14272        .unwrap();
14273    assert_eq!(
14274        follower_1.update(cx, |editor, cx| editor.text(cx)),
14275        leader.update(cx, |editor, cx| editor.text(cx))
14276    );
14277    update_message.borrow_mut().take();
14278
14279    // Start following separately after it already has excerpts.
14280    let mut state_message =
14281        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
14282    let workspace_entity = workspace.root(cx).unwrap();
14283    let follower_2 = cx
14284        .update_window(*workspace.deref(), |_, window, cx| {
14285            Editor::from_state_proto(
14286                workspace_entity,
14287                ViewId {
14288                    creator: CollaboratorId::PeerId(PeerId::default()),
14289                    id: 0,
14290                },
14291                &mut state_message,
14292                window,
14293                cx,
14294            )
14295        })
14296        .unwrap()
14297        .unwrap()
14298        .await
14299        .unwrap();
14300    assert_eq!(
14301        follower_2.update(cx, |editor, cx| editor.text(cx)),
14302        leader.update(cx, |editor, cx| editor.text(cx))
14303    );
14304
14305    // Remove some excerpts.
14306    leader.update(cx, |leader, cx| {
14307        leader.buffer.update(cx, |multibuffer, cx| {
14308            let excerpt_ids = multibuffer.excerpt_ids();
14309            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
14310            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
14311        });
14312    });
14313
14314    // Apply the update of removing the excerpts.
14315    follower_1
14316        .update_in(cx, |follower, window, cx| {
14317            follower.apply_update_proto(
14318                &project,
14319                update_message.borrow().clone().unwrap(),
14320                window,
14321                cx,
14322            )
14323        })
14324        .await
14325        .unwrap();
14326    follower_2
14327        .update_in(cx, |follower, window, cx| {
14328            follower.apply_update_proto(
14329                &project,
14330                update_message.borrow().clone().unwrap(),
14331                window,
14332                cx,
14333            )
14334        })
14335        .await
14336        .unwrap();
14337    update_message.borrow_mut().take();
14338    assert_eq!(
14339        follower_1.update(cx, |editor, cx| editor.text(cx)),
14340        leader.update(cx, |editor, cx| editor.text(cx))
14341    );
14342}
14343
14344#[gpui::test]
14345async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14346    init_test(cx, |_| {});
14347
14348    let mut cx = EditorTestContext::new(cx).await;
14349    let lsp_store =
14350        cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
14351
14352    cx.set_state(indoc! {"
14353        ˇfn func(abc def: i32) -> u32 {
14354        }
14355    "});
14356
14357    cx.update(|_, cx| {
14358        lsp_store.update(cx, |lsp_store, cx| {
14359            lsp_store
14360                .update_diagnostics(
14361                    LanguageServerId(0),
14362                    lsp::PublishDiagnosticsParams {
14363                        uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
14364                        version: None,
14365                        diagnostics: vec![
14366                            lsp::Diagnostic {
14367                                range: lsp::Range::new(
14368                                    lsp::Position::new(0, 11),
14369                                    lsp::Position::new(0, 12),
14370                                ),
14371                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14372                                ..Default::default()
14373                            },
14374                            lsp::Diagnostic {
14375                                range: lsp::Range::new(
14376                                    lsp::Position::new(0, 12),
14377                                    lsp::Position::new(0, 15),
14378                                ),
14379                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14380                                ..Default::default()
14381                            },
14382                            lsp::Diagnostic {
14383                                range: lsp::Range::new(
14384                                    lsp::Position::new(0, 25),
14385                                    lsp::Position::new(0, 28),
14386                                ),
14387                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14388                                ..Default::default()
14389                            },
14390                        ],
14391                    },
14392                    None,
14393                    DiagnosticSourceKind::Pushed,
14394                    &[],
14395                    cx,
14396                )
14397                .unwrap()
14398        });
14399    });
14400
14401    executor.run_until_parked();
14402
14403    cx.update_editor(|editor, window, cx| {
14404        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
14405    });
14406
14407    cx.assert_editor_state(indoc! {"
14408        fn func(abc def: i32) -> ˇu32 {
14409        }
14410    "});
14411
14412    cx.update_editor(|editor, window, cx| {
14413        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
14414    });
14415
14416    cx.assert_editor_state(indoc! {"
14417        fn func(abc ˇdef: i32) -> u32 {
14418        }
14419    "});
14420
14421    cx.update_editor(|editor, window, cx| {
14422        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
14423    });
14424
14425    cx.assert_editor_state(indoc! {"
14426        fn func(abcˇ def: i32) -> u32 {
14427        }
14428    "});
14429
14430    cx.update_editor(|editor, window, cx| {
14431        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
14432    });
14433
14434    cx.assert_editor_state(indoc! {"
14435        fn func(abc def: i32) -> ˇu32 {
14436        }
14437    "});
14438}
14439
14440#[gpui::test]
14441async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14442    init_test(cx, |_| {});
14443
14444    let mut cx = EditorTestContext::new(cx).await;
14445
14446    let diff_base = r#"
14447        use some::mod;
14448
14449        const A: u32 = 42;
14450
14451        fn main() {
14452            println!("hello");
14453
14454            println!("world");
14455        }
14456        "#
14457    .unindent();
14458
14459    // Edits are modified, removed, modified, added
14460    cx.set_state(
14461        &r#"
14462        use some::modified;
14463
14464        ˇ
14465        fn main() {
14466            println!("hello there");
14467
14468            println!("around the");
14469            println!("world");
14470        }
14471        "#
14472        .unindent(),
14473    );
14474
14475    cx.set_head_text(&diff_base);
14476    executor.run_until_parked();
14477
14478    cx.update_editor(|editor, window, cx| {
14479        //Wrap around the bottom of the buffer
14480        for _ in 0..3 {
14481            editor.go_to_next_hunk(&GoToHunk, window, cx);
14482        }
14483    });
14484
14485    cx.assert_editor_state(
14486        &r#"
14487        ˇuse some::modified;
14488
14489
14490        fn main() {
14491            println!("hello there");
14492
14493            println!("around the");
14494            println!("world");
14495        }
14496        "#
14497        .unindent(),
14498    );
14499
14500    cx.update_editor(|editor, window, cx| {
14501        //Wrap around the top of the buffer
14502        for _ in 0..2 {
14503            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14504        }
14505    });
14506
14507    cx.assert_editor_state(
14508        &r#"
14509        use some::modified;
14510
14511
14512        fn main() {
14513        ˇ    println!("hello there");
14514
14515            println!("around the");
14516            println!("world");
14517        }
14518        "#
14519        .unindent(),
14520    );
14521
14522    cx.update_editor(|editor, window, cx| {
14523        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14524    });
14525
14526    cx.assert_editor_state(
14527        &r#"
14528        use some::modified;
14529
14530        ˇ
14531        fn main() {
14532            println!("hello there");
14533
14534            println!("around the");
14535            println!("world");
14536        }
14537        "#
14538        .unindent(),
14539    );
14540
14541    cx.update_editor(|editor, window, cx| {
14542        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14543    });
14544
14545    cx.assert_editor_state(
14546        &r#"
14547        ˇuse some::modified;
14548
14549
14550        fn main() {
14551            println!("hello there");
14552
14553            println!("around the");
14554            println!("world");
14555        }
14556        "#
14557        .unindent(),
14558    );
14559
14560    cx.update_editor(|editor, window, cx| {
14561        for _ in 0..2 {
14562            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14563        }
14564    });
14565
14566    cx.assert_editor_state(
14567        &r#"
14568        use some::modified;
14569
14570
14571        fn main() {
14572        ˇ    println!("hello there");
14573
14574            println!("around the");
14575            println!("world");
14576        }
14577        "#
14578        .unindent(),
14579    );
14580
14581    cx.update_editor(|editor, window, cx| {
14582        editor.fold(&Fold, window, cx);
14583    });
14584
14585    cx.update_editor(|editor, window, cx| {
14586        editor.go_to_next_hunk(&GoToHunk, window, cx);
14587    });
14588
14589    cx.assert_editor_state(
14590        &r#"
14591        ˇuse some::modified;
14592
14593
14594        fn main() {
14595            println!("hello there");
14596
14597            println!("around the");
14598            println!("world");
14599        }
14600        "#
14601        .unindent(),
14602    );
14603}
14604
14605#[test]
14606fn test_split_words() {
14607    fn split(text: &str) -> Vec<&str> {
14608        split_words(text).collect()
14609    }
14610
14611    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
14612    assert_eq!(split("hello_world"), &["hello_", "world"]);
14613    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
14614    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
14615    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
14616    assert_eq!(split("helloworld"), &["helloworld"]);
14617
14618    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
14619}
14620
14621#[gpui::test]
14622async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
14623    init_test(cx, |_| {});
14624
14625    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
14626    let mut assert = |before, after| {
14627        let _state_context = cx.set_state(before);
14628        cx.run_until_parked();
14629        cx.update_editor(|editor, window, cx| {
14630            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
14631        });
14632        cx.run_until_parked();
14633        cx.assert_editor_state(after);
14634    };
14635
14636    // Outside bracket jumps to outside of matching bracket
14637    assert("console.logˇ(var);", "console.log(var)ˇ;");
14638    assert("console.log(var)ˇ;", "console.logˇ(var);");
14639
14640    // Inside bracket jumps to inside of matching bracket
14641    assert("console.log(ˇvar);", "console.log(varˇ);");
14642    assert("console.log(varˇ);", "console.log(ˇvar);");
14643
14644    // When outside a bracket and inside, favor jumping to the inside bracket
14645    assert(
14646        "console.log('foo', [1, 2, 3]ˇ);",
14647        "console.log(ˇ'foo', [1, 2, 3]);",
14648    );
14649    assert(
14650        "console.log(ˇ'foo', [1, 2, 3]);",
14651        "console.log('foo', [1, 2, 3]ˇ);",
14652    );
14653
14654    // Bias forward if two options are equally likely
14655    assert(
14656        "let result = curried_fun()ˇ();",
14657        "let result = curried_fun()()ˇ;",
14658    );
14659
14660    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
14661    assert(
14662        indoc! {"
14663            function test() {
14664                console.log('test')ˇ
14665            }"},
14666        indoc! {"
14667            function test() {
14668                console.logˇ('test')
14669            }"},
14670    );
14671}
14672
14673#[gpui::test]
14674async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
14675    init_test(cx, |_| {});
14676
14677    let fs = FakeFs::new(cx.executor());
14678    fs.insert_tree(
14679        path!("/a"),
14680        json!({
14681            "main.rs": "fn main() { let a = 5; }",
14682            "other.rs": "// Test file",
14683        }),
14684    )
14685    .await;
14686    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14687
14688    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14689    language_registry.add(Arc::new(Language::new(
14690        LanguageConfig {
14691            name: "Rust".into(),
14692            matcher: LanguageMatcher {
14693                path_suffixes: vec!["rs".to_string()],
14694                ..Default::default()
14695            },
14696            brackets: BracketPairConfig {
14697                pairs: vec![BracketPair {
14698                    start: "{".to_string(),
14699                    end: "}".to_string(),
14700                    close: true,
14701                    surround: true,
14702                    newline: true,
14703                }],
14704                disabled_scopes_by_bracket_ix: Vec::new(),
14705            },
14706            ..Default::default()
14707        },
14708        Some(tree_sitter_rust::LANGUAGE.into()),
14709    )));
14710    let mut fake_servers = language_registry.register_fake_lsp(
14711        "Rust",
14712        FakeLspAdapter {
14713            capabilities: lsp::ServerCapabilities {
14714                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
14715                    first_trigger_character: "{".to_string(),
14716                    more_trigger_character: None,
14717                }),
14718                ..Default::default()
14719            },
14720            ..Default::default()
14721        },
14722    );
14723
14724    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14725
14726    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14727
14728    let worktree_id = workspace
14729        .update(cx, |workspace, _, cx| {
14730            workspace.project().update(cx, |project, cx| {
14731                project.worktrees(cx).next().unwrap().read(cx).id()
14732            })
14733        })
14734        .unwrap();
14735
14736    let buffer = project
14737        .update(cx, |project, cx| {
14738            project.open_local_buffer(path!("/a/main.rs"), cx)
14739        })
14740        .await
14741        .unwrap();
14742    let editor_handle = workspace
14743        .update(cx, |workspace, window, cx| {
14744            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
14745        })
14746        .unwrap()
14747        .await
14748        .unwrap()
14749        .downcast::<Editor>()
14750        .unwrap();
14751
14752    cx.executor().start_waiting();
14753    let fake_server = fake_servers.next().await.unwrap();
14754
14755    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
14756        |params, _| async move {
14757            assert_eq!(
14758                params.text_document_position.text_document.uri,
14759                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
14760            );
14761            assert_eq!(
14762                params.text_document_position.position,
14763                lsp::Position::new(0, 21),
14764            );
14765
14766            Ok(Some(vec![lsp::TextEdit {
14767                new_text: "]".to_string(),
14768                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
14769            }]))
14770        },
14771    );
14772
14773    editor_handle.update_in(cx, |editor, window, cx| {
14774        window.focus(&editor.focus_handle(cx));
14775        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14776            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
14777        });
14778        editor.handle_input("{", window, cx);
14779    });
14780
14781    cx.executor().run_until_parked();
14782
14783    buffer.update(cx, |buffer, _| {
14784        assert_eq!(
14785            buffer.text(),
14786            "fn main() { let a = {5}; }",
14787            "No extra braces from on type formatting should appear in the buffer"
14788        )
14789    });
14790}
14791
14792#[gpui::test(iterations = 20, seeds(31))]
14793async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
14794    init_test(cx, |_| {});
14795
14796    let mut cx = EditorLspTestContext::new_rust(
14797        lsp::ServerCapabilities {
14798            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
14799                first_trigger_character: ".".to_string(),
14800                more_trigger_character: None,
14801            }),
14802            ..Default::default()
14803        },
14804        cx,
14805    )
14806    .await;
14807
14808    cx.update_buffer(|buffer, _| {
14809        // This causes autoindent to be async.
14810        buffer.set_sync_parse_timeout(Duration::ZERO)
14811    });
14812
14813    cx.set_state("fn c() {\n    d()ˇ\n}\n");
14814    cx.simulate_keystroke("\n");
14815    cx.run_until_parked();
14816
14817    let buffer_cloned =
14818        cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap().clone());
14819    let mut request =
14820        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
14821            let buffer_cloned = buffer_cloned.clone();
14822            async move {
14823                buffer_cloned.update(&mut cx, |buffer, _| {
14824                    assert_eq!(
14825                        buffer.text(),
14826                        "fn c() {\n    d()\n        .\n}\n",
14827                        "OnTypeFormatting should triggered after autoindent applied"
14828                    )
14829                })?;
14830
14831                Ok(Some(vec![]))
14832            }
14833        });
14834
14835    cx.simulate_keystroke(".");
14836    cx.run_until_parked();
14837
14838    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
14839    assert!(request.next().await.is_some());
14840    request.close();
14841    assert!(request.next().await.is_none());
14842}
14843
14844#[gpui::test]
14845async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
14846    init_test(cx, |_| {});
14847
14848    let fs = FakeFs::new(cx.executor());
14849    fs.insert_tree(
14850        path!("/a"),
14851        json!({
14852            "main.rs": "fn main() { let a = 5; }",
14853            "other.rs": "// Test file",
14854        }),
14855    )
14856    .await;
14857
14858    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14859
14860    let server_restarts = Arc::new(AtomicUsize::new(0));
14861    let closure_restarts = Arc::clone(&server_restarts);
14862    let language_server_name = "test language server";
14863    let language_name: LanguageName = "Rust".into();
14864
14865    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14866    language_registry.add(Arc::new(Language::new(
14867        LanguageConfig {
14868            name: language_name.clone(),
14869            matcher: LanguageMatcher {
14870                path_suffixes: vec!["rs".to_string()],
14871                ..Default::default()
14872            },
14873            ..Default::default()
14874        },
14875        Some(tree_sitter_rust::LANGUAGE.into()),
14876    )));
14877    let mut fake_servers = language_registry.register_fake_lsp(
14878        "Rust",
14879        FakeLspAdapter {
14880            name: language_server_name,
14881            initialization_options: Some(json!({
14882                "testOptionValue": true
14883            })),
14884            initializer: Some(Box::new(move |fake_server| {
14885                let task_restarts = Arc::clone(&closure_restarts);
14886                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
14887                    task_restarts.fetch_add(1, atomic::Ordering::Release);
14888                    futures::future::ready(Ok(()))
14889                });
14890            })),
14891            ..Default::default()
14892        },
14893    );
14894
14895    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14896    let _buffer = project
14897        .update(cx, |project, cx| {
14898            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
14899        })
14900        .await
14901        .unwrap();
14902    let _fake_server = fake_servers.next().await.unwrap();
14903    update_test_language_settings(cx, |language_settings| {
14904        language_settings.languages.0.insert(
14905            language_name.clone(),
14906            LanguageSettingsContent {
14907                tab_size: NonZeroU32::new(8),
14908                ..Default::default()
14909            },
14910        );
14911    });
14912    cx.executor().run_until_parked();
14913    assert_eq!(
14914        server_restarts.load(atomic::Ordering::Acquire),
14915        0,
14916        "Should not restart LSP server on an unrelated change"
14917    );
14918
14919    update_test_project_settings(cx, |project_settings| {
14920        project_settings.lsp.insert(
14921            "Some other server name".into(),
14922            LspSettings {
14923                binary: None,
14924                settings: None,
14925                initialization_options: Some(json!({
14926                    "some other init value": false
14927                })),
14928                enable_lsp_tasks: false,
14929            },
14930        );
14931    });
14932    cx.executor().run_until_parked();
14933    assert_eq!(
14934        server_restarts.load(atomic::Ordering::Acquire),
14935        0,
14936        "Should not restart LSP server on an unrelated LSP settings change"
14937    );
14938
14939    update_test_project_settings(cx, |project_settings| {
14940        project_settings.lsp.insert(
14941            language_server_name.into(),
14942            LspSettings {
14943                binary: None,
14944                settings: None,
14945                initialization_options: Some(json!({
14946                    "anotherInitValue": false
14947                })),
14948                enable_lsp_tasks: false,
14949            },
14950        );
14951    });
14952    cx.executor().run_until_parked();
14953    assert_eq!(
14954        server_restarts.load(atomic::Ordering::Acquire),
14955        1,
14956        "Should restart LSP server on a related LSP settings change"
14957    );
14958
14959    update_test_project_settings(cx, |project_settings| {
14960        project_settings.lsp.insert(
14961            language_server_name.into(),
14962            LspSettings {
14963                binary: None,
14964                settings: None,
14965                initialization_options: Some(json!({
14966                    "anotherInitValue": false
14967                })),
14968                enable_lsp_tasks: false,
14969            },
14970        );
14971    });
14972    cx.executor().run_until_parked();
14973    assert_eq!(
14974        server_restarts.load(atomic::Ordering::Acquire),
14975        1,
14976        "Should not restart LSP server on a related LSP settings change that is the same"
14977    );
14978
14979    update_test_project_settings(cx, |project_settings| {
14980        project_settings.lsp.insert(
14981            language_server_name.into(),
14982            LspSettings {
14983                binary: None,
14984                settings: None,
14985                initialization_options: None,
14986                enable_lsp_tasks: false,
14987            },
14988        );
14989    });
14990    cx.executor().run_until_parked();
14991    assert_eq!(
14992        server_restarts.load(atomic::Ordering::Acquire),
14993        2,
14994        "Should restart LSP server on another related LSP settings change"
14995    );
14996}
14997
14998#[gpui::test]
14999async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
15000    init_test(cx, |_| {});
15001
15002    let mut cx = EditorLspTestContext::new_rust(
15003        lsp::ServerCapabilities {
15004            completion_provider: Some(lsp::CompletionOptions {
15005                trigger_characters: Some(vec![".".to_string()]),
15006                resolve_provider: Some(true),
15007                ..Default::default()
15008            }),
15009            ..Default::default()
15010        },
15011        cx,
15012    )
15013    .await;
15014
15015    cx.set_state("fn main() { let a = 2ˇ; }");
15016    cx.simulate_keystroke(".");
15017    let completion_item = lsp::CompletionItem {
15018        label: "some".into(),
15019        kind: Some(lsp::CompletionItemKind::SNIPPET),
15020        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15021        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15022            kind: lsp::MarkupKind::Markdown,
15023            value: "```rust\nSome(2)\n```".to_string(),
15024        })),
15025        deprecated: Some(false),
15026        sort_text: Some("fffffff2".to_string()),
15027        filter_text: Some("some".to_string()),
15028        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15029        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15030            range: lsp::Range {
15031                start: lsp::Position {
15032                    line: 0,
15033                    character: 22,
15034                },
15035                end: lsp::Position {
15036                    line: 0,
15037                    character: 22,
15038                },
15039            },
15040            new_text: "Some(2)".to_string(),
15041        })),
15042        additional_text_edits: Some(vec![lsp::TextEdit {
15043            range: lsp::Range {
15044                start: lsp::Position {
15045                    line: 0,
15046                    character: 20,
15047                },
15048                end: lsp::Position {
15049                    line: 0,
15050                    character: 22,
15051                },
15052            },
15053            new_text: "".to_string(),
15054        }]),
15055        ..Default::default()
15056    };
15057
15058    let closure_completion_item = completion_item.clone();
15059    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15060        let task_completion_item = closure_completion_item.clone();
15061        async move {
15062            Ok(Some(lsp::CompletionResponse::Array(vec![
15063                task_completion_item,
15064            ])))
15065        }
15066    });
15067
15068    request.next().await;
15069
15070    cx.condition(|editor, _| editor.context_menu_visible())
15071        .await;
15072    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15073        editor
15074            .confirm_completion(&ConfirmCompletion::default(), window, cx)
15075            .unwrap()
15076    });
15077    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
15078
15079    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
15080        let task_completion_item = completion_item.clone();
15081        async move { Ok(task_completion_item) }
15082    })
15083    .next()
15084    .await
15085    .unwrap();
15086    apply_additional_edits.await.unwrap();
15087    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
15088}
15089
15090#[gpui::test]
15091async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
15092    init_test(cx, |_| {});
15093
15094    let mut cx = EditorLspTestContext::new_rust(
15095        lsp::ServerCapabilities {
15096            completion_provider: Some(lsp::CompletionOptions {
15097                trigger_characters: Some(vec![".".to_string()]),
15098                resolve_provider: Some(true),
15099                ..Default::default()
15100            }),
15101            ..Default::default()
15102        },
15103        cx,
15104    )
15105    .await;
15106
15107    cx.set_state("fn main() { let a = 2ˇ; }");
15108    cx.simulate_keystroke(".");
15109
15110    let item1 = lsp::CompletionItem {
15111        label: "method id()".to_string(),
15112        filter_text: Some("id".to_string()),
15113        detail: None,
15114        documentation: None,
15115        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15116            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15117            new_text: ".id".to_string(),
15118        })),
15119        ..lsp::CompletionItem::default()
15120    };
15121
15122    let item2 = lsp::CompletionItem {
15123        label: "other".to_string(),
15124        filter_text: Some("other".to_string()),
15125        detail: None,
15126        documentation: None,
15127        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15128            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15129            new_text: ".other".to_string(),
15130        })),
15131        ..lsp::CompletionItem::default()
15132    };
15133
15134    let item1 = item1.clone();
15135    cx.set_request_handler::<lsp::request::Completion, _, _>({
15136        let item1 = item1.clone();
15137        move |_, _, _| {
15138            let item1 = item1.clone();
15139            let item2 = item2.clone();
15140            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
15141        }
15142    })
15143    .next()
15144    .await;
15145
15146    cx.condition(|editor, _| editor.context_menu_visible())
15147        .await;
15148    cx.update_editor(|editor, _, _| {
15149        let context_menu = editor.context_menu.borrow_mut();
15150        let context_menu = context_menu
15151            .as_ref()
15152            .expect("Should have the context menu deployed");
15153        match context_menu {
15154            CodeContextMenu::Completions(completions_menu) => {
15155                let completions = completions_menu.completions.borrow_mut();
15156                assert_eq!(
15157                    completions
15158                        .iter()
15159                        .map(|completion| &completion.label.text)
15160                        .collect::<Vec<_>>(),
15161                    vec!["method id()", "other"]
15162                )
15163            }
15164            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15165        }
15166    });
15167
15168    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
15169        let item1 = item1.clone();
15170        move |_, item_to_resolve, _| {
15171            let item1 = item1.clone();
15172            async move {
15173                if item1 == item_to_resolve {
15174                    Ok(lsp::CompletionItem {
15175                        label: "method id()".to_string(),
15176                        filter_text: Some("id".to_string()),
15177                        detail: Some("Now resolved!".to_string()),
15178                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
15179                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15180                            range: lsp::Range::new(
15181                                lsp::Position::new(0, 22),
15182                                lsp::Position::new(0, 22),
15183                            ),
15184                            new_text: ".id".to_string(),
15185                        })),
15186                        ..lsp::CompletionItem::default()
15187                    })
15188                } else {
15189                    Ok(item_to_resolve)
15190                }
15191            }
15192        }
15193    })
15194    .next()
15195    .await
15196    .unwrap();
15197    cx.run_until_parked();
15198
15199    cx.update_editor(|editor, window, cx| {
15200        editor.context_menu_next(&Default::default(), window, cx);
15201    });
15202
15203    cx.update_editor(|editor, _, _| {
15204        let context_menu = editor.context_menu.borrow_mut();
15205        let context_menu = context_menu
15206            .as_ref()
15207            .expect("Should have the context menu deployed");
15208        match context_menu {
15209            CodeContextMenu::Completions(completions_menu) => {
15210                let completions = completions_menu.completions.borrow_mut();
15211                assert_eq!(
15212                    completions
15213                        .iter()
15214                        .map(|completion| &completion.label.text)
15215                        .collect::<Vec<_>>(),
15216                    vec!["method id() Now resolved!", "other"],
15217                    "Should update first completion label, but not second as the filter text did not match."
15218                );
15219            }
15220            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15221        }
15222    });
15223}
15224
15225#[gpui::test]
15226async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
15227    init_test(cx, |_| {});
15228    let mut cx = EditorLspTestContext::new_rust(
15229        lsp::ServerCapabilities {
15230            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
15231            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
15232            completion_provider: Some(lsp::CompletionOptions {
15233                resolve_provider: Some(true),
15234                ..Default::default()
15235            }),
15236            ..Default::default()
15237        },
15238        cx,
15239    )
15240    .await;
15241    cx.set_state(indoc! {"
15242        struct TestStruct {
15243            field: i32
15244        }
15245
15246        fn mainˇ() {
15247            let unused_var = 42;
15248            let test_struct = TestStruct { field: 42 };
15249        }
15250    "});
15251    let symbol_range = cx.lsp_range(indoc! {"
15252        struct TestStruct {
15253            field: i32
15254        }
15255
15256        «fn main»() {
15257            let unused_var = 42;
15258            let test_struct = TestStruct { field: 42 };
15259        }
15260    "});
15261    let mut hover_requests =
15262        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
15263            Ok(Some(lsp::Hover {
15264                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
15265                    kind: lsp::MarkupKind::Markdown,
15266                    value: "Function documentation".to_string(),
15267                }),
15268                range: Some(symbol_range),
15269            }))
15270        });
15271
15272    // Case 1: Test that code action menu hide hover popover
15273    cx.dispatch_action(Hover);
15274    hover_requests.next().await;
15275    cx.condition(|editor, _| editor.hover_state.visible()).await;
15276    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
15277        move |_, _, _| async move {
15278            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
15279                lsp::CodeAction {
15280                    title: "Remove unused variable".to_string(),
15281                    kind: Some(CodeActionKind::QUICKFIX),
15282                    edit: Some(lsp::WorkspaceEdit {
15283                        changes: Some(
15284                            [(
15285                                lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
15286                                vec![lsp::TextEdit {
15287                                    range: lsp::Range::new(
15288                                        lsp::Position::new(5, 4),
15289                                        lsp::Position::new(5, 27),
15290                                    ),
15291                                    new_text: "".to_string(),
15292                                }],
15293                            )]
15294                            .into_iter()
15295                            .collect(),
15296                        ),
15297                        ..Default::default()
15298                    }),
15299                    ..Default::default()
15300                },
15301            )]))
15302        },
15303    );
15304    cx.update_editor(|editor, window, cx| {
15305        editor.toggle_code_actions(
15306            &ToggleCodeActions {
15307                deployed_from: None,
15308                quick_launch: false,
15309            },
15310            window,
15311            cx,
15312        );
15313    });
15314    code_action_requests.next().await;
15315    cx.run_until_parked();
15316    cx.condition(|editor, _| editor.context_menu_visible())
15317        .await;
15318    cx.update_editor(|editor, _, _| {
15319        assert!(
15320            !editor.hover_state.visible(),
15321            "Hover popover should be hidden when code action menu is shown"
15322        );
15323        // Hide code actions
15324        editor.context_menu.take();
15325    });
15326
15327    // Case 2: Test that code completions hide hover popover
15328    cx.dispatch_action(Hover);
15329    hover_requests.next().await;
15330    cx.condition(|editor, _| editor.hover_state.visible()).await;
15331    let counter = Arc::new(AtomicUsize::new(0));
15332    let mut completion_requests =
15333        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15334            let counter = counter.clone();
15335            async move {
15336                counter.fetch_add(1, atomic::Ordering::Release);
15337                Ok(Some(lsp::CompletionResponse::Array(vec![
15338                    lsp::CompletionItem {
15339                        label: "main".into(),
15340                        kind: Some(lsp::CompletionItemKind::FUNCTION),
15341                        detail: Some("() -> ()".to_string()),
15342                        ..Default::default()
15343                    },
15344                    lsp::CompletionItem {
15345                        label: "TestStruct".into(),
15346                        kind: Some(lsp::CompletionItemKind::STRUCT),
15347                        detail: Some("struct TestStruct".to_string()),
15348                        ..Default::default()
15349                    },
15350                ])))
15351            }
15352        });
15353    cx.update_editor(|editor, window, cx| {
15354        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15355    });
15356    completion_requests.next().await;
15357    cx.condition(|editor, _| editor.context_menu_visible())
15358        .await;
15359    cx.update_editor(|editor, _, _| {
15360        assert!(
15361            !editor.hover_state.visible(),
15362            "Hover popover should be hidden when completion menu is shown"
15363        );
15364    });
15365}
15366
15367#[gpui::test]
15368async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
15369    init_test(cx, |_| {});
15370
15371    let mut cx = EditorLspTestContext::new_rust(
15372        lsp::ServerCapabilities {
15373            completion_provider: Some(lsp::CompletionOptions {
15374                trigger_characters: Some(vec![".".to_string()]),
15375                resolve_provider: Some(true),
15376                ..Default::default()
15377            }),
15378            ..Default::default()
15379        },
15380        cx,
15381    )
15382    .await;
15383
15384    cx.set_state("fn main() { let a = 2ˇ; }");
15385    cx.simulate_keystroke(".");
15386
15387    let unresolved_item_1 = lsp::CompletionItem {
15388        label: "id".to_string(),
15389        filter_text: Some("id".to_string()),
15390        detail: None,
15391        documentation: None,
15392        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15393            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15394            new_text: ".id".to_string(),
15395        })),
15396        ..lsp::CompletionItem::default()
15397    };
15398    let resolved_item_1 = lsp::CompletionItem {
15399        additional_text_edits: Some(vec![lsp::TextEdit {
15400            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
15401            new_text: "!!".to_string(),
15402        }]),
15403        ..unresolved_item_1.clone()
15404    };
15405    let unresolved_item_2 = lsp::CompletionItem {
15406        label: "other".to_string(),
15407        filter_text: Some("other".to_string()),
15408        detail: None,
15409        documentation: None,
15410        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15411            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15412            new_text: ".other".to_string(),
15413        })),
15414        ..lsp::CompletionItem::default()
15415    };
15416    let resolved_item_2 = lsp::CompletionItem {
15417        additional_text_edits: Some(vec![lsp::TextEdit {
15418            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
15419            new_text: "??".to_string(),
15420        }]),
15421        ..unresolved_item_2.clone()
15422    };
15423
15424    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
15425    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
15426    cx.lsp
15427        .server
15428        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
15429            let unresolved_item_1 = unresolved_item_1.clone();
15430            let resolved_item_1 = resolved_item_1.clone();
15431            let unresolved_item_2 = unresolved_item_2.clone();
15432            let resolved_item_2 = resolved_item_2.clone();
15433            let resolve_requests_1 = resolve_requests_1.clone();
15434            let resolve_requests_2 = resolve_requests_2.clone();
15435            move |unresolved_request, _| {
15436                let unresolved_item_1 = unresolved_item_1.clone();
15437                let resolved_item_1 = resolved_item_1.clone();
15438                let unresolved_item_2 = unresolved_item_2.clone();
15439                let resolved_item_2 = resolved_item_2.clone();
15440                let resolve_requests_1 = resolve_requests_1.clone();
15441                let resolve_requests_2 = resolve_requests_2.clone();
15442                async move {
15443                    if unresolved_request == unresolved_item_1 {
15444                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
15445                        Ok(resolved_item_1.clone())
15446                    } else if unresolved_request == unresolved_item_2 {
15447                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
15448                        Ok(resolved_item_2.clone())
15449                    } else {
15450                        panic!("Unexpected completion item {unresolved_request:?}")
15451                    }
15452                }
15453            }
15454        })
15455        .detach();
15456
15457    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15458        let unresolved_item_1 = unresolved_item_1.clone();
15459        let unresolved_item_2 = unresolved_item_2.clone();
15460        async move {
15461            Ok(Some(lsp::CompletionResponse::Array(vec![
15462                unresolved_item_1,
15463                unresolved_item_2,
15464            ])))
15465        }
15466    })
15467    .next()
15468    .await;
15469
15470    cx.condition(|editor, _| editor.context_menu_visible())
15471        .await;
15472    cx.update_editor(|editor, _, _| {
15473        let context_menu = editor.context_menu.borrow_mut();
15474        let context_menu = context_menu
15475            .as_ref()
15476            .expect("Should have the context menu deployed");
15477        match context_menu {
15478            CodeContextMenu::Completions(completions_menu) => {
15479                let completions = completions_menu.completions.borrow_mut();
15480                assert_eq!(
15481                    completions
15482                        .iter()
15483                        .map(|completion| &completion.label.text)
15484                        .collect::<Vec<_>>(),
15485                    vec!["id", "other"]
15486                )
15487            }
15488            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15489        }
15490    });
15491    cx.run_until_parked();
15492
15493    cx.update_editor(|editor, window, cx| {
15494        editor.context_menu_next(&ContextMenuNext, window, cx);
15495    });
15496    cx.run_until_parked();
15497    cx.update_editor(|editor, window, cx| {
15498        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
15499    });
15500    cx.run_until_parked();
15501    cx.update_editor(|editor, window, cx| {
15502        editor.context_menu_next(&ContextMenuNext, window, cx);
15503    });
15504    cx.run_until_parked();
15505    cx.update_editor(|editor, window, cx| {
15506        editor
15507            .compose_completion(&ComposeCompletion::default(), window, cx)
15508            .expect("No task returned")
15509    })
15510    .await
15511    .expect("Completion failed");
15512    cx.run_until_parked();
15513
15514    cx.update_editor(|editor, _, cx| {
15515        assert_eq!(
15516            resolve_requests_1.load(atomic::Ordering::Acquire),
15517            1,
15518            "Should always resolve once despite multiple selections"
15519        );
15520        assert_eq!(
15521            resolve_requests_2.load(atomic::Ordering::Acquire),
15522            1,
15523            "Should always resolve once after multiple selections and applying the completion"
15524        );
15525        assert_eq!(
15526            editor.text(cx),
15527            "fn main() { let a = ??.other; }",
15528            "Should use resolved data when applying the completion"
15529        );
15530    });
15531}
15532
15533#[gpui::test]
15534async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
15535    init_test(cx, |_| {});
15536
15537    let item_0 = lsp::CompletionItem {
15538        label: "abs".into(),
15539        insert_text: Some("abs".into()),
15540        data: Some(json!({ "very": "special"})),
15541        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
15542        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
15543            lsp::InsertReplaceEdit {
15544                new_text: "abs".to_string(),
15545                insert: lsp::Range::default(),
15546                replace: lsp::Range::default(),
15547            },
15548        )),
15549        ..lsp::CompletionItem::default()
15550    };
15551    let items = iter::once(item_0.clone())
15552        .chain((11..51).map(|i| lsp::CompletionItem {
15553            label: format!("item_{}", i),
15554            insert_text: Some(format!("item_{}", i)),
15555            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15556            ..lsp::CompletionItem::default()
15557        }))
15558        .collect::<Vec<_>>();
15559
15560    let default_commit_characters = vec!["?".to_string()];
15561    let default_data = json!({ "default": "data"});
15562    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
15563    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
15564    let default_edit_range = lsp::Range {
15565        start: lsp::Position {
15566            line: 0,
15567            character: 5,
15568        },
15569        end: lsp::Position {
15570            line: 0,
15571            character: 5,
15572        },
15573    };
15574
15575    let mut cx = EditorLspTestContext::new_rust(
15576        lsp::ServerCapabilities {
15577            completion_provider: Some(lsp::CompletionOptions {
15578                trigger_characters: Some(vec![".".to_string()]),
15579                resolve_provider: Some(true),
15580                ..Default::default()
15581            }),
15582            ..Default::default()
15583        },
15584        cx,
15585    )
15586    .await;
15587
15588    cx.set_state("fn main() { let a = 2ˇ; }");
15589    cx.simulate_keystroke(".");
15590
15591    let completion_data = default_data.clone();
15592    let completion_characters = default_commit_characters.clone();
15593    let completion_items = items.clone();
15594    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15595        let default_data = completion_data.clone();
15596        let default_commit_characters = completion_characters.clone();
15597        let items = completion_items.clone();
15598        async move {
15599            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15600                items,
15601                item_defaults: Some(lsp::CompletionListItemDefaults {
15602                    data: Some(default_data.clone()),
15603                    commit_characters: Some(default_commit_characters.clone()),
15604                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
15605                        default_edit_range,
15606                    )),
15607                    insert_text_format: Some(default_insert_text_format),
15608                    insert_text_mode: Some(default_insert_text_mode),
15609                }),
15610                ..lsp::CompletionList::default()
15611            })))
15612        }
15613    })
15614    .next()
15615    .await;
15616
15617    let resolved_items = Arc::new(Mutex::new(Vec::new()));
15618    cx.lsp
15619        .server
15620        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
15621            let closure_resolved_items = resolved_items.clone();
15622            move |item_to_resolve, _| {
15623                let closure_resolved_items = closure_resolved_items.clone();
15624                async move {
15625                    closure_resolved_items.lock().push(item_to_resolve.clone());
15626                    Ok(item_to_resolve)
15627                }
15628            }
15629        })
15630        .detach();
15631
15632    cx.condition(|editor, _| editor.context_menu_visible())
15633        .await;
15634    cx.run_until_parked();
15635    cx.update_editor(|editor, _, _| {
15636        let menu = editor.context_menu.borrow_mut();
15637        match menu.as_ref().expect("should have the completions menu") {
15638            CodeContextMenu::Completions(completions_menu) => {
15639                assert_eq!(
15640                    completions_menu
15641                        .entries
15642                        .borrow()
15643                        .iter()
15644                        .map(|mat| mat.string.clone())
15645                        .collect::<Vec<String>>(),
15646                    items
15647                        .iter()
15648                        .map(|completion| completion.label.clone())
15649                        .collect::<Vec<String>>()
15650                );
15651            }
15652            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
15653        }
15654    });
15655    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
15656    // with 4 from the end.
15657    assert_eq!(
15658        *resolved_items.lock(),
15659        [&items[0..16], &items[items.len() - 4..items.len()]]
15660            .concat()
15661            .iter()
15662            .cloned()
15663            .map(|mut item| {
15664                if item.data.is_none() {
15665                    item.data = Some(default_data.clone());
15666                }
15667                item
15668            })
15669            .collect::<Vec<lsp::CompletionItem>>(),
15670        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
15671    );
15672    resolved_items.lock().clear();
15673
15674    cx.update_editor(|editor, window, cx| {
15675        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
15676    });
15677    cx.run_until_parked();
15678    // Completions that have already been resolved are skipped.
15679    assert_eq!(
15680        *resolved_items.lock(),
15681        items[items.len() - 17..items.len() - 4]
15682            .iter()
15683            .cloned()
15684            .map(|mut item| {
15685                if item.data.is_none() {
15686                    item.data = Some(default_data.clone());
15687                }
15688                item
15689            })
15690            .collect::<Vec<lsp::CompletionItem>>()
15691    );
15692    resolved_items.lock().clear();
15693}
15694
15695#[gpui::test]
15696async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
15697    init_test(cx, |_| {});
15698
15699    let mut cx = EditorLspTestContext::new(
15700        Language::new(
15701            LanguageConfig {
15702                matcher: LanguageMatcher {
15703                    path_suffixes: vec!["jsx".into()],
15704                    ..Default::default()
15705                },
15706                overrides: [(
15707                    "element".into(),
15708                    LanguageConfigOverride {
15709                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
15710                        ..Default::default()
15711                    },
15712                )]
15713                .into_iter()
15714                .collect(),
15715                ..Default::default()
15716            },
15717            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
15718        )
15719        .with_override_query("(jsx_self_closing_element) @element")
15720        .unwrap(),
15721        lsp::ServerCapabilities {
15722            completion_provider: Some(lsp::CompletionOptions {
15723                trigger_characters: Some(vec![":".to_string()]),
15724                ..Default::default()
15725            }),
15726            ..Default::default()
15727        },
15728        cx,
15729    )
15730    .await;
15731
15732    cx.lsp
15733        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15734            Ok(Some(lsp::CompletionResponse::Array(vec![
15735                lsp::CompletionItem {
15736                    label: "bg-blue".into(),
15737                    ..Default::default()
15738                },
15739                lsp::CompletionItem {
15740                    label: "bg-red".into(),
15741                    ..Default::default()
15742                },
15743                lsp::CompletionItem {
15744                    label: "bg-yellow".into(),
15745                    ..Default::default()
15746                },
15747            ])))
15748        });
15749
15750    cx.set_state(r#"<p class="bgˇ" />"#);
15751
15752    // Trigger completion when typing a dash, because the dash is an extra
15753    // word character in the 'element' scope, which contains the cursor.
15754    cx.simulate_keystroke("-");
15755    cx.executor().run_until_parked();
15756    cx.update_editor(|editor, _, _| {
15757        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15758        {
15759            assert_eq!(
15760                completion_menu_entries(&menu),
15761                &["bg-blue", "bg-red", "bg-yellow"]
15762            );
15763        } else {
15764            panic!("expected completion menu to be open");
15765        }
15766    });
15767
15768    cx.simulate_keystroke("l");
15769    cx.executor().run_until_parked();
15770    cx.update_editor(|editor, _, _| {
15771        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15772        {
15773            assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
15774        } else {
15775            panic!("expected completion menu to be open");
15776        }
15777    });
15778
15779    // When filtering completions, consider the character after the '-' to
15780    // be the start of a subword.
15781    cx.set_state(r#"<p class="yelˇ" />"#);
15782    cx.simulate_keystroke("l");
15783    cx.executor().run_until_parked();
15784    cx.update_editor(|editor, _, _| {
15785        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15786        {
15787            assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
15788        } else {
15789            panic!("expected completion menu to be open");
15790        }
15791    });
15792}
15793
15794fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
15795    let entries = menu.entries.borrow();
15796    entries.iter().map(|mat| mat.string.clone()).collect()
15797}
15798
15799#[gpui::test]
15800async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
15801    init_test(cx, |settings| {
15802        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(vec![
15803            Formatter::Prettier,
15804        ]))
15805    });
15806
15807    let fs = FakeFs::new(cx.executor());
15808    fs.insert_file(path!("/file.ts"), Default::default()).await;
15809
15810    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
15811    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15812
15813    language_registry.add(Arc::new(Language::new(
15814        LanguageConfig {
15815            name: "TypeScript".into(),
15816            matcher: LanguageMatcher {
15817                path_suffixes: vec!["ts".to_string()],
15818                ..Default::default()
15819            },
15820            ..Default::default()
15821        },
15822        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
15823    )));
15824    update_test_language_settings(cx, |settings| {
15825        settings.defaults.prettier = Some(PrettierSettings {
15826            allowed: true,
15827            ..PrettierSettings::default()
15828        });
15829    });
15830
15831    let test_plugin = "test_plugin";
15832    let _ = language_registry.register_fake_lsp(
15833        "TypeScript",
15834        FakeLspAdapter {
15835            prettier_plugins: vec![test_plugin],
15836            ..Default::default()
15837        },
15838    );
15839
15840    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
15841    let buffer = project
15842        .update(cx, |project, cx| {
15843            project.open_local_buffer(path!("/file.ts"), cx)
15844        })
15845        .await
15846        .unwrap();
15847
15848    let buffer_text = "one\ntwo\nthree\n";
15849    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
15850    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
15851    editor.update_in(cx, |editor, window, cx| {
15852        editor.set_text(buffer_text, window, cx)
15853    });
15854
15855    editor
15856        .update_in(cx, |editor, window, cx| {
15857            editor.perform_format(
15858                project.clone(),
15859                FormatTrigger::Manual,
15860                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
15861                window,
15862                cx,
15863            )
15864        })
15865        .unwrap()
15866        .await;
15867    assert_eq!(
15868        editor.update(cx, |editor, cx| editor.text(cx)),
15869        buffer_text.to_string() + prettier_format_suffix,
15870        "Test prettier formatting was not applied to the original buffer text",
15871    );
15872
15873    update_test_language_settings(cx, |settings| {
15874        settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
15875    });
15876    let format = editor.update_in(cx, |editor, window, cx| {
15877        editor.perform_format(
15878            project.clone(),
15879            FormatTrigger::Manual,
15880            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
15881            window,
15882            cx,
15883        )
15884    });
15885    format.await.unwrap();
15886    assert_eq!(
15887        editor.update(cx, |editor, cx| editor.text(cx)),
15888        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
15889        "Autoformatting (via test prettier) was not applied to the original buffer text",
15890    );
15891}
15892
15893#[gpui::test]
15894async fn test_addition_reverts(cx: &mut TestAppContext) {
15895    init_test(cx, |_| {});
15896    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15897    let base_text = indoc! {r#"
15898        struct Row;
15899        struct Row1;
15900        struct Row2;
15901
15902        struct Row4;
15903        struct Row5;
15904        struct Row6;
15905
15906        struct Row8;
15907        struct Row9;
15908        struct Row10;"#};
15909
15910    // When addition hunks are not adjacent to carets, no hunk revert is performed
15911    assert_hunk_revert(
15912        indoc! {r#"struct Row;
15913                   struct Row1;
15914                   struct Row1.1;
15915                   struct Row1.2;
15916                   struct Row2;ˇ
15917
15918                   struct Row4;
15919                   struct Row5;
15920                   struct Row6;
15921
15922                   struct Row8;
15923                   ˇstruct Row9;
15924                   struct Row9.1;
15925                   struct Row9.2;
15926                   struct Row9.3;
15927                   struct Row10;"#},
15928        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
15929        indoc! {r#"struct Row;
15930                   struct Row1;
15931                   struct Row1.1;
15932                   struct Row1.2;
15933                   struct Row2;ˇ
15934
15935                   struct Row4;
15936                   struct Row5;
15937                   struct Row6;
15938
15939                   struct Row8;
15940                   ˇstruct Row9;
15941                   struct Row9.1;
15942                   struct Row9.2;
15943                   struct Row9.3;
15944                   struct Row10;"#},
15945        base_text,
15946        &mut cx,
15947    );
15948    // Same for selections
15949    assert_hunk_revert(
15950        indoc! {r#"struct Row;
15951                   struct Row1;
15952                   struct Row2;
15953                   struct Row2.1;
15954                   struct Row2.2;
15955                   «ˇ
15956                   struct Row4;
15957                   struct» Row5;
15958                   «struct Row6;
15959                   ˇ»
15960                   struct Row9.1;
15961                   struct Row9.2;
15962                   struct Row9.3;
15963                   struct Row8;
15964                   struct Row9;
15965                   struct Row10;"#},
15966        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
15967        indoc! {r#"struct Row;
15968                   struct Row1;
15969                   struct Row2;
15970                   struct Row2.1;
15971                   struct Row2.2;
15972                   «ˇ
15973                   struct Row4;
15974                   struct» Row5;
15975                   «struct Row6;
15976                   ˇ»
15977                   struct Row9.1;
15978                   struct Row9.2;
15979                   struct Row9.3;
15980                   struct Row8;
15981                   struct Row9;
15982                   struct Row10;"#},
15983        base_text,
15984        &mut cx,
15985    );
15986
15987    // When carets and selections intersect the addition hunks, those are reverted.
15988    // Adjacent carets got merged.
15989    assert_hunk_revert(
15990        indoc! {r#"struct Row;
15991                   ˇ// something on the top
15992                   struct Row1;
15993                   struct Row2;
15994                   struct Roˇw3.1;
15995                   struct Row2.2;
15996                   struct Row2.3;ˇ
15997
15998                   struct Row4;
15999                   struct ˇRow5.1;
16000                   struct Row5.2;
16001                   struct «Rowˇ»5.3;
16002                   struct Row5;
16003                   struct Row6;
16004                   ˇ
16005                   struct Row9.1;
16006                   struct «Rowˇ»9.2;
16007                   struct «ˇRow»9.3;
16008                   struct Row8;
16009                   struct Row9;
16010                   «ˇ// something on bottom»
16011                   struct Row10;"#},
16012        vec![
16013            DiffHunkStatusKind::Added,
16014            DiffHunkStatusKind::Added,
16015            DiffHunkStatusKind::Added,
16016            DiffHunkStatusKind::Added,
16017            DiffHunkStatusKind::Added,
16018        ],
16019        indoc! {r#"struct Row;
16020                   ˇstruct Row1;
16021                   struct Row2;
16022                   ˇ
16023                   struct Row4;
16024                   ˇstruct Row5;
16025                   struct Row6;
16026                   ˇ
16027                   ˇstruct Row8;
16028                   struct Row9;
16029                   ˇstruct Row10;"#},
16030        base_text,
16031        &mut cx,
16032    );
16033}
16034
16035#[gpui::test]
16036async fn test_modification_reverts(cx: &mut TestAppContext) {
16037    init_test(cx, |_| {});
16038    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16039    let base_text = indoc! {r#"
16040        struct Row;
16041        struct Row1;
16042        struct Row2;
16043
16044        struct Row4;
16045        struct Row5;
16046        struct Row6;
16047
16048        struct Row8;
16049        struct Row9;
16050        struct Row10;"#};
16051
16052    // Modification hunks behave the same as the addition ones.
16053    assert_hunk_revert(
16054        indoc! {r#"struct Row;
16055                   struct Row1;
16056                   struct Row33;
16057                   ˇ
16058                   struct Row4;
16059                   struct Row5;
16060                   struct Row6;
16061                   ˇ
16062                   struct Row99;
16063                   struct Row9;
16064                   struct Row10;"#},
16065        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
16066        indoc! {r#"struct Row;
16067                   struct Row1;
16068                   struct Row33;
16069                   ˇ
16070                   struct Row4;
16071                   struct Row5;
16072                   struct Row6;
16073                   ˇ
16074                   struct Row99;
16075                   struct Row9;
16076                   struct Row10;"#},
16077        base_text,
16078        &mut cx,
16079    );
16080    assert_hunk_revert(
16081        indoc! {r#"struct Row;
16082                   struct Row1;
16083                   struct Row33;
16084                   «ˇ
16085                   struct Row4;
16086                   struct» Row5;
16087                   «struct Row6;
16088                   ˇ»
16089                   struct Row99;
16090                   struct Row9;
16091                   struct Row10;"#},
16092        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
16093        indoc! {r#"struct Row;
16094                   struct Row1;
16095                   struct Row33;
16096                   «ˇ
16097                   struct Row4;
16098                   struct» Row5;
16099                   «struct Row6;
16100                   ˇ»
16101                   struct Row99;
16102                   struct Row9;
16103                   struct Row10;"#},
16104        base_text,
16105        &mut cx,
16106    );
16107
16108    assert_hunk_revert(
16109        indoc! {r#"ˇstruct Row1.1;
16110                   struct Row1;
16111                   «ˇstr»uct Row22;
16112
16113                   struct ˇRow44;
16114                   struct Row5;
16115                   struct «Rˇ»ow66;ˇ
16116
16117                   «struˇ»ct Row88;
16118                   struct Row9;
16119                   struct Row1011;ˇ"#},
16120        vec![
16121            DiffHunkStatusKind::Modified,
16122            DiffHunkStatusKind::Modified,
16123            DiffHunkStatusKind::Modified,
16124            DiffHunkStatusKind::Modified,
16125            DiffHunkStatusKind::Modified,
16126            DiffHunkStatusKind::Modified,
16127        ],
16128        indoc! {r#"struct Row;
16129                   ˇstruct Row1;
16130                   struct Row2;
16131                   ˇ
16132                   struct Row4;
16133                   ˇstruct Row5;
16134                   struct Row6;
16135                   ˇ
16136                   struct Row8;
16137                   ˇstruct Row9;
16138                   struct Row10;ˇ"#},
16139        base_text,
16140        &mut cx,
16141    );
16142}
16143
16144#[gpui::test]
16145async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
16146    init_test(cx, |_| {});
16147    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16148    let base_text = indoc! {r#"
16149        one
16150
16151        two
16152        three
16153        "#};
16154
16155    cx.set_head_text(base_text);
16156    cx.set_state("\nˇ\n");
16157    cx.executor().run_until_parked();
16158    cx.update_editor(|editor, _window, cx| {
16159        editor.expand_selected_diff_hunks(cx);
16160    });
16161    cx.executor().run_until_parked();
16162    cx.update_editor(|editor, window, cx| {
16163        editor.backspace(&Default::default(), window, cx);
16164    });
16165    cx.run_until_parked();
16166    cx.assert_state_with_diff(
16167        indoc! {r#"
16168
16169        - two
16170        - threeˇ
16171        +
16172        "#}
16173        .to_string(),
16174    );
16175}
16176
16177#[gpui::test]
16178async fn test_deletion_reverts(cx: &mut TestAppContext) {
16179    init_test(cx, |_| {});
16180    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16181    let base_text = indoc! {r#"struct Row;
16182struct Row1;
16183struct Row2;
16184
16185struct Row4;
16186struct Row5;
16187struct Row6;
16188
16189struct Row8;
16190struct Row9;
16191struct Row10;"#};
16192
16193    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
16194    assert_hunk_revert(
16195        indoc! {r#"struct Row;
16196                   struct Row2;
16197
16198                   ˇstruct Row4;
16199                   struct Row5;
16200                   struct Row6;
16201                   ˇ
16202                   struct Row8;
16203                   struct Row10;"#},
16204        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16205        indoc! {r#"struct Row;
16206                   struct Row2;
16207
16208                   ˇstruct Row4;
16209                   struct Row5;
16210                   struct Row6;
16211                   ˇ
16212                   struct Row8;
16213                   struct Row10;"#},
16214        base_text,
16215        &mut cx,
16216    );
16217    assert_hunk_revert(
16218        indoc! {r#"struct Row;
16219                   struct Row2;
16220
16221                   «ˇstruct Row4;
16222                   struct» Row5;
16223                   «struct Row6;
16224                   ˇ»
16225                   struct Row8;
16226                   struct Row10;"#},
16227        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16228        indoc! {r#"struct Row;
16229                   struct Row2;
16230
16231                   «ˇstruct Row4;
16232                   struct» Row5;
16233                   «struct Row6;
16234                   ˇ»
16235                   struct Row8;
16236                   struct Row10;"#},
16237        base_text,
16238        &mut cx,
16239    );
16240
16241    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
16242    assert_hunk_revert(
16243        indoc! {r#"struct Row;
16244                   ˇstruct Row2;
16245
16246                   struct Row4;
16247                   struct Row5;
16248                   struct Row6;
16249
16250                   struct Row8;ˇ
16251                   struct Row10;"#},
16252        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16253        indoc! {r#"struct Row;
16254                   struct Row1;
16255                   ˇstruct Row2;
16256
16257                   struct Row4;
16258                   struct Row5;
16259                   struct Row6;
16260
16261                   struct Row8;ˇ
16262                   struct Row9;
16263                   struct Row10;"#},
16264        base_text,
16265        &mut cx,
16266    );
16267    assert_hunk_revert(
16268        indoc! {r#"struct Row;
16269                   struct Row2«ˇ;
16270                   struct Row4;
16271                   struct» Row5;
16272                   «struct Row6;
16273
16274                   struct Row8;ˇ»
16275                   struct Row10;"#},
16276        vec![
16277            DiffHunkStatusKind::Deleted,
16278            DiffHunkStatusKind::Deleted,
16279            DiffHunkStatusKind::Deleted,
16280        ],
16281        indoc! {r#"struct Row;
16282                   struct Row1;
16283                   struct Row2«ˇ;
16284
16285                   struct Row4;
16286                   struct» Row5;
16287                   «struct Row6;
16288
16289                   struct Row8;ˇ»
16290                   struct Row9;
16291                   struct Row10;"#},
16292        base_text,
16293        &mut cx,
16294    );
16295}
16296
16297#[gpui::test]
16298async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
16299    init_test(cx, |_| {});
16300
16301    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
16302    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
16303    let base_text_3 =
16304        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
16305
16306    let text_1 = edit_first_char_of_every_line(base_text_1);
16307    let text_2 = edit_first_char_of_every_line(base_text_2);
16308    let text_3 = edit_first_char_of_every_line(base_text_3);
16309
16310    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
16311    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
16312    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
16313
16314    let multibuffer = cx.new(|cx| {
16315        let mut multibuffer = MultiBuffer::new(ReadWrite);
16316        multibuffer.push_excerpts(
16317            buffer_1.clone(),
16318            [
16319                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16320                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16321                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16322            ],
16323            cx,
16324        );
16325        multibuffer.push_excerpts(
16326            buffer_2.clone(),
16327            [
16328                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16329                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16330                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16331            ],
16332            cx,
16333        );
16334        multibuffer.push_excerpts(
16335            buffer_3.clone(),
16336            [
16337                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16338                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16339                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16340            ],
16341            cx,
16342        );
16343        multibuffer
16344    });
16345
16346    let fs = FakeFs::new(cx.executor());
16347    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
16348    let (editor, cx) = cx
16349        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
16350    editor.update_in(cx, |editor, _window, cx| {
16351        for (buffer, diff_base) in [
16352            (buffer_1.clone(), base_text_1),
16353            (buffer_2.clone(), base_text_2),
16354            (buffer_3.clone(), base_text_3),
16355        ] {
16356            let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
16357            editor
16358                .buffer
16359                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
16360        }
16361    });
16362    cx.executor().run_until_parked();
16363
16364    editor.update_in(cx, |editor, window, cx| {
16365        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}");
16366        editor.select_all(&SelectAll, window, cx);
16367        editor.git_restore(&Default::default(), window, cx);
16368    });
16369    cx.executor().run_until_parked();
16370
16371    // When all ranges are selected, all buffer hunks are reverted.
16372    editor.update(cx, |editor, cx| {
16373        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");
16374    });
16375    buffer_1.update(cx, |buffer, _| {
16376        assert_eq!(buffer.text(), base_text_1);
16377    });
16378    buffer_2.update(cx, |buffer, _| {
16379        assert_eq!(buffer.text(), base_text_2);
16380    });
16381    buffer_3.update(cx, |buffer, _| {
16382        assert_eq!(buffer.text(), base_text_3);
16383    });
16384
16385    editor.update_in(cx, |editor, window, cx| {
16386        editor.undo(&Default::default(), window, cx);
16387    });
16388
16389    editor.update_in(cx, |editor, window, cx| {
16390        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16391            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
16392        });
16393        editor.git_restore(&Default::default(), window, cx);
16394    });
16395
16396    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
16397    // but not affect buffer_2 and its related excerpts.
16398    editor.update(cx, |editor, cx| {
16399        assert_eq!(
16400            editor.text(cx),
16401            "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}"
16402        );
16403    });
16404    buffer_1.update(cx, |buffer, _| {
16405        assert_eq!(buffer.text(), base_text_1);
16406    });
16407    buffer_2.update(cx, |buffer, _| {
16408        assert_eq!(
16409            buffer.text(),
16410            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
16411        );
16412    });
16413    buffer_3.update(cx, |buffer, _| {
16414        assert_eq!(
16415            buffer.text(),
16416            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
16417        );
16418    });
16419
16420    fn edit_first_char_of_every_line(text: &str) -> String {
16421        text.split('\n')
16422            .map(|line| format!("X{}", &line[1..]))
16423            .collect::<Vec<_>>()
16424            .join("\n")
16425    }
16426}
16427
16428#[gpui::test]
16429async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
16430    init_test(cx, |_| {});
16431
16432    let cols = 4;
16433    let rows = 10;
16434    let sample_text_1 = sample_text(rows, cols, 'a');
16435    assert_eq!(
16436        sample_text_1,
16437        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
16438    );
16439    let sample_text_2 = sample_text(rows, cols, 'l');
16440    assert_eq!(
16441        sample_text_2,
16442        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
16443    );
16444    let sample_text_3 = sample_text(rows, cols, 'v');
16445    assert_eq!(
16446        sample_text_3,
16447        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
16448    );
16449
16450    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
16451    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
16452    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
16453
16454    let multi_buffer = cx.new(|cx| {
16455        let mut multibuffer = MultiBuffer::new(ReadWrite);
16456        multibuffer.push_excerpts(
16457            buffer_1.clone(),
16458            [
16459                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16460                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16461                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16462            ],
16463            cx,
16464        );
16465        multibuffer.push_excerpts(
16466            buffer_2.clone(),
16467            [
16468                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16469                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16470                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16471            ],
16472            cx,
16473        );
16474        multibuffer.push_excerpts(
16475            buffer_3.clone(),
16476            [
16477                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16478                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16479                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16480            ],
16481            cx,
16482        );
16483        multibuffer
16484    });
16485
16486    let fs = FakeFs::new(cx.executor());
16487    fs.insert_tree(
16488        "/a",
16489        json!({
16490            "main.rs": sample_text_1,
16491            "other.rs": sample_text_2,
16492            "lib.rs": sample_text_3,
16493        }),
16494    )
16495    .await;
16496    let project = Project::test(fs, ["/a".as_ref()], cx).await;
16497    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16498    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16499    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16500        Editor::new(
16501            EditorMode::full(),
16502            multi_buffer,
16503            Some(project.clone()),
16504            window,
16505            cx,
16506        )
16507    });
16508    let multibuffer_item_id = workspace
16509        .update(cx, |workspace, window, cx| {
16510            assert!(
16511                workspace.active_item(cx).is_none(),
16512                "active item should be None before the first item is added"
16513            );
16514            workspace.add_item_to_active_pane(
16515                Box::new(multi_buffer_editor.clone()),
16516                None,
16517                true,
16518                window,
16519                cx,
16520            );
16521            let active_item = workspace
16522                .active_item(cx)
16523                .expect("should have an active item after adding the multi buffer");
16524            assert!(
16525                !active_item.is_singleton(cx),
16526                "A multi buffer was expected to active after adding"
16527            );
16528            active_item.item_id()
16529        })
16530        .unwrap();
16531    cx.executor().run_until_parked();
16532
16533    multi_buffer_editor.update_in(cx, |editor, window, cx| {
16534        editor.change_selections(
16535            SelectionEffects::scroll(Autoscroll::Next),
16536            window,
16537            cx,
16538            |s| s.select_ranges(Some(1..2)),
16539        );
16540        editor.open_excerpts(&OpenExcerpts, window, cx);
16541    });
16542    cx.executor().run_until_parked();
16543    let first_item_id = workspace
16544        .update(cx, |workspace, window, cx| {
16545            let active_item = workspace
16546                .active_item(cx)
16547                .expect("should have an active item after navigating into the 1st buffer");
16548            let first_item_id = active_item.item_id();
16549            assert_ne!(
16550                first_item_id, multibuffer_item_id,
16551                "Should navigate into the 1st buffer and activate it"
16552            );
16553            assert!(
16554                active_item.is_singleton(cx),
16555                "New active item should be a singleton buffer"
16556            );
16557            assert_eq!(
16558                active_item
16559                    .act_as::<Editor>(cx)
16560                    .expect("should have navigated into an editor for the 1st buffer")
16561                    .read(cx)
16562                    .text(cx),
16563                sample_text_1
16564            );
16565
16566            workspace
16567                .go_back(workspace.active_pane().downgrade(), window, cx)
16568                .detach_and_log_err(cx);
16569
16570            first_item_id
16571        })
16572        .unwrap();
16573    cx.executor().run_until_parked();
16574    workspace
16575        .update(cx, |workspace, _, cx| {
16576            let active_item = workspace
16577                .active_item(cx)
16578                .expect("should have an active item after navigating back");
16579            assert_eq!(
16580                active_item.item_id(),
16581                multibuffer_item_id,
16582                "Should navigate back to the multi buffer"
16583            );
16584            assert!(!active_item.is_singleton(cx));
16585        })
16586        .unwrap();
16587
16588    multi_buffer_editor.update_in(cx, |editor, window, cx| {
16589        editor.change_selections(
16590            SelectionEffects::scroll(Autoscroll::Next),
16591            window,
16592            cx,
16593            |s| s.select_ranges(Some(39..40)),
16594        );
16595        editor.open_excerpts(&OpenExcerpts, window, cx);
16596    });
16597    cx.executor().run_until_parked();
16598    let second_item_id = workspace
16599        .update(cx, |workspace, window, cx| {
16600            let active_item = workspace
16601                .active_item(cx)
16602                .expect("should have an active item after navigating into the 2nd buffer");
16603            let second_item_id = active_item.item_id();
16604            assert_ne!(
16605                second_item_id, multibuffer_item_id,
16606                "Should navigate away from the multibuffer"
16607            );
16608            assert_ne!(
16609                second_item_id, first_item_id,
16610                "Should navigate into the 2nd buffer and activate it"
16611            );
16612            assert!(
16613                active_item.is_singleton(cx),
16614                "New active item should be a singleton buffer"
16615            );
16616            assert_eq!(
16617                active_item
16618                    .act_as::<Editor>(cx)
16619                    .expect("should have navigated into an editor")
16620                    .read(cx)
16621                    .text(cx),
16622                sample_text_2
16623            );
16624
16625            workspace
16626                .go_back(workspace.active_pane().downgrade(), window, cx)
16627                .detach_and_log_err(cx);
16628
16629            second_item_id
16630        })
16631        .unwrap();
16632    cx.executor().run_until_parked();
16633    workspace
16634        .update(cx, |workspace, _, cx| {
16635            let active_item = workspace
16636                .active_item(cx)
16637                .expect("should have an active item after navigating back from the 2nd buffer");
16638            assert_eq!(
16639                active_item.item_id(),
16640                multibuffer_item_id,
16641                "Should navigate back from the 2nd buffer to the multi buffer"
16642            );
16643            assert!(!active_item.is_singleton(cx));
16644        })
16645        .unwrap();
16646
16647    multi_buffer_editor.update_in(cx, |editor, window, cx| {
16648        editor.change_selections(
16649            SelectionEffects::scroll(Autoscroll::Next),
16650            window,
16651            cx,
16652            |s| s.select_ranges(Some(70..70)),
16653        );
16654        editor.open_excerpts(&OpenExcerpts, window, cx);
16655    });
16656    cx.executor().run_until_parked();
16657    workspace
16658        .update(cx, |workspace, window, cx| {
16659            let active_item = workspace
16660                .active_item(cx)
16661                .expect("should have an active item after navigating into the 3rd buffer");
16662            let third_item_id = active_item.item_id();
16663            assert_ne!(
16664                third_item_id, multibuffer_item_id,
16665                "Should navigate into the 3rd buffer and activate it"
16666            );
16667            assert_ne!(third_item_id, first_item_id);
16668            assert_ne!(third_item_id, second_item_id);
16669            assert!(
16670                active_item.is_singleton(cx),
16671                "New active item should be a singleton buffer"
16672            );
16673            assert_eq!(
16674                active_item
16675                    .act_as::<Editor>(cx)
16676                    .expect("should have navigated into an editor")
16677                    .read(cx)
16678                    .text(cx),
16679                sample_text_3
16680            );
16681
16682            workspace
16683                .go_back(workspace.active_pane().downgrade(), window, cx)
16684                .detach_and_log_err(cx);
16685        })
16686        .unwrap();
16687    cx.executor().run_until_parked();
16688    workspace
16689        .update(cx, |workspace, _, cx| {
16690            let active_item = workspace
16691                .active_item(cx)
16692                .expect("should have an active item after navigating back from the 3rd buffer");
16693            assert_eq!(
16694                active_item.item_id(),
16695                multibuffer_item_id,
16696                "Should navigate back from the 3rd buffer to the multi buffer"
16697            );
16698            assert!(!active_item.is_singleton(cx));
16699        })
16700        .unwrap();
16701}
16702
16703#[gpui::test]
16704async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16705    init_test(cx, |_| {});
16706
16707    let mut cx = EditorTestContext::new(cx).await;
16708
16709    let diff_base = r#"
16710        use some::mod;
16711
16712        const A: u32 = 42;
16713
16714        fn main() {
16715            println!("hello");
16716
16717            println!("world");
16718        }
16719        "#
16720    .unindent();
16721
16722    cx.set_state(
16723        &r#"
16724        use some::modified;
16725
16726        ˇ
16727        fn main() {
16728            println!("hello there");
16729
16730            println!("around the");
16731            println!("world");
16732        }
16733        "#
16734        .unindent(),
16735    );
16736
16737    cx.set_head_text(&diff_base);
16738    executor.run_until_parked();
16739
16740    cx.update_editor(|editor, window, cx| {
16741        editor.go_to_next_hunk(&GoToHunk, window, cx);
16742        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
16743    });
16744    executor.run_until_parked();
16745    cx.assert_state_with_diff(
16746        r#"
16747          use some::modified;
16748
16749
16750          fn main() {
16751        -     println!("hello");
16752        + ˇ    println!("hello there");
16753
16754              println!("around the");
16755              println!("world");
16756          }
16757        "#
16758        .unindent(),
16759    );
16760
16761    cx.update_editor(|editor, window, cx| {
16762        for _ in 0..2 {
16763            editor.go_to_next_hunk(&GoToHunk, window, cx);
16764            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
16765        }
16766    });
16767    executor.run_until_parked();
16768    cx.assert_state_with_diff(
16769        r#"
16770        - use some::mod;
16771        + ˇuse some::modified;
16772
16773
16774          fn main() {
16775        -     println!("hello");
16776        +     println!("hello there");
16777
16778        +     println!("around the");
16779              println!("world");
16780          }
16781        "#
16782        .unindent(),
16783    );
16784
16785    cx.update_editor(|editor, window, cx| {
16786        editor.go_to_next_hunk(&GoToHunk, window, cx);
16787        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
16788    });
16789    executor.run_until_parked();
16790    cx.assert_state_with_diff(
16791        r#"
16792        - use some::mod;
16793        + use some::modified;
16794
16795        - const A: u32 = 42;
16796          ˇ
16797          fn main() {
16798        -     println!("hello");
16799        +     println!("hello there");
16800
16801        +     println!("around the");
16802              println!("world");
16803          }
16804        "#
16805        .unindent(),
16806    );
16807
16808    cx.update_editor(|editor, window, cx| {
16809        editor.cancel(&Cancel, window, cx);
16810    });
16811
16812    cx.assert_state_with_diff(
16813        r#"
16814          use some::modified;
16815
16816          ˇ
16817          fn main() {
16818              println!("hello there");
16819
16820              println!("around the");
16821              println!("world");
16822          }
16823        "#
16824        .unindent(),
16825    );
16826}
16827
16828#[gpui::test]
16829async fn test_diff_base_change_with_expanded_diff_hunks(
16830    executor: BackgroundExecutor,
16831    cx: &mut TestAppContext,
16832) {
16833    init_test(cx, |_| {});
16834
16835    let mut cx = EditorTestContext::new(cx).await;
16836
16837    let diff_base = r#"
16838        use some::mod1;
16839        use some::mod2;
16840
16841        const A: u32 = 42;
16842        const B: u32 = 42;
16843        const C: u32 = 42;
16844
16845        fn main() {
16846            println!("hello");
16847
16848            println!("world");
16849        }
16850        "#
16851    .unindent();
16852
16853    cx.set_state(
16854        &r#"
16855        use some::mod2;
16856
16857        const A: u32 = 42;
16858        const C: u32 = 42;
16859
16860        fn main(ˇ) {
16861            //println!("hello");
16862
16863            println!("world");
16864            //
16865            //
16866        }
16867        "#
16868        .unindent(),
16869    );
16870
16871    cx.set_head_text(&diff_base);
16872    executor.run_until_parked();
16873
16874    cx.update_editor(|editor, window, cx| {
16875        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
16876    });
16877    executor.run_until_parked();
16878    cx.assert_state_with_diff(
16879        r#"
16880        - use some::mod1;
16881          use some::mod2;
16882
16883          const A: u32 = 42;
16884        - const B: u32 = 42;
16885          const C: u32 = 42;
16886
16887          fn main(ˇ) {
16888        -     println!("hello");
16889        +     //println!("hello");
16890
16891              println!("world");
16892        +     //
16893        +     //
16894          }
16895        "#
16896        .unindent(),
16897    );
16898
16899    cx.set_head_text("new diff base!");
16900    executor.run_until_parked();
16901    cx.assert_state_with_diff(
16902        r#"
16903        - new diff base!
16904        + use some::mod2;
16905        +
16906        + const A: u32 = 42;
16907        + const C: u32 = 42;
16908        +
16909        + fn main(ˇ) {
16910        +     //println!("hello");
16911        +
16912        +     println!("world");
16913        +     //
16914        +     //
16915        + }
16916        "#
16917        .unindent(),
16918    );
16919}
16920
16921#[gpui::test]
16922async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
16923    init_test(cx, |_| {});
16924
16925    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
16926    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
16927    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
16928    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
16929    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
16930    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
16931
16932    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
16933    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
16934    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
16935
16936    let multi_buffer = cx.new(|cx| {
16937        let mut multibuffer = MultiBuffer::new(ReadWrite);
16938        multibuffer.push_excerpts(
16939            buffer_1.clone(),
16940            [
16941                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16942                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16943                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
16944            ],
16945            cx,
16946        );
16947        multibuffer.push_excerpts(
16948            buffer_2.clone(),
16949            [
16950                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16951                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16952                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
16953            ],
16954            cx,
16955        );
16956        multibuffer.push_excerpts(
16957            buffer_3.clone(),
16958            [
16959                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16960                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16961                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
16962            ],
16963            cx,
16964        );
16965        multibuffer
16966    });
16967
16968    let editor =
16969        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
16970    editor
16971        .update(cx, |editor, _window, cx| {
16972            for (buffer, diff_base) in [
16973                (buffer_1.clone(), file_1_old),
16974                (buffer_2.clone(), file_2_old),
16975                (buffer_3.clone(), file_3_old),
16976            ] {
16977                let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
16978                editor
16979                    .buffer
16980                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
16981            }
16982        })
16983        .unwrap();
16984
16985    let mut cx = EditorTestContext::for_editor(editor, cx).await;
16986    cx.run_until_parked();
16987
16988    cx.assert_editor_state(
16989        &"
16990            ˇaaa
16991            ccc
16992            ddd
16993
16994            ggg
16995            hhh
16996
16997
16998            lll
16999            mmm
17000            NNN
17001
17002            qqq
17003            rrr
17004
17005            uuu
17006            111
17007            222
17008            333
17009
17010            666
17011            777
17012
17013            000
17014            !!!"
17015        .unindent(),
17016    );
17017
17018    cx.update_editor(|editor, window, cx| {
17019        editor.select_all(&SelectAll, window, cx);
17020        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17021    });
17022    cx.executor().run_until_parked();
17023
17024    cx.assert_state_with_diff(
17025        "
17026            «aaa
17027          - bbb
17028            ccc
17029            ddd
17030
17031            ggg
17032            hhh
17033
17034
17035            lll
17036            mmm
17037          - nnn
17038          + NNN
17039
17040            qqq
17041            rrr
17042
17043            uuu
17044            111
17045            222
17046            333
17047
17048          + 666
17049            777
17050
17051            000
17052            !!!ˇ»"
17053            .unindent(),
17054    );
17055}
17056
17057#[gpui::test]
17058async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
17059    init_test(cx, |_| {});
17060
17061    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
17062    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
17063
17064    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
17065    let multi_buffer = cx.new(|cx| {
17066        let mut multibuffer = MultiBuffer::new(ReadWrite);
17067        multibuffer.push_excerpts(
17068            buffer.clone(),
17069            [
17070                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
17071                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
17072                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
17073            ],
17074            cx,
17075        );
17076        multibuffer
17077    });
17078
17079    let editor =
17080        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
17081    editor
17082        .update(cx, |editor, _window, cx| {
17083            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
17084            editor
17085                .buffer
17086                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
17087        })
17088        .unwrap();
17089
17090    let mut cx = EditorTestContext::for_editor(editor, cx).await;
17091    cx.run_until_parked();
17092
17093    cx.update_editor(|editor, window, cx| {
17094        editor.expand_all_diff_hunks(&Default::default(), window, cx)
17095    });
17096    cx.executor().run_until_parked();
17097
17098    // When the start of a hunk coincides with the start of its excerpt,
17099    // the hunk is expanded. When the start of a a hunk is earlier than
17100    // the start of its excerpt, the hunk is not expanded.
17101    cx.assert_state_with_diff(
17102        "
17103            ˇaaa
17104          - bbb
17105          + BBB
17106
17107          - ddd
17108          - eee
17109          + DDD
17110          + EEE
17111            fff
17112
17113            iii
17114        "
17115        .unindent(),
17116    );
17117}
17118
17119#[gpui::test]
17120async fn test_edits_around_expanded_insertion_hunks(
17121    executor: BackgroundExecutor,
17122    cx: &mut TestAppContext,
17123) {
17124    init_test(cx, |_| {});
17125
17126    let mut cx = EditorTestContext::new(cx).await;
17127
17128    let diff_base = r#"
17129        use some::mod1;
17130        use some::mod2;
17131
17132        const A: u32 = 42;
17133
17134        fn main() {
17135            println!("hello");
17136
17137            println!("world");
17138        }
17139        "#
17140    .unindent();
17141    executor.run_until_parked();
17142    cx.set_state(
17143        &r#"
17144        use some::mod1;
17145        use some::mod2;
17146
17147        const A: u32 = 42;
17148        const B: u32 = 42;
17149        const C: u32 = 42;
17150        ˇ
17151
17152        fn main() {
17153            println!("hello");
17154
17155            println!("world");
17156        }
17157        "#
17158        .unindent(),
17159    );
17160
17161    cx.set_head_text(&diff_base);
17162    executor.run_until_parked();
17163
17164    cx.update_editor(|editor, window, cx| {
17165        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17166    });
17167    executor.run_until_parked();
17168
17169    cx.assert_state_with_diff(
17170        r#"
17171        use some::mod1;
17172        use some::mod2;
17173
17174        const A: u32 = 42;
17175      + const B: u32 = 42;
17176      + const C: u32 = 42;
17177      + ˇ
17178
17179        fn main() {
17180            println!("hello");
17181
17182            println!("world");
17183        }
17184      "#
17185        .unindent(),
17186    );
17187
17188    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
17189    executor.run_until_parked();
17190
17191    cx.assert_state_with_diff(
17192        r#"
17193        use some::mod1;
17194        use some::mod2;
17195
17196        const A: u32 = 42;
17197      + const B: u32 = 42;
17198      + const C: u32 = 42;
17199      + const D: u32 = 42;
17200      + ˇ
17201
17202        fn main() {
17203            println!("hello");
17204
17205            println!("world");
17206        }
17207      "#
17208        .unindent(),
17209    );
17210
17211    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
17212    executor.run_until_parked();
17213
17214    cx.assert_state_with_diff(
17215        r#"
17216        use some::mod1;
17217        use some::mod2;
17218
17219        const A: u32 = 42;
17220      + const B: u32 = 42;
17221      + const C: u32 = 42;
17222      + const D: u32 = 42;
17223      + const E: u32 = 42;
17224      + ˇ
17225
17226        fn main() {
17227            println!("hello");
17228
17229            println!("world");
17230        }
17231      "#
17232        .unindent(),
17233    );
17234
17235    cx.update_editor(|editor, window, cx| {
17236        editor.delete_line(&DeleteLine, window, cx);
17237    });
17238    executor.run_until_parked();
17239
17240    cx.assert_state_with_diff(
17241        r#"
17242        use some::mod1;
17243        use some::mod2;
17244
17245        const A: u32 = 42;
17246      + const B: u32 = 42;
17247      + const C: u32 = 42;
17248      + const D: u32 = 42;
17249      + const E: u32 = 42;
17250        ˇ
17251        fn main() {
17252            println!("hello");
17253
17254            println!("world");
17255        }
17256      "#
17257        .unindent(),
17258    );
17259
17260    cx.update_editor(|editor, window, cx| {
17261        editor.move_up(&MoveUp, window, cx);
17262        editor.delete_line(&DeleteLine, window, cx);
17263        editor.move_up(&MoveUp, window, cx);
17264        editor.delete_line(&DeleteLine, window, cx);
17265        editor.move_up(&MoveUp, window, cx);
17266        editor.delete_line(&DeleteLine, window, cx);
17267    });
17268    executor.run_until_parked();
17269    cx.assert_state_with_diff(
17270        r#"
17271        use some::mod1;
17272        use some::mod2;
17273
17274        const A: u32 = 42;
17275      + const B: u32 = 42;
17276        ˇ
17277        fn main() {
17278            println!("hello");
17279
17280            println!("world");
17281        }
17282      "#
17283        .unindent(),
17284    );
17285
17286    cx.update_editor(|editor, window, cx| {
17287        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
17288        editor.delete_line(&DeleteLine, window, cx);
17289    });
17290    executor.run_until_parked();
17291    cx.assert_state_with_diff(
17292        r#"
17293        ˇ
17294        fn main() {
17295            println!("hello");
17296
17297            println!("world");
17298        }
17299      "#
17300        .unindent(),
17301    );
17302}
17303
17304#[gpui::test]
17305async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
17306    init_test(cx, |_| {});
17307
17308    let mut cx = EditorTestContext::new(cx).await;
17309    cx.set_head_text(indoc! { "
17310        one
17311        two
17312        three
17313        four
17314        five
17315        "
17316    });
17317    cx.set_state(indoc! { "
17318        one
17319        ˇthree
17320        five
17321    "});
17322    cx.run_until_parked();
17323    cx.update_editor(|editor, window, cx| {
17324        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17325    });
17326    cx.assert_state_with_diff(
17327        indoc! { "
17328        one
17329      - two
17330        ˇthree
17331      - four
17332        five
17333    "}
17334        .to_string(),
17335    );
17336    cx.update_editor(|editor, window, cx| {
17337        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17338    });
17339
17340    cx.assert_state_with_diff(
17341        indoc! { "
17342        one
17343        ˇthree
17344        five
17345    "}
17346        .to_string(),
17347    );
17348
17349    cx.set_state(indoc! { "
17350        one
17351        ˇTWO
17352        three
17353        four
17354        five
17355    "});
17356    cx.run_until_parked();
17357    cx.update_editor(|editor, window, cx| {
17358        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17359    });
17360
17361    cx.assert_state_with_diff(
17362        indoc! { "
17363            one
17364          - two
17365          + ˇTWO
17366            three
17367            four
17368            five
17369        "}
17370        .to_string(),
17371    );
17372    cx.update_editor(|editor, window, cx| {
17373        editor.move_up(&Default::default(), window, cx);
17374        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17375    });
17376    cx.assert_state_with_diff(
17377        indoc! { "
17378            one
17379            ˇTWO
17380            three
17381            four
17382            five
17383        "}
17384        .to_string(),
17385    );
17386}
17387
17388#[gpui::test]
17389async fn test_edits_around_expanded_deletion_hunks(
17390    executor: BackgroundExecutor,
17391    cx: &mut TestAppContext,
17392) {
17393    init_test(cx, |_| {});
17394
17395    let mut cx = EditorTestContext::new(cx).await;
17396
17397    let diff_base = r#"
17398        use some::mod1;
17399        use some::mod2;
17400
17401        const A: u32 = 42;
17402        const B: u32 = 42;
17403        const C: u32 = 42;
17404
17405
17406        fn main() {
17407            println!("hello");
17408
17409            println!("world");
17410        }
17411    "#
17412    .unindent();
17413    executor.run_until_parked();
17414    cx.set_state(
17415        &r#"
17416        use some::mod1;
17417        use some::mod2;
17418
17419        ˇconst B: u32 = 42;
17420        const C: u32 = 42;
17421
17422
17423        fn main() {
17424            println!("hello");
17425
17426            println!("world");
17427        }
17428        "#
17429        .unindent(),
17430    );
17431
17432    cx.set_head_text(&diff_base);
17433    executor.run_until_parked();
17434
17435    cx.update_editor(|editor, window, cx| {
17436        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17437    });
17438    executor.run_until_parked();
17439
17440    cx.assert_state_with_diff(
17441        r#"
17442        use some::mod1;
17443        use some::mod2;
17444
17445      - const A: u32 = 42;
17446        ˇconst B: u32 = 42;
17447        const C: u32 = 42;
17448
17449
17450        fn main() {
17451            println!("hello");
17452
17453            println!("world");
17454        }
17455      "#
17456        .unindent(),
17457    );
17458
17459    cx.update_editor(|editor, window, cx| {
17460        editor.delete_line(&DeleteLine, window, cx);
17461    });
17462    executor.run_until_parked();
17463    cx.assert_state_with_diff(
17464        r#"
17465        use some::mod1;
17466        use some::mod2;
17467
17468      - const A: u32 = 42;
17469      - const B: u32 = 42;
17470        ˇconst C: u32 = 42;
17471
17472
17473        fn main() {
17474            println!("hello");
17475
17476            println!("world");
17477        }
17478      "#
17479        .unindent(),
17480    );
17481
17482    cx.update_editor(|editor, window, cx| {
17483        editor.delete_line(&DeleteLine, window, cx);
17484    });
17485    executor.run_until_parked();
17486    cx.assert_state_with_diff(
17487        r#"
17488        use some::mod1;
17489        use some::mod2;
17490
17491      - const A: u32 = 42;
17492      - const B: u32 = 42;
17493      - const C: u32 = 42;
17494        ˇ
17495
17496        fn main() {
17497            println!("hello");
17498
17499            println!("world");
17500        }
17501      "#
17502        .unindent(),
17503    );
17504
17505    cx.update_editor(|editor, window, cx| {
17506        editor.handle_input("replacement", window, cx);
17507    });
17508    executor.run_until_parked();
17509    cx.assert_state_with_diff(
17510        r#"
17511        use some::mod1;
17512        use some::mod2;
17513
17514      - const A: u32 = 42;
17515      - const B: u32 = 42;
17516      - const C: u32 = 42;
17517      -
17518      + replacementˇ
17519
17520        fn main() {
17521            println!("hello");
17522
17523            println!("world");
17524        }
17525      "#
17526        .unindent(),
17527    );
17528}
17529
17530#[gpui::test]
17531async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17532    init_test(cx, |_| {});
17533
17534    let mut cx = EditorTestContext::new(cx).await;
17535
17536    let base_text = r#"
17537        one
17538        two
17539        three
17540        four
17541        five
17542    "#
17543    .unindent();
17544    executor.run_until_parked();
17545    cx.set_state(
17546        &r#"
17547        one
17548        two
17549        fˇour
17550        five
17551        "#
17552        .unindent(),
17553    );
17554
17555    cx.set_head_text(&base_text);
17556    executor.run_until_parked();
17557
17558    cx.update_editor(|editor, window, cx| {
17559        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17560    });
17561    executor.run_until_parked();
17562
17563    cx.assert_state_with_diff(
17564        r#"
17565          one
17566          two
17567        - three
17568          fˇour
17569          five
17570        "#
17571        .unindent(),
17572    );
17573
17574    cx.update_editor(|editor, window, cx| {
17575        editor.backspace(&Backspace, window, cx);
17576        editor.backspace(&Backspace, window, cx);
17577    });
17578    executor.run_until_parked();
17579    cx.assert_state_with_diff(
17580        r#"
17581          one
17582          two
17583        - threeˇ
17584        - four
17585        + our
17586          five
17587        "#
17588        .unindent(),
17589    );
17590}
17591
17592#[gpui::test]
17593async fn test_edit_after_expanded_modification_hunk(
17594    executor: BackgroundExecutor,
17595    cx: &mut TestAppContext,
17596) {
17597    init_test(cx, |_| {});
17598
17599    let mut cx = EditorTestContext::new(cx).await;
17600
17601    let diff_base = r#"
17602        use some::mod1;
17603        use some::mod2;
17604
17605        const A: u32 = 42;
17606        const B: u32 = 42;
17607        const C: u32 = 42;
17608        const D: u32 = 42;
17609
17610
17611        fn main() {
17612            println!("hello");
17613
17614            println!("world");
17615        }"#
17616    .unindent();
17617
17618    cx.set_state(
17619        &r#"
17620        use some::mod1;
17621        use some::mod2;
17622
17623        const A: u32 = 42;
17624        const B: u32 = 42;
17625        const C: u32 = 43ˇ
17626        const D: u32 = 42;
17627
17628
17629        fn main() {
17630            println!("hello");
17631
17632            println!("world");
17633        }"#
17634        .unindent(),
17635    );
17636
17637    cx.set_head_text(&diff_base);
17638    executor.run_until_parked();
17639    cx.update_editor(|editor, window, cx| {
17640        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17641    });
17642    executor.run_until_parked();
17643
17644    cx.assert_state_with_diff(
17645        r#"
17646        use some::mod1;
17647        use some::mod2;
17648
17649        const A: u32 = 42;
17650        const B: u32 = 42;
17651      - const C: u32 = 42;
17652      + const C: u32 = 43ˇ
17653        const D: u32 = 42;
17654
17655
17656        fn main() {
17657            println!("hello");
17658
17659            println!("world");
17660        }"#
17661        .unindent(),
17662    );
17663
17664    cx.update_editor(|editor, window, cx| {
17665        editor.handle_input("\nnew_line\n", window, cx);
17666    });
17667    executor.run_until_parked();
17668
17669    cx.assert_state_with_diff(
17670        r#"
17671        use some::mod1;
17672        use some::mod2;
17673
17674        const A: u32 = 42;
17675        const B: u32 = 42;
17676      - const C: u32 = 42;
17677      + const C: u32 = 43
17678      + new_line
17679      + ˇ
17680        const D: u32 = 42;
17681
17682
17683        fn main() {
17684            println!("hello");
17685
17686            println!("world");
17687        }"#
17688        .unindent(),
17689    );
17690}
17691
17692#[gpui::test]
17693async fn test_stage_and_unstage_added_file_hunk(
17694    executor: BackgroundExecutor,
17695    cx: &mut TestAppContext,
17696) {
17697    init_test(cx, |_| {});
17698
17699    let mut cx = EditorTestContext::new(cx).await;
17700    cx.update_editor(|editor, _, cx| {
17701        editor.set_expand_all_diff_hunks(cx);
17702    });
17703
17704    let working_copy = r#"
17705            ˇfn main() {
17706                println!("hello, world!");
17707            }
17708        "#
17709    .unindent();
17710
17711    cx.set_state(&working_copy);
17712    executor.run_until_parked();
17713
17714    cx.assert_state_with_diff(
17715        r#"
17716            + ˇfn main() {
17717            +     println!("hello, world!");
17718            + }
17719        "#
17720        .unindent(),
17721    );
17722    cx.assert_index_text(None);
17723
17724    cx.update_editor(|editor, window, cx| {
17725        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
17726    });
17727    executor.run_until_parked();
17728    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
17729    cx.assert_state_with_diff(
17730        r#"
17731            + ˇfn main() {
17732            +     println!("hello, world!");
17733            + }
17734        "#
17735        .unindent(),
17736    );
17737
17738    cx.update_editor(|editor, window, cx| {
17739        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
17740    });
17741    executor.run_until_parked();
17742    cx.assert_index_text(None);
17743}
17744
17745async fn setup_indent_guides_editor(
17746    text: &str,
17747    cx: &mut TestAppContext,
17748) -> (BufferId, EditorTestContext) {
17749    init_test(cx, |_| {});
17750
17751    let mut cx = EditorTestContext::new(cx).await;
17752
17753    let buffer_id = cx.update_editor(|editor, window, cx| {
17754        editor.set_text(text, window, cx);
17755        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
17756
17757        buffer_ids[0]
17758    });
17759
17760    (buffer_id, cx)
17761}
17762
17763fn assert_indent_guides(
17764    range: Range<u32>,
17765    expected: Vec<IndentGuide>,
17766    active_indices: Option<Vec<usize>>,
17767    cx: &mut EditorTestContext,
17768) {
17769    let indent_guides = cx.update_editor(|editor, window, cx| {
17770        let snapshot = editor.snapshot(window, cx).display_snapshot;
17771        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
17772            editor,
17773            MultiBufferRow(range.start)..MultiBufferRow(range.end),
17774            true,
17775            &snapshot,
17776            cx,
17777        );
17778
17779        indent_guides.sort_by(|a, b| {
17780            a.depth.cmp(&b.depth).then(
17781                a.start_row
17782                    .cmp(&b.start_row)
17783                    .then(a.end_row.cmp(&b.end_row)),
17784            )
17785        });
17786        indent_guides
17787    });
17788
17789    if let Some(expected) = active_indices {
17790        let active_indices = cx.update_editor(|editor, window, cx| {
17791            let snapshot = editor.snapshot(window, cx).display_snapshot;
17792            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
17793        });
17794
17795        assert_eq!(
17796            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
17797            expected,
17798            "Active indent guide indices do not match"
17799        );
17800    }
17801
17802    assert_eq!(indent_guides, expected, "Indent guides do not match");
17803}
17804
17805fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
17806    IndentGuide {
17807        buffer_id,
17808        start_row: MultiBufferRow(start_row),
17809        end_row: MultiBufferRow(end_row),
17810        depth,
17811        tab_size: 4,
17812        settings: IndentGuideSettings {
17813            enabled: true,
17814            line_width: 1,
17815            active_line_width: 1,
17816            ..Default::default()
17817        },
17818    }
17819}
17820
17821#[gpui::test]
17822async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
17823    let (buffer_id, mut cx) = setup_indent_guides_editor(
17824        &"
17825        fn main() {
17826            let a = 1;
17827        }"
17828        .unindent(),
17829        cx,
17830    )
17831    .await;
17832
17833    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
17834}
17835
17836#[gpui::test]
17837async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
17838    let (buffer_id, mut cx) = setup_indent_guides_editor(
17839        &"
17840        fn main() {
17841            let a = 1;
17842            let b = 2;
17843        }"
17844        .unindent(),
17845        cx,
17846    )
17847    .await;
17848
17849    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
17850}
17851
17852#[gpui::test]
17853async fn test_indent_guide_nested(cx: &mut TestAppContext) {
17854    let (buffer_id, mut cx) = setup_indent_guides_editor(
17855        &"
17856        fn main() {
17857            let a = 1;
17858            if a == 3 {
17859                let b = 2;
17860            } else {
17861                let c = 3;
17862            }
17863        }"
17864        .unindent(),
17865        cx,
17866    )
17867    .await;
17868
17869    assert_indent_guides(
17870        0..8,
17871        vec![
17872            indent_guide(buffer_id, 1, 6, 0),
17873            indent_guide(buffer_id, 3, 3, 1),
17874            indent_guide(buffer_id, 5, 5, 1),
17875        ],
17876        None,
17877        &mut cx,
17878    );
17879}
17880
17881#[gpui::test]
17882async fn test_indent_guide_tab(cx: &mut TestAppContext) {
17883    let (buffer_id, mut cx) = setup_indent_guides_editor(
17884        &"
17885        fn main() {
17886            let a = 1;
17887                let b = 2;
17888            let c = 3;
17889        }"
17890        .unindent(),
17891        cx,
17892    )
17893    .await;
17894
17895    assert_indent_guides(
17896        0..5,
17897        vec![
17898            indent_guide(buffer_id, 1, 3, 0),
17899            indent_guide(buffer_id, 2, 2, 1),
17900        ],
17901        None,
17902        &mut cx,
17903    );
17904}
17905
17906#[gpui::test]
17907async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
17908    let (buffer_id, mut cx) = setup_indent_guides_editor(
17909        &"
17910        fn main() {
17911            let a = 1;
17912
17913            let c = 3;
17914        }"
17915        .unindent(),
17916        cx,
17917    )
17918    .await;
17919
17920    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
17921}
17922
17923#[gpui::test]
17924async fn test_indent_guide_complex(cx: &mut TestAppContext) {
17925    let (buffer_id, mut cx) = setup_indent_guides_editor(
17926        &"
17927        fn main() {
17928            let a = 1;
17929
17930            let c = 3;
17931
17932            if a == 3 {
17933                let b = 2;
17934            } else {
17935                let c = 3;
17936            }
17937        }"
17938        .unindent(),
17939        cx,
17940    )
17941    .await;
17942
17943    assert_indent_guides(
17944        0..11,
17945        vec![
17946            indent_guide(buffer_id, 1, 9, 0),
17947            indent_guide(buffer_id, 6, 6, 1),
17948            indent_guide(buffer_id, 8, 8, 1),
17949        ],
17950        None,
17951        &mut cx,
17952    );
17953}
17954
17955#[gpui::test]
17956async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
17957    let (buffer_id, mut cx) = setup_indent_guides_editor(
17958        &"
17959        fn main() {
17960            let a = 1;
17961
17962            let c = 3;
17963
17964            if a == 3 {
17965                let b = 2;
17966            } else {
17967                let c = 3;
17968            }
17969        }"
17970        .unindent(),
17971        cx,
17972    )
17973    .await;
17974
17975    assert_indent_guides(
17976        1..11,
17977        vec![
17978            indent_guide(buffer_id, 1, 9, 0),
17979            indent_guide(buffer_id, 6, 6, 1),
17980            indent_guide(buffer_id, 8, 8, 1),
17981        ],
17982        None,
17983        &mut cx,
17984    );
17985}
17986
17987#[gpui::test]
17988async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
17989    let (buffer_id, mut cx) = setup_indent_guides_editor(
17990        &"
17991        fn main() {
17992            let a = 1;
17993
17994            let c = 3;
17995
17996            if a == 3 {
17997                let b = 2;
17998            } else {
17999                let c = 3;
18000            }
18001        }"
18002        .unindent(),
18003        cx,
18004    )
18005    .await;
18006
18007    assert_indent_guides(
18008        1..10,
18009        vec![
18010            indent_guide(buffer_id, 1, 9, 0),
18011            indent_guide(buffer_id, 6, 6, 1),
18012            indent_guide(buffer_id, 8, 8, 1),
18013        ],
18014        None,
18015        &mut cx,
18016    );
18017}
18018
18019#[gpui::test]
18020async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
18021    let (buffer_id, mut cx) = setup_indent_guides_editor(
18022        &"
18023        fn main() {
18024            if a {
18025                b(
18026                    c,
18027                    d,
18028                )
18029            } else {
18030                e(
18031                    f
18032                )
18033            }
18034        }"
18035        .unindent(),
18036        cx,
18037    )
18038    .await;
18039
18040    assert_indent_guides(
18041        0..11,
18042        vec![
18043            indent_guide(buffer_id, 1, 10, 0),
18044            indent_guide(buffer_id, 2, 5, 1),
18045            indent_guide(buffer_id, 7, 9, 1),
18046            indent_guide(buffer_id, 3, 4, 2),
18047            indent_guide(buffer_id, 8, 8, 2),
18048        ],
18049        None,
18050        &mut cx,
18051    );
18052
18053    cx.update_editor(|editor, window, cx| {
18054        editor.fold_at(MultiBufferRow(2), window, cx);
18055        assert_eq!(
18056            editor.display_text(cx),
18057            "
18058            fn main() {
18059                if a {
18060                    b(⋯
18061                    )
18062                } else {
18063                    e(
18064                        f
18065                    )
18066                }
18067            }"
18068            .unindent()
18069        );
18070    });
18071
18072    assert_indent_guides(
18073        0..11,
18074        vec![
18075            indent_guide(buffer_id, 1, 10, 0),
18076            indent_guide(buffer_id, 2, 5, 1),
18077            indent_guide(buffer_id, 7, 9, 1),
18078            indent_guide(buffer_id, 8, 8, 2),
18079        ],
18080        None,
18081        &mut cx,
18082    );
18083}
18084
18085#[gpui::test]
18086async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
18087    let (buffer_id, mut cx) = setup_indent_guides_editor(
18088        &"
18089        block1
18090            block2
18091                block3
18092                    block4
18093            block2
18094        block1
18095        block1"
18096            .unindent(),
18097        cx,
18098    )
18099    .await;
18100
18101    assert_indent_guides(
18102        1..10,
18103        vec![
18104            indent_guide(buffer_id, 1, 4, 0),
18105            indent_guide(buffer_id, 2, 3, 1),
18106            indent_guide(buffer_id, 3, 3, 2),
18107        ],
18108        None,
18109        &mut cx,
18110    );
18111}
18112
18113#[gpui::test]
18114async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
18115    let (buffer_id, mut cx) = setup_indent_guides_editor(
18116        &"
18117        block1
18118            block2
18119                block3
18120
18121        block1
18122        block1"
18123            .unindent(),
18124        cx,
18125    )
18126    .await;
18127
18128    assert_indent_guides(
18129        0..6,
18130        vec![
18131            indent_guide(buffer_id, 1, 2, 0),
18132            indent_guide(buffer_id, 2, 2, 1),
18133        ],
18134        None,
18135        &mut cx,
18136    );
18137}
18138
18139#[gpui::test]
18140async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
18141    let (buffer_id, mut cx) = setup_indent_guides_editor(
18142        &"
18143        function component() {
18144        \treturn (
18145        \t\t\t
18146        \t\t<div>
18147        \t\t\t<abc></abc>
18148        \t\t</div>
18149        \t)
18150        }"
18151        .unindent(),
18152        cx,
18153    )
18154    .await;
18155
18156    assert_indent_guides(
18157        0..8,
18158        vec![
18159            indent_guide(buffer_id, 1, 6, 0),
18160            indent_guide(buffer_id, 2, 5, 1),
18161            indent_guide(buffer_id, 4, 4, 2),
18162        ],
18163        None,
18164        &mut cx,
18165    );
18166}
18167
18168#[gpui::test]
18169async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
18170    let (buffer_id, mut cx) = setup_indent_guides_editor(
18171        &"
18172        function component() {
18173        \treturn (
18174        \t
18175        \t\t<div>
18176        \t\t\t<abc></abc>
18177        \t\t</div>
18178        \t)
18179        }"
18180        .unindent(),
18181        cx,
18182    )
18183    .await;
18184
18185    assert_indent_guides(
18186        0..8,
18187        vec![
18188            indent_guide(buffer_id, 1, 6, 0),
18189            indent_guide(buffer_id, 2, 5, 1),
18190            indent_guide(buffer_id, 4, 4, 2),
18191        ],
18192        None,
18193        &mut cx,
18194    );
18195}
18196
18197#[gpui::test]
18198async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
18199    let (buffer_id, mut cx) = setup_indent_guides_editor(
18200        &"
18201        block1
18202
18203
18204
18205            block2
18206        "
18207        .unindent(),
18208        cx,
18209    )
18210    .await;
18211
18212    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
18213}
18214
18215#[gpui::test]
18216async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
18217    let (buffer_id, mut cx) = setup_indent_guides_editor(
18218        &"
18219        def a:
18220        \tb = 3
18221        \tif True:
18222        \t\tc = 4
18223        \t\td = 5
18224        \tprint(b)
18225        "
18226        .unindent(),
18227        cx,
18228    )
18229    .await;
18230
18231    assert_indent_guides(
18232        0..6,
18233        vec![
18234            indent_guide(buffer_id, 1, 5, 0),
18235            indent_guide(buffer_id, 3, 4, 1),
18236        ],
18237        None,
18238        &mut cx,
18239    );
18240}
18241
18242#[gpui::test]
18243async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
18244    let (buffer_id, mut cx) = setup_indent_guides_editor(
18245        &"
18246    fn main() {
18247        let a = 1;
18248    }"
18249        .unindent(),
18250        cx,
18251    )
18252    .await;
18253
18254    cx.update_editor(|editor, window, cx| {
18255        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18256            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18257        });
18258    });
18259
18260    assert_indent_guides(
18261        0..3,
18262        vec![indent_guide(buffer_id, 1, 1, 0)],
18263        Some(vec![0]),
18264        &mut cx,
18265    );
18266}
18267
18268#[gpui::test]
18269async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
18270    let (buffer_id, mut cx) = setup_indent_guides_editor(
18271        &"
18272    fn main() {
18273        if 1 == 2 {
18274            let a = 1;
18275        }
18276    }"
18277        .unindent(),
18278        cx,
18279    )
18280    .await;
18281
18282    cx.update_editor(|editor, window, cx| {
18283        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18284            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18285        });
18286    });
18287
18288    assert_indent_guides(
18289        0..4,
18290        vec![
18291            indent_guide(buffer_id, 1, 3, 0),
18292            indent_guide(buffer_id, 2, 2, 1),
18293        ],
18294        Some(vec![1]),
18295        &mut cx,
18296    );
18297
18298    cx.update_editor(|editor, window, cx| {
18299        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18300            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
18301        });
18302    });
18303
18304    assert_indent_guides(
18305        0..4,
18306        vec![
18307            indent_guide(buffer_id, 1, 3, 0),
18308            indent_guide(buffer_id, 2, 2, 1),
18309        ],
18310        Some(vec![1]),
18311        &mut cx,
18312    );
18313
18314    cx.update_editor(|editor, window, cx| {
18315        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18316            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
18317        });
18318    });
18319
18320    assert_indent_guides(
18321        0..4,
18322        vec![
18323            indent_guide(buffer_id, 1, 3, 0),
18324            indent_guide(buffer_id, 2, 2, 1),
18325        ],
18326        Some(vec![0]),
18327        &mut cx,
18328    );
18329}
18330
18331#[gpui::test]
18332async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
18333    let (buffer_id, mut cx) = setup_indent_guides_editor(
18334        &"
18335    fn main() {
18336        let a = 1;
18337
18338        let b = 2;
18339    }"
18340        .unindent(),
18341        cx,
18342    )
18343    .await;
18344
18345    cx.update_editor(|editor, window, cx| {
18346        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18347            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
18348        });
18349    });
18350
18351    assert_indent_guides(
18352        0..5,
18353        vec![indent_guide(buffer_id, 1, 3, 0)],
18354        Some(vec![0]),
18355        &mut cx,
18356    );
18357}
18358
18359#[gpui::test]
18360async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
18361    let (buffer_id, mut cx) = setup_indent_guides_editor(
18362        &"
18363    def m:
18364        a = 1
18365        pass"
18366            .unindent(),
18367        cx,
18368    )
18369    .await;
18370
18371    cx.update_editor(|editor, window, cx| {
18372        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18373            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18374        });
18375    });
18376
18377    assert_indent_guides(
18378        0..3,
18379        vec![indent_guide(buffer_id, 1, 2, 0)],
18380        Some(vec![0]),
18381        &mut cx,
18382    );
18383}
18384
18385#[gpui::test]
18386async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
18387    init_test(cx, |_| {});
18388    let mut cx = EditorTestContext::new(cx).await;
18389    let text = indoc! {
18390        "
18391        impl A {
18392            fn b() {
18393                0;
18394                3;
18395                5;
18396                6;
18397                7;
18398            }
18399        }
18400        "
18401    };
18402    let base_text = indoc! {
18403        "
18404        impl A {
18405            fn b() {
18406                0;
18407                1;
18408                2;
18409                3;
18410                4;
18411            }
18412            fn c() {
18413                5;
18414                6;
18415                7;
18416            }
18417        }
18418        "
18419    };
18420
18421    cx.update_editor(|editor, window, cx| {
18422        editor.set_text(text, window, cx);
18423
18424        editor.buffer().update(cx, |multibuffer, cx| {
18425            let buffer = multibuffer.as_singleton().unwrap();
18426            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
18427
18428            multibuffer.set_all_diff_hunks_expanded(cx);
18429            multibuffer.add_diff(diff, cx);
18430
18431            buffer.read(cx).remote_id()
18432        })
18433    });
18434    cx.run_until_parked();
18435
18436    cx.assert_state_with_diff(
18437        indoc! { "
18438          impl A {
18439              fn b() {
18440                  0;
18441        -         1;
18442        -         2;
18443                  3;
18444        -         4;
18445        -     }
18446        -     fn c() {
18447                  5;
18448                  6;
18449                  7;
18450              }
18451          }
18452          ˇ"
18453        }
18454        .to_string(),
18455    );
18456
18457    let mut actual_guides = cx.update_editor(|editor, window, cx| {
18458        editor
18459            .snapshot(window, cx)
18460            .buffer_snapshot
18461            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
18462            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
18463            .collect::<Vec<_>>()
18464    });
18465    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
18466    assert_eq!(
18467        actual_guides,
18468        vec![
18469            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
18470            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
18471            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
18472        ]
18473    );
18474}
18475
18476#[gpui::test]
18477async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18478    init_test(cx, |_| {});
18479    let mut cx = EditorTestContext::new(cx).await;
18480
18481    let diff_base = r#"
18482        a
18483        b
18484        c
18485        "#
18486    .unindent();
18487
18488    cx.set_state(
18489        &r#"
18490        ˇA
18491        b
18492        C
18493        "#
18494        .unindent(),
18495    );
18496    cx.set_head_text(&diff_base);
18497    cx.update_editor(|editor, window, cx| {
18498        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18499    });
18500    executor.run_until_parked();
18501
18502    let both_hunks_expanded = r#"
18503        - a
18504        + ˇA
18505          b
18506        - c
18507        + C
18508        "#
18509    .unindent();
18510
18511    cx.assert_state_with_diff(both_hunks_expanded.clone());
18512
18513    let hunk_ranges = cx.update_editor(|editor, window, cx| {
18514        let snapshot = editor.snapshot(window, cx);
18515        let hunks = editor
18516            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18517            .collect::<Vec<_>>();
18518        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
18519        let buffer_id = hunks[0].buffer_id;
18520        hunks
18521            .into_iter()
18522            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
18523            .collect::<Vec<_>>()
18524    });
18525    assert_eq!(hunk_ranges.len(), 2);
18526
18527    cx.update_editor(|editor, _, cx| {
18528        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18529    });
18530    executor.run_until_parked();
18531
18532    let second_hunk_expanded = r#"
18533          ˇA
18534          b
18535        - c
18536        + C
18537        "#
18538    .unindent();
18539
18540    cx.assert_state_with_diff(second_hunk_expanded);
18541
18542    cx.update_editor(|editor, _, cx| {
18543        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18544    });
18545    executor.run_until_parked();
18546
18547    cx.assert_state_with_diff(both_hunks_expanded.clone());
18548
18549    cx.update_editor(|editor, _, cx| {
18550        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
18551    });
18552    executor.run_until_parked();
18553
18554    let first_hunk_expanded = r#"
18555        - a
18556        + ˇA
18557          b
18558          C
18559        "#
18560    .unindent();
18561
18562    cx.assert_state_with_diff(first_hunk_expanded);
18563
18564    cx.update_editor(|editor, _, cx| {
18565        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
18566    });
18567    executor.run_until_parked();
18568
18569    cx.assert_state_with_diff(both_hunks_expanded);
18570
18571    cx.set_state(
18572        &r#"
18573        ˇA
18574        b
18575        "#
18576        .unindent(),
18577    );
18578    cx.run_until_parked();
18579
18580    // TODO this cursor position seems bad
18581    cx.assert_state_with_diff(
18582        r#"
18583        - ˇa
18584        + A
18585          b
18586        "#
18587        .unindent(),
18588    );
18589
18590    cx.update_editor(|editor, window, cx| {
18591        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18592    });
18593
18594    cx.assert_state_with_diff(
18595        r#"
18596            - ˇa
18597            + A
18598              b
18599            - c
18600            "#
18601        .unindent(),
18602    );
18603
18604    let hunk_ranges = cx.update_editor(|editor, window, cx| {
18605        let snapshot = editor.snapshot(window, cx);
18606        let hunks = editor
18607            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18608            .collect::<Vec<_>>();
18609        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
18610        let buffer_id = hunks[0].buffer_id;
18611        hunks
18612            .into_iter()
18613            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
18614            .collect::<Vec<_>>()
18615    });
18616    assert_eq!(hunk_ranges.len(), 2);
18617
18618    cx.update_editor(|editor, _, cx| {
18619        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
18620    });
18621    executor.run_until_parked();
18622
18623    cx.assert_state_with_diff(
18624        r#"
18625        - ˇa
18626        + A
18627          b
18628        "#
18629        .unindent(),
18630    );
18631}
18632
18633#[gpui::test]
18634async fn test_toggle_deletion_hunk_at_start_of_file(
18635    executor: BackgroundExecutor,
18636    cx: &mut TestAppContext,
18637) {
18638    init_test(cx, |_| {});
18639    let mut cx = EditorTestContext::new(cx).await;
18640
18641    let diff_base = r#"
18642        a
18643        b
18644        c
18645        "#
18646    .unindent();
18647
18648    cx.set_state(
18649        &r#"
18650        ˇb
18651        c
18652        "#
18653        .unindent(),
18654    );
18655    cx.set_head_text(&diff_base);
18656    cx.update_editor(|editor, window, cx| {
18657        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18658    });
18659    executor.run_until_parked();
18660
18661    let hunk_expanded = r#"
18662        - a
18663          ˇb
18664          c
18665        "#
18666    .unindent();
18667
18668    cx.assert_state_with_diff(hunk_expanded.clone());
18669
18670    let hunk_ranges = cx.update_editor(|editor, window, cx| {
18671        let snapshot = editor.snapshot(window, cx);
18672        let hunks = editor
18673            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18674            .collect::<Vec<_>>();
18675        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
18676        let buffer_id = hunks[0].buffer_id;
18677        hunks
18678            .into_iter()
18679            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
18680            .collect::<Vec<_>>()
18681    });
18682    assert_eq!(hunk_ranges.len(), 1);
18683
18684    cx.update_editor(|editor, _, cx| {
18685        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18686    });
18687    executor.run_until_parked();
18688
18689    let hunk_collapsed = r#"
18690          ˇb
18691          c
18692        "#
18693    .unindent();
18694
18695    cx.assert_state_with_diff(hunk_collapsed);
18696
18697    cx.update_editor(|editor, _, cx| {
18698        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18699    });
18700    executor.run_until_parked();
18701
18702    cx.assert_state_with_diff(hunk_expanded.clone());
18703}
18704
18705#[gpui::test]
18706async fn test_display_diff_hunks(cx: &mut TestAppContext) {
18707    init_test(cx, |_| {});
18708
18709    let fs = FakeFs::new(cx.executor());
18710    fs.insert_tree(
18711        path!("/test"),
18712        json!({
18713            ".git": {},
18714            "file-1": "ONE\n",
18715            "file-2": "TWO\n",
18716            "file-3": "THREE\n",
18717        }),
18718    )
18719    .await;
18720
18721    fs.set_head_for_repo(
18722        path!("/test/.git").as_ref(),
18723        &[
18724            ("file-1".into(), "one\n".into()),
18725            ("file-2".into(), "two\n".into()),
18726            ("file-3".into(), "three\n".into()),
18727        ],
18728        "deadbeef",
18729    );
18730
18731    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
18732    let mut buffers = vec![];
18733    for i in 1..=3 {
18734        let buffer = project
18735            .update(cx, |project, cx| {
18736                let path = format!(path!("/test/file-{}"), i);
18737                project.open_local_buffer(path, cx)
18738            })
18739            .await
18740            .unwrap();
18741        buffers.push(buffer);
18742    }
18743
18744    let multibuffer = cx.new(|cx| {
18745        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
18746        multibuffer.set_all_diff_hunks_expanded(cx);
18747        for buffer in &buffers {
18748            let snapshot = buffer.read(cx).snapshot();
18749            multibuffer.set_excerpts_for_path(
18750                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
18751                buffer.clone(),
18752                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
18753                DEFAULT_MULTIBUFFER_CONTEXT,
18754                cx,
18755            );
18756        }
18757        multibuffer
18758    });
18759
18760    let editor = cx.add_window(|window, cx| {
18761        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
18762    });
18763    cx.run_until_parked();
18764
18765    let snapshot = editor
18766        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
18767        .unwrap();
18768    let hunks = snapshot
18769        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
18770        .map(|hunk| match hunk {
18771            DisplayDiffHunk::Unfolded {
18772                display_row_range, ..
18773            } => display_row_range,
18774            DisplayDiffHunk::Folded { .. } => unreachable!(),
18775        })
18776        .collect::<Vec<_>>();
18777    assert_eq!(
18778        hunks,
18779        [
18780            DisplayRow(2)..DisplayRow(4),
18781            DisplayRow(7)..DisplayRow(9),
18782            DisplayRow(12)..DisplayRow(14),
18783        ]
18784    );
18785}
18786
18787#[gpui::test]
18788async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
18789    init_test(cx, |_| {});
18790
18791    let mut cx = EditorTestContext::new(cx).await;
18792    cx.set_head_text(indoc! { "
18793        one
18794        two
18795        three
18796        four
18797        five
18798        "
18799    });
18800    cx.set_index_text(indoc! { "
18801        one
18802        two
18803        three
18804        four
18805        five
18806        "
18807    });
18808    cx.set_state(indoc! {"
18809        one
18810        TWO
18811        ˇTHREE
18812        FOUR
18813        five
18814    "});
18815    cx.run_until_parked();
18816    cx.update_editor(|editor, window, cx| {
18817        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18818    });
18819    cx.run_until_parked();
18820    cx.assert_index_text(Some(indoc! {"
18821        one
18822        TWO
18823        THREE
18824        FOUR
18825        five
18826    "}));
18827    cx.set_state(indoc! { "
18828        one
18829        TWO
18830        ˇTHREE-HUNDRED
18831        FOUR
18832        five
18833    "});
18834    cx.run_until_parked();
18835    cx.update_editor(|editor, window, cx| {
18836        let snapshot = editor.snapshot(window, cx);
18837        let hunks = editor
18838            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18839            .collect::<Vec<_>>();
18840        assert_eq!(hunks.len(), 1);
18841        assert_eq!(
18842            hunks[0].status(),
18843            DiffHunkStatus {
18844                kind: DiffHunkStatusKind::Modified,
18845                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
18846            }
18847        );
18848
18849        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18850    });
18851    cx.run_until_parked();
18852    cx.assert_index_text(Some(indoc! {"
18853        one
18854        TWO
18855        THREE-HUNDRED
18856        FOUR
18857        five
18858    "}));
18859}
18860
18861#[gpui::test]
18862fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
18863    init_test(cx, |_| {});
18864
18865    let editor = cx.add_window(|window, cx| {
18866        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
18867        build_editor(buffer, window, cx)
18868    });
18869
18870    let render_args = Arc::new(Mutex::new(None));
18871    let snapshot = editor
18872        .update(cx, |editor, window, cx| {
18873            let snapshot = editor.buffer().read(cx).snapshot(cx);
18874            let range =
18875                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
18876
18877            struct RenderArgs {
18878                row: MultiBufferRow,
18879                folded: bool,
18880                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
18881            }
18882
18883            let crease = Crease::inline(
18884                range,
18885                FoldPlaceholder::test(),
18886                {
18887                    let toggle_callback = render_args.clone();
18888                    move |row, folded, callback, _window, _cx| {
18889                        *toggle_callback.lock() = Some(RenderArgs {
18890                            row,
18891                            folded,
18892                            callback,
18893                        });
18894                        div()
18895                    }
18896                },
18897                |_row, _folded, _window, _cx| div(),
18898            );
18899
18900            editor.insert_creases(Some(crease), cx);
18901            let snapshot = editor.snapshot(window, cx);
18902            let _div = snapshot.render_crease_toggle(
18903                MultiBufferRow(1),
18904                false,
18905                cx.entity().clone(),
18906                window,
18907                cx,
18908            );
18909            snapshot
18910        })
18911        .unwrap();
18912
18913    let render_args = render_args.lock().take().unwrap();
18914    assert_eq!(render_args.row, MultiBufferRow(1));
18915    assert!(!render_args.folded);
18916    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
18917
18918    cx.update_window(*editor, |_, window, cx| {
18919        (render_args.callback)(true, window, cx)
18920    })
18921    .unwrap();
18922    let snapshot = editor
18923        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
18924        .unwrap();
18925    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
18926
18927    cx.update_window(*editor, |_, window, cx| {
18928        (render_args.callback)(false, window, cx)
18929    })
18930    .unwrap();
18931    let snapshot = editor
18932        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
18933        .unwrap();
18934    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
18935}
18936
18937#[gpui::test]
18938async fn test_input_text(cx: &mut TestAppContext) {
18939    init_test(cx, |_| {});
18940    let mut cx = EditorTestContext::new(cx).await;
18941
18942    cx.set_state(
18943        &r#"ˇone
18944        two
18945
18946        three
18947        fourˇ
18948        five
18949
18950        siˇx"#
18951            .unindent(),
18952    );
18953
18954    cx.dispatch_action(HandleInput(String::new()));
18955    cx.assert_editor_state(
18956        &r#"ˇone
18957        two
18958
18959        three
18960        fourˇ
18961        five
18962
18963        siˇx"#
18964            .unindent(),
18965    );
18966
18967    cx.dispatch_action(HandleInput("AAAA".to_string()));
18968    cx.assert_editor_state(
18969        &r#"AAAAˇone
18970        two
18971
18972        three
18973        fourAAAAˇ
18974        five
18975
18976        siAAAAˇx"#
18977            .unindent(),
18978    );
18979}
18980
18981#[gpui::test]
18982async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
18983    init_test(cx, |_| {});
18984
18985    let mut cx = EditorTestContext::new(cx).await;
18986    cx.set_state(
18987        r#"let foo = 1;
18988let foo = 2;
18989let foo = 3;
18990let fooˇ = 4;
18991let foo = 5;
18992let foo = 6;
18993let foo = 7;
18994let foo = 8;
18995let foo = 9;
18996let foo = 10;
18997let foo = 11;
18998let foo = 12;
18999let foo = 13;
19000let foo = 14;
19001let foo = 15;"#,
19002    );
19003
19004    cx.update_editor(|e, window, cx| {
19005        assert_eq!(
19006            e.next_scroll_position,
19007            NextScrollCursorCenterTopBottom::Center,
19008            "Default next scroll direction is center",
19009        );
19010
19011        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19012        assert_eq!(
19013            e.next_scroll_position,
19014            NextScrollCursorCenterTopBottom::Top,
19015            "After center, next scroll direction should be top",
19016        );
19017
19018        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19019        assert_eq!(
19020            e.next_scroll_position,
19021            NextScrollCursorCenterTopBottom::Bottom,
19022            "After top, next scroll direction should be bottom",
19023        );
19024
19025        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19026        assert_eq!(
19027            e.next_scroll_position,
19028            NextScrollCursorCenterTopBottom::Center,
19029            "After bottom, scrolling should start over",
19030        );
19031
19032        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19033        assert_eq!(
19034            e.next_scroll_position,
19035            NextScrollCursorCenterTopBottom::Top,
19036            "Scrolling continues if retriggered fast enough"
19037        );
19038    });
19039
19040    cx.executor()
19041        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
19042    cx.executor().run_until_parked();
19043    cx.update_editor(|e, _, _| {
19044        assert_eq!(
19045            e.next_scroll_position,
19046            NextScrollCursorCenterTopBottom::Center,
19047            "If scrolling is not triggered fast enough, it should reset"
19048        );
19049    });
19050}
19051
19052#[gpui::test]
19053async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
19054    init_test(cx, |_| {});
19055    let mut cx = EditorLspTestContext::new_rust(
19056        lsp::ServerCapabilities {
19057            definition_provider: Some(lsp::OneOf::Left(true)),
19058            references_provider: Some(lsp::OneOf::Left(true)),
19059            ..lsp::ServerCapabilities::default()
19060        },
19061        cx,
19062    )
19063    .await;
19064
19065    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
19066        let go_to_definition = cx
19067            .lsp
19068            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
19069                move |params, _| async move {
19070                    if empty_go_to_definition {
19071                        Ok(None)
19072                    } else {
19073                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
19074                            uri: params.text_document_position_params.text_document.uri,
19075                            range: lsp::Range::new(
19076                                lsp::Position::new(4, 3),
19077                                lsp::Position::new(4, 6),
19078                            ),
19079                        })))
19080                    }
19081                },
19082            );
19083        let references = cx
19084            .lsp
19085            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
19086                Ok(Some(vec![lsp::Location {
19087                    uri: params.text_document_position.text_document.uri,
19088                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
19089                }]))
19090            });
19091        (go_to_definition, references)
19092    };
19093
19094    cx.set_state(
19095        &r#"fn one() {
19096            let mut a = ˇtwo();
19097        }
19098
19099        fn two() {}"#
19100            .unindent(),
19101    );
19102    set_up_lsp_handlers(false, &mut cx);
19103    let navigated = cx
19104        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19105        .await
19106        .expect("Failed to navigate to definition");
19107    assert_eq!(
19108        navigated,
19109        Navigated::Yes,
19110        "Should have navigated to definition from the GetDefinition response"
19111    );
19112    cx.assert_editor_state(
19113        &r#"fn one() {
19114            let mut a = two();
19115        }
19116
19117        fn «twoˇ»() {}"#
19118            .unindent(),
19119    );
19120
19121    let editors = cx.update_workspace(|workspace, _, cx| {
19122        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19123    });
19124    cx.update_editor(|_, _, test_editor_cx| {
19125        assert_eq!(
19126            editors.len(),
19127            1,
19128            "Initially, only one, test, editor should be open in the workspace"
19129        );
19130        assert_eq!(
19131            test_editor_cx.entity(),
19132            editors.last().expect("Asserted len is 1").clone()
19133        );
19134    });
19135
19136    set_up_lsp_handlers(true, &mut cx);
19137    let navigated = cx
19138        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19139        .await
19140        .expect("Failed to navigate to lookup references");
19141    assert_eq!(
19142        navigated,
19143        Navigated::Yes,
19144        "Should have navigated to references as a fallback after empty GoToDefinition response"
19145    );
19146    // We should not change the selections in the existing file,
19147    // if opening another milti buffer with the references
19148    cx.assert_editor_state(
19149        &r#"fn one() {
19150            let mut a = two();
19151        }
19152
19153        fn «twoˇ»() {}"#
19154            .unindent(),
19155    );
19156    let editors = cx.update_workspace(|workspace, _, cx| {
19157        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19158    });
19159    cx.update_editor(|_, _, test_editor_cx| {
19160        assert_eq!(
19161            editors.len(),
19162            2,
19163            "After falling back to references search, we open a new editor with the results"
19164        );
19165        let references_fallback_text = editors
19166            .into_iter()
19167            .find(|new_editor| *new_editor != test_editor_cx.entity())
19168            .expect("Should have one non-test editor now")
19169            .read(test_editor_cx)
19170            .text(test_editor_cx);
19171        assert_eq!(
19172            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
19173            "Should use the range from the references response and not the GoToDefinition one"
19174        );
19175    });
19176}
19177
19178#[gpui::test]
19179async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
19180    init_test(cx, |_| {});
19181    cx.update(|cx| {
19182        let mut editor_settings = EditorSettings::get_global(cx).clone();
19183        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
19184        EditorSettings::override_global(editor_settings, cx);
19185    });
19186    let mut cx = EditorLspTestContext::new_rust(
19187        lsp::ServerCapabilities {
19188            definition_provider: Some(lsp::OneOf::Left(true)),
19189            references_provider: Some(lsp::OneOf::Left(true)),
19190            ..lsp::ServerCapabilities::default()
19191        },
19192        cx,
19193    )
19194    .await;
19195    let original_state = r#"fn one() {
19196        let mut a = ˇtwo();
19197    }
19198
19199    fn two() {}"#
19200        .unindent();
19201    cx.set_state(&original_state);
19202
19203    let mut go_to_definition = cx
19204        .lsp
19205        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
19206            move |_, _| async move { Ok(None) },
19207        );
19208    let _references = cx
19209        .lsp
19210        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
19211            panic!("Should not call for references with no go to definition fallback")
19212        });
19213
19214    let navigated = cx
19215        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19216        .await
19217        .expect("Failed to navigate to lookup references");
19218    go_to_definition
19219        .next()
19220        .await
19221        .expect("Should have called the go_to_definition handler");
19222
19223    assert_eq!(
19224        navigated,
19225        Navigated::No,
19226        "Should have navigated to references as a fallback after empty GoToDefinition response"
19227    );
19228    cx.assert_editor_state(&original_state);
19229    let editors = cx.update_workspace(|workspace, _, cx| {
19230        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19231    });
19232    cx.update_editor(|_, _, _| {
19233        assert_eq!(
19234            editors.len(),
19235            1,
19236            "After unsuccessful fallback, no other editor should have been opened"
19237        );
19238    });
19239}
19240
19241#[gpui::test]
19242async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
19243    init_test(cx, |_| {});
19244
19245    let language = Arc::new(Language::new(
19246        LanguageConfig::default(),
19247        Some(tree_sitter_rust::LANGUAGE.into()),
19248    ));
19249
19250    let text = r#"
19251        #[cfg(test)]
19252        mod tests() {
19253            #[test]
19254            fn runnable_1() {
19255                let a = 1;
19256            }
19257
19258            #[test]
19259            fn runnable_2() {
19260                let a = 1;
19261                let b = 2;
19262            }
19263        }
19264    "#
19265    .unindent();
19266
19267    let fs = FakeFs::new(cx.executor());
19268    fs.insert_file("/file.rs", Default::default()).await;
19269
19270    let project = Project::test(fs, ["/a".as_ref()], cx).await;
19271    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19272    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19273    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
19274    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
19275
19276    let editor = cx.new_window_entity(|window, cx| {
19277        Editor::new(
19278            EditorMode::full(),
19279            multi_buffer,
19280            Some(project.clone()),
19281            window,
19282            cx,
19283        )
19284    });
19285
19286    editor.update_in(cx, |editor, window, cx| {
19287        let snapshot = editor.buffer().read(cx).snapshot(cx);
19288        editor.tasks.insert(
19289            (buffer.read(cx).remote_id(), 3),
19290            RunnableTasks {
19291                templates: vec![],
19292                offset: snapshot.anchor_before(43),
19293                column: 0,
19294                extra_variables: HashMap::default(),
19295                context_range: BufferOffset(43)..BufferOffset(85),
19296            },
19297        );
19298        editor.tasks.insert(
19299            (buffer.read(cx).remote_id(), 8),
19300            RunnableTasks {
19301                templates: vec![],
19302                offset: snapshot.anchor_before(86),
19303                column: 0,
19304                extra_variables: HashMap::default(),
19305                context_range: BufferOffset(86)..BufferOffset(191),
19306            },
19307        );
19308
19309        // Test finding task when cursor is inside function body
19310        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19311            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
19312        });
19313        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
19314        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
19315
19316        // Test finding task when cursor is on function name
19317        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19318            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
19319        });
19320        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
19321        assert_eq!(row, 8, "Should find task when cursor is on function name");
19322    });
19323}
19324
19325#[gpui::test]
19326async fn test_folding_buffers(cx: &mut TestAppContext) {
19327    init_test(cx, |_| {});
19328
19329    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
19330    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
19331    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
19332
19333    let fs = FakeFs::new(cx.executor());
19334    fs.insert_tree(
19335        path!("/a"),
19336        json!({
19337            "first.rs": sample_text_1,
19338            "second.rs": sample_text_2,
19339            "third.rs": sample_text_3,
19340        }),
19341    )
19342    .await;
19343    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19344    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19345    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19346    let worktree = project.update(cx, |project, cx| {
19347        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19348        assert_eq!(worktrees.len(), 1);
19349        worktrees.pop().unwrap()
19350    });
19351    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19352
19353    let buffer_1 = project
19354        .update(cx, |project, cx| {
19355            project.open_buffer((worktree_id, "first.rs"), cx)
19356        })
19357        .await
19358        .unwrap();
19359    let buffer_2 = project
19360        .update(cx, |project, cx| {
19361            project.open_buffer((worktree_id, "second.rs"), cx)
19362        })
19363        .await
19364        .unwrap();
19365    let buffer_3 = project
19366        .update(cx, |project, cx| {
19367            project.open_buffer((worktree_id, "third.rs"), cx)
19368        })
19369        .await
19370        .unwrap();
19371
19372    let multi_buffer = cx.new(|cx| {
19373        let mut multi_buffer = MultiBuffer::new(ReadWrite);
19374        multi_buffer.push_excerpts(
19375            buffer_1.clone(),
19376            [
19377                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19378                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19379                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19380            ],
19381            cx,
19382        );
19383        multi_buffer.push_excerpts(
19384            buffer_2.clone(),
19385            [
19386                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19387                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19388                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19389            ],
19390            cx,
19391        );
19392        multi_buffer.push_excerpts(
19393            buffer_3.clone(),
19394            [
19395                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19396                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19397                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19398            ],
19399            cx,
19400        );
19401        multi_buffer
19402    });
19403    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19404        Editor::new(
19405            EditorMode::full(),
19406            multi_buffer.clone(),
19407            Some(project.clone()),
19408            window,
19409            cx,
19410        )
19411    });
19412
19413    assert_eq!(
19414        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19415        "\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",
19416    );
19417
19418    multi_buffer_editor.update(cx, |editor, cx| {
19419        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
19420    });
19421    assert_eq!(
19422        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19423        "\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",
19424        "After folding the first buffer, its text should not be displayed"
19425    );
19426
19427    multi_buffer_editor.update(cx, |editor, cx| {
19428        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
19429    });
19430    assert_eq!(
19431        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19432        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
19433        "After folding the second buffer, its text should not be displayed"
19434    );
19435
19436    multi_buffer_editor.update(cx, |editor, cx| {
19437        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
19438    });
19439    assert_eq!(
19440        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19441        "\n\n\n\n\n",
19442        "After folding the third buffer, its text should not be displayed"
19443    );
19444
19445    // Emulate selection inside the fold logic, that should work
19446    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19447        editor
19448            .snapshot(window, cx)
19449            .next_line_boundary(Point::new(0, 4));
19450    });
19451
19452    multi_buffer_editor.update(cx, |editor, cx| {
19453        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
19454    });
19455    assert_eq!(
19456        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19457        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
19458        "After unfolding the second buffer, its text should be displayed"
19459    );
19460
19461    // Typing inside of buffer 1 causes that buffer to be unfolded.
19462    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19463        assert_eq!(
19464            multi_buffer
19465                .read(cx)
19466                .snapshot(cx)
19467                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
19468                .collect::<String>(),
19469            "bbbb"
19470        );
19471        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
19472            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
19473        });
19474        editor.handle_input("B", window, cx);
19475    });
19476
19477    assert_eq!(
19478        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19479        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
19480        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
19481    );
19482
19483    multi_buffer_editor.update(cx, |editor, cx| {
19484        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
19485    });
19486    assert_eq!(
19487        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19488        "\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",
19489        "After unfolding the all buffers, all original text should be displayed"
19490    );
19491}
19492
19493#[gpui::test]
19494async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
19495    init_test(cx, |_| {});
19496
19497    let sample_text_1 = "1111\n2222\n3333".to_string();
19498    let sample_text_2 = "4444\n5555\n6666".to_string();
19499    let sample_text_3 = "7777\n8888\n9999".to_string();
19500
19501    let fs = FakeFs::new(cx.executor());
19502    fs.insert_tree(
19503        path!("/a"),
19504        json!({
19505            "first.rs": sample_text_1,
19506            "second.rs": sample_text_2,
19507            "third.rs": sample_text_3,
19508        }),
19509    )
19510    .await;
19511    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19512    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19513    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19514    let worktree = project.update(cx, |project, cx| {
19515        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19516        assert_eq!(worktrees.len(), 1);
19517        worktrees.pop().unwrap()
19518    });
19519    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19520
19521    let buffer_1 = project
19522        .update(cx, |project, cx| {
19523            project.open_buffer((worktree_id, "first.rs"), cx)
19524        })
19525        .await
19526        .unwrap();
19527    let buffer_2 = project
19528        .update(cx, |project, cx| {
19529            project.open_buffer((worktree_id, "second.rs"), cx)
19530        })
19531        .await
19532        .unwrap();
19533    let buffer_3 = project
19534        .update(cx, |project, cx| {
19535            project.open_buffer((worktree_id, "third.rs"), cx)
19536        })
19537        .await
19538        .unwrap();
19539
19540    let multi_buffer = cx.new(|cx| {
19541        let mut multi_buffer = MultiBuffer::new(ReadWrite);
19542        multi_buffer.push_excerpts(
19543            buffer_1.clone(),
19544            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19545            cx,
19546        );
19547        multi_buffer.push_excerpts(
19548            buffer_2.clone(),
19549            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19550            cx,
19551        );
19552        multi_buffer.push_excerpts(
19553            buffer_3.clone(),
19554            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19555            cx,
19556        );
19557        multi_buffer
19558    });
19559
19560    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19561        Editor::new(
19562            EditorMode::full(),
19563            multi_buffer,
19564            Some(project.clone()),
19565            window,
19566            cx,
19567        )
19568    });
19569
19570    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
19571    assert_eq!(
19572        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19573        full_text,
19574    );
19575
19576    multi_buffer_editor.update(cx, |editor, cx| {
19577        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
19578    });
19579    assert_eq!(
19580        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19581        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
19582        "After folding the first buffer, its text should not be displayed"
19583    );
19584
19585    multi_buffer_editor.update(cx, |editor, cx| {
19586        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
19587    });
19588
19589    assert_eq!(
19590        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19591        "\n\n\n\n\n\n7777\n8888\n9999",
19592        "After folding the second buffer, its text should not be displayed"
19593    );
19594
19595    multi_buffer_editor.update(cx, |editor, cx| {
19596        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
19597    });
19598    assert_eq!(
19599        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19600        "\n\n\n\n\n",
19601        "After folding the third buffer, its text should not be displayed"
19602    );
19603
19604    multi_buffer_editor.update(cx, |editor, cx| {
19605        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
19606    });
19607    assert_eq!(
19608        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19609        "\n\n\n\n4444\n5555\n6666\n\n",
19610        "After unfolding the second buffer, its text should be displayed"
19611    );
19612
19613    multi_buffer_editor.update(cx, |editor, cx| {
19614        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
19615    });
19616    assert_eq!(
19617        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19618        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
19619        "After unfolding the first buffer, its text should be displayed"
19620    );
19621
19622    multi_buffer_editor.update(cx, |editor, cx| {
19623        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
19624    });
19625    assert_eq!(
19626        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19627        full_text,
19628        "After unfolding all buffers, all original text should be displayed"
19629    );
19630}
19631
19632#[gpui::test]
19633async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
19634    init_test(cx, |_| {});
19635
19636    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
19637
19638    let fs = FakeFs::new(cx.executor());
19639    fs.insert_tree(
19640        path!("/a"),
19641        json!({
19642            "main.rs": sample_text,
19643        }),
19644    )
19645    .await;
19646    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19647    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19648    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19649    let worktree = project.update(cx, |project, cx| {
19650        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19651        assert_eq!(worktrees.len(), 1);
19652        worktrees.pop().unwrap()
19653    });
19654    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19655
19656    let buffer_1 = project
19657        .update(cx, |project, cx| {
19658            project.open_buffer((worktree_id, "main.rs"), cx)
19659        })
19660        .await
19661        .unwrap();
19662
19663    let multi_buffer = cx.new(|cx| {
19664        let mut multi_buffer = MultiBuffer::new(ReadWrite);
19665        multi_buffer.push_excerpts(
19666            buffer_1.clone(),
19667            [ExcerptRange::new(
19668                Point::new(0, 0)
19669                    ..Point::new(
19670                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
19671                        0,
19672                    ),
19673            )],
19674            cx,
19675        );
19676        multi_buffer
19677    });
19678    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19679        Editor::new(
19680            EditorMode::full(),
19681            multi_buffer,
19682            Some(project.clone()),
19683            window,
19684            cx,
19685        )
19686    });
19687
19688    let selection_range = Point::new(1, 0)..Point::new(2, 0);
19689    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19690        enum TestHighlight {}
19691        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
19692        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
19693        editor.highlight_text::<TestHighlight>(
19694            vec![highlight_range.clone()],
19695            HighlightStyle::color(Hsla::green()),
19696            cx,
19697        );
19698        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19699            s.select_ranges(Some(highlight_range))
19700        });
19701    });
19702
19703    let full_text = format!("\n\n{sample_text}");
19704    assert_eq!(
19705        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19706        full_text,
19707    );
19708}
19709
19710#[gpui::test]
19711async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
19712    init_test(cx, |_| {});
19713    cx.update(|cx| {
19714        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
19715            "keymaps/default-linux.json",
19716            cx,
19717        )
19718        .unwrap();
19719        cx.bind_keys(default_key_bindings);
19720    });
19721
19722    let (editor, cx) = cx.add_window_view(|window, cx| {
19723        let multi_buffer = MultiBuffer::build_multi(
19724            [
19725                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
19726                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
19727                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
19728                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
19729            ],
19730            cx,
19731        );
19732        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
19733
19734        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
19735        // fold all but the second buffer, so that we test navigating between two
19736        // adjacent folded buffers, as well as folded buffers at the start and
19737        // end the multibuffer
19738        editor.fold_buffer(buffer_ids[0], cx);
19739        editor.fold_buffer(buffer_ids[2], cx);
19740        editor.fold_buffer(buffer_ids[3], cx);
19741
19742        editor
19743    });
19744    cx.simulate_resize(size(px(1000.), px(1000.)));
19745
19746    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
19747    cx.assert_excerpts_with_selections(indoc! {"
19748        [EXCERPT]
19749        ˇ[FOLDED]
19750        [EXCERPT]
19751        a1
19752        b1
19753        [EXCERPT]
19754        [FOLDED]
19755        [EXCERPT]
19756        [FOLDED]
19757        "
19758    });
19759    cx.simulate_keystroke("down");
19760    cx.assert_excerpts_with_selections(indoc! {"
19761        [EXCERPT]
19762        [FOLDED]
19763        [EXCERPT]
19764        ˇa1
19765        b1
19766        [EXCERPT]
19767        [FOLDED]
19768        [EXCERPT]
19769        [FOLDED]
19770        "
19771    });
19772    cx.simulate_keystroke("down");
19773    cx.assert_excerpts_with_selections(indoc! {"
19774        [EXCERPT]
19775        [FOLDED]
19776        [EXCERPT]
19777        a1
19778        ˇb1
19779        [EXCERPT]
19780        [FOLDED]
19781        [EXCERPT]
19782        [FOLDED]
19783        "
19784    });
19785    cx.simulate_keystroke("down");
19786    cx.assert_excerpts_with_selections(indoc! {"
19787        [EXCERPT]
19788        [FOLDED]
19789        [EXCERPT]
19790        a1
19791        b1
19792        ˇ[EXCERPT]
19793        [FOLDED]
19794        [EXCERPT]
19795        [FOLDED]
19796        "
19797    });
19798    cx.simulate_keystroke("down");
19799    cx.assert_excerpts_with_selections(indoc! {"
19800        [EXCERPT]
19801        [FOLDED]
19802        [EXCERPT]
19803        a1
19804        b1
19805        [EXCERPT]
19806        ˇ[FOLDED]
19807        [EXCERPT]
19808        [FOLDED]
19809        "
19810    });
19811    for _ in 0..5 {
19812        cx.simulate_keystroke("down");
19813        cx.assert_excerpts_with_selections(indoc! {"
19814            [EXCERPT]
19815            [FOLDED]
19816            [EXCERPT]
19817            a1
19818            b1
19819            [EXCERPT]
19820            [FOLDED]
19821            [EXCERPT]
19822            ˇ[FOLDED]
19823            "
19824        });
19825    }
19826
19827    cx.simulate_keystroke("up");
19828    cx.assert_excerpts_with_selections(indoc! {"
19829        [EXCERPT]
19830        [FOLDED]
19831        [EXCERPT]
19832        a1
19833        b1
19834        [EXCERPT]
19835        ˇ[FOLDED]
19836        [EXCERPT]
19837        [FOLDED]
19838        "
19839    });
19840    cx.simulate_keystroke("up");
19841    cx.assert_excerpts_with_selections(indoc! {"
19842        [EXCERPT]
19843        [FOLDED]
19844        [EXCERPT]
19845        a1
19846        b1
19847        ˇ[EXCERPT]
19848        [FOLDED]
19849        [EXCERPT]
19850        [FOLDED]
19851        "
19852    });
19853    cx.simulate_keystroke("up");
19854    cx.assert_excerpts_with_selections(indoc! {"
19855        [EXCERPT]
19856        [FOLDED]
19857        [EXCERPT]
19858        a1
19859        ˇb1
19860        [EXCERPT]
19861        [FOLDED]
19862        [EXCERPT]
19863        [FOLDED]
19864        "
19865    });
19866    cx.simulate_keystroke("up");
19867    cx.assert_excerpts_with_selections(indoc! {"
19868        [EXCERPT]
19869        [FOLDED]
19870        [EXCERPT]
19871        ˇa1
19872        b1
19873        [EXCERPT]
19874        [FOLDED]
19875        [EXCERPT]
19876        [FOLDED]
19877        "
19878    });
19879    for _ in 0..5 {
19880        cx.simulate_keystroke("up");
19881        cx.assert_excerpts_with_selections(indoc! {"
19882            [EXCERPT]
19883            ˇ[FOLDED]
19884            [EXCERPT]
19885            a1
19886            b1
19887            [EXCERPT]
19888            [FOLDED]
19889            [EXCERPT]
19890            [FOLDED]
19891            "
19892        });
19893    }
19894}
19895
19896#[gpui::test]
19897async fn test_inline_completion_text(cx: &mut TestAppContext) {
19898    init_test(cx, |_| {});
19899
19900    // Simple insertion
19901    assert_highlighted_edits(
19902        "Hello, world!",
19903        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
19904        true,
19905        cx,
19906        |highlighted_edits, cx| {
19907            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
19908            assert_eq!(highlighted_edits.highlights.len(), 1);
19909            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
19910            assert_eq!(
19911                highlighted_edits.highlights[0].1.background_color,
19912                Some(cx.theme().status().created_background)
19913            );
19914        },
19915    )
19916    .await;
19917
19918    // Replacement
19919    assert_highlighted_edits(
19920        "This is a test.",
19921        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
19922        false,
19923        cx,
19924        |highlighted_edits, cx| {
19925            assert_eq!(highlighted_edits.text, "That is a test.");
19926            assert_eq!(highlighted_edits.highlights.len(), 1);
19927            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
19928            assert_eq!(
19929                highlighted_edits.highlights[0].1.background_color,
19930                Some(cx.theme().status().created_background)
19931            );
19932        },
19933    )
19934    .await;
19935
19936    // Multiple edits
19937    assert_highlighted_edits(
19938        "Hello, world!",
19939        vec![
19940            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
19941            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
19942        ],
19943        false,
19944        cx,
19945        |highlighted_edits, cx| {
19946            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
19947            assert_eq!(highlighted_edits.highlights.len(), 2);
19948            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
19949            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
19950            assert_eq!(
19951                highlighted_edits.highlights[0].1.background_color,
19952                Some(cx.theme().status().created_background)
19953            );
19954            assert_eq!(
19955                highlighted_edits.highlights[1].1.background_color,
19956                Some(cx.theme().status().created_background)
19957            );
19958        },
19959    )
19960    .await;
19961
19962    // Multiple lines with edits
19963    assert_highlighted_edits(
19964        "First line\nSecond line\nThird line\nFourth line",
19965        vec![
19966            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
19967            (
19968                Point::new(2, 0)..Point::new(2, 10),
19969                "New third line".to_string(),
19970            ),
19971            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
19972        ],
19973        false,
19974        cx,
19975        |highlighted_edits, cx| {
19976            assert_eq!(
19977                highlighted_edits.text,
19978                "Second modified\nNew third line\nFourth updated line"
19979            );
19980            assert_eq!(highlighted_edits.highlights.len(), 3);
19981            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
19982            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
19983            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
19984            for highlight in &highlighted_edits.highlights {
19985                assert_eq!(
19986                    highlight.1.background_color,
19987                    Some(cx.theme().status().created_background)
19988                );
19989            }
19990        },
19991    )
19992    .await;
19993}
19994
19995#[gpui::test]
19996async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
19997    init_test(cx, |_| {});
19998
19999    // Deletion
20000    assert_highlighted_edits(
20001        "Hello, world!",
20002        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
20003        true,
20004        cx,
20005        |highlighted_edits, cx| {
20006            assert_eq!(highlighted_edits.text, "Hello, world!");
20007            assert_eq!(highlighted_edits.highlights.len(), 1);
20008            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
20009            assert_eq!(
20010                highlighted_edits.highlights[0].1.background_color,
20011                Some(cx.theme().status().deleted_background)
20012            );
20013        },
20014    )
20015    .await;
20016
20017    // Insertion
20018    assert_highlighted_edits(
20019        "Hello, world!",
20020        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
20021        true,
20022        cx,
20023        |highlighted_edits, cx| {
20024            assert_eq!(highlighted_edits.highlights.len(), 1);
20025            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
20026            assert_eq!(
20027                highlighted_edits.highlights[0].1.background_color,
20028                Some(cx.theme().status().created_background)
20029            );
20030        },
20031    )
20032    .await;
20033}
20034
20035async fn assert_highlighted_edits(
20036    text: &str,
20037    edits: Vec<(Range<Point>, String)>,
20038    include_deletions: bool,
20039    cx: &mut TestAppContext,
20040    assertion_fn: impl Fn(HighlightedText, &App),
20041) {
20042    let window = cx.add_window(|window, cx| {
20043        let buffer = MultiBuffer::build_simple(text, cx);
20044        Editor::new(EditorMode::full(), buffer, None, window, cx)
20045    });
20046    let cx = &mut VisualTestContext::from_window(*window, cx);
20047
20048    let (buffer, snapshot) = window
20049        .update(cx, |editor, _window, cx| {
20050            (
20051                editor.buffer().clone(),
20052                editor.buffer().read(cx).snapshot(cx),
20053            )
20054        })
20055        .unwrap();
20056
20057    let edits = edits
20058        .into_iter()
20059        .map(|(range, edit)| {
20060            (
20061                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
20062                edit,
20063            )
20064        })
20065        .collect::<Vec<_>>();
20066
20067    let text_anchor_edits = edits
20068        .clone()
20069        .into_iter()
20070        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
20071        .collect::<Vec<_>>();
20072
20073    let edit_preview = window
20074        .update(cx, |_, _window, cx| {
20075            buffer
20076                .read(cx)
20077                .as_singleton()
20078                .unwrap()
20079                .read(cx)
20080                .preview_edits(text_anchor_edits.into(), cx)
20081        })
20082        .unwrap()
20083        .await;
20084
20085    cx.update(|_window, cx| {
20086        let highlighted_edits = inline_completion_edit_text(
20087            &snapshot.as_singleton().unwrap().2,
20088            &edits,
20089            &edit_preview,
20090            include_deletions,
20091            cx,
20092        );
20093        assertion_fn(highlighted_edits, cx)
20094    });
20095}
20096
20097#[track_caller]
20098fn assert_breakpoint(
20099    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
20100    path: &Arc<Path>,
20101    expected: Vec<(u32, Breakpoint)>,
20102) {
20103    if expected.len() == 0usize {
20104        assert!(!breakpoints.contains_key(path), "{}", path.display());
20105    } else {
20106        let mut breakpoint = breakpoints
20107            .get(path)
20108            .unwrap()
20109            .into_iter()
20110            .map(|breakpoint| {
20111                (
20112                    breakpoint.row,
20113                    Breakpoint {
20114                        message: breakpoint.message.clone(),
20115                        state: breakpoint.state,
20116                        condition: breakpoint.condition.clone(),
20117                        hit_condition: breakpoint.hit_condition.clone(),
20118                    },
20119                )
20120            })
20121            .collect::<Vec<_>>();
20122
20123        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
20124
20125        assert_eq!(expected, breakpoint);
20126    }
20127}
20128
20129fn add_log_breakpoint_at_cursor(
20130    editor: &mut Editor,
20131    log_message: &str,
20132    window: &mut Window,
20133    cx: &mut Context<Editor>,
20134) {
20135    let (anchor, bp) = editor
20136        .breakpoints_at_cursors(window, cx)
20137        .first()
20138        .and_then(|(anchor, bp)| {
20139            if let Some(bp) = bp {
20140                Some((*anchor, bp.clone()))
20141            } else {
20142                None
20143            }
20144        })
20145        .unwrap_or_else(|| {
20146            let cursor_position: Point = editor.selections.newest(cx).head();
20147
20148            let breakpoint_position = editor
20149                .snapshot(window, cx)
20150                .display_snapshot
20151                .buffer_snapshot
20152                .anchor_before(Point::new(cursor_position.row, 0));
20153
20154            (breakpoint_position, Breakpoint::new_log(&log_message))
20155        });
20156
20157    editor.edit_breakpoint_at_anchor(
20158        anchor,
20159        bp,
20160        BreakpointEditAction::EditLogMessage(log_message.into()),
20161        cx,
20162    );
20163}
20164
20165#[gpui::test]
20166async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
20167    init_test(cx, |_| {});
20168
20169    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20170    let fs = FakeFs::new(cx.executor());
20171    fs.insert_tree(
20172        path!("/a"),
20173        json!({
20174            "main.rs": sample_text,
20175        }),
20176    )
20177    .await;
20178    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20179    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20180    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20181
20182    let fs = FakeFs::new(cx.executor());
20183    fs.insert_tree(
20184        path!("/a"),
20185        json!({
20186            "main.rs": sample_text,
20187        }),
20188    )
20189    .await;
20190    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20191    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20192    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20193    let worktree_id = workspace
20194        .update(cx, |workspace, _window, cx| {
20195            workspace.project().update(cx, |project, cx| {
20196                project.worktrees(cx).next().unwrap().read(cx).id()
20197            })
20198        })
20199        .unwrap();
20200
20201    let buffer = project
20202        .update(cx, |project, cx| {
20203            project.open_buffer((worktree_id, "main.rs"), cx)
20204        })
20205        .await
20206        .unwrap();
20207
20208    let (editor, cx) = cx.add_window_view(|window, cx| {
20209        Editor::new(
20210            EditorMode::full(),
20211            MultiBuffer::build_from_buffer(buffer, cx),
20212            Some(project.clone()),
20213            window,
20214            cx,
20215        )
20216    });
20217
20218    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20219    let abs_path = project.read_with(cx, |project, cx| {
20220        project
20221            .absolute_path(&project_path, cx)
20222            .map(|path_buf| Arc::from(path_buf.to_owned()))
20223            .unwrap()
20224    });
20225
20226    // assert we can add breakpoint on the first line
20227    editor.update_in(cx, |editor, window, cx| {
20228        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20229        editor.move_to_end(&MoveToEnd, window, cx);
20230        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20231    });
20232
20233    let breakpoints = editor.update(cx, |editor, cx| {
20234        editor
20235            .breakpoint_store()
20236            .as_ref()
20237            .unwrap()
20238            .read(cx)
20239            .all_source_breakpoints(cx)
20240            .clone()
20241    });
20242
20243    assert_eq!(1, breakpoints.len());
20244    assert_breakpoint(
20245        &breakpoints,
20246        &abs_path,
20247        vec![
20248            (0, Breakpoint::new_standard()),
20249            (3, Breakpoint::new_standard()),
20250        ],
20251    );
20252
20253    editor.update_in(cx, |editor, window, cx| {
20254        editor.move_to_beginning(&MoveToBeginning, window, cx);
20255        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20256    });
20257
20258    let breakpoints = editor.update(cx, |editor, cx| {
20259        editor
20260            .breakpoint_store()
20261            .as_ref()
20262            .unwrap()
20263            .read(cx)
20264            .all_source_breakpoints(cx)
20265            .clone()
20266    });
20267
20268    assert_eq!(1, breakpoints.len());
20269    assert_breakpoint(
20270        &breakpoints,
20271        &abs_path,
20272        vec![(3, Breakpoint::new_standard())],
20273    );
20274
20275    editor.update_in(cx, |editor, window, cx| {
20276        editor.move_to_end(&MoveToEnd, window, cx);
20277        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20278    });
20279
20280    let breakpoints = editor.update(cx, |editor, cx| {
20281        editor
20282            .breakpoint_store()
20283            .as_ref()
20284            .unwrap()
20285            .read(cx)
20286            .all_source_breakpoints(cx)
20287            .clone()
20288    });
20289
20290    assert_eq!(0, breakpoints.len());
20291    assert_breakpoint(&breakpoints, &abs_path, vec![]);
20292}
20293
20294#[gpui::test]
20295async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
20296    init_test(cx, |_| {});
20297
20298    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20299
20300    let fs = FakeFs::new(cx.executor());
20301    fs.insert_tree(
20302        path!("/a"),
20303        json!({
20304            "main.rs": sample_text,
20305        }),
20306    )
20307    .await;
20308    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20309    let (workspace, cx) =
20310        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
20311
20312    let worktree_id = workspace.update(cx, |workspace, cx| {
20313        workspace.project().update(cx, |project, cx| {
20314            project.worktrees(cx).next().unwrap().read(cx).id()
20315        })
20316    });
20317
20318    let buffer = project
20319        .update(cx, |project, cx| {
20320            project.open_buffer((worktree_id, "main.rs"), cx)
20321        })
20322        .await
20323        .unwrap();
20324
20325    let (editor, cx) = cx.add_window_view(|window, cx| {
20326        Editor::new(
20327            EditorMode::full(),
20328            MultiBuffer::build_from_buffer(buffer, cx),
20329            Some(project.clone()),
20330            window,
20331            cx,
20332        )
20333    });
20334
20335    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20336    let abs_path = project.read_with(cx, |project, cx| {
20337        project
20338            .absolute_path(&project_path, cx)
20339            .map(|path_buf| Arc::from(path_buf.to_owned()))
20340            .unwrap()
20341    });
20342
20343    editor.update_in(cx, |editor, window, cx| {
20344        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
20345    });
20346
20347    let breakpoints = editor.update(cx, |editor, cx| {
20348        editor
20349            .breakpoint_store()
20350            .as_ref()
20351            .unwrap()
20352            .read(cx)
20353            .all_source_breakpoints(cx)
20354            .clone()
20355    });
20356
20357    assert_breakpoint(
20358        &breakpoints,
20359        &abs_path,
20360        vec![(0, Breakpoint::new_log("hello world"))],
20361    );
20362
20363    // Removing a log message from a log breakpoint should remove it
20364    editor.update_in(cx, |editor, window, cx| {
20365        add_log_breakpoint_at_cursor(editor, "", window, cx);
20366    });
20367
20368    let breakpoints = editor.update(cx, |editor, cx| {
20369        editor
20370            .breakpoint_store()
20371            .as_ref()
20372            .unwrap()
20373            .read(cx)
20374            .all_source_breakpoints(cx)
20375            .clone()
20376    });
20377
20378    assert_breakpoint(&breakpoints, &abs_path, vec![]);
20379
20380    editor.update_in(cx, |editor, window, cx| {
20381        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20382        editor.move_to_end(&MoveToEnd, window, cx);
20383        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20384        // Not adding a log message to a standard breakpoint shouldn't remove it
20385        add_log_breakpoint_at_cursor(editor, "", window, cx);
20386    });
20387
20388    let breakpoints = editor.update(cx, |editor, cx| {
20389        editor
20390            .breakpoint_store()
20391            .as_ref()
20392            .unwrap()
20393            .read(cx)
20394            .all_source_breakpoints(cx)
20395            .clone()
20396    });
20397
20398    assert_breakpoint(
20399        &breakpoints,
20400        &abs_path,
20401        vec![
20402            (0, Breakpoint::new_standard()),
20403            (3, Breakpoint::new_standard()),
20404        ],
20405    );
20406
20407    editor.update_in(cx, |editor, window, cx| {
20408        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
20409    });
20410
20411    let breakpoints = editor.update(cx, |editor, cx| {
20412        editor
20413            .breakpoint_store()
20414            .as_ref()
20415            .unwrap()
20416            .read(cx)
20417            .all_source_breakpoints(cx)
20418            .clone()
20419    });
20420
20421    assert_breakpoint(
20422        &breakpoints,
20423        &abs_path,
20424        vec![
20425            (0, Breakpoint::new_standard()),
20426            (3, Breakpoint::new_log("hello world")),
20427        ],
20428    );
20429
20430    editor.update_in(cx, |editor, window, cx| {
20431        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
20432    });
20433
20434    let breakpoints = editor.update(cx, |editor, cx| {
20435        editor
20436            .breakpoint_store()
20437            .as_ref()
20438            .unwrap()
20439            .read(cx)
20440            .all_source_breakpoints(cx)
20441            .clone()
20442    });
20443
20444    assert_breakpoint(
20445        &breakpoints,
20446        &abs_path,
20447        vec![
20448            (0, Breakpoint::new_standard()),
20449            (3, Breakpoint::new_log("hello Earth!!")),
20450        ],
20451    );
20452}
20453
20454/// This also tests that Editor::breakpoint_at_cursor_head is working properly
20455/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
20456/// or when breakpoints were placed out of order. This tests for a regression too
20457#[gpui::test]
20458async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
20459    init_test(cx, |_| {});
20460
20461    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20462    let fs = FakeFs::new(cx.executor());
20463    fs.insert_tree(
20464        path!("/a"),
20465        json!({
20466            "main.rs": sample_text,
20467        }),
20468    )
20469    .await;
20470    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20471    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20472    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20473
20474    let fs = FakeFs::new(cx.executor());
20475    fs.insert_tree(
20476        path!("/a"),
20477        json!({
20478            "main.rs": sample_text,
20479        }),
20480    )
20481    .await;
20482    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20483    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20484    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20485    let worktree_id = workspace
20486        .update(cx, |workspace, _window, cx| {
20487            workspace.project().update(cx, |project, cx| {
20488                project.worktrees(cx).next().unwrap().read(cx).id()
20489            })
20490        })
20491        .unwrap();
20492
20493    let buffer = project
20494        .update(cx, |project, cx| {
20495            project.open_buffer((worktree_id, "main.rs"), cx)
20496        })
20497        .await
20498        .unwrap();
20499
20500    let (editor, cx) = cx.add_window_view(|window, cx| {
20501        Editor::new(
20502            EditorMode::full(),
20503            MultiBuffer::build_from_buffer(buffer, cx),
20504            Some(project.clone()),
20505            window,
20506            cx,
20507        )
20508    });
20509
20510    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20511    let abs_path = project.read_with(cx, |project, cx| {
20512        project
20513            .absolute_path(&project_path, cx)
20514            .map(|path_buf| Arc::from(path_buf.to_owned()))
20515            .unwrap()
20516    });
20517
20518    // assert we can add breakpoint on the first line
20519    editor.update_in(cx, |editor, window, cx| {
20520        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20521        editor.move_to_end(&MoveToEnd, window, cx);
20522        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20523        editor.move_up(&MoveUp, window, cx);
20524        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20525    });
20526
20527    let breakpoints = editor.update(cx, |editor, cx| {
20528        editor
20529            .breakpoint_store()
20530            .as_ref()
20531            .unwrap()
20532            .read(cx)
20533            .all_source_breakpoints(cx)
20534            .clone()
20535    });
20536
20537    assert_eq!(1, breakpoints.len());
20538    assert_breakpoint(
20539        &breakpoints,
20540        &abs_path,
20541        vec![
20542            (0, Breakpoint::new_standard()),
20543            (2, Breakpoint::new_standard()),
20544            (3, Breakpoint::new_standard()),
20545        ],
20546    );
20547
20548    editor.update_in(cx, |editor, window, cx| {
20549        editor.move_to_beginning(&MoveToBeginning, window, cx);
20550        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20551        editor.move_to_end(&MoveToEnd, window, cx);
20552        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20553        // Disabling a breakpoint that doesn't exist should do nothing
20554        editor.move_up(&MoveUp, window, cx);
20555        editor.move_up(&MoveUp, window, cx);
20556        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20557    });
20558
20559    let breakpoints = editor.update(cx, |editor, cx| {
20560        editor
20561            .breakpoint_store()
20562            .as_ref()
20563            .unwrap()
20564            .read(cx)
20565            .all_source_breakpoints(cx)
20566            .clone()
20567    });
20568
20569    let disable_breakpoint = {
20570        let mut bp = Breakpoint::new_standard();
20571        bp.state = BreakpointState::Disabled;
20572        bp
20573    };
20574
20575    assert_eq!(1, breakpoints.len());
20576    assert_breakpoint(
20577        &breakpoints,
20578        &abs_path,
20579        vec![
20580            (0, disable_breakpoint.clone()),
20581            (2, Breakpoint::new_standard()),
20582            (3, disable_breakpoint.clone()),
20583        ],
20584    );
20585
20586    editor.update_in(cx, |editor, window, cx| {
20587        editor.move_to_beginning(&MoveToBeginning, window, cx);
20588        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
20589        editor.move_to_end(&MoveToEnd, window, cx);
20590        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
20591        editor.move_up(&MoveUp, window, cx);
20592        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20593    });
20594
20595    let breakpoints = editor.update(cx, |editor, cx| {
20596        editor
20597            .breakpoint_store()
20598            .as_ref()
20599            .unwrap()
20600            .read(cx)
20601            .all_source_breakpoints(cx)
20602            .clone()
20603    });
20604
20605    assert_eq!(1, breakpoints.len());
20606    assert_breakpoint(
20607        &breakpoints,
20608        &abs_path,
20609        vec![
20610            (0, Breakpoint::new_standard()),
20611            (2, disable_breakpoint),
20612            (3, Breakpoint::new_standard()),
20613        ],
20614    );
20615}
20616
20617#[gpui::test]
20618async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
20619    init_test(cx, |_| {});
20620    let capabilities = lsp::ServerCapabilities {
20621        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
20622            prepare_provider: Some(true),
20623            work_done_progress_options: Default::default(),
20624        })),
20625        ..Default::default()
20626    };
20627    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
20628
20629    cx.set_state(indoc! {"
20630        struct Fˇoo {}
20631    "});
20632
20633    cx.update_editor(|editor, _, cx| {
20634        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
20635        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
20636        editor.highlight_background::<DocumentHighlightRead>(
20637            &[highlight_range],
20638            |theme| theme.colors().editor_document_highlight_read_background,
20639            cx,
20640        );
20641    });
20642
20643    let mut prepare_rename_handler = cx
20644        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
20645            move |_, _, _| async move {
20646                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
20647                    start: lsp::Position {
20648                        line: 0,
20649                        character: 7,
20650                    },
20651                    end: lsp::Position {
20652                        line: 0,
20653                        character: 10,
20654                    },
20655                })))
20656            },
20657        );
20658    let prepare_rename_task = cx
20659        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
20660        .expect("Prepare rename was not started");
20661    prepare_rename_handler.next().await.unwrap();
20662    prepare_rename_task.await.expect("Prepare rename failed");
20663
20664    let mut rename_handler =
20665        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
20666            let edit = lsp::TextEdit {
20667                range: lsp::Range {
20668                    start: lsp::Position {
20669                        line: 0,
20670                        character: 7,
20671                    },
20672                    end: lsp::Position {
20673                        line: 0,
20674                        character: 10,
20675                    },
20676                },
20677                new_text: "FooRenamed".to_string(),
20678            };
20679            Ok(Some(lsp::WorkspaceEdit::new(
20680                // Specify the same edit twice
20681                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
20682            )))
20683        });
20684    let rename_task = cx
20685        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
20686        .expect("Confirm rename was not started");
20687    rename_handler.next().await.unwrap();
20688    rename_task.await.expect("Confirm rename failed");
20689    cx.run_until_parked();
20690
20691    // Despite two edits, only one is actually applied as those are identical
20692    cx.assert_editor_state(indoc! {"
20693        struct FooRenamedˇ {}
20694    "});
20695}
20696
20697#[gpui::test]
20698async fn test_rename_without_prepare(cx: &mut TestAppContext) {
20699    init_test(cx, |_| {});
20700    // These capabilities indicate that the server does not support prepare rename.
20701    let capabilities = lsp::ServerCapabilities {
20702        rename_provider: Some(lsp::OneOf::Left(true)),
20703        ..Default::default()
20704    };
20705    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
20706
20707    cx.set_state(indoc! {"
20708        struct Fˇoo {}
20709    "});
20710
20711    cx.update_editor(|editor, _window, cx| {
20712        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
20713        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
20714        editor.highlight_background::<DocumentHighlightRead>(
20715            &[highlight_range],
20716            |theme| theme.colors().editor_document_highlight_read_background,
20717            cx,
20718        );
20719    });
20720
20721    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
20722        .expect("Prepare rename was not started")
20723        .await
20724        .expect("Prepare rename failed");
20725
20726    let mut rename_handler =
20727        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
20728            let edit = lsp::TextEdit {
20729                range: lsp::Range {
20730                    start: lsp::Position {
20731                        line: 0,
20732                        character: 7,
20733                    },
20734                    end: lsp::Position {
20735                        line: 0,
20736                        character: 10,
20737                    },
20738                },
20739                new_text: "FooRenamed".to_string(),
20740            };
20741            Ok(Some(lsp::WorkspaceEdit::new(
20742                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
20743            )))
20744        });
20745    let rename_task = cx
20746        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
20747        .expect("Confirm rename was not started");
20748    rename_handler.next().await.unwrap();
20749    rename_task.await.expect("Confirm rename failed");
20750    cx.run_until_parked();
20751
20752    // Correct range is renamed, as `surrounding_word` is used to find it.
20753    cx.assert_editor_state(indoc! {"
20754        struct FooRenamedˇ {}
20755    "});
20756}
20757
20758#[gpui::test]
20759async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
20760    init_test(cx, |_| {});
20761    let mut cx = EditorTestContext::new(cx).await;
20762
20763    let language = Arc::new(
20764        Language::new(
20765            LanguageConfig::default(),
20766            Some(tree_sitter_html::LANGUAGE.into()),
20767        )
20768        .with_brackets_query(
20769            r#"
20770            ("<" @open "/>" @close)
20771            ("</" @open ">" @close)
20772            ("<" @open ">" @close)
20773            ("\"" @open "\"" @close)
20774            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
20775        "#,
20776        )
20777        .unwrap(),
20778    );
20779    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
20780
20781    cx.set_state(indoc! {"
20782        <span>ˇ</span>
20783    "});
20784    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
20785    cx.assert_editor_state(indoc! {"
20786        <span>
20787        ˇ
20788        </span>
20789    "});
20790
20791    cx.set_state(indoc! {"
20792        <span><span></span>ˇ</span>
20793    "});
20794    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
20795    cx.assert_editor_state(indoc! {"
20796        <span><span></span>
20797        ˇ</span>
20798    "});
20799
20800    cx.set_state(indoc! {"
20801        <span>ˇ
20802        </span>
20803    "});
20804    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
20805    cx.assert_editor_state(indoc! {"
20806        <span>
20807        ˇ
20808        </span>
20809    "});
20810}
20811
20812#[gpui::test(iterations = 10)]
20813async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
20814    init_test(cx, |_| {});
20815
20816    let fs = FakeFs::new(cx.executor());
20817    fs.insert_tree(
20818        path!("/dir"),
20819        json!({
20820            "a.ts": "a",
20821        }),
20822    )
20823    .await;
20824
20825    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
20826    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20827    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20828
20829    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
20830    language_registry.add(Arc::new(Language::new(
20831        LanguageConfig {
20832            name: "TypeScript".into(),
20833            matcher: LanguageMatcher {
20834                path_suffixes: vec!["ts".to_string()],
20835                ..Default::default()
20836            },
20837            ..Default::default()
20838        },
20839        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
20840    )));
20841    let mut fake_language_servers = language_registry.register_fake_lsp(
20842        "TypeScript",
20843        FakeLspAdapter {
20844            capabilities: lsp::ServerCapabilities {
20845                code_lens_provider: Some(lsp::CodeLensOptions {
20846                    resolve_provider: Some(true),
20847                }),
20848                execute_command_provider: Some(lsp::ExecuteCommandOptions {
20849                    commands: vec!["_the/command".to_string()],
20850                    ..lsp::ExecuteCommandOptions::default()
20851                }),
20852                ..lsp::ServerCapabilities::default()
20853            },
20854            ..FakeLspAdapter::default()
20855        },
20856    );
20857
20858    let (buffer, _handle) = project
20859        .update(cx, |p, cx| {
20860            p.open_local_buffer_with_lsp(path!("/dir/a.ts"), cx)
20861        })
20862        .await
20863        .unwrap();
20864    cx.executor().run_until_parked();
20865
20866    let fake_server = fake_language_servers.next().await.unwrap();
20867
20868    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
20869    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
20870    drop(buffer_snapshot);
20871    let actions = cx
20872        .update_window(*workspace, |_, window, cx| {
20873            project.code_actions(&buffer, anchor..anchor, window, cx)
20874        })
20875        .unwrap();
20876
20877    fake_server
20878        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
20879            Ok(Some(vec![
20880                lsp::CodeLens {
20881                    range: lsp::Range::default(),
20882                    command: Some(lsp::Command {
20883                        title: "Code lens command".to_owned(),
20884                        command: "_the/command".to_owned(),
20885                        arguments: None,
20886                    }),
20887                    data: None,
20888                },
20889                lsp::CodeLens {
20890                    range: lsp::Range::default(),
20891                    command: Some(lsp::Command {
20892                        title: "Command not in capabilities".to_owned(),
20893                        command: "not in capabilities".to_owned(),
20894                        arguments: None,
20895                    }),
20896                    data: None,
20897                },
20898                lsp::CodeLens {
20899                    range: lsp::Range {
20900                        start: lsp::Position {
20901                            line: 1,
20902                            character: 1,
20903                        },
20904                        end: lsp::Position {
20905                            line: 1,
20906                            character: 1,
20907                        },
20908                    },
20909                    command: Some(lsp::Command {
20910                        title: "Command not in range".to_owned(),
20911                        command: "_the/command".to_owned(),
20912                        arguments: None,
20913                    }),
20914                    data: None,
20915                },
20916            ]))
20917        })
20918        .next()
20919        .await;
20920
20921    let actions = actions.await.unwrap();
20922    assert_eq!(
20923        actions.len(),
20924        1,
20925        "Should have only one valid action for the 0..0 range"
20926    );
20927    let action = actions[0].clone();
20928    let apply = project.update(cx, |project, cx| {
20929        project.apply_code_action(buffer.clone(), action, true, cx)
20930    });
20931
20932    // Resolving the code action does not populate its edits. In absence of
20933    // edits, we must execute the given command.
20934    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
20935        |mut lens, _| async move {
20936            let lens_command = lens.command.as_mut().expect("should have a command");
20937            assert_eq!(lens_command.title, "Code lens command");
20938            lens_command.arguments = Some(vec![json!("the-argument")]);
20939            Ok(lens)
20940        },
20941    );
20942
20943    // While executing the command, the language server sends the editor
20944    // a `workspaceEdit` request.
20945    fake_server
20946        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
20947            let fake = fake_server.clone();
20948            move |params, _| {
20949                assert_eq!(params.command, "_the/command");
20950                let fake = fake.clone();
20951                async move {
20952                    fake.server
20953                        .request::<lsp::request::ApplyWorkspaceEdit>(
20954                            lsp::ApplyWorkspaceEditParams {
20955                                label: None,
20956                                edit: lsp::WorkspaceEdit {
20957                                    changes: Some(
20958                                        [(
20959                                            lsp::Url::from_file_path(path!("/dir/a.ts")).unwrap(),
20960                                            vec![lsp::TextEdit {
20961                                                range: lsp::Range::new(
20962                                                    lsp::Position::new(0, 0),
20963                                                    lsp::Position::new(0, 0),
20964                                                ),
20965                                                new_text: "X".into(),
20966                                            }],
20967                                        )]
20968                                        .into_iter()
20969                                        .collect(),
20970                                    ),
20971                                    ..Default::default()
20972                                },
20973                            },
20974                        )
20975                        .await
20976                        .into_response()
20977                        .unwrap();
20978                    Ok(Some(json!(null)))
20979                }
20980            }
20981        })
20982        .next()
20983        .await;
20984
20985    // Applying the code lens command returns a project transaction containing the edits
20986    // sent by the language server in its `workspaceEdit` request.
20987    let transaction = apply.await.unwrap();
20988    assert!(transaction.0.contains_key(&buffer));
20989    buffer.update(cx, |buffer, cx| {
20990        assert_eq!(buffer.text(), "Xa");
20991        buffer.undo(cx);
20992        assert_eq!(buffer.text(), "a");
20993    });
20994}
20995
20996#[gpui::test]
20997async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
20998    init_test(cx, |_| {});
20999
21000    let fs = FakeFs::new(cx.executor());
21001    let main_text = r#"fn main() {
21002println!("1");
21003println!("2");
21004println!("3");
21005println!("4");
21006println!("5");
21007}"#;
21008    let lib_text = "mod foo {}";
21009    fs.insert_tree(
21010        path!("/a"),
21011        json!({
21012            "lib.rs": lib_text,
21013            "main.rs": main_text,
21014        }),
21015    )
21016    .await;
21017
21018    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21019    let (workspace, cx) =
21020        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21021    let worktree_id = workspace.update(cx, |workspace, cx| {
21022        workspace.project().update(cx, |project, cx| {
21023            project.worktrees(cx).next().unwrap().read(cx).id()
21024        })
21025    });
21026
21027    let expected_ranges = vec![
21028        Point::new(0, 0)..Point::new(0, 0),
21029        Point::new(1, 0)..Point::new(1, 1),
21030        Point::new(2, 0)..Point::new(2, 2),
21031        Point::new(3, 0)..Point::new(3, 3),
21032    ];
21033
21034    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21035    let editor_1 = workspace
21036        .update_in(cx, |workspace, window, cx| {
21037            workspace.open_path(
21038                (worktree_id, "main.rs"),
21039                Some(pane_1.downgrade()),
21040                true,
21041                window,
21042                cx,
21043            )
21044        })
21045        .unwrap()
21046        .await
21047        .downcast::<Editor>()
21048        .unwrap();
21049    pane_1.update(cx, |pane, cx| {
21050        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21051        open_editor.update(cx, |editor, cx| {
21052            assert_eq!(
21053                editor.display_text(cx),
21054                main_text,
21055                "Original main.rs text on initial open",
21056            );
21057            assert_eq!(
21058                editor
21059                    .selections
21060                    .all::<Point>(cx)
21061                    .into_iter()
21062                    .map(|s| s.range())
21063                    .collect::<Vec<_>>(),
21064                vec![Point::zero()..Point::zero()],
21065                "Default selections on initial open",
21066            );
21067        })
21068    });
21069    editor_1.update_in(cx, |editor, window, cx| {
21070        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21071            s.select_ranges(expected_ranges.clone());
21072        });
21073    });
21074
21075    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
21076        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
21077    });
21078    let editor_2 = workspace
21079        .update_in(cx, |workspace, window, cx| {
21080            workspace.open_path(
21081                (worktree_id, "main.rs"),
21082                Some(pane_2.downgrade()),
21083                true,
21084                window,
21085                cx,
21086            )
21087        })
21088        .unwrap()
21089        .await
21090        .downcast::<Editor>()
21091        .unwrap();
21092    pane_2.update(cx, |pane, cx| {
21093        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21094        open_editor.update(cx, |editor, cx| {
21095            assert_eq!(
21096                editor.display_text(cx),
21097                main_text,
21098                "Original main.rs text on initial open in another panel",
21099            );
21100            assert_eq!(
21101                editor
21102                    .selections
21103                    .all::<Point>(cx)
21104                    .into_iter()
21105                    .map(|s| s.range())
21106                    .collect::<Vec<_>>(),
21107                vec![Point::zero()..Point::zero()],
21108                "Default selections on initial open in another panel",
21109            );
21110        })
21111    });
21112
21113    editor_2.update_in(cx, |editor, window, cx| {
21114        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
21115    });
21116
21117    let _other_editor_1 = workspace
21118        .update_in(cx, |workspace, window, cx| {
21119            workspace.open_path(
21120                (worktree_id, "lib.rs"),
21121                Some(pane_1.downgrade()),
21122                true,
21123                window,
21124                cx,
21125            )
21126        })
21127        .unwrap()
21128        .await
21129        .downcast::<Editor>()
21130        .unwrap();
21131    pane_1
21132        .update_in(cx, |pane, window, cx| {
21133            pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
21134        })
21135        .await
21136        .unwrap();
21137    drop(editor_1);
21138    pane_1.update(cx, |pane, cx| {
21139        pane.active_item()
21140            .unwrap()
21141            .downcast::<Editor>()
21142            .unwrap()
21143            .update(cx, |editor, cx| {
21144                assert_eq!(
21145                    editor.display_text(cx),
21146                    lib_text,
21147                    "Other file should be open and active",
21148                );
21149            });
21150        assert_eq!(pane.items().count(), 1, "No other editors should be open");
21151    });
21152
21153    let _other_editor_2 = workspace
21154        .update_in(cx, |workspace, window, cx| {
21155            workspace.open_path(
21156                (worktree_id, "lib.rs"),
21157                Some(pane_2.downgrade()),
21158                true,
21159                window,
21160                cx,
21161            )
21162        })
21163        .unwrap()
21164        .await
21165        .downcast::<Editor>()
21166        .unwrap();
21167    pane_2
21168        .update_in(cx, |pane, window, cx| {
21169            pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
21170        })
21171        .await
21172        .unwrap();
21173    drop(editor_2);
21174    pane_2.update(cx, |pane, cx| {
21175        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21176        open_editor.update(cx, |editor, cx| {
21177            assert_eq!(
21178                editor.display_text(cx),
21179                lib_text,
21180                "Other file should be open and active in another panel too",
21181            );
21182        });
21183        assert_eq!(
21184            pane.items().count(),
21185            1,
21186            "No other editors should be open in another pane",
21187        );
21188    });
21189
21190    let _editor_1_reopened = workspace
21191        .update_in(cx, |workspace, window, cx| {
21192            workspace.open_path(
21193                (worktree_id, "main.rs"),
21194                Some(pane_1.downgrade()),
21195                true,
21196                window,
21197                cx,
21198            )
21199        })
21200        .unwrap()
21201        .await
21202        .downcast::<Editor>()
21203        .unwrap();
21204    let _editor_2_reopened = workspace
21205        .update_in(cx, |workspace, window, cx| {
21206            workspace.open_path(
21207                (worktree_id, "main.rs"),
21208                Some(pane_2.downgrade()),
21209                true,
21210                window,
21211                cx,
21212            )
21213        })
21214        .unwrap()
21215        .await
21216        .downcast::<Editor>()
21217        .unwrap();
21218    pane_1.update(cx, |pane, cx| {
21219        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21220        open_editor.update(cx, |editor, cx| {
21221            assert_eq!(
21222                editor.display_text(cx),
21223                main_text,
21224                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
21225            );
21226            assert_eq!(
21227                editor
21228                    .selections
21229                    .all::<Point>(cx)
21230                    .into_iter()
21231                    .map(|s| s.range())
21232                    .collect::<Vec<_>>(),
21233                expected_ranges,
21234                "Previous editor in the 1st panel had selections and should get them restored on reopen",
21235            );
21236        })
21237    });
21238    pane_2.update(cx, |pane, cx| {
21239        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21240        open_editor.update(cx, |editor, cx| {
21241            assert_eq!(
21242                editor.display_text(cx),
21243                r#"fn main() {
21244⋯rintln!("1");
21245⋯intln!("2");
21246⋯ntln!("3");
21247println!("4");
21248println!("5");
21249}"#,
21250                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
21251            );
21252            assert_eq!(
21253                editor
21254                    .selections
21255                    .all::<Point>(cx)
21256                    .into_iter()
21257                    .map(|s| s.range())
21258                    .collect::<Vec<_>>(),
21259                vec![Point::zero()..Point::zero()],
21260                "Previous editor in the 2nd pane had no selections changed hence should restore none",
21261            );
21262        })
21263    });
21264}
21265
21266#[gpui::test]
21267async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
21268    init_test(cx, |_| {});
21269
21270    let fs = FakeFs::new(cx.executor());
21271    let main_text = r#"fn main() {
21272println!("1");
21273println!("2");
21274println!("3");
21275println!("4");
21276println!("5");
21277}"#;
21278    let lib_text = "mod foo {}";
21279    fs.insert_tree(
21280        path!("/a"),
21281        json!({
21282            "lib.rs": lib_text,
21283            "main.rs": main_text,
21284        }),
21285    )
21286    .await;
21287
21288    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21289    let (workspace, cx) =
21290        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21291    let worktree_id = workspace.update(cx, |workspace, cx| {
21292        workspace.project().update(cx, |project, cx| {
21293            project.worktrees(cx).next().unwrap().read(cx).id()
21294        })
21295    });
21296
21297    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21298    let editor = workspace
21299        .update_in(cx, |workspace, window, cx| {
21300            workspace.open_path(
21301                (worktree_id, "main.rs"),
21302                Some(pane.downgrade()),
21303                true,
21304                window,
21305                cx,
21306            )
21307        })
21308        .unwrap()
21309        .await
21310        .downcast::<Editor>()
21311        .unwrap();
21312    pane.update(cx, |pane, cx| {
21313        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21314        open_editor.update(cx, |editor, cx| {
21315            assert_eq!(
21316                editor.display_text(cx),
21317                main_text,
21318                "Original main.rs text on initial open",
21319            );
21320        })
21321    });
21322    editor.update_in(cx, |editor, window, cx| {
21323        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
21324    });
21325
21326    cx.update_global(|store: &mut SettingsStore, cx| {
21327        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
21328            s.restore_on_file_reopen = Some(false);
21329        });
21330    });
21331    editor.update_in(cx, |editor, window, cx| {
21332        editor.fold_ranges(
21333            vec![
21334                Point::new(1, 0)..Point::new(1, 1),
21335                Point::new(2, 0)..Point::new(2, 2),
21336                Point::new(3, 0)..Point::new(3, 3),
21337            ],
21338            false,
21339            window,
21340            cx,
21341        );
21342    });
21343    pane.update_in(cx, |pane, window, cx| {
21344        pane.close_all_items(&CloseAllItems::default(), window, cx)
21345    })
21346    .await
21347    .unwrap();
21348    pane.update(cx, |pane, _| {
21349        assert!(pane.active_item().is_none());
21350    });
21351    cx.update_global(|store: &mut SettingsStore, cx| {
21352        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
21353            s.restore_on_file_reopen = Some(true);
21354        });
21355    });
21356
21357    let _editor_reopened = workspace
21358        .update_in(cx, |workspace, window, cx| {
21359            workspace.open_path(
21360                (worktree_id, "main.rs"),
21361                Some(pane.downgrade()),
21362                true,
21363                window,
21364                cx,
21365            )
21366        })
21367        .unwrap()
21368        .await
21369        .downcast::<Editor>()
21370        .unwrap();
21371    pane.update(cx, |pane, cx| {
21372        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21373        open_editor.update(cx, |editor, cx| {
21374            assert_eq!(
21375                editor.display_text(cx),
21376                main_text,
21377                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
21378            );
21379        })
21380    });
21381}
21382
21383#[gpui::test]
21384async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
21385    struct EmptyModalView {
21386        focus_handle: gpui::FocusHandle,
21387    }
21388    impl EventEmitter<DismissEvent> for EmptyModalView {}
21389    impl Render for EmptyModalView {
21390        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
21391            div()
21392        }
21393    }
21394    impl Focusable for EmptyModalView {
21395        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
21396            self.focus_handle.clone()
21397        }
21398    }
21399    impl workspace::ModalView for EmptyModalView {}
21400    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
21401        EmptyModalView {
21402            focus_handle: cx.focus_handle(),
21403        }
21404    }
21405
21406    init_test(cx, |_| {});
21407
21408    let fs = FakeFs::new(cx.executor());
21409    let project = Project::test(fs, [], cx).await;
21410    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21411    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
21412    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21413    let editor = cx.new_window_entity(|window, cx| {
21414        Editor::new(
21415            EditorMode::full(),
21416            buffer,
21417            Some(project.clone()),
21418            window,
21419            cx,
21420        )
21421    });
21422    workspace
21423        .update(cx, |workspace, window, cx| {
21424            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
21425        })
21426        .unwrap();
21427    editor.update_in(cx, |editor, window, cx| {
21428        editor.open_context_menu(&OpenContextMenu, window, cx);
21429        assert!(editor.mouse_context_menu.is_some());
21430    });
21431    workspace
21432        .update(cx, |workspace, window, cx| {
21433            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
21434        })
21435        .unwrap();
21436    cx.read(|cx| {
21437        assert!(editor.read(cx).mouse_context_menu.is_none());
21438    });
21439}
21440
21441#[gpui::test]
21442async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
21443    init_test(cx, |_| {});
21444
21445    let fs = FakeFs::new(cx.executor());
21446    fs.insert_file(path!("/file.html"), Default::default())
21447        .await;
21448
21449    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
21450
21451    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21452    let html_language = Arc::new(Language::new(
21453        LanguageConfig {
21454            name: "HTML".into(),
21455            matcher: LanguageMatcher {
21456                path_suffixes: vec!["html".to_string()],
21457                ..LanguageMatcher::default()
21458            },
21459            brackets: BracketPairConfig {
21460                pairs: vec![BracketPair {
21461                    start: "<".into(),
21462                    end: ">".into(),
21463                    close: true,
21464                    ..Default::default()
21465                }],
21466                ..Default::default()
21467            },
21468            ..Default::default()
21469        },
21470        Some(tree_sitter_html::LANGUAGE.into()),
21471    ));
21472    language_registry.add(html_language);
21473    let mut fake_servers = language_registry.register_fake_lsp(
21474        "HTML",
21475        FakeLspAdapter {
21476            capabilities: lsp::ServerCapabilities {
21477                completion_provider: Some(lsp::CompletionOptions {
21478                    resolve_provider: Some(true),
21479                    ..Default::default()
21480                }),
21481                ..Default::default()
21482            },
21483            ..Default::default()
21484        },
21485    );
21486
21487    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21488    let cx = &mut VisualTestContext::from_window(*workspace, cx);
21489
21490    let worktree_id = workspace
21491        .update(cx, |workspace, _window, cx| {
21492            workspace.project().update(cx, |project, cx| {
21493                project.worktrees(cx).next().unwrap().read(cx).id()
21494            })
21495        })
21496        .unwrap();
21497    project
21498        .update(cx, |project, cx| {
21499            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
21500        })
21501        .await
21502        .unwrap();
21503    let editor = workspace
21504        .update(cx, |workspace, window, cx| {
21505            workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
21506        })
21507        .unwrap()
21508        .await
21509        .unwrap()
21510        .downcast::<Editor>()
21511        .unwrap();
21512
21513    let fake_server = fake_servers.next().await.unwrap();
21514    editor.update_in(cx, |editor, window, cx| {
21515        editor.set_text("<ad></ad>", window, cx);
21516        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21517            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
21518        });
21519        let Some((buffer, _)) = editor
21520            .buffer
21521            .read(cx)
21522            .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
21523        else {
21524            panic!("Failed to get buffer for selection position");
21525        };
21526        let buffer = buffer.read(cx);
21527        let buffer_id = buffer.remote_id();
21528        let opening_range =
21529            buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3));
21530        let closing_range =
21531            buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8));
21532        let mut linked_ranges = HashMap::default();
21533        linked_ranges.insert(
21534            buffer_id,
21535            vec![(opening_range.clone(), vec![closing_range.clone()])],
21536        );
21537        editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
21538    });
21539    let mut completion_handle =
21540        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
21541            Ok(Some(lsp::CompletionResponse::Array(vec![
21542                lsp::CompletionItem {
21543                    label: "head".to_string(),
21544                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21545                        lsp::InsertReplaceEdit {
21546                            new_text: "head".to_string(),
21547                            insert: lsp::Range::new(
21548                                lsp::Position::new(0, 1),
21549                                lsp::Position::new(0, 3),
21550                            ),
21551                            replace: lsp::Range::new(
21552                                lsp::Position::new(0, 1),
21553                                lsp::Position::new(0, 3),
21554                            ),
21555                        },
21556                    )),
21557                    ..Default::default()
21558                },
21559            ])))
21560        });
21561    editor.update_in(cx, |editor, window, cx| {
21562        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
21563    });
21564    cx.run_until_parked();
21565    completion_handle.next().await.unwrap();
21566    editor.update(cx, |editor, _| {
21567        assert!(
21568            editor.context_menu_visible(),
21569            "Completion menu should be visible"
21570        );
21571    });
21572    editor.update_in(cx, |editor, window, cx| {
21573        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
21574    });
21575    cx.executor().run_until_parked();
21576    editor.update(cx, |editor, cx| {
21577        assert_eq!(editor.text(cx), "<head></head>");
21578    });
21579}
21580
21581#[gpui::test]
21582async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
21583    init_test(cx, |_| {});
21584
21585    let fs = FakeFs::new(cx.executor());
21586    fs.insert_tree(
21587        path!("/root"),
21588        json!({
21589            "a": {
21590                "main.rs": "fn main() {}",
21591            },
21592            "foo": {
21593                "bar": {
21594                    "external_file.rs": "pub mod external {}",
21595                }
21596            }
21597        }),
21598    )
21599    .await;
21600
21601    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
21602    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21603    language_registry.add(rust_lang());
21604    let _fake_servers = language_registry.register_fake_lsp(
21605        "Rust",
21606        FakeLspAdapter {
21607            ..FakeLspAdapter::default()
21608        },
21609    );
21610    let (workspace, cx) =
21611        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21612    let worktree_id = workspace.update(cx, |workspace, cx| {
21613        workspace.project().update(cx, |project, cx| {
21614            project.worktrees(cx).next().unwrap().read(cx).id()
21615        })
21616    });
21617
21618    let assert_language_servers_count =
21619        |expected: usize, context: &str, cx: &mut VisualTestContext| {
21620            project.update(cx, |project, cx| {
21621                let current = project
21622                    .lsp_store()
21623                    .read(cx)
21624                    .as_local()
21625                    .unwrap()
21626                    .language_servers
21627                    .len();
21628                assert_eq!(expected, current, "{context}");
21629            });
21630        };
21631
21632    assert_language_servers_count(
21633        0,
21634        "No servers should be running before any file is open",
21635        cx,
21636    );
21637    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21638    let main_editor = workspace
21639        .update_in(cx, |workspace, window, cx| {
21640            workspace.open_path(
21641                (worktree_id, "main.rs"),
21642                Some(pane.downgrade()),
21643                true,
21644                window,
21645                cx,
21646            )
21647        })
21648        .unwrap()
21649        .await
21650        .downcast::<Editor>()
21651        .unwrap();
21652    pane.update(cx, |pane, cx| {
21653        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21654        open_editor.update(cx, |editor, cx| {
21655            assert_eq!(
21656                editor.display_text(cx),
21657                "fn main() {}",
21658                "Original main.rs text on initial open",
21659            );
21660        });
21661        assert_eq!(open_editor, main_editor);
21662    });
21663    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
21664
21665    let external_editor = workspace
21666        .update_in(cx, |workspace, window, cx| {
21667            workspace.open_abs_path(
21668                PathBuf::from("/root/foo/bar/external_file.rs"),
21669                OpenOptions::default(),
21670                window,
21671                cx,
21672            )
21673        })
21674        .await
21675        .expect("opening external file")
21676        .downcast::<Editor>()
21677        .expect("downcasted external file's open element to editor");
21678    pane.update(cx, |pane, cx| {
21679        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21680        open_editor.update(cx, |editor, cx| {
21681            assert_eq!(
21682                editor.display_text(cx),
21683                "pub mod external {}",
21684                "External file is open now",
21685            );
21686        });
21687        assert_eq!(open_editor, external_editor);
21688    });
21689    assert_language_servers_count(
21690        1,
21691        "Second, external, *.rs file should join the existing server",
21692        cx,
21693    );
21694
21695    pane.update_in(cx, |pane, window, cx| {
21696        pane.close_active_item(&CloseActiveItem::default(), window, cx)
21697    })
21698    .await
21699    .unwrap();
21700    pane.update_in(cx, |pane, window, cx| {
21701        pane.navigate_backward(window, cx);
21702    });
21703    cx.run_until_parked();
21704    pane.update(cx, |pane, cx| {
21705        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21706        open_editor.update(cx, |editor, cx| {
21707            assert_eq!(
21708                editor.display_text(cx),
21709                "pub mod external {}",
21710                "External file is open now",
21711            );
21712        });
21713    });
21714    assert_language_servers_count(
21715        1,
21716        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
21717        cx,
21718    );
21719
21720    cx.update(|_, cx| {
21721        workspace::reload(&workspace::Reload::default(), cx);
21722    });
21723    assert_language_servers_count(
21724        1,
21725        "After reloading the worktree with local and external files opened, only one project should be started",
21726        cx,
21727    );
21728}
21729
21730#[gpui::test]
21731async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
21732    init_test(cx, |_| {});
21733
21734    let mut cx = EditorTestContext::new(cx).await;
21735    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
21736    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
21737
21738    // test cursor move to start of each line on tab
21739    // for `if`, `elif`, `else`, `while`, `with` and `for`
21740    cx.set_state(indoc! {"
21741        def main():
21742        ˇ    for item in items:
21743        ˇ        while item.active:
21744        ˇ            if item.value > 10:
21745        ˇ                continue
21746        ˇ            elif item.value < 0:
21747        ˇ                break
21748        ˇ            else:
21749        ˇ                with item.context() as ctx:
21750        ˇ                    yield count
21751        ˇ        else:
21752        ˇ            log('while else')
21753        ˇ    else:
21754        ˇ        log('for else')
21755    "});
21756    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
21757    cx.assert_editor_state(indoc! {"
21758        def main():
21759            ˇfor item in items:
21760                ˇwhile item.active:
21761                    ˇif item.value > 10:
21762                        ˇcontinue
21763                    ˇelif item.value < 0:
21764                        ˇbreak
21765                    ˇelse:
21766                        ˇwith item.context() as ctx:
21767                            ˇyield count
21768                ˇelse:
21769                    ˇlog('while else')
21770            ˇelse:
21771                ˇlog('for else')
21772    "});
21773    // test relative indent is preserved when tab
21774    // for `if`, `elif`, `else`, `while`, `with` and `for`
21775    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
21776    cx.assert_editor_state(indoc! {"
21777        def main():
21778                ˇfor item in items:
21779                    ˇwhile item.active:
21780                        ˇif item.value > 10:
21781                            ˇcontinue
21782                        ˇelif item.value < 0:
21783                            ˇbreak
21784                        ˇelse:
21785                            ˇwith item.context() as ctx:
21786                                ˇyield count
21787                    ˇelse:
21788                        ˇlog('while else')
21789                ˇelse:
21790                    ˇlog('for else')
21791    "});
21792
21793    // test cursor move to start of each line on tab
21794    // for `try`, `except`, `else`, `finally`, `match` and `def`
21795    cx.set_state(indoc! {"
21796        def main():
21797        ˇ    try:
21798        ˇ        fetch()
21799        ˇ    except ValueError:
21800        ˇ        handle_error()
21801        ˇ    else:
21802        ˇ        match value:
21803        ˇ            case _:
21804        ˇ    finally:
21805        ˇ        def status():
21806        ˇ            return 0
21807    "});
21808    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
21809    cx.assert_editor_state(indoc! {"
21810        def main():
21811            ˇtry:
21812                ˇfetch()
21813            ˇexcept ValueError:
21814                ˇhandle_error()
21815            ˇelse:
21816                ˇmatch value:
21817                    ˇcase _:
21818            ˇfinally:
21819                ˇdef status():
21820                    ˇreturn 0
21821    "});
21822    // test relative indent is preserved when tab
21823    // for `try`, `except`, `else`, `finally`, `match` and `def`
21824    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
21825    cx.assert_editor_state(indoc! {"
21826        def main():
21827                ˇtry:
21828                    ˇfetch()
21829                ˇexcept ValueError:
21830                    ˇhandle_error()
21831                ˇelse:
21832                    ˇmatch value:
21833                        ˇcase _:
21834                ˇfinally:
21835                    ˇdef status():
21836                        ˇreturn 0
21837    "});
21838}
21839
21840#[gpui::test]
21841async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
21842    init_test(cx, |_| {});
21843
21844    let mut cx = EditorTestContext::new(cx).await;
21845    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
21846    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
21847
21848    // test `else` auto outdents when typed inside `if` block
21849    cx.set_state(indoc! {"
21850        def main():
21851            if i == 2:
21852                return
21853                ˇ
21854    "});
21855    cx.update_editor(|editor, window, cx| {
21856        editor.handle_input("else:", window, cx);
21857    });
21858    cx.assert_editor_state(indoc! {"
21859        def main():
21860            if i == 2:
21861                return
21862            else:ˇ
21863    "});
21864
21865    // test `except` auto outdents when typed inside `try` block
21866    cx.set_state(indoc! {"
21867        def main():
21868            try:
21869                i = 2
21870                ˇ
21871    "});
21872    cx.update_editor(|editor, window, cx| {
21873        editor.handle_input("except:", window, cx);
21874    });
21875    cx.assert_editor_state(indoc! {"
21876        def main():
21877            try:
21878                i = 2
21879            except:ˇ
21880    "});
21881
21882    // test `else` auto outdents when typed inside `except` block
21883    cx.set_state(indoc! {"
21884        def main():
21885            try:
21886                i = 2
21887            except:
21888                j = 2
21889                ˇ
21890    "});
21891    cx.update_editor(|editor, window, cx| {
21892        editor.handle_input("else:", window, cx);
21893    });
21894    cx.assert_editor_state(indoc! {"
21895        def main():
21896            try:
21897                i = 2
21898            except:
21899                j = 2
21900            else:ˇ
21901    "});
21902
21903    // test `finally` auto outdents when typed inside `else` block
21904    cx.set_state(indoc! {"
21905        def main():
21906            try:
21907                i = 2
21908            except:
21909                j = 2
21910            else:
21911                k = 2
21912                ˇ
21913    "});
21914    cx.update_editor(|editor, window, cx| {
21915        editor.handle_input("finally:", window, cx);
21916    });
21917    cx.assert_editor_state(indoc! {"
21918        def main():
21919            try:
21920                i = 2
21921            except:
21922                j = 2
21923            else:
21924                k = 2
21925            finally:ˇ
21926    "});
21927
21928    // test `else` does not outdents when typed inside `except` block right after for block
21929    cx.set_state(indoc! {"
21930        def main():
21931            try:
21932                i = 2
21933            except:
21934                for i in range(n):
21935                    pass
21936                ˇ
21937    "});
21938    cx.update_editor(|editor, window, cx| {
21939        editor.handle_input("else:", window, cx);
21940    });
21941    cx.assert_editor_state(indoc! {"
21942        def main():
21943            try:
21944                i = 2
21945            except:
21946                for i in range(n):
21947                    pass
21948                else:ˇ
21949    "});
21950
21951    // test `finally` auto outdents when typed inside `else` block right after for block
21952    cx.set_state(indoc! {"
21953        def main():
21954            try:
21955                i = 2
21956            except:
21957                j = 2
21958            else:
21959                for i in range(n):
21960                    pass
21961                ˇ
21962    "});
21963    cx.update_editor(|editor, window, cx| {
21964        editor.handle_input("finally:", window, cx);
21965    });
21966    cx.assert_editor_state(indoc! {"
21967        def main():
21968            try:
21969                i = 2
21970            except:
21971                j = 2
21972            else:
21973                for i in range(n):
21974                    pass
21975            finally:ˇ
21976    "});
21977
21978    // test `except` outdents to inner "try" block
21979    cx.set_state(indoc! {"
21980        def main():
21981            try:
21982                i = 2
21983                if i == 2:
21984                    try:
21985                        i = 3
21986                        ˇ
21987    "});
21988    cx.update_editor(|editor, window, cx| {
21989        editor.handle_input("except:", window, cx);
21990    });
21991    cx.assert_editor_state(indoc! {"
21992        def main():
21993            try:
21994                i = 2
21995                if i == 2:
21996                    try:
21997                        i = 3
21998                    except:ˇ
21999    "});
22000
22001    // test `except` outdents to outer "try" block
22002    cx.set_state(indoc! {"
22003        def main():
22004            try:
22005                i = 2
22006                if i == 2:
22007                    try:
22008                        i = 3
22009                ˇ
22010    "});
22011    cx.update_editor(|editor, window, cx| {
22012        editor.handle_input("except:", window, cx);
22013    });
22014    cx.assert_editor_state(indoc! {"
22015        def main():
22016            try:
22017                i = 2
22018                if i == 2:
22019                    try:
22020                        i = 3
22021            except:ˇ
22022    "});
22023
22024    // test `else` stays at correct indent when typed after `for` block
22025    cx.set_state(indoc! {"
22026        def main():
22027            for i in range(10):
22028                if i == 3:
22029                    break
22030            ˇ
22031    "});
22032    cx.update_editor(|editor, window, cx| {
22033        editor.handle_input("else:", window, cx);
22034    });
22035    cx.assert_editor_state(indoc! {"
22036        def main():
22037            for i in range(10):
22038                if i == 3:
22039                    break
22040            else:ˇ
22041    "});
22042
22043    // test does not outdent on typing after line with square brackets
22044    cx.set_state(indoc! {"
22045        def f() -> list[str]:
22046            ˇ
22047    "});
22048    cx.update_editor(|editor, window, cx| {
22049        editor.handle_input("a", window, cx);
22050    });
22051    cx.assert_editor_state(indoc! {"
22052        def f() -> list[str]:
2205322054    "});
22055}
22056
22057#[gpui::test]
22058async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
22059    init_test(cx, |_| {});
22060    update_test_language_settings(cx, |settings| {
22061        settings.defaults.extend_comment_on_newline = Some(false);
22062    });
22063    let mut cx = EditorTestContext::new(cx).await;
22064    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22065    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22066
22067    // test correct indent after newline on comment
22068    cx.set_state(indoc! {"
22069        # COMMENT:ˇ
22070    "});
22071    cx.update_editor(|editor, window, cx| {
22072        editor.newline(&Newline, window, cx);
22073    });
22074    cx.assert_editor_state(indoc! {"
22075        # COMMENT:
22076        ˇ
22077    "});
22078
22079    // test correct indent after newline in brackets
22080    cx.set_state(indoc! {"
22081        {ˇ}
22082    "});
22083    cx.update_editor(|editor, window, cx| {
22084        editor.newline(&Newline, window, cx);
22085    });
22086    cx.run_until_parked();
22087    cx.assert_editor_state(indoc! {"
22088        {
22089            ˇ
22090        }
22091    "});
22092
22093    cx.set_state(indoc! {"
22094        (ˇ)
22095    "});
22096    cx.update_editor(|editor, window, cx| {
22097        editor.newline(&Newline, window, cx);
22098    });
22099    cx.run_until_parked();
22100    cx.assert_editor_state(indoc! {"
22101        (
22102            ˇ
22103        )
22104    "});
22105
22106    // do not indent after empty lists or dictionaries
22107    cx.set_state(indoc! {"
22108        a = []ˇ
22109    "});
22110    cx.update_editor(|editor, window, cx| {
22111        editor.newline(&Newline, window, cx);
22112    });
22113    cx.run_until_parked();
22114    cx.assert_editor_state(indoc! {"
22115        a = []
22116        ˇ
22117    "});
22118}
22119
22120fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
22121    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
22122    point..point
22123}
22124
22125#[track_caller]
22126fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
22127    let (text, ranges) = marked_text_ranges(marked_text, true);
22128    assert_eq!(editor.text(cx), text);
22129    assert_eq!(
22130        editor.selections.ranges(cx),
22131        ranges,
22132        "Assert selections are {}",
22133        marked_text
22134    );
22135}
22136
22137pub fn handle_signature_help_request(
22138    cx: &mut EditorLspTestContext,
22139    mocked_response: lsp::SignatureHelp,
22140) -> impl Future<Output = ()> + use<> {
22141    let mut request =
22142        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
22143            let mocked_response = mocked_response.clone();
22144            async move { Ok(Some(mocked_response)) }
22145        });
22146
22147    async move {
22148        request.next().await;
22149    }
22150}
22151
22152#[track_caller]
22153pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
22154    cx.update_editor(|editor, _, _| {
22155        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
22156            let entries = menu.entries.borrow();
22157            let entries = entries
22158                .iter()
22159                .map(|entry| entry.string.as_str())
22160                .collect::<Vec<_>>();
22161            assert_eq!(entries, expected);
22162        } else {
22163            panic!("Expected completions menu");
22164        }
22165    });
22166}
22167
22168/// Handle completion request passing a marked string specifying where the completion
22169/// should be triggered from using '|' character, what range should be replaced, and what completions
22170/// should be returned using '<' and '>' to delimit the range.
22171///
22172/// Also see `handle_completion_request_with_insert_and_replace`.
22173#[track_caller]
22174pub fn handle_completion_request(
22175    marked_string: &str,
22176    completions: Vec<&'static str>,
22177    is_incomplete: bool,
22178    counter: Arc<AtomicUsize>,
22179    cx: &mut EditorLspTestContext,
22180) -> impl Future<Output = ()> {
22181    let complete_from_marker: TextRangeMarker = '|'.into();
22182    let replace_range_marker: TextRangeMarker = ('<', '>').into();
22183    let (_, mut marked_ranges) = marked_text_ranges_by(
22184        marked_string,
22185        vec![complete_from_marker.clone(), replace_range_marker.clone()],
22186    );
22187
22188    let complete_from_position =
22189        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
22190    let replace_range =
22191        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
22192
22193    let mut request =
22194        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
22195            let completions = completions.clone();
22196            counter.fetch_add(1, atomic::Ordering::Release);
22197            async move {
22198                assert_eq!(params.text_document_position.text_document.uri, url.clone());
22199                assert_eq!(
22200                    params.text_document_position.position,
22201                    complete_from_position
22202                );
22203                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
22204                    is_incomplete: is_incomplete,
22205                    item_defaults: None,
22206                    items: completions
22207                        .iter()
22208                        .map(|completion_text| lsp::CompletionItem {
22209                            label: completion_text.to_string(),
22210                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
22211                                range: replace_range,
22212                                new_text: completion_text.to_string(),
22213                            })),
22214                            ..Default::default()
22215                        })
22216                        .collect(),
22217                })))
22218            }
22219        });
22220
22221    async move {
22222        request.next().await;
22223    }
22224}
22225
22226/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
22227/// given instead, which also contains an `insert` range.
22228///
22229/// This function uses markers to define ranges:
22230/// - `|` marks the cursor position
22231/// - `<>` marks the replace range
22232/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
22233pub fn handle_completion_request_with_insert_and_replace(
22234    cx: &mut EditorLspTestContext,
22235    marked_string: &str,
22236    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
22237    counter: Arc<AtomicUsize>,
22238) -> impl Future<Output = ()> {
22239    let complete_from_marker: TextRangeMarker = '|'.into();
22240    let replace_range_marker: TextRangeMarker = ('<', '>').into();
22241    let insert_range_marker: TextRangeMarker = ('{', '}').into();
22242
22243    let (_, mut marked_ranges) = marked_text_ranges_by(
22244        marked_string,
22245        vec![
22246            complete_from_marker.clone(),
22247            replace_range_marker.clone(),
22248            insert_range_marker.clone(),
22249        ],
22250    );
22251
22252    let complete_from_position =
22253        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
22254    let replace_range =
22255        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
22256
22257    let insert_range = match marked_ranges.remove(&insert_range_marker) {
22258        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
22259        _ => lsp::Range {
22260            start: replace_range.start,
22261            end: complete_from_position,
22262        },
22263    };
22264
22265    let mut request =
22266        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
22267            let completions = completions.clone();
22268            counter.fetch_add(1, atomic::Ordering::Release);
22269            async move {
22270                assert_eq!(params.text_document_position.text_document.uri, url.clone());
22271                assert_eq!(
22272                    params.text_document_position.position, complete_from_position,
22273                    "marker `|` position doesn't match",
22274                );
22275                Ok(Some(lsp::CompletionResponse::Array(
22276                    completions
22277                        .iter()
22278                        .map(|(label, new_text)| lsp::CompletionItem {
22279                            label: label.to_string(),
22280                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22281                                lsp::InsertReplaceEdit {
22282                                    insert: insert_range,
22283                                    replace: replace_range,
22284                                    new_text: new_text.to_string(),
22285                                },
22286                            )),
22287                            ..Default::default()
22288                        })
22289                        .collect(),
22290                )))
22291            }
22292        });
22293
22294    async move {
22295        request.next().await;
22296    }
22297}
22298
22299fn handle_resolve_completion_request(
22300    cx: &mut EditorLspTestContext,
22301    edits: Option<Vec<(&'static str, &'static str)>>,
22302) -> impl Future<Output = ()> {
22303    let edits = edits.map(|edits| {
22304        edits
22305            .iter()
22306            .map(|(marked_string, new_text)| {
22307                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
22308                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
22309                lsp::TextEdit::new(replace_range, new_text.to_string())
22310            })
22311            .collect::<Vec<_>>()
22312    });
22313
22314    let mut request =
22315        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
22316            let edits = edits.clone();
22317            async move {
22318                Ok(lsp::CompletionItem {
22319                    additional_text_edits: edits,
22320                    ..Default::default()
22321                })
22322            }
22323        });
22324
22325    async move {
22326        request.next().await;
22327    }
22328}
22329
22330pub(crate) fn update_test_language_settings(
22331    cx: &mut TestAppContext,
22332    f: impl Fn(&mut AllLanguageSettingsContent),
22333) {
22334    cx.update(|cx| {
22335        SettingsStore::update_global(cx, |store, cx| {
22336            store.update_user_settings::<AllLanguageSettings>(cx, f);
22337        });
22338    });
22339}
22340
22341pub(crate) fn update_test_project_settings(
22342    cx: &mut TestAppContext,
22343    f: impl Fn(&mut ProjectSettings),
22344) {
22345    cx.update(|cx| {
22346        SettingsStore::update_global(cx, |store, cx| {
22347            store.update_user_settings::<ProjectSettings>(cx, f);
22348        });
22349    });
22350}
22351
22352pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
22353    cx.update(|cx| {
22354        assets::Assets.load_test_fonts(cx);
22355        let store = SettingsStore::test(cx);
22356        cx.set_global(store);
22357        theme::init(theme::LoadThemes::JustBase, cx);
22358        release_channel::init(SemanticVersion::default(), cx);
22359        client::init_settings(cx);
22360        language::init(cx);
22361        Project::init_settings(cx);
22362        workspace::init_settings(cx);
22363        crate::init(cx);
22364    });
22365
22366    update_test_language_settings(cx, f);
22367}
22368
22369#[track_caller]
22370fn assert_hunk_revert(
22371    not_reverted_text_with_selections: &str,
22372    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
22373    expected_reverted_text_with_selections: &str,
22374    base_text: &str,
22375    cx: &mut EditorLspTestContext,
22376) {
22377    cx.set_state(not_reverted_text_with_selections);
22378    cx.set_head_text(base_text);
22379    cx.executor().run_until_parked();
22380
22381    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
22382        let snapshot = editor.snapshot(window, cx);
22383        let reverted_hunk_statuses = snapshot
22384            .buffer_snapshot
22385            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
22386            .map(|hunk| hunk.status().kind)
22387            .collect::<Vec<_>>();
22388
22389        editor.git_restore(&Default::default(), window, cx);
22390        reverted_hunk_statuses
22391    });
22392    cx.executor().run_until_parked();
22393    cx.assert_editor_state(expected_reverted_text_with_selections);
22394    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
22395}
22396
22397#[gpui::test(iterations = 10)]
22398async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
22399    init_test(cx, |_| {});
22400
22401    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
22402    let counter = diagnostic_requests.clone();
22403
22404    let fs = FakeFs::new(cx.executor());
22405    fs.insert_tree(
22406        path!("/a"),
22407        json!({
22408            "first.rs": "fn main() { let a = 5; }",
22409            "second.rs": "// Test file",
22410        }),
22411    )
22412    .await;
22413
22414    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22415    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22416    let cx = &mut VisualTestContext::from_window(*workspace, cx);
22417
22418    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22419    language_registry.add(rust_lang());
22420    let mut fake_servers = language_registry.register_fake_lsp(
22421        "Rust",
22422        FakeLspAdapter {
22423            capabilities: lsp::ServerCapabilities {
22424                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
22425                    lsp::DiagnosticOptions {
22426                        identifier: None,
22427                        inter_file_dependencies: true,
22428                        workspace_diagnostics: true,
22429                        work_done_progress_options: Default::default(),
22430                    },
22431                )),
22432                ..Default::default()
22433            },
22434            ..Default::default()
22435        },
22436    );
22437
22438    let editor = workspace
22439        .update(cx, |workspace, window, cx| {
22440            workspace.open_abs_path(
22441                PathBuf::from(path!("/a/first.rs")),
22442                OpenOptions::default(),
22443                window,
22444                cx,
22445            )
22446        })
22447        .unwrap()
22448        .await
22449        .unwrap()
22450        .downcast::<Editor>()
22451        .unwrap();
22452    let fake_server = fake_servers.next().await.unwrap();
22453    let server_id = fake_server.server.server_id();
22454    let mut first_request = fake_server
22455        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
22456            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
22457            let result_id = Some(new_result_id.to_string());
22458            assert_eq!(
22459                params.text_document.uri,
22460                lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
22461            );
22462            async move {
22463                Ok(lsp::DocumentDiagnosticReportResult::Report(
22464                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
22465                        related_documents: None,
22466                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
22467                            items: Vec::new(),
22468                            result_id,
22469                        },
22470                    }),
22471                ))
22472            }
22473        });
22474
22475    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
22476        project.update(cx, |project, cx| {
22477            let buffer_id = editor
22478                .read(cx)
22479                .buffer()
22480                .read(cx)
22481                .as_singleton()
22482                .expect("created a singleton buffer")
22483                .read(cx)
22484                .remote_id();
22485            let buffer_result_id = project
22486                .lsp_store()
22487                .read(cx)
22488                .result_id(server_id, buffer_id, cx);
22489            assert_eq!(expected, buffer_result_id);
22490        });
22491    };
22492
22493    ensure_result_id(None, cx);
22494    cx.executor().advance_clock(Duration::from_millis(60));
22495    cx.executor().run_until_parked();
22496    assert_eq!(
22497        diagnostic_requests.load(atomic::Ordering::Acquire),
22498        1,
22499        "Opening file should trigger diagnostic request"
22500    );
22501    first_request
22502        .next()
22503        .await
22504        .expect("should have sent the first diagnostics pull request");
22505    ensure_result_id(Some("1".to_string()), cx);
22506
22507    // Editing should trigger diagnostics
22508    editor.update_in(cx, |editor, window, cx| {
22509        editor.handle_input("2", window, cx)
22510    });
22511    cx.executor().advance_clock(Duration::from_millis(60));
22512    cx.executor().run_until_parked();
22513    assert_eq!(
22514        diagnostic_requests.load(atomic::Ordering::Acquire),
22515        2,
22516        "Editing should trigger diagnostic request"
22517    );
22518    ensure_result_id(Some("2".to_string()), cx);
22519
22520    // Moving cursor should not trigger diagnostic request
22521    editor.update_in(cx, |editor, window, cx| {
22522        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22523            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
22524        });
22525    });
22526    cx.executor().advance_clock(Duration::from_millis(60));
22527    cx.executor().run_until_parked();
22528    assert_eq!(
22529        diagnostic_requests.load(atomic::Ordering::Acquire),
22530        2,
22531        "Cursor movement should not trigger diagnostic request"
22532    );
22533    ensure_result_id(Some("2".to_string()), cx);
22534    // Multiple rapid edits should be debounced
22535    for _ in 0..5 {
22536        editor.update_in(cx, |editor, window, cx| {
22537            editor.handle_input("x", window, cx)
22538        });
22539    }
22540    cx.executor().advance_clock(Duration::from_millis(60));
22541    cx.executor().run_until_parked();
22542
22543    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
22544    assert!(
22545        final_requests <= 4,
22546        "Multiple rapid edits should be debounced (got {final_requests} requests)",
22547    );
22548    ensure_result_id(Some(final_requests.to_string()), cx);
22549}
22550
22551#[gpui::test]
22552async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
22553    // Regression test for issue #11671
22554    // Previously, adding a cursor after moving multiple cursors would reset
22555    // the cursor count instead of adding to the existing cursors.
22556    init_test(cx, |_| {});
22557    let mut cx = EditorTestContext::new(cx).await;
22558
22559    // Create a simple buffer with cursor at start
22560    cx.set_state(indoc! {"
22561        ˇaaaa
22562        bbbb
22563        cccc
22564        dddd
22565        eeee
22566        ffff
22567        gggg
22568        hhhh"});
22569
22570    // Add 2 cursors below (so we have 3 total)
22571    cx.update_editor(|editor, window, cx| {
22572        editor.add_selection_below(&Default::default(), window, cx);
22573        editor.add_selection_below(&Default::default(), window, cx);
22574    });
22575
22576    // Verify we have 3 cursors
22577    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
22578    assert_eq!(
22579        initial_count, 3,
22580        "Should have 3 cursors after adding 2 below"
22581    );
22582
22583    // Move down one line
22584    cx.update_editor(|editor, window, cx| {
22585        editor.move_down(&MoveDown, window, cx);
22586    });
22587
22588    // Add another cursor below
22589    cx.update_editor(|editor, window, cx| {
22590        editor.add_selection_below(&Default::default(), window, cx);
22591    });
22592
22593    // Should now have 4 cursors (3 original + 1 new)
22594    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
22595    assert_eq!(
22596        final_count, 4,
22597        "Should have 4 cursors after moving and adding another"
22598    );
22599}
22600
22601#[gpui::test(iterations = 10)]
22602async fn test_document_colors(cx: &mut TestAppContext) {
22603    let expected_color = Rgba {
22604        r: 0.33,
22605        g: 0.33,
22606        b: 0.33,
22607        a: 0.33,
22608    };
22609
22610    init_test(cx, |_| {});
22611
22612    let fs = FakeFs::new(cx.executor());
22613    fs.insert_tree(
22614        path!("/a"),
22615        json!({
22616            "first.rs": "fn main() { let a = 5; }",
22617        }),
22618    )
22619    .await;
22620
22621    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22622    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22623    let cx = &mut VisualTestContext::from_window(*workspace, cx);
22624
22625    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22626    language_registry.add(rust_lang());
22627    let mut fake_servers = language_registry.register_fake_lsp(
22628        "Rust",
22629        FakeLspAdapter {
22630            capabilities: lsp::ServerCapabilities {
22631                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
22632                ..lsp::ServerCapabilities::default()
22633            },
22634            name: "rust-analyzer",
22635            ..FakeLspAdapter::default()
22636        },
22637    );
22638    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
22639        "Rust",
22640        FakeLspAdapter {
22641            capabilities: lsp::ServerCapabilities {
22642                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
22643                ..lsp::ServerCapabilities::default()
22644            },
22645            name: "not-rust-analyzer",
22646            ..FakeLspAdapter::default()
22647        },
22648    );
22649
22650    let editor = workspace
22651        .update(cx, |workspace, window, cx| {
22652            workspace.open_abs_path(
22653                PathBuf::from(path!("/a/first.rs")),
22654                OpenOptions::default(),
22655                window,
22656                cx,
22657            )
22658        })
22659        .unwrap()
22660        .await
22661        .unwrap()
22662        .downcast::<Editor>()
22663        .unwrap();
22664    let fake_language_server = fake_servers.next().await.unwrap();
22665    let fake_language_server_without_capabilities =
22666        fake_servers_without_capabilities.next().await.unwrap();
22667    let requests_made = Arc::new(AtomicUsize::new(0));
22668    let closure_requests_made = Arc::clone(&requests_made);
22669    let mut color_request_handle = fake_language_server
22670        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
22671            let requests_made = Arc::clone(&closure_requests_made);
22672            async move {
22673                assert_eq!(
22674                    params.text_document.uri,
22675                    lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
22676                );
22677                requests_made.fetch_add(1, atomic::Ordering::Release);
22678                Ok(vec![
22679                    lsp::ColorInformation {
22680                        range: lsp::Range {
22681                            start: lsp::Position {
22682                                line: 0,
22683                                character: 0,
22684                            },
22685                            end: lsp::Position {
22686                                line: 0,
22687                                character: 1,
22688                            },
22689                        },
22690                        color: lsp::Color {
22691                            red: 0.33,
22692                            green: 0.33,
22693                            blue: 0.33,
22694                            alpha: 0.33,
22695                        },
22696                    },
22697                    lsp::ColorInformation {
22698                        range: lsp::Range {
22699                            start: lsp::Position {
22700                                line: 0,
22701                                character: 0,
22702                            },
22703                            end: lsp::Position {
22704                                line: 0,
22705                                character: 1,
22706                            },
22707                        },
22708                        color: lsp::Color {
22709                            red: 0.33,
22710                            green: 0.33,
22711                            blue: 0.33,
22712                            alpha: 0.33,
22713                        },
22714                    },
22715                ])
22716            }
22717        });
22718
22719    let _handle = fake_language_server_without_capabilities
22720        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
22721            panic!("Should not be called");
22722        });
22723    cx.executor().advance_clock(Duration::from_millis(100));
22724    color_request_handle.next().await.unwrap();
22725    cx.run_until_parked();
22726    assert_eq!(
22727        1,
22728        requests_made.load(atomic::Ordering::Acquire),
22729        "Should query for colors once per editor open"
22730    );
22731    editor.update_in(cx, |editor, _, cx| {
22732        assert_eq!(
22733            vec![expected_color],
22734            extract_color_inlays(editor, cx),
22735            "Should have an initial inlay"
22736        );
22737    });
22738
22739    // opening another file in a split should not influence the LSP query counter
22740    workspace
22741        .update(cx, |workspace, window, cx| {
22742            assert_eq!(
22743                workspace.panes().len(),
22744                1,
22745                "Should have one pane with one editor"
22746            );
22747            workspace.move_item_to_pane_in_direction(
22748                &MoveItemToPaneInDirection {
22749                    direction: SplitDirection::Right,
22750                    focus: false,
22751                    clone: true,
22752                },
22753                window,
22754                cx,
22755            );
22756        })
22757        .unwrap();
22758    cx.run_until_parked();
22759    workspace
22760        .update(cx, |workspace, _, cx| {
22761            let panes = workspace.panes();
22762            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
22763            for pane in panes {
22764                let editor = pane
22765                    .read(cx)
22766                    .active_item()
22767                    .and_then(|item| item.downcast::<Editor>())
22768                    .expect("Should have opened an editor in each split");
22769                let editor_file = editor
22770                    .read(cx)
22771                    .buffer()
22772                    .read(cx)
22773                    .as_singleton()
22774                    .expect("test deals with singleton buffers")
22775                    .read(cx)
22776                    .file()
22777                    .expect("test buffese should have a file")
22778                    .path();
22779                assert_eq!(
22780                    editor_file.as_ref(),
22781                    Path::new("first.rs"),
22782                    "Both editors should be opened for the same file"
22783                )
22784            }
22785        })
22786        .unwrap();
22787
22788    cx.executor().advance_clock(Duration::from_millis(500));
22789    let save = editor.update_in(cx, |editor, window, cx| {
22790        editor.move_to_end(&MoveToEnd, window, cx);
22791        editor.handle_input("dirty", window, cx);
22792        editor.save(
22793            SaveOptions {
22794                format: true,
22795                autosave: true,
22796            },
22797            project.clone(),
22798            window,
22799            cx,
22800        )
22801    });
22802    save.await.unwrap();
22803
22804    color_request_handle.next().await.unwrap();
22805    cx.run_until_parked();
22806    assert_eq!(
22807        3,
22808        requests_made.load(atomic::Ordering::Acquire),
22809        "Should query for colors once per save and once per formatting after save"
22810    );
22811
22812    drop(editor);
22813    let close = workspace
22814        .update(cx, |workspace, window, cx| {
22815            workspace.active_pane().update(cx, |pane, cx| {
22816                pane.close_active_item(&CloseActiveItem::default(), window, cx)
22817            })
22818        })
22819        .unwrap();
22820    close.await.unwrap();
22821    let close = workspace
22822        .update(cx, |workspace, window, cx| {
22823            workspace.active_pane().update(cx, |pane, cx| {
22824                pane.close_active_item(&CloseActiveItem::default(), window, cx)
22825            })
22826        })
22827        .unwrap();
22828    close.await.unwrap();
22829    assert_eq!(
22830        3,
22831        requests_made.load(atomic::Ordering::Acquire),
22832        "After saving and closing all editors, no extra requests should be made"
22833    );
22834    workspace
22835        .update(cx, |workspace, _, cx| {
22836            assert!(
22837                workspace.active_item(cx).is_none(),
22838                "Should close all editors"
22839            )
22840        })
22841        .unwrap();
22842
22843    workspace
22844        .update(cx, |workspace, window, cx| {
22845            workspace.active_pane().update(cx, |pane, cx| {
22846                pane.navigate_backward(window, cx);
22847            })
22848        })
22849        .unwrap();
22850    cx.executor().advance_clock(Duration::from_millis(100));
22851    cx.run_until_parked();
22852    let editor = workspace
22853        .update(cx, |workspace, _, cx| {
22854            workspace
22855                .active_item(cx)
22856                .expect("Should have reopened the editor again after navigating back")
22857                .downcast::<Editor>()
22858                .expect("Should be an editor")
22859        })
22860        .unwrap();
22861    color_request_handle.next().await.unwrap();
22862    assert_eq!(
22863        3,
22864        requests_made.load(atomic::Ordering::Acquire),
22865        "Cache should be reused on buffer close and reopen"
22866    );
22867    editor.update(cx, |editor, cx| {
22868        assert_eq!(
22869            vec![expected_color],
22870            extract_color_inlays(editor, cx),
22871            "Should have an initial inlay"
22872        );
22873    });
22874}
22875
22876#[gpui::test]
22877async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
22878    init_test(cx, |_| {});
22879    let (editor, cx) = cx.add_window_view(Editor::single_line);
22880    editor.update_in(cx, |editor, window, cx| {
22881        editor.set_text("oops\n\nwow\n", window, cx)
22882    });
22883    cx.run_until_parked();
22884    editor.update(cx, |editor, cx| {
22885        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
22886    });
22887    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
22888    cx.run_until_parked();
22889    editor.update(cx, |editor, cx| {
22890        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
22891    });
22892}
22893
22894#[track_caller]
22895fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
22896    editor
22897        .all_inlays(cx)
22898        .into_iter()
22899        .filter_map(|inlay| inlay.get_color())
22900        .map(Rgba::from)
22901        .collect()
22902}