editor_tests.rs

    1use super::*;
    2use crate::{
    3    JoinLines,
    4    code_context_menus::CodeContextMenu,
    5    inline_completion_tests::FakeInlineCompletionProvider,
    6    linked_editing_ranges::LinkedEditingRanges,
    7    scroll::scroll_amount::ScrollAmount,
    8    test::{
    9        assert_text_with_selections, build_editor,
   10        editor_lsp_test_context::{EditorLspTestContext, git_commit_lang},
   11        editor_test_context::EditorTestContext,
   12        select_ranges,
   13    },
   14};
   15use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
   16use futures::StreamExt;
   17use gpui::{
   18    BackgroundExecutor, DismissEvent, Rgba, SemanticVersion, TestAppContext, UpdateGlobal,
   19    VisualTestContext, WindowBounds, WindowOptions, div,
   20};
   21use indoc::indoc;
   22use language::{
   23    BracketPairConfig,
   24    Capability::ReadWrite,
   25    DiagnosticSourceKind, FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageMatcher,
   26    LanguageName, Override, Point,
   27    language_settings::{
   28        AllLanguageSettings, AllLanguageSettingsContent, CompletionSettings, FormatterList,
   29        LanguageSettingsContent, LspInsertMode, PrettierSettings, SelectedFormatter,
   30    },
   31    tree_sitter_python,
   32};
   33use language_settings::{Formatter, IndentGuideSettings};
   34use lsp::CompletionParams;
   35use multi_buffer::{IndentGuide, PathKey};
   36use parking_lot::Mutex;
   37use pretty_assertions::{assert_eq, assert_ne};
   38use project::{
   39    FakeFs,
   40    debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
   41    project_settings::{LspSettings, ProjectSettings},
   42};
   43use serde_json::{self, json};
   44use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
   45use std::{
   46    iter,
   47    sync::atomic::{self, AtomicUsize},
   48};
   49use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
   50use text::ToPoint as _;
   51use unindent::Unindent;
   52use util::{
   53    assert_set_eq, path,
   54    test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text},
   55    uri,
   56};
   57use workspace::{
   58    CloseActiveItem, CloseAllItems, CloseInactiveItems, MoveItemToPaneInDirection, NavigationEntry,
   59    OpenOptions, ViewId,
   60    item::{FollowEvent, FollowableItem, Item, ItemHandle, SaveOptions},
   61};
   62
   63#[gpui::test]
   64fn test_edit_events(cx: &mut TestAppContext) {
   65    init_test(cx, |_| {});
   66
   67    let buffer = cx.new(|cx| {
   68        let mut buffer = language::Buffer::local("123456", cx);
   69        buffer.set_group_interval(Duration::from_secs(1));
   70        buffer
   71    });
   72
   73    let events = Rc::new(RefCell::new(Vec::new()));
   74    let editor1 = cx.add_window({
   75        let events = events.clone();
   76        |window, cx| {
   77            let entity = cx.entity().clone();
   78            cx.subscribe_in(
   79                &entity,
   80                window,
   81                move |_, _, event: &EditorEvent, _, _| match event {
   82                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
   83                    EditorEvent::BufferEdited => {
   84                        events.borrow_mut().push(("editor1", "buffer edited"))
   85                    }
   86                    _ => {}
   87                },
   88            )
   89            .detach();
   90            Editor::for_buffer(buffer.clone(), None, window, cx)
   91        }
   92    });
   93
   94    let editor2 = cx.add_window({
   95        let events = events.clone();
   96        |window, cx| {
   97            cx.subscribe_in(
   98                &cx.entity().clone(),
   99                window,
  100                move |_, _, event: &EditorEvent, _, _| match event {
  101                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
  102                    EditorEvent::BufferEdited => {
  103                        events.borrow_mut().push(("editor2", "buffer edited"))
  104                    }
  105                    _ => {}
  106                },
  107            )
  108            .detach();
  109            Editor::for_buffer(buffer.clone(), None, window, cx)
  110        }
  111    });
  112
  113    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  114
  115    // Mutating editor 1 will emit an `Edited` event only for that editor.
  116    _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
  117    assert_eq!(
  118        mem::take(&mut *events.borrow_mut()),
  119        [
  120            ("editor1", "edited"),
  121            ("editor1", "buffer edited"),
  122            ("editor2", "buffer edited"),
  123        ]
  124    );
  125
  126    // Mutating editor 2 will emit an `Edited` event only for that editor.
  127    _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
  128    assert_eq!(
  129        mem::take(&mut *events.borrow_mut()),
  130        [
  131            ("editor2", "edited"),
  132            ("editor1", "buffer edited"),
  133            ("editor2", "buffer edited"),
  134        ]
  135    );
  136
  137    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  138    _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  139    assert_eq!(
  140        mem::take(&mut *events.borrow_mut()),
  141        [
  142            ("editor1", "edited"),
  143            ("editor1", "buffer edited"),
  144            ("editor2", "buffer edited"),
  145        ]
  146    );
  147
  148    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  149    _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  150    assert_eq!(
  151        mem::take(&mut *events.borrow_mut()),
  152        [
  153            ("editor1", "edited"),
  154            ("editor1", "buffer edited"),
  155            ("editor2", "buffer edited"),
  156        ]
  157    );
  158
  159    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  160    _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  161    assert_eq!(
  162        mem::take(&mut *events.borrow_mut()),
  163        [
  164            ("editor2", "edited"),
  165            ("editor1", "buffer edited"),
  166            ("editor2", "buffer edited"),
  167        ]
  168    );
  169
  170    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  171    _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  172    assert_eq!(
  173        mem::take(&mut *events.borrow_mut()),
  174        [
  175            ("editor2", "edited"),
  176            ("editor1", "buffer edited"),
  177            ("editor2", "buffer edited"),
  178        ]
  179    );
  180
  181    // No event is emitted when the mutation is a no-op.
  182    _ = editor2.update(cx, |editor, window, cx| {
  183        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  184            s.select_ranges([0..0])
  185        });
  186
  187        editor.backspace(&Backspace, window, cx);
  188    });
  189    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  190}
  191
  192#[gpui::test]
  193fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  194    init_test(cx, |_| {});
  195
  196    let mut now = Instant::now();
  197    let group_interval = Duration::from_millis(1);
  198    let buffer = cx.new(|cx| {
  199        let mut buf = language::Buffer::local("123456", cx);
  200        buf.set_group_interval(group_interval);
  201        buf
  202    });
  203    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  204    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
  205
  206    _ = editor.update(cx, |editor, window, cx| {
  207        editor.start_transaction_at(now, window, cx);
  208        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  209            s.select_ranges([2..4])
  210        });
  211
  212        editor.insert("cd", window, cx);
  213        editor.end_transaction_at(now, cx);
  214        assert_eq!(editor.text(cx), "12cd56");
  215        assert_eq!(editor.selections.ranges(cx), vec![4..4]);
  216
  217        editor.start_transaction_at(now, window, cx);
  218        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  219            s.select_ranges([4..5])
  220        });
  221        editor.insert("e", window, cx);
  222        editor.end_transaction_at(now, cx);
  223        assert_eq!(editor.text(cx), "12cde6");
  224        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  225
  226        now += group_interval + Duration::from_millis(1);
  227        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  228            s.select_ranges([2..2])
  229        });
  230
  231        // Simulate an edit in another editor
  232        buffer.update(cx, |buffer, cx| {
  233            buffer.start_transaction_at(now, cx);
  234            buffer.edit([(0..1, "a")], None, cx);
  235            buffer.edit([(1..1, "b")], None, cx);
  236            buffer.end_transaction_at(now, cx);
  237        });
  238
  239        assert_eq!(editor.text(cx), "ab2cde6");
  240        assert_eq!(editor.selections.ranges(cx), vec![3..3]);
  241
  242        // Last transaction happened past the group interval in a different editor.
  243        // Undo it individually and don't restore selections.
  244        editor.undo(&Undo, window, cx);
  245        assert_eq!(editor.text(cx), "12cde6");
  246        assert_eq!(editor.selections.ranges(cx), vec![2..2]);
  247
  248        // First two transactions happened within the group interval in this editor.
  249        // Undo them together and restore selections.
  250        editor.undo(&Undo, window, cx);
  251        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  252        assert_eq!(editor.text(cx), "123456");
  253        assert_eq!(editor.selections.ranges(cx), vec![0..0]);
  254
  255        // Redo the first two transactions together.
  256        editor.redo(&Redo, window, cx);
  257        assert_eq!(editor.text(cx), "12cde6");
  258        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  259
  260        // Redo the last transaction on its own.
  261        editor.redo(&Redo, window, cx);
  262        assert_eq!(editor.text(cx), "ab2cde6");
  263        assert_eq!(editor.selections.ranges(cx), vec![6..6]);
  264
  265        // Test empty transactions.
  266        editor.start_transaction_at(now, window, cx);
  267        editor.end_transaction_at(now, cx);
  268        editor.undo(&Undo, window, cx);
  269        assert_eq!(editor.text(cx), "12cde6");
  270    });
  271}
  272
  273#[gpui::test]
  274fn test_ime_composition(cx: &mut TestAppContext) {
  275    init_test(cx, |_| {});
  276
  277    let buffer = cx.new(|cx| {
  278        let mut buffer = language::Buffer::local("abcde", cx);
  279        // Ensure automatic grouping doesn't occur.
  280        buffer.set_group_interval(Duration::ZERO);
  281        buffer
  282    });
  283
  284    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  285    cx.add_window(|window, cx| {
  286        let mut editor = build_editor(buffer.clone(), window, cx);
  287
  288        // Start a new IME composition.
  289        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  290        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
  291        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  292        assert_eq!(editor.text(cx), "äbcde");
  293        assert_eq!(
  294            editor.marked_text_ranges(cx),
  295            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  296        );
  297
  298        // Finalize IME composition.
  299        editor.replace_text_in_range(None, "ā", window, cx);
  300        assert_eq!(editor.text(cx), "ābcde");
  301        assert_eq!(editor.marked_text_ranges(cx), None);
  302
  303        // IME composition edits are grouped and are undone/redone at once.
  304        editor.undo(&Default::default(), window, cx);
  305        assert_eq!(editor.text(cx), "abcde");
  306        assert_eq!(editor.marked_text_ranges(cx), None);
  307        editor.redo(&Default::default(), window, cx);
  308        assert_eq!(editor.text(cx), "ābcde");
  309        assert_eq!(editor.marked_text_ranges(cx), None);
  310
  311        // Start a new IME composition.
  312        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  313        assert_eq!(
  314            editor.marked_text_ranges(cx),
  315            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  316        );
  317
  318        // Undoing during an IME composition cancels it.
  319        editor.undo(&Default::default(), window, cx);
  320        assert_eq!(editor.text(cx), "ābcde");
  321        assert_eq!(editor.marked_text_ranges(cx), None);
  322
  323        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  324        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  325        assert_eq!(editor.text(cx), "ābcdè");
  326        assert_eq!(
  327            editor.marked_text_ranges(cx),
  328            Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
  329        );
  330
  331        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  332        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  333        assert_eq!(editor.text(cx), "ābcdę");
  334        assert_eq!(editor.marked_text_ranges(cx), None);
  335
  336        // Start a new IME composition with multiple cursors.
  337        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  338            s.select_ranges([
  339                OffsetUtf16(1)..OffsetUtf16(1),
  340                OffsetUtf16(3)..OffsetUtf16(3),
  341                OffsetUtf16(5)..OffsetUtf16(5),
  342            ])
  343        });
  344        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  345        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  346        assert_eq!(
  347            editor.marked_text_ranges(cx),
  348            Some(vec![
  349                OffsetUtf16(0)..OffsetUtf16(3),
  350                OffsetUtf16(4)..OffsetUtf16(7),
  351                OffsetUtf16(8)..OffsetUtf16(11)
  352            ])
  353        );
  354
  355        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  356        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  357        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  358        assert_eq!(
  359            editor.marked_text_ranges(cx),
  360            Some(vec![
  361                OffsetUtf16(1)..OffsetUtf16(2),
  362                OffsetUtf16(5)..OffsetUtf16(6),
  363                OffsetUtf16(9)..OffsetUtf16(10)
  364            ])
  365        );
  366
  367        // Finalize IME composition with multiple cursors.
  368        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  369        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  370        assert_eq!(editor.marked_text_ranges(cx), None);
  371
  372        editor
  373    });
  374}
  375
  376#[gpui::test]
  377fn test_selection_with_mouse(cx: &mut TestAppContext) {
  378    init_test(cx, |_| {});
  379
  380    let editor = cx.add_window(|window, cx| {
  381        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  382        build_editor(buffer, window, cx)
  383    });
  384
  385    _ = editor.update(cx, |editor, window, cx| {
  386        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  387    });
  388    assert_eq!(
  389        editor
  390            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  391            .unwrap(),
  392        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  393    );
  394
  395    _ = editor.update(cx, |editor, window, cx| {
  396        editor.update_selection(
  397            DisplayPoint::new(DisplayRow(3), 3),
  398            0,
  399            gpui::Point::<f32>::default(),
  400            window,
  401            cx,
  402        );
  403    });
  404
  405    assert_eq!(
  406        editor
  407            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  408            .unwrap(),
  409        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  410    );
  411
  412    _ = editor.update(cx, |editor, window, cx| {
  413        editor.update_selection(
  414            DisplayPoint::new(DisplayRow(1), 1),
  415            0,
  416            gpui::Point::<f32>::default(),
  417            window,
  418            cx,
  419        );
  420    });
  421
  422    assert_eq!(
  423        editor
  424            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  425            .unwrap(),
  426        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  427    );
  428
  429    _ = editor.update(cx, |editor, window, cx| {
  430        editor.end_selection(window, cx);
  431        editor.update_selection(
  432            DisplayPoint::new(DisplayRow(3), 3),
  433            0,
  434            gpui::Point::<f32>::default(),
  435            window,
  436            cx,
  437        );
  438    });
  439
  440    assert_eq!(
  441        editor
  442            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  443            .unwrap(),
  444        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  445    );
  446
  447    _ = editor.update(cx, |editor, window, cx| {
  448        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  449        editor.update_selection(
  450            DisplayPoint::new(DisplayRow(0), 0),
  451            0,
  452            gpui::Point::<f32>::default(),
  453            window,
  454            cx,
  455        );
  456    });
  457
  458    assert_eq!(
  459        editor
  460            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  461            .unwrap(),
  462        [
  463            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  464            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  465        ]
  466    );
  467
  468    _ = editor.update(cx, |editor, window, cx| {
  469        editor.end_selection(window, cx);
  470    });
  471
  472    assert_eq!(
  473        editor
  474            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  475            .unwrap(),
  476        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  477    );
  478}
  479
  480#[gpui::test]
  481fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  482    init_test(cx, |_| {});
  483
  484    let editor = cx.add_window(|window, cx| {
  485        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  486        build_editor(buffer, window, cx)
  487    });
  488
  489    _ = editor.update(cx, |editor, window, cx| {
  490        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  491    });
  492
  493    _ = editor.update(cx, |editor, window, cx| {
  494        editor.end_selection(window, cx);
  495    });
  496
  497    _ = editor.update(cx, |editor, window, cx| {
  498        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  499    });
  500
  501    _ = editor.update(cx, |editor, window, cx| {
  502        editor.end_selection(window, cx);
  503    });
  504
  505    assert_eq!(
  506        editor
  507            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  508            .unwrap(),
  509        [
  510            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  511            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  512        ]
  513    );
  514
  515    _ = editor.update(cx, |editor, window, cx| {
  516        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  517    });
  518
  519    _ = editor.update(cx, |editor, window, cx| {
  520        editor.end_selection(window, cx);
  521    });
  522
  523    assert_eq!(
  524        editor
  525            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  526            .unwrap(),
  527        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  528    );
  529}
  530
  531#[gpui::test]
  532fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  533    init_test(cx, |_| {});
  534
  535    let editor = cx.add_window(|window, cx| {
  536        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  537        build_editor(buffer, window, cx)
  538    });
  539
  540    _ = editor.update(cx, |editor, window, cx| {
  541        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  542        assert_eq!(
  543            editor.selections.display_ranges(cx),
  544            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  545        );
  546    });
  547
  548    _ = editor.update(cx, |editor, window, cx| {
  549        editor.update_selection(
  550            DisplayPoint::new(DisplayRow(3), 3),
  551            0,
  552            gpui::Point::<f32>::default(),
  553            window,
  554            cx,
  555        );
  556        assert_eq!(
  557            editor.selections.display_ranges(cx),
  558            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  559        );
  560    });
  561
  562    _ = editor.update(cx, |editor, window, cx| {
  563        editor.cancel(&Cancel, window, cx);
  564        editor.update_selection(
  565            DisplayPoint::new(DisplayRow(1), 1),
  566            0,
  567            gpui::Point::<f32>::default(),
  568            window,
  569            cx,
  570        );
  571        assert_eq!(
  572            editor.selections.display_ranges(cx),
  573            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  574        );
  575    });
  576}
  577
  578#[gpui::test]
  579fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  580    init_test(cx, |_| {});
  581
  582    let editor = cx.add_window(|window, cx| {
  583        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  584        build_editor(buffer, window, cx)
  585    });
  586
  587    _ = editor.update(cx, |editor, window, cx| {
  588        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  589        assert_eq!(
  590            editor.selections.display_ranges(cx),
  591            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  592        );
  593
  594        editor.move_down(&Default::default(), window, cx);
  595        assert_eq!(
  596            editor.selections.display_ranges(cx),
  597            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  598        );
  599
  600        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  601        assert_eq!(
  602            editor.selections.display_ranges(cx),
  603            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  604        );
  605
  606        editor.move_up(&Default::default(), window, cx);
  607        assert_eq!(
  608            editor.selections.display_ranges(cx),
  609            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  610        );
  611    });
  612}
  613
  614#[gpui::test]
  615fn test_clone(cx: &mut TestAppContext) {
  616    init_test(cx, |_| {});
  617
  618    let (text, selection_ranges) = marked_text_ranges(
  619        indoc! {"
  620            one
  621            two
  622            threeˇ
  623            four
  624            fiveˇ
  625        "},
  626        true,
  627    );
  628
  629    let editor = cx.add_window(|window, cx| {
  630        let buffer = MultiBuffer::build_simple(&text, cx);
  631        build_editor(buffer, window, cx)
  632    });
  633
  634    _ = editor.update(cx, |editor, window, cx| {
  635        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  636            s.select_ranges(selection_ranges.clone())
  637        });
  638        editor.fold_creases(
  639            vec![
  640                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  641                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  642            ],
  643            true,
  644            window,
  645            cx,
  646        );
  647    });
  648
  649    let cloned_editor = editor
  650        .update(cx, |editor, _, cx| {
  651            cx.open_window(Default::default(), |window, cx| {
  652                cx.new(|cx| editor.clone(window, cx))
  653            })
  654        })
  655        .unwrap()
  656        .unwrap();
  657
  658    let snapshot = editor
  659        .update(cx, |e, window, cx| e.snapshot(window, cx))
  660        .unwrap();
  661    let cloned_snapshot = cloned_editor
  662        .update(cx, |e, window, cx| e.snapshot(window, cx))
  663        .unwrap();
  664
  665    assert_eq!(
  666        cloned_editor
  667            .update(cx, |e, _, cx| e.display_text(cx))
  668            .unwrap(),
  669        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  670    );
  671    assert_eq!(
  672        cloned_snapshot
  673            .folds_in_range(0..text.len())
  674            .collect::<Vec<_>>(),
  675        snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
  676    );
  677    assert_set_eq!(
  678        cloned_editor
  679            .update(cx, |editor, _, cx| editor.selections.ranges::<Point>(cx))
  680            .unwrap(),
  681        editor
  682            .update(cx, |editor, _, cx| editor.selections.ranges(cx))
  683            .unwrap()
  684    );
  685    assert_set_eq!(
  686        cloned_editor
  687            .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
  688            .unwrap(),
  689        editor
  690            .update(cx, |e, _, cx| e.selections.display_ranges(cx))
  691            .unwrap()
  692    );
  693}
  694
  695#[gpui::test]
  696async fn test_navigation_history(cx: &mut TestAppContext) {
  697    init_test(cx, |_| {});
  698
  699    use workspace::item::Item;
  700
  701    let fs = FakeFs::new(cx.executor());
  702    let project = Project::test(fs, [], cx).await;
  703    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  704    let pane = workspace
  705        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  706        .unwrap();
  707
  708    _ = workspace.update(cx, |_v, window, cx| {
  709        cx.new(|cx| {
  710            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  711            let mut editor = build_editor(buffer.clone(), window, cx);
  712            let handle = cx.entity();
  713            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  714
  715            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  716                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  717            }
  718
  719            // Move the cursor a small distance.
  720            // Nothing is added to the navigation history.
  721            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  722                s.select_display_ranges([
  723                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  724                ])
  725            });
  726            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  727                s.select_display_ranges([
  728                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  729                ])
  730            });
  731            assert!(pop_history(&mut editor, cx).is_none());
  732
  733            // Move the cursor a large distance.
  734            // The history can jump back to the previous position.
  735            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  736                s.select_display_ranges([
  737                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  738                ])
  739            });
  740            let nav_entry = pop_history(&mut editor, cx).unwrap();
  741            editor.navigate(nav_entry.data.unwrap(), window, cx);
  742            assert_eq!(nav_entry.item.id(), cx.entity_id());
  743            assert_eq!(
  744                editor.selections.display_ranges(cx),
  745                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  746            );
  747            assert!(pop_history(&mut editor, cx).is_none());
  748
  749            // Move the cursor a small distance via the mouse.
  750            // Nothing is added to the navigation history.
  751            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  752            editor.end_selection(window, cx);
  753            assert_eq!(
  754                editor.selections.display_ranges(cx),
  755                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  756            );
  757            assert!(pop_history(&mut editor, cx).is_none());
  758
  759            // Move the cursor a large distance via the mouse.
  760            // The history can jump back to the previous position.
  761            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  762            editor.end_selection(window, cx);
  763            assert_eq!(
  764                editor.selections.display_ranges(cx),
  765                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  766            );
  767            let nav_entry = pop_history(&mut editor, cx).unwrap();
  768            editor.navigate(nav_entry.data.unwrap(), window, cx);
  769            assert_eq!(nav_entry.item.id(), cx.entity_id());
  770            assert_eq!(
  771                editor.selections.display_ranges(cx),
  772                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  773            );
  774            assert!(pop_history(&mut editor, cx).is_none());
  775
  776            // Set scroll position to check later
  777            editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), window, cx);
  778            let original_scroll_position = editor.scroll_manager.anchor();
  779
  780            // Jump to the end of the document and adjust scroll
  781            editor.move_to_end(&MoveToEnd, window, cx);
  782            editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), window, cx);
  783            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  784
  785            let nav_entry = pop_history(&mut editor, cx).unwrap();
  786            editor.navigate(nav_entry.data.unwrap(), window, cx);
  787            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  788
  789            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  790            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  791            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  792            let invalid_point = Point::new(9999, 0);
  793            editor.navigate(
  794                Box::new(NavigationData {
  795                    cursor_anchor: invalid_anchor,
  796                    cursor_position: invalid_point,
  797                    scroll_anchor: ScrollAnchor {
  798                        anchor: invalid_anchor,
  799                        offset: Default::default(),
  800                    },
  801                    scroll_top_row: invalid_point.row,
  802                }),
  803                window,
  804                cx,
  805            );
  806            assert_eq!(
  807                editor.selections.display_ranges(cx),
  808                &[editor.max_point(cx)..editor.max_point(cx)]
  809            );
  810            assert_eq!(
  811                editor.scroll_position(cx),
  812                gpui::Point::new(0., editor.max_point(cx).row().as_f32())
  813            );
  814
  815            editor
  816        })
  817    });
  818}
  819
  820#[gpui::test]
  821fn test_cancel(cx: &mut TestAppContext) {
  822    init_test(cx, |_| {});
  823
  824    let editor = cx.add_window(|window, cx| {
  825        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  826        build_editor(buffer, window, cx)
  827    });
  828
  829    _ = editor.update(cx, |editor, window, cx| {
  830        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
  831        editor.update_selection(
  832            DisplayPoint::new(DisplayRow(1), 1),
  833            0,
  834            gpui::Point::<f32>::default(),
  835            window,
  836            cx,
  837        );
  838        editor.end_selection(window, cx);
  839
  840        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
  841        editor.update_selection(
  842            DisplayPoint::new(DisplayRow(0), 3),
  843            0,
  844            gpui::Point::<f32>::default(),
  845            window,
  846            cx,
  847        );
  848        editor.end_selection(window, cx);
  849        assert_eq!(
  850            editor.selections.display_ranges(cx),
  851            [
  852                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
  853                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
  854            ]
  855        );
  856    });
  857
  858    _ = editor.update(cx, |editor, window, cx| {
  859        editor.cancel(&Cancel, window, cx);
  860        assert_eq!(
  861            editor.selections.display_ranges(cx),
  862            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
  863        );
  864    });
  865
  866    _ = editor.update(cx, |editor, window, cx| {
  867        editor.cancel(&Cancel, window, cx);
  868        assert_eq!(
  869            editor.selections.display_ranges(cx),
  870            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
  871        );
  872    });
  873}
  874
  875#[gpui::test]
  876fn test_fold_action(cx: &mut TestAppContext) {
  877    init_test(cx, |_| {});
  878
  879    let editor = cx.add_window(|window, cx| {
  880        let buffer = MultiBuffer::build_simple(
  881            &"
  882                impl Foo {
  883                    // Hello!
  884
  885                    fn a() {
  886                        1
  887                    }
  888
  889                    fn b() {
  890                        2
  891                    }
  892
  893                    fn c() {
  894                        3
  895                    }
  896                }
  897            "
  898            .unindent(),
  899            cx,
  900        );
  901        build_editor(buffer.clone(), window, cx)
  902    });
  903
  904    _ = editor.update(cx, |editor, window, cx| {
  905        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  906            s.select_display_ranges([
  907                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
  908            ]);
  909        });
  910        editor.fold(&Fold, window, cx);
  911        assert_eq!(
  912            editor.display_text(cx),
  913            "
  914                impl Foo {
  915                    // Hello!
  916
  917                    fn a() {
  918                        1
  919                    }
  920
  921                    fn b() {⋯
  922                    }
  923
  924                    fn c() {⋯
  925                    }
  926                }
  927            "
  928            .unindent(),
  929        );
  930
  931        editor.fold(&Fold, window, cx);
  932        assert_eq!(
  933            editor.display_text(cx),
  934            "
  935                impl Foo {⋯
  936                }
  937            "
  938            .unindent(),
  939        );
  940
  941        editor.unfold_lines(&UnfoldLines, window, cx);
  942        assert_eq!(
  943            editor.display_text(cx),
  944            "
  945                impl Foo {
  946                    // Hello!
  947
  948                    fn a() {
  949                        1
  950                    }
  951
  952                    fn b() {⋯
  953                    }
  954
  955                    fn c() {⋯
  956                    }
  957                }
  958            "
  959            .unindent(),
  960        );
  961
  962        editor.unfold_lines(&UnfoldLines, window, cx);
  963        assert_eq!(
  964            editor.display_text(cx),
  965            editor.buffer.read(cx).read(cx).text()
  966        );
  967    });
  968}
  969
  970#[gpui::test]
  971fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
  972    init_test(cx, |_| {});
  973
  974    let editor = cx.add_window(|window, cx| {
  975        let buffer = MultiBuffer::build_simple(
  976            &"
  977                class Foo:
  978                    # Hello!
  979
  980                    def a():
  981                        print(1)
  982
  983                    def b():
  984                        print(2)
  985
  986                    def c():
  987                        print(3)
  988            "
  989            .unindent(),
  990            cx,
  991        );
  992        build_editor(buffer.clone(), window, cx)
  993    });
  994
  995    _ = editor.update(cx, |editor, window, cx| {
  996        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  997            s.select_display_ranges([
  998                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
  999            ]);
 1000        });
 1001        editor.fold(&Fold, window, cx);
 1002        assert_eq!(
 1003            editor.display_text(cx),
 1004            "
 1005                class Foo:
 1006                    # Hello!
 1007
 1008                    def a():
 1009                        print(1)
 1010
 1011                    def b():⋯
 1012
 1013                    def c():⋯
 1014            "
 1015            .unindent(),
 1016        );
 1017
 1018        editor.fold(&Fold, window, cx);
 1019        assert_eq!(
 1020            editor.display_text(cx),
 1021            "
 1022                class Foo:⋯
 1023            "
 1024            .unindent(),
 1025        );
 1026
 1027        editor.unfold_lines(&UnfoldLines, window, cx);
 1028        assert_eq!(
 1029            editor.display_text(cx),
 1030            "
 1031                class Foo:
 1032                    # Hello!
 1033
 1034                    def a():
 1035                        print(1)
 1036
 1037                    def b():⋯
 1038
 1039                    def c():⋯
 1040            "
 1041            .unindent(),
 1042        );
 1043
 1044        editor.unfold_lines(&UnfoldLines, window, cx);
 1045        assert_eq!(
 1046            editor.display_text(cx),
 1047            editor.buffer.read(cx).read(cx).text()
 1048        );
 1049    });
 1050}
 1051
 1052#[gpui::test]
 1053fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1054    init_test(cx, |_| {});
 1055
 1056    let editor = cx.add_window(|window, cx| {
 1057        let buffer = MultiBuffer::build_simple(
 1058            &"
 1059                class Foo:
 1060                    # Hello!
 1061
 1062                    def a():
 1063                        print(1)
 1064
 1065                    def b():
 1066                        print(2)
 1067
 1068
 1069                    def c():
 1070                        print(3)
 1071
 1072
 1073            "
 1074            .unindent(),
 1075            cx,
 1076        );
 1077        build_editor(buffer.clone(), window, cx)
 1078    });
 1079
 1080    _ = editor.update(cx, |editor, window, cx| {
 1081        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1082            s.select_display_ranges([
 1083                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1084            ]);
 1085        });
 1086        editor.fold(&Fold, window, cx);
 1087        assert_eq!(
 1088            editor.display_text(cx),
 1089            "
 1090                class Foo:
 1091                    # Hello!
 1092
 1093                    def a():
 1094                        print(1)
 1095
 1096                    def b():⋯
 1097
 1098
 1099                    def c():⋯
 1100
 1101
 1102            "
 1103            .unindent(),
 1104        );
 1105
 1106        editor.fold(&Fold, window, cx);
 1107        assert_eq!(
 1108            editor.display_text(cx),
 1109            "
 1110                class Foo:⋯
 1111
 1112
 1113            "
 1114            .unindent(),
 1115        );
 1116
 1117        editor.unfold_lines(&UnfoldLines, window, cx);
 1118        assert_eq!(
 1119            editor.display_text(cx),
 1120            "
 1121                class Foo:
 1122                    # Hello!
 1123
 1124                    def a():
 1125                        print(1)
 1126
 1127                    def b():⋯
 1128
 1129
 1130                    def c():⋯
 1131
 1132
 1133            "
 1134            .unindent(),
 1135        );
 1136
 1137        editor.unfold_lines(&UnfoldLines, window, cx);
 1138        assert_eq!(
 1139            editor.display_text(cx),
 1140            editor.buffer.read(cx).read(cx).text()
 1141        );
 1142    });
 1143}
 1144
 1145#[gpui::test]
 1146fn test_fold_at_level(cx: &mut TestAppContext) {
 1147    init_test(cx, |_| {});
 1148
 1149    let editor = cx.add_window(|window, cx| {
 1150        let buffer = MultiBuffer::build_simple(
 1151            &"
 1152                class Foo:
 1153                    # Hello!
 1154
 1155                    def a():
 1156                        print(1)
 1157
 1158                    def b():
 1159                        print(2)
 1160
 1161
 1162                class Bar:
 1163                    # World!
 1164
 1165                    def a():
 1166                        print(1)
 1167
 1168                    def b():
 1169                        print(2)
 1170
 1171
 1172            "
 1173            .unindent(),
 1174            cx,
 1175        );
 1176        build_editor(buffer.clone(), window, cx)
 1177    });
 1178
 1179    _ = editor.update(cx, |editor, window, cx| {
 1180        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1181        assert_eq!(
 1182            editor.display_text(cx),
 1183            "
 1184                class Foo:
 1185                    # Hello!
 1186
 1187                    def a():⋯
 1188
 1189                    def b():⋯
 1190
 1191
 1192                class Bar:
 1193                    # World!
 1194
 1195                    def a():⋯
 1196
 1197                    def b():⋯
 1198
 1199
 1200            "
 1201            .unindent(),
 1202        );
 1203
 1204        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1205        assert_eq!(
 1206            editor.display_text(cx),
 1207            "
 1208                class Foo:⋯
 1209
 1210
 1211                class Bar:⋯
 1212
 1213
 1214            "
 1215            .unindent(),
 1216        );
 1217
 1218        editor.unfold_all(&UnfoldAll, window, cx);
 1219        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1220        assert_eq!(
 1221            editor.display_text(cx),
 1222            "
 1223                class Foo:
 1224                    # Hello!
 1225
 1226                    def a():
 1227                        print(1)
 1228
 1229                    def b():
 1230                        print(2)
 1231
 1232
 1233                class Bar:
 1234                    # World!
 1235
 1236                    def a():
 1237                        print(1)
 1238
 1239                    def b():
 1240                        print(2)
 1241
 1242
 1243            "
 1244            .unindent(),
 1245        );
 1246
 1247        assert_eq!(
 1248            editor.display_text(cx),
 1249            editor.buffer.read(cx).read(cx).text()
 1250        );
 1251    });
 1252}
 1253
 1254#[gpui::test]
 1255fn test_move_cursor(cx: &mut TestAppContext) {
 1256    init_test(cx, |_| {});
 1257
 1258    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1259    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1260
 1261    buffer.update(cx, |buffer, cx| {
 1262        buffer.edit(
 1263            vec![
 1264                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1265                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1266            ],
 1267            None,
 1268            cx,
 1269        );
 1270    });
 1271    _ = editor.update(cx, |editor, window, cx| {
 1272        assert_eq!(
 1273            editor.selections.display_ranges(cx),
 1274            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1275        );
 1276
 1277        editor.move_down(&MoveDown, window, cx);
 1278        assert_eq!(
 1279            editor.selections.display_ranges(cx),
 1280            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1281        );
 1282
 1283        editor.move_right(&MoveRight, window, cx);
 1284        assert_eq!(
 1285            editor.selections.display_ranges(cx),
 1286            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1287        );
 1288
 1289        editor.move_left(&MoveLeft, window, cx);
 1290        assert_eq!(
 1291            editor.selections.display_ranges(cx),
 1292            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1293        );
 1294
 1295        editor.move_up(&MoveUp, window, cx);
 1296        assert_eq!(
 1297            editor.selections.display_ranges(cx),
 1298            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1299        );
 1300
 1301        editor.move_to_end(&MoveToEnd, window, cx);
 1302        assert_eq!(
 1303            editor.selections.display_ranges(cx),
 1304            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1305        );
 1306
 1307        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1308        assert_eq!(
 1309            editor.selections.display_ranges(cx),
 1310            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1311        );
 1312
 1313        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1314            s.select_display_ranges([
 1315                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1316            ]);
 1317        });
 1318        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1319        assert_eq!(
 1320            editor.selections.display_ranges(cx),
 1321            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1322        );
 1323
 1324        editor.select_to_end(&SelectToEnd, window, cx);
 1325        assert_eq!(
 1326            editor.selections.display_ranges(cx),
 1327            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1328        );
 1329    });
 1330}
 1331
 1332#[gpui::test]
 1333fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1334    init_test(cx, |_| {});
 1335
 1336    let editor = cx.add_window(|window, cx| {
 1337        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1338        build_editor(buffer.clone(), window, cx)
 1339    });
 1340
 1341    assert_eq!('🟥'.len_utf8(), 4);
 1342    assert_eq!('α'.len_utf8(), 2);
 1343
 1344    _ = editor.update(cx, |editor, window, cx| {
 1345        editor.fold_creases(
 1346            vec![
 1347                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1348                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1349                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1350            ],
 1351            true,
 1352            window,
 1353            cx,
 1354        );
 1355        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1356
 1357        editor.move_right(&MoveRight, window, cx);
 1358        assert_eq!(
 1359            editor.selections.display_ranges(cx),
 1360            &[empty_range(0, "🟥".len())]
 1361        );
 1362        editor.move_right(&MoveRight, window, cx);
 1363        assert_eq!(
 1364            editor.selections.display_ranges(cx),
 1365            &[empty_range(0, "🟥🟧".len())]
 1366        );
 1367        editor.move_right(&MoveRight, window, cx);
 1368        assert_eq!(
 1369            editor.selections.display_ranges(cx),
 1370            &[empty_range(0, "🟥🟧⋯".len())]
 1371        );
 1372
 1373        editor.move_down(&MoveDown, window, cx);
 1374        assert_eq!(
 1375            editor.selections.display_ranges(cx),
 1376            &[empty_range(1, "ab⋯e".len())]
 1377        );
 1378        editor.move_left(&MoveLeft, window, cx);
 1379        assert_eq!(
 1380            editor.selections.display_ranges(cx),
 1381            &[empty_range(1, "ab⋯".len())]
 1382        );
 1383        editor.move_left(&MoveLeft, window, cx);
 1384        assert_eq!(
 1385            editor.selections.display_ranges(cx),
 1386            &[empty_range(1, "ab".len())]
 1387        );
 1388        editor.move_left(&MoveLeft, window, cx);
 1389        assert_eq!(
 1390            editor.selections.display_ranges(cx),
 1391            &[empty_range(1, "a".len())]
 1392        );
 1393
 1394        editor.move_down(&MoveDown, window, cx);
 1395        assert_eq!(
 1396            editor.selections.display_ranges(cx),
 1397            &[empty_range(2, "α".len())]
 1398        );
 1399        editor.move_right(&MoveRight, window, cx);
 1400        assert_eq!(
 1401            editor.selections.display_ranges(cx),
 1402            &[empty_range(2, "αβ".len())]
 1403        );
 1404        editor.move_right(&MoveRight, window, cx);
 1405        assert_eq!(
 1406            editor.selections.display_ranges(cx),
 1407            &[empty_range(2, "αβ⋯".len())]
 1408        );
 1409        editor.move_right(&MoveRight, window, cx);
 1410        assert_eq!(
 1411            editor.selections.display_ranges(cx),
 1412            &[empty_range(2, "αβ⋯ε".len())]
 1413        );
 1414
 1415        editor.move_up(&MoveUp, window, cx);
 1416        assert_eq!(
 1417            editor.selections.display_ranges(cx),
 1418            &[empty_range(1, "ab⋯e".len())]
 1419        );
 1420        editor.move_down(&MoveDown, window, cx);
 1421        assert_eq!(
 1422            editor.selections.display_ranges(cx),
 1423            &[empty_range(2, "αβ⋯ε".len())]
 1424        );
 1425        editor.move_up(&MoveUp, window, cx);
 1426        assert_eq!(
 1427            editor.selections.display_ranges(cx),
 1428            &[empty_range(1, "ab⋯e".len())]
 1429        );
 1430
 1431        editor.move_up(&MoveUp, window, cx);
 1432        assert_eq!(
 1433            editor.selections.display_ranges(cx),
 1434            &[empty_range(0, "🟥🟧".len())]
 1435        );
 1436        editor.move_left(&MoveLeft, window, cx);
 1437        assert_eq!(
 1438            editor.selections.display_ranges(cx),
 1439            &[empty_range(0, "🟥".len())]
 1440        );
 1441        editor.move_left(&MoveLeft, window, cx);
 1442        assert_eq!(
 1443            editor.selections.display_ranges(cx),
 1444            &[empty_range(0, "".len())]
 1445        );
 1446    });
 1447}
 1448
 1449#[gpui::test]
 1450fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1451    init_test(cx, |_| {});
 1452
 1453    let editor = cx.add_window(|window, cx| {
 1454        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1455        build_editor(buffer.clone(), window, cx)
 1456    });
 1457    _ = editor.update(cx, |editor, window, cx| {
 1458        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1459            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1460        });
 1461
 1462        // moving above start of document should move selection to start of document,
 1463        // but the next move down should still be at the original goal_x
 1464        editor.move_up(&MoveUp, window, cx);
 1465        assert_eq!(
 1466            editor.selections.display_ranges(cx),
 1467            &[empty_range(0, "".len())]
 1468        );
 1469
 1470        editor.move_down(&MoveDown, window, cx);
 1471        assert_eq!(
 1472            editor.selections.display_ranges(cx),
 1473            &[empty_range(1, "abcd".len())]
 1474        );
 1475
 1476        editor.move_down(&MoveDown, window, cx);
 1477        assert_eq!(
 1478            editor.selections.display_ranges(cx),
 1479            &[empty_range(2, "αβγ".len())]
 1480        );
 1481
 1482        editor.move_down(&MoveDown, window, cx);
 1483        assert_eq!(
 1484            editor.selections.display_ranges(cx),
 1485            &[empty_range(3, "abcd".len())]
 1486        );
 1487
 1488        editor.move_down(&MoveDown, window, cx);
 1489        assert_eq!(
 1490            editor.selections.display_ranges(cx),
 1491            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1492        );
 1493
 1494        // moving past end of document should not change goal_x
 1495        editor.move_down(&MoveDown, window, cx);
 1496        assert_eq!(
 1497            editor.selections.display_ranges(cx),
 1498            &[empty_range(5, "".len())]
 1499        );
 1500
 1501        editor.move_down(&MoveDown, window, cx);
 1502        assert_eq!(
 1503            editor.selections.display_ranges(cx),
 1504            &[empty_range(5, "".len())]
 1505        );
 1506
 1507        editor.move_up(&MoveUp, window, cx);
 1508        assert_eq!(
 1509            editor.selections.display_ranges(cx),
 1510            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1511        );
 1512
 1513        editor.move_up(&MoveUp, window, cx);
 1514        assert_eq!(
 1515            editor.selections.display_ranges(cx),
 1516            &[empty_range(3, "abcd".len())]
 1517        );
 1518
 1519        editor.move_up(&MoveUp, window, cx);
 1520        assert_eq!(
 1521            editor.selections.display_ranges(cx),
 1522            &[empty_range(2, "αβγ".len())]
 1523        );
 1524    });
 1525}
 1526
 1527#[gpui::test]
 1528fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1529    init_test(cx, |_| {});
 1530    let move_to_beg = MoveToBeginningOfLine {
 1531        stop_at_soft_wraps: true,
 1532        stop_at_indent: true,
 1533    };
 1534
 1535    let delete_to_beg = DeleteToBeginningOfLine {
 1536        stop_at_indent: false,
 1537    };
 1538
 1539    let move_to_end = MoveToEndOfLine {
 1540        stop_at_soft_wraps: true,
 1541    };
 1542
 1543    let editor = cx.add_window(|window, cx| {
 1544        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1545        build_editor(buffer, window, cx)
 1546    });
 1547    _ = editor.update(cx, |editor, window, cx| {
 1548        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1549            s.select_display_ranges([
 1550                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1551                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1552            ]);
 1553        });
 1554    });
 1555
 1556    _ = editor.update(cx, |editor, window, cx| {
 1557        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1558        assert_eq!(
 1559            editor.selections.display_ranges(cx),
 1560            &[
 1561                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1562                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1563            ]
 1564        );
 1565    });
 1566
 1567    _ = editor.update(cx, |editor, window, cx| {
 1568        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1569        assert_eq!(
 1570            editor.selections.display_ranges(cx),
 1571            &[
 1572                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1573                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1574            ]
 1575        );
 1576    });
 1577
 1578    _ = editor.update(cx, |editor, window, cx| {
 1579        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1580        assert_eq!(
 1581            editor.selections.display_ranges(cx),
 1582            &[
 1583                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1584                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1585            ]
 1586        );
 1587    });
 1588
 1589    _ = editor.update(cx, |editor, window, cx| {
 1590        editor.move_to_end_of_line(&move_to_end, window, cx);
 1591        assert_eq!(
 1592            editor.selections.display_ranges(cx),
 1593            &[
 1594                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1595                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1596            ]
 1597        );
 1598    });
 1599
 1600    // Moving to the end of line again is a no-op.
 1601    _ = editor.update(cx, |editor, window, cx| {
 1602        editor.move_to_end_of_line(&move_to_end, window, cx);
 1603        assert_eq!(
 1604            editor.selections.display_ranges(cx),
 1605            &[
 1606                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1607                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1608            ]
 1609        );
 1610    });
 1611
 1612    _ = editor.update(cx, |editor, window, cx| {
 1613        editor.move_left(&MoveLeft, window, cx);
 1614        editor.select_to_beginning_of_line(
 1615            &SelectToBeginningOfLine {
 1616                stop_at_soft_wraps: true,
 1617                stop_at_indent: true,
 1618            },
 1619            window,
 1620            cx,
 1621        );
 1622        assert_eq!(
 1623            editor.selections.display_ranges(cx),
 1624            &[
 1625                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1626                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1627            ]
 1628        );
 1629    });
 1630
 1631    _ = editor.update(cx, |editor, window, cx| {
 1632        editor.select_to_beginning_of_line(
 1633            &SelectToBeginningOfLine {
 1634                stop_at_soft_wraps: true,
 1635                stop_at_indent: true,
 1636            },
 1637            window,
 1638            cx,
 1639        );
 1640        assert_eq!(
 1641            editor.selections.display_ranges(cx),
 1642            &[
 1643                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1644                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1645            ]
 1646        );
 1647    });
 1648
 1649    _ = editor.update(cx, |editor, window, cx| {
 1650        editor.select_to_beginning_of_line(
 1651            &SelectToBeginningOfLine {
 1652                stop_at_soft_wraps: true,
 1653                stop_at_indent: true,
 1654            },
 1655            window,
 1656            cx,
 1657        );
 1658        assert_eq!(
 1659            editor.selections.display_ranges(cx),
 1660            &[
 1661                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1662                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1663            ]
 1664        );
 1665    });
 1666
 1667    _ = editor.update(cx, |editor, window, cx| {
 1668        editor.select_to_end_of_line(
 1669            &SelectToEndOfLine {
 1670                stop_at_soft_wraps: true,
 1671            },
 1672            window,
 1673            cx,
 1674        );
 1675        assert_eq!(
 1676            editor.selections.display_ranges(cx),
 1677            &[
 1678                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1679                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1680            ]
 1681        );
 1682    });
 1683
 1684    _ = editor.update(cx, |editor, window, cx| {
 1685        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1686        assert_eq!(editor.display_text(cx), "ab\n  de");
 1687        assert_eq!(
 1688            editor.selections.display_ranges(cx),
 1689            &[
 1690                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1691                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1692            ]
 1693        );
 1694    });
 1695
 1696    _ = editor.update(cx, |editor, window, cx| {
 1697        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1698        assert_eq!(editor.display_text(cx), "\n");
 1699        assert_eq!(
 1700            editor.selections.display_ranges(cx),
 1701            &[
 1702                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1703                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1704            ]
 1705        );
 1706    });
 1707}
 1708
 1709#[gpui::test]
 1710fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1711    init_test(cx, |_| {});
 1712    let move_to_beg = MoveToBeginningOfLine {
 1713        stop_at_soft_wraps: false,
 1714        stop_at_indent: false,
 1715    };
 1716
 1717    let move_to_end = MoveToEndOfLine {
 1718        stop_at_soft_wraps: false,
 1719    };
 1720
 1721    let editor = cx.add_window(|window, cx| {
 1722        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1723        build_editor(buffer, window, cx)
 1724    });
 1725
 1726    _ = editor.update(cx, |editor, window, cx| {
 1727        editor.set_wrap_width(Some(140.0.into()), cx);
 1728
 1729        // We expect the following lines after wrapping
 1730        // ```
 1731        // thequickbrownfox
 1732        // jumpedoverthelazydo
 1733        // gs
 1734        // ```
 1735        // The final `gs` was soft-wrapped onto a new line.
 1736        assert_eq!(
 1737            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1738            editor.display_text(cx),
 1739        );
 1740
 1741        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1742        // Start the cursor at the `k` on the first line
 1743        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1744            s.select_display_ranges([
 1745                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1746            ]);
 1747        });
 1748
 1749        // Moving to the beginning of the line should put us at the beginning of the line.
 1750        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1751        assert_eq!(
 1752            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1753            editor.selections.display_ranges(cx)
 1754        );
 1755
 1756        // Moving to the end of the line should put us at the end of the line.
 1757        editor.move_to_end_of_line(&move_to_end, window, cx);
 1758        assert_eq!(
 1759            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1760            editor.selections.display_ranges(cx)
 1761        );
 1762
 1763        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1764        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1765        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1766            s.select_display_ranges([
 1767                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1768            ]);
 1769        });
 1770
 1771        // Moving to the beginning of the line should put us at the start of the second line of
 1772        // display text, i.e., the `j`.
 1773        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1774        assert_eq!(
 1775            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1776            editor.selections.display_ranges(cx)
 1777        );
 1778
 1779        // Moving to the beginning of the line again should be a no-op.
 1780        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1781        assert_eq!(
 1782            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1783            editor.selections.display_ranges(cx)
 1784        );
 1785
 1786        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1787        // next display line.
 1788        editor.move_to_end_of_line(&move_to_end, window, cx);
 1789        assert_eq!(
 1790            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1791            editor.selections.display_ranges(cx)
 1792        );
 1793
 1794        // Moving to the end of the line again should be a no-op.
 1795        editor.move_to_end_of_line(&move_to_end, window, cx);
 1796        assert_eq!(
 1797            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1798            editor.selections.display_ranges(cx)
 1799        );
 1800    });
 1801}
 1802
 1803#[gpui::test]
 1804fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1805    init_test(cx, |_| {});
 1806
 1807    let move_to_beg = MoveToBeginningOfLine {
 1808        stop_at_soft_wraps: true,
 1809        stop_at_indent: true,
 1810    };
 1811
 1812    let select_to_beg = SelectToBeginningOfLine {
 1813        stop_at_soft_wraps: true,
 1814        stop_at_indent: true,
 1815    };
 1816
 1817    let delete_to_beg = DeleteToBeginningOfLine {
 1818        stop_at_indent: true,
 1819    };
 1820
 1821    let move_to_end = MoveToEndOfLine {
 1822        stop_at_soft_wraps: false,
 1823    };
 1824
 1825    let editor = cx.add_window(|window, cx| {
 1826        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1827        build_editor(buffer, window, cx)
 1828    });
 1829
 1830    _ = editor.update(cx, |editor, window, cx| {
 1831        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1832            s.select_display_ranges([
 1833                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1834                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1835            ]);
 1836        });
 1837
 1838        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 1839        // and the second cursor at the first non-whitespace character in the line.
 1840        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1841        assert_eq!(
 1842            editor.selections.display_ranges(cx),
 1843            &[
 1844                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1845                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1846            ]
 1847        );
 1848
 1849        // Moving to the beginning of the line again should be a no-op for the first cursor,
 1850        // and should move the second cursor to the beginning of the line.
 1851        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1852        assert_eq!(
 1853            editor.selections.display_ranges(cx),
 1854            &[
 1855                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1856                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1857            ]
 1858        );
 1859
 1860        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 1861        // and should move the second cursor back to the first non-whitespace character in the line.
 1862        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1863        assert_eq!(
 1864            editor.selections.display_ranges(cx),
 1865            &[
 1866                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1867                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1868            ]
 1869        );
 1870
 1871        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 1872        // and to the first non-whitespace character in the line for the second cursor.
 1873        editor.move_to_end_of_line(&move_to_end, window, cx);
 1874        editor.move_left(&MoveLeft, window, cx);
 1875        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1876        assert_eq!(
 1877            editor.selections.display_ranges(cx),
 1878            &[
 1879                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1880                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1881            ]
 1882        );
 1883
 1884        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 1885        // and should select to the beginning of the line for the second cursor.
 1886        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1887        assert_eq!(
 1888            editor.selections.display_ranges(cx),
 1889            &[
 1890                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1891                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1892            ]
 1893        );
 1894
 1895        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 1896        // and should delete to the first non-whitespace character in the line for the second cursor.
 1897        editor.move_to_end_of_line(&move_to_end, window, cx);
 1898        editor.move_left(&MoveLeft, window, cx);
 1899        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1900        assert_eq!(editor.text(cx), "c\n  f");
 1901    });
 1902}
 1903
 1904#[gpui::test]
 1905fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 1906    init_test(cx, |_| {});
 1907
 1908    let editor = cx.add_window(|window, cx| {
 1909        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 1910        build_editor(buffer, window, cx)
 1911    });
 1912    _ = editor.update(cx, |editor, window, cx| {
 1913        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1914            s.select_display_ranges([
 1915                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 1916                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 1917            ])
 1918        });
 1919        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1920        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 1921
 1922        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1923        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 1924
 1925        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1926        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1927
 1928        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1929        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1930
 1931        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1932        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 1933
 1934        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1935        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1936
 1937        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1938        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 1939
 1940        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1941        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1942
 1943        editor.move_right(&MoveRight, window, cx);
 1944        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1945        assert_selection_ranges(
 1946            "use std::«ˇs»tr::{foo, bar}\n«ˇ\n»  {baz.qux()}",
 1947            editor,
 1948            cx,
 1949        );
 1950
 1951        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1952        assert_selection_ranges(
 1953            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 1954            editor,
 1955            cx,
 1956        );
 1957
 1958        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 1959        assert_selection_ranges(
 1960            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 1961            editor,
 1962            cx,
 1963        );
 1964    });
 1965}
 1966
 1967#[gpui::test]
 1968fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 1969    init_test(cx, |_| {});
 1970
 1971    let editor = cx.add_window(|window, cx| {
 1972        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 1973        build_editor(buffer, window, cx)
 1974    });
 1975
 1976    _ = editor.update(cx, |editor, window, cx| {
 1977        editor.set_wrap_width(Some(140.0.into()), cx);
 1978        assert_eq!(
 1979            editor.display_text(cx),
 1980            "use one::{\n    two::three::\n    four::five\n};"
 1981        );
 1982
 1983        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1984            s.select_display_ranges([
 1985                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 1986            ]);
 1987        });
 1988
 1989        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1990        assert_eq!(
 1991            editor.selections.display_ranges(cx),
 1992            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 1993        );
 1994
 1995        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1996        assert_eq!(
 1997            editor.selections.display_ranges(cx),
 1998            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 1999        );
 2000
 2001        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2002        assert_eq!(
 2003            editor.selections.display_ranges(cx),
 2004            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2005        );
 2006
 2007        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2008        assert_eq!(
 2009            editor.selections.display_ranges(cx),
 2010            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2011        );
 2012
 2013        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2014        assert_eq!(
 2015            editor.selections.display_ranges(cx),
 2016            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2017        );
 2018
 2019        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2020        assert_eq!(
 2021            editor.selections.display_ranges(cx),
 2022            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2023        );
 2024    });
 2025}
 2026
 2027#[gpui::test]
 2028async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2029    init_test(cx, |_| {});
 2030    let mut cx = EditorTestContext::new(cx).await;
 2031
 2032    let line_height = cx.editor(|editor, window, _| {
 2033        editor
 2034            .style()
 2035            .unwrap()
 2036            .text
 2037            .line_height_in_pixels(window.rem_size())
 2038    });
 2039    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2040
 2041    cx.set_state(
 2042        &r#"ˇone
 2043        two
 2044
 2045        three
 2046        fourˇ
 2047        five
 2048
 2049        six"#
 2050            .unindent(),
 2051    );
 2052
 2053    cx.update_editor(|editor, window, cx| {
 2054        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2055    });
 2056    cx.assert_editor_state(
 2057        &r#"one
 2058        two
 2059        ˇ
 2060        three
 2061        four
 2062        five
 2063        ˇ
 2064        six"#
 2065            .unindent(),
 2066    );
 2067
 2068    cx.update_editor(|editor, window, cx| {
 2069        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2070    });
 2071    cx.assert_editor_state(
 2072        &r#"one
 2073        two
 2074
 2075        three
 2076        four
 2077        five
 2078        ˇ
 2079        sixˇ"#
 2080            .unindent(),
 2081    );
 2082
 2083    cx.update_editor(|editor, window, cx| {
 2084        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2085    });
 2086    cx.assert_editor_state(
 2087        &r#"one
 2088        two
 2089
 2090        three
 2091        four
 2092        five
 2093
 2094        sixˇ"#
 2095            .unindent(),
 2096    );
 2097
 2098    cx.update_editor(|editor, window, cx| {
 2099        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2100    });
 2101    cx.assert_editor_state(
 2102        &r#"one
 2103        two
 2104
 2105        three
 2106        four
 2107        five
 2108        ˇ
 2109        six"#
 2110            .unindent(),
 2111    );
 2112
 2113    cx.update_editor(|editor, window, cx| {
 2114        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2115    });
 2116    cx.assert_editor_state(
 2117        &r#"one
 2118        two
 2119        ˇ
 2120        three
 2121        four
 2122        five
 2123
 2124        six"#
 2125            .unindent(),
 2126    );
 2127
 2128    cx.update_editor(|editor, window, cx| {
 2129        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2130    });
 2131    cx.assert_editor_state(
 2132        &r#"ˇone
 2133        two
 2134
 2135        three
 2136        four
 2137        five
 2138
 2139        six"#
 2140            .unindent(),
 2141    );
 2142}
 2143
 2144#[gpui::test]
 2145async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2146    init_test(cx, |_| {});
 2147    let mut cx = EditorTestContext::new(cx).await;
 2148    let line_height = cx.editor(|editor, window, _| {
 2149        editor
 2150            .style()
 2151            .unwrap()
 2152            .text
 2153            .line_height_in_pixels(window.rem_size())
 2154    });
 2155    let window = cx.window;
 2156    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2157
 2158    cx.set_state(
 2159        r#"ˇone
 2160        two
 2161        three
 2162        four
 2163        five
 2164        six
 2165        seven
 2166        eight
 2167        nine
 2168        ten
 2169        "#,
 2170    );
 2171
 2172    cx.update_editor(|editor, window, cx| {
 2173        assert_eq!(
 2174            editor.snapshot(window, cx).scroll_position(),
 2175            gpui::Point::new(0., 0.)
 2176        );
 2177        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2178        assert_eq!(
 2179            editor.snapshot(window, cx).scroll_position(),
 2180            gpui::Point::new(0., 3.)
 2181        );
 2182        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2183        assert_eq!(
 2184            editor.snapshot(window, cx).scroll_position(),
 2185            gpui::Point::new(0., 6.)
 2186        );
 2187        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2188        assert_eq!(
 2189            editor.snapshot(window, cx).scroll_position(),
 2190            gpui::Point::new(0., 3.)
 2191        );
 2192
 2193        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2194        assert_eq!(
 2195            editor.snapshot(window, cx).scroll_position(),
 2196            gpui::Point::new(0., 1.)
 2197        );
 2198        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2199        assert_eq!(
 2200            editor.snapshot(window, cx).scroll_position(),
 2201            gpui::Point::new(0., 3.)
 2202        );
 2203    });
 2204}
 2205
 2206#[gpui::test]
 2207async fn test_autoscroll(cx: &mut TestAppContext) {
 2208    init_test(cx, |_| {});
 2209    let mut cx = EditorTestContext::new(cx).await;
 2210
 2211    let line_height = cx.update_editor(|editor, window, cx| {
 2212        editor.set_vertical_scroll_margin(2, cx);
 2213        editor
 2214            .style()
 2215            .unwrap()
 2216            .text
 2217            .line_height_in_pixels(window.rem_size())
 2218    });
 2219    let window = cx.window;
 2220    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2221
 2222    cx.set_state(
 2223        r#"ˇone
 2224            two
 2225            three
 2226            four
 2227            five
 2228            six
 2229            seven
 2230            eight
 2231            nine
 2232            ten
 2233        "#,
 2234    );
 2235    cx.update_editor(|editor, window, cx| {
 2236        assert_eq!(
 2237            editor.snapshot(window, cx).scroll_position(),
 2238            gpui::Point::new(0., 0.0)
 2239        );
 2240    });
 2241
 2242    // Add a cursor below the visible area. Since both cursors cannot fit
 2243    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2244    // allows the vertical scroll margin below that cursor.
 2245    cx.update_editor(|editor, window, cx| {
 2246        editor.change_selections(Default::default(), window, cx, |selections| {
 2247            selections.select_ranges([
 2248                Point::new(0, 0)..Point::new(0, 0),
 2249                Point::new(6, 0)..Point::new(6, 0),
 2250            ]);
 2251        })
 2252    });
 2253    cx.update_editor(|editor, window, cx| {
 2254        assert_eq!(
 2255            editor.snapshot(window, cx).scroll_position(),
 2256            gpui::Point::new(0., 3.0)
 2257        );
 2258    });
 2259
 2260    // Move down. The editor cursor scrolls down to track the newest cursor.
 2261    cx.update_editor(|editor, window, cx| {
 2262        editor.move_down(&Default::default(), window, cx);
 2263    });
 2264    cx.update_editor(|editor, window, cx| {
 2265        assert_eq!(
 2266            editor.snapshot(window, cx).scroll_position(),
 2267            gpui::Point::new(0., 4.0)
 2268        );
 2269    });
 2270
 2271    // Add a cursor above the visible area. Since both cursors fit on screen,
 2272    // the editor scrolls to show both.
 2273    cx.update_editor(|editor, window, cx| {
 2274        editor.change_selections(Default::default(), window, cx, |selections| {
 2275            selections.select_ranges([
 2276                Point::new(1, 0)..Point::new(1, 0),
 2277                Point::new(6, 0)..Point::new(6, 0),
 2278            ]);
 2279        })
 2280    });
 2281    cx.update_editor(|editor, window, cx| {
 2282        assert_eq!(
 2283            editor.snapshot(window, cx).scroll_position(),
 2284            gpui::Point::new(0., 1.0)
 2285        );
 2286    });
 2287}
 2288
 2289#[gpui::test]
 2290async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2291    init_test(cx, |_| {});
 2292    let mut cx = EditorTestContext::new(cx).await;
 2293
 2294    let line_height = cx.editor(|editor, window, _cx| {
 2295        editor
 2296            .style()
 2297            .unwrap()
 2298            .text
 2299            .line_height_in_pixels(window.rem_size())
 2300    });
 2301    let window = cx.window;
 2302    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2303    cx.set_state(
 2304        &r#"
 2305        ˇone
 2306        two
 2307        threeˇ
 2308        four
 2309        five
 2310        six
 2311        seven
 2312        eight
 2313        nine
 2314        ten
 2315        "#
 2316        .unindent(),
 2317    );
 2318
 2319    cx.update_editor(|editor, window, cx| {
 2320        editor.move_page_down(&MovePageDown::default(), window, cx)
 2321    });
 2322    cx.assert_editor_state(
 2323        &r#"
 2324        one
 2325        two
 2326        three
 2327        ˇfour
 2328        five
 2329        sixˇ
 2330        seven
 2331        eight
 2332        nine
 2333        ten
 2334        "#
 2335        .unindent(),
 2336    );
 2337
 2338    cx.update_editor(|editor, window, cx| {
 2339        editor.move_page_down(&MovePageDown::default(), window, cx)
 2340    });
 2341    cx.assert_editor_state(
 2342        &r#"
 2343        one
 2344        two
 2345        three
 2346        four
 2347        five
 2348        six
 2349        ˇseven
 2350        eight
 2351        nineˇ
 2352        ten
 2353        "#
 2354        .unindent(),
 2355    );
 2356
 2357    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2358    cx.assert_editor_state(
 2359        &r#"
 2360        one
 2361        two
 2362        three
 2363        ˇfour
 2364        five
 2365        sixˇ
 2366        seven
 2367        eight
 2368        nine
 2369        ten
 2370        "#
 2371        .unindent(),
 2372    );
 2373
 2374    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2375    cx.assert_editor_state(
 2376        &r#"
 2377        ˇone
 2378        two
 2379        threeˇ
 2380        four
 2381        five
 2382        six
 2383        seven
 2384        eight
 2385        nine
 2386        ten
 2387        "#
 2388        .unindent(),
 2389    );
 2390
 2391    // Test select collapsing
 2392    cx.update_editor(|editor, window, cx| {
 2393        editor.move_page_down(&MovePageDown::default(), window, cx);
 2394        editor.move_page_down(&MovePageDown::default(), window, cx);
 2395        editor.move_page_down(&MovePageDown::default(), window, cx);
 2396    });
 2397    cx.assert_editor_state(
 2398        &r#"
 2399        one
 2400        two
 2401        three
 2402        four
 2403        five
 2404        six
 2405        seven
 2406        eight
 2407        nine
 2408        ˇten
 2409        ˇ"#
 2410        .unindent(),
 2411    );
 2412}
 2413
 2414#[gpui::test]
 2415async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2416    init_test(cx, |_| {});
 2417    let mut cx = EditorTestContext::new(cx).await;
 2418    cx.set_state("one «two threeˇ» four");
 2419    cx.update_editor(|editor, window, cx| {
 2420        editor.delete_to_beginning_of_line(
 2421            &DeleteToBeginningOfLine {
 2422                stop_at_indent: false,
 2423            },
 2424            window,
 2425            cx,
 2426        );
 2427        assert_eq!(editor.text(cx), " four");
 2428    });
 2429}
 2430
 2431#[gpui::test]
 2432fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2433    init_test(cx, |_| {});
 2434
 2435    let editor = cx.add_window(|window, cx| {
 2436        let buffer = MultiBuffer::build_simple("one two three four", cx);
 2437        build_editor(buffer.clone(), window, cx)
 2438    });
 2439
 2440    _ = editor.update(cx, |editor, window, cx| {
 2441        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2442            s.select_display_ranges([
 2443                // an empty selection - the preceding word fragment is deleted
 2444                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2445                // characters selected - they are deleted
 2446                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
 2447            ])
 2448        });
 2449        editor.delete_to_previous_word_start(
 2450            &DeleteToPreviousWordStart {
 2451                ignore_newlines: false,
 2452            },
 2453            window,
 2454            cx,
 2455        );
 2456        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e two te four");
 2457    });
 2458
 2459    _ = editor.update(cx, |editor, window, cx| {
 2460        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2461            s.select_display_ranges([
 2462                // an empty selection - the following word fragment is deleted
 2463                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 2464                // characters selected - they are deleted
 2465                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
 2466            ])
 2467        });
 2468        editor.delete_to_next_word_end(
 2469            &DeleteToNextWordEnd {
 2470                ignore_newlines: false,
 2471            },
 2472            window,
 2473            cx,
 2474        );
 2475        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e t te our");
 2476    });
 2477}
 2478
 2479#[gpui::test]
 2480fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2481    init_test(cx, |_| {});
 2482
 2483    let editor = cx.add_window(|window, cx| {
 2484        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 2485        build_editor(buffer.clone(), window, cx)
 2486    });
 2487    let del_to_prev_word_start = DeleteToPreviousWordStart {
 2488        ignore_newlines: false,
 2489    };
 2490    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 2491        ignore_newlines: true,
 2492    };
 2493
 2494    _ = editor.update(cx, |editor, window, cx| {
 2495        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2496            s.select_display_ranges([
 2497                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 2498            ])
 2499        });
 2500        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2501        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 2502        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2503        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 2504        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2505        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 2506        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2507        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 2508        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2509        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 2510        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2511        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2512    });
 2513}
 2514
 2515#[gpui::test]
 2516fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 2517    init_test(cx, |_| {});
 2518
 2519    let editor = cx.add_window(|window, cx| {
 2520        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 2521        build_editor(buffer.clone(), window, cx)
 2522    });
 2523    let del_to_next_word_end = DeleteToNextWordEnd {
 2524        ignore_newlines: false,
 2525    };
 2526    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 2527        ignore_newlines: true,
 2528    };
 2529
 2530    _ = editor.update(cx, |editor, window, cx| {
 2531        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2532            s.select_display_ranges([
 2533                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 2534            ])
 2535        });
 2536        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2537        assert_eq!(
 2538            editor.buffer.read(cx).read(cx).text(),
 2539            "one\n   two\nthree\n   four"
 2540        );
 2541        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2542        assert_eq!(
 2543            editor.buffer.read(cx).read(cx).text(),
 2544            "\n   two\nthree\n   four"
 2545        );
 2546        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2547        assert_eq!(
 2548            editor.buffer.read(cx).read(cx).text(),
 2549            "two\nthree\n   four"
 2550        );
 2551        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2552        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 2553        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2554        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 2555        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2556        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2557    });
 2558}
 2559
 2560#[gpui::test]
 2561fn test_newline(cx: &mut TestAppContext) {
 2562    init_test(cx, |_| {});
 2563
 2564    let editor = cx.add_window(|window, cx| {
 2565        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 2566        build_editor(buffer.clone(), window, cx)
 2567    });
 2568
 2569    _ = editor.update(cx, |editor, window, cx| {
 2570        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2571            s.select_display_ranges([
 2572                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2573                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2574                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 2575            ])
 2576        });
 2577
 2578        editor.newline(&Newline, window, cx);
 2579        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 2580    });
 2581}
 2582
 2583#[gpui::test]
 2584fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 2585    init_test(cx, |_| {});
 2586
 2587    let editor = cx.add_window(|window, cx| {
 2588        let buffer = MultiBuffer::build_simple(
 2589            "
 2590                a
 2591                b(
 2592                    X
 2593                )
 2594                c(
 2595                    X
 2596                )
 2597            "
 2598            .unindent()
 2599            .as_str(),
 2600            cx,
 2601        );
 2602        let mut editor = build_editor(buffer.clone(), window, cx);
 2603        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2604            s.select_ranges([
 2605                Point::new(2, 4)..Point::new(2, 5),
 2606                Point::new(5, 4)..Point::new(5, 5),
 2607            ])
 2608        });
 2609        editor
 2610    });
 2611
 2612    _ = editor.update(cx, |editor, window, cx| {
 2613        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 2614        editor.buffer.update(cx, |buffer, cx| {
 2615            buffer.edit(
 2616                [
 2617                    (Point::new(1, 2)..Point::new(3, 0), ""),
 2618                    (Point::new(4, 2)..Point::new(6, 0), ""),
 2619                ],
 2620                None,
 2621                cx,
 2622            );
 2623            assert_eq!(
 2624                buffer.read(cx).text(),
 2625                "
 2626                    a
 2627                    b()
 2628                    c()
 2629                "
 2630                .unindent()
 2631            );
 2632        });
 2633        assert_eq!(
 2634            editor.selections.ranges(cx),
 2635            &[
 2636                Point::new(1, 2)..Point::new(1, 2),
 2637                Point::new(2, 2)..Point::new(2, 2),
 2638            ],
 2639        );
 2640
 2641        editor.newline(&Newline, window, cx);
 2642        assert_eq!(
 2643            editor.text(cx),
 2644            "
 2645                a
 2646                b(
 2647                )
 2648                c(
 2649                )
 2650            "
 2651            .unindent()
 2652        );
 2653
 2654        // The selections are moved after the inserted newlines
 2655        assert_eq!(
 2656            editor.selections.ranges(cx),
 2657            &[
 2658                Point::new(2, 0)..Point::new(2, 0),
 2659                Point::new(4, 0)..Point::new(4, 0),
 2660            ],
 2661        );
 2662    });
 2663}
 2664
 2665#[gpui::test]
 2666async fn test_newline_above(cx: &mut TestAppContext) {
 2667    init_test(cx, |settings| {
 2668        settings.defaults.tab_size = NonZeroU32::new(4)
 2669    });
 2670
 2671    let language = Arc::new(
 2672        Language::new(
 2673            LanguageConfig::default(),
 2674            Some(tree_sitter_rust::LANGUAGE.into()),
 2675        )
 2676        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2677        .unwrap(),
 2678    );
 2679
 2680    let mut cx = EditorTestContext::new(cx).await;
 2681    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2682    cx.set_state(indoc! {"
 2683        const a: ˇA = (
 2684 2685                «const_functionˇ»(ˇ),
 2686                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2687 2688        ˇ);ˇ
 2689    "});
 2690
 2691    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 2692    cx.assert_editor_state(indoc! {"
 2693        ˇ
 2694        const a: A = (
 2695            ˇ
 2696            (
 2697                ˇ
 2698                ˇ
 2699                const_function(),
 2700                ˇ
 2701                ˇ
 2702                ˇ
 2703                ˇ
 2704                something_else,
 2705                ˇ
 2706            )
 2707            ˇ
 2708            ˇ
 2709        );
 2710    "});
 2711}
 2712
 2713#[gpui::test]
 2714async fn test_newline_below(cx: &mut TestAppContext) {
 2715    init_test(cx, |settings| {
 2716        settings.defaults.tab_size = NonZeroU32::new(4)
 2717    });
 2718
 2719    let language = Arc::new(
 2720        Language::new(
 2721            LanguageConfig::default(),
 2722            Some(tree_sitter_rust::LANGUAGE.into()),
 2723        )
 2724        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2725        .unwrap(),
 2726    );
 2727
 2728    let mut cx = EditorTestContext::new(cx).await;
 2729    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2730    cx.set_state(indoc! {"
 2731        const a: ˇA = (
 2732 2733                «const_functionˇ»(ˇ),
 2734                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2735 2736        ˇ);ˇ
 2737    "});
 2738
 2739    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 2740    cx.assert_editor_state(indoc! {"
 2741        const a: A = (
 2742            ˇ
 2743            (
 2744                ˇ
 2745                const_function(),
 2746                ˇ
 2747                ˇ
 2748                something_else,
 2749                ˇ
 2750                ˇ
 2751                ˇ
 2752                ˇ
 2753            )
 2754            ˇ
 2755        );
 2756        ˇ
 2757        ˇ
 2758    "});
 2759}
 2760
 2761#[gpui::test]
 2762async fn test_newline_comments(cx: &mut TestAppContext) {
 2763    init_test(cx, |settings| {
 2764        settings.defaults.tab_size = NonZeroU32::new(4)
 2765    });
 2766
 2767    let language = Arc::new(Language::new(
 2768        LanguageConfig {
 2769            line_comments: vec!["// ".into()],
 2770            ..LanguageConfig::default()
 2771        },
 2772        None,
 2773    ));
 2774    {
 2775        let mut cx = EditorTestContext::new(cx).await;
 2776        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2777        cx.set_state(indoc! {"
 2778        // Fooˇ
 2779    "});
 2780
 2781        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2782        cx.assert_editor_state(indoc! {"
 2783        // Foo
 2784        // ˇ
 2785    "});
 2786        // Ensure that we add comment prefix when existing line contains space
 2787        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2788        cx.assert_editor_state(
 2789            indoc! {"
 2790        // Foo
 2791        //s
 2792        // ˇ
 2793    "}
 2794            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2795            .as_str(),
 2796        );
 2797        // Ensure that we add comment prefix when existing line does not contain space
 2798        cx.set_state(indoc! {"
 2799        // Foo
 2800        //ˇ
 2801    "});
 2802        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2803        cx.assert_editor_state(indoc! {"
 2804        // Foo
 2805        //
 2806        // ˇ
 2807    "});
 2808        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 2809        cx.set_state(indoc! {"
 2810        ˇ// Foo
 2811    "});
 2812        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2813        cx.assert_editor_state(indoc! {"
 2814
 2815        ˇ// Foo
 2816    "});
 2817    }
 2818    // Ensure that comment continuations can be disabled.
 2819    update_test_language_settings(cx, |settings| {
 2820        settings.defaults.extend_comment_on_newline = Some(false);
 2821    });
 2822    let mut cx = EditorTestContext::new(cx).await;
 2823    cx.set_state(indoc! {"
 2824        // Fooˇ
 2825    "});
 2826    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2827    cx.assert_editor_state(indoc! {"
 2828        // Foo
 2829        ˇ
 2830    "});
 2831}
 2832
 2833#[gpui::test]
 2834async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 2835    init_test(cx, |settings| {
 2836        settings.defaults.tab_size = NonZeroU32::new(4)
 2837    });
 2838
 2839    let language = Arc::new(Language::new(
 2840        LanguageConfig {
 2841            line_comments: vec!["// ".into(), "/// ".into()],
 2842            ..LanguageConfig::default()
 2843        },
 2844        None,
 2845    ));
 2846    {
 2847        let mut cx = EditorTestContext::new(cx).await;
 2848        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2849        cx.set_state(indoc! {"
 2850        //ˇ
 2851    "});
 2852        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2853        cx.assert_editor_state(indoc! {"
 2854        //
 2855        // ˇ
 2856    "});
 2857
 2858        cx.set_state(indoc! {"
 2859        ///ˇ
 2860    "});
 2861        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2862        cx.assert_editor_state(indoc! {"
 2863        ///
 2864        /// ˇ
 2865    "});
 2866    }
 2867}
 2868
 2869#[gpui::test]
 2870async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 2871    init_test(cx, |settings| {
 2872        settings.defaults.tab_size = NonZeroU32::new(4)
 2873    });
 2874
 2875    let language = Arc::new(
 2876        Language::new(
 2877            LanguageConfig {
 2878                documentation: Some(language::DocumentationConfig {
 2879                    start: "/**".into(),
 2880                    end: "*/".into(),
 2881                    prefix: "* ".into(),
 2882                    tab_size: NonZeroU32::new(1).unwrap(),
 2883                }),
 2884
 2885                ..LanguageConfig::default()
 2886            },
 2887            Some(tree_sitter_rust::LANGUAGE.into()),
 2888        )
 2889        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 2890        .unwrap(),
 2891    );
 2892
 2893    {
 2894        let mut cx = EditorTestContext::new(cx).await;
 2895        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2896        cx.set_state(indoc! {"
 2897        /**ˇ
 2898    "});
 2899
 2900        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2901        cx.assert_editor_state(indoc! {"
 2902        /**
 2903         * ˇ
 2904    "});
 2905        // Ensure that if cursor is before the comment start,
 2906        // we do not actually insert a comment prefix.
 2907        cx.set_state(indoc! {"
 2908        ˇ/**
 2909    "});
 2910        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2911        cx.assert_editor_state(indoc! {"
 2912
 2913        ˇ/**
 2914    "});
 2915        // Ensure that if cursor is between it doesn't add comment prefix.
 2916        cx.set_state(indoc! {"
 2917        /*ˇ*
 2918    "});
 2919        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2920        cx.assert_editor_state(indoc! {"
 2921        /*
 2922        ˇ*
 2923    "});
 2924        // Ensure that if suffix exists on same line after cursor it adds new line.
 2925        cx.set_state(indoc! {"
 2926        /**ˇ*/
 2927    "});
 2928        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2929        cx.assert_editor_state(indoc! {"
 2930        /**
 2931         * ˇ
 2932         */
 2933    "});
 2934        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2935        cx.set_state(indoc! {"
 2936        /**ˇ */
 2937    "});
 2938        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2939        cx.assert_editor_state(indoc! {"
 2940        /**
 2941         * ˇ
 2942         */
 2943    "});
 2944        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2945        cx.set_state(indoc! {"
 2946        /** ˇ*/
 2947    "});
 2948        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2949        cx.assert_editor_state(
 2950            indoc! {"
 2951        /**s
 2952         * ˇ
 2953         */
 2954    "}
 2955            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2956            .as_str(),
 2957        );
 2958        // Ensure that delimiter space is preserved when newline on already
 2959        // spaced delimiter.
 2960        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2961        cx.assert_editor_state(
 2962            indoc! {"
 2963        /**s
 2964         *s
 2965         * ˇ
 2966         */
 2967    "}
 2968            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2969            .as_str(),
 2970        );
 2971        // Ensure that delimiter space is preserved when space is not
 2972        // on existing delimiter.
 2973        cx.set_state(indoc! {"
 2974        /**
 2975 2976         */
 2977    "});
 2978        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2979        cx.assert_editor_state(indoc! {"
 2980        /**
 2981         *
 2982         * ˇ
 2983         */
 2984    "});
 2985        // Ensure that if suffix exists on same line after cursor it
 2986        // doesn't add extra new line if prefix is not on same line.
 2987        cx.set_state(indoc! {"
 2988        /**
 2989        ˇ*/
 2990    "});
 2991        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2992        cx.assert_editor_state(indoc! {"
 2993        /**
 2994
 2995        ˇ*/
 2996    "});
 2997        // Ensure that it detects suffix after existing prefix.
 2998        cx.set_state(indoc! {"
 2999        /**ˇ/
 3000    "});
 3001        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3002        cx.assert_editor_state(indoc! {"
 3003        /**
 3004        ˇ/
 3005    "});
 3006        // Ensure that if suffix exists on same line before
 3007        // cursor it does not add comment prefix.
 3008        cx.set_state(indoc! {"
 3009        /** */ˇ
 3010    "});
 3011        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3012        cx.assert_editor_state(indoc! {"
 3013        /** */
 3014        ˇ
 3015    "});
 3016        // Ensure that if suffix exists on same line before
 3017        // cursor it does not add comment prefix.
 3018        cx.set_state(indoc! {"
 3019        /**
 3020         *
 3021         */ˇ
 3022    "});
 3023        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3024        cx.assert_editor_state(indoc! {"
 3025        /**
 3026         *
 3027         */
 3028         ˇ
 3029    "});
 3030
 3031        // Ensure that inline comment followed by code
 3032        // doesn't add comment prefix on newline
 3033        cx.set_state(indoc! {"
 3034        /** */ textˇ
 3035    "});
 3036        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3037        cx.assert_editor_state(indoc! {"
 3038        /** */ text
 3039        ˇ
 3040    "});
 3041
 3042        // Ensure that text after comment end tag
 3043        // doesn't add comment prefix on newline
 3044        cx.set_state(indoc! {"
 3045        /**
 3046         *
 3047         */ˇtext
 3048    "});
 3049        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3050        cx.assert_editor_state(indoc! {"
 3051        /**
 3052         *
 3053         */
 3054         ˇtext
 3055    "});
 3056
 3057        // Ensure if not comment block it doesn't
 3058        // add comment prefix on newline
 3059        cx.set_state(indoc! {"
 3060        * textˇ
 3061    "});
 3062        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3063        cx.assert_editor_state(indoc! {"
 3064        * text
 3065        ˇ
 3066    "});
 3067    }
 3068    // Ensure that comment continuations can be disabled.
 3069    update_test_language_settings(cx, |settings| {
 3070        settings.defaults.extend_comment_on_newline = Some(false);
 3071    });
 3072    let mut cx = EditorTestContext::new(cx).await;
 3073    cx.set_state(indoc! {"
 3074        /**ˇ
 3075    "});
 3076    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3077    cx.assert_editor_state(indoc! {"
 3078        /**
 3079        ˇ
 3080    "});
 3081}
 3082
 3083#[gpui::test]
 3084async fn test_newline_comments_with_block_comment(cx: &mut TestAppContext) {
 3085    init_test(cx, |settings| {
 3086        settings.defaults.tab_size = NonZeroU32::new(4)
 3087    });
 3088
 3089    let lua_language = Arc::new(Language::new(
 3090        LanguageConfig {
 3091            line_comments: vec!["--".into()],
 3092            block_comment: Some(("--[[".into(), "]]".into())),
 3093            ..LanguageConfig::default()
 3094        },
 3095        None,
 3096    ));
 3097
 3098    let mut cx = EditorTestContext::new(cx).await;
 3099    cx.update_buffer(|buffer, cx| buffer.set_language(Some(lua_language), cx));
 3100
 3101    // Line with line comment should extend
 3102    cx.set_state(indoc! {"
 3103        --ˇ
 3104    "});
 3105    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3106    cx.assert_editor_state(indoc! {"
 3107        --
 3108        --ˇ
 3109    "});
 3110
 3111    // Line with block comment that matches line comment should not extend
 3112    cx.set_state(indoc! {"
 3113        --[[ˇ
 3114    "});
 3115    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3116    cx.assert_editor_state(indoc! {"
 3117        --[[
 3118        ˇ
 3119    "});
 3120}
 3121
 3122#[gpui::test]
 3123fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3124    init_test(cx, |_| {});
 3125
 3126    let editor = cx.add_window(|window, cx| {
 3127        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3128        let mut editor = build_editor(buffer.clone(), window, cx);
 3129        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3130            s.select_ranges([3..4, 11..12, 19..20])
 3131        });
 3132        editor
 3133    });
 3134
 3135    _ = editor.update(cx, |editor, window, cx| {
 3136        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3137        editor.buffer.update(cx, |buffer, cx| {
 3138            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 3139            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3140        });
 3141        assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
 3142
 3143        editor.insert("Z", window, cx);
 3144        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3145
 3146        // The selections are moved after the inserted characters
 3147        assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
 3148    });
 3149}
 3150
 3151#[gpui::test]
 3152async fn test_tab(cx: &mut TestAppContext) {
 3153    init_test(cx, |settings| {
 3154        settings.defaults.tab_size = NonZeroU32::new(3)
 3155    });
 3156
 3157    let mut cx = EditorTestContext::new(cx).await;
 3158    cx.set_state(indoc! {"
 3159        ˇabˇc
 3160        ˇ🏀ˇ🏀ˇefg
 3161 3162    "});
 3163    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3164    cx.assert_editor_state(indoc! {"
 3165           ˇab ˇc
 3166           ˇ🏀  ˇ🏀  ˇefg
 3167        d  ˇ
 3168    "});
 3169
 3170    cx.set_state(indoc! {"
 3171        a
 3172        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3173    "});
 3174    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3175    cx.assert_editor_state(indoc! {"
 3176        a
 3177           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3178    "});
 3179}
 3180
 3181#[gpui::test]
 3182async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3183    init_test(cx, |_| {});
 3184
 3185    let mut cx = EditorTestContext::new(cx).await;
 3186    let language = Arc::new(
 3187        Language::new(
 3188            LanguageConfig::default(),
 3189            Some(tree_sitter_rust::LANGUAGE.into()),
 3190        )
 3191        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3192        .unwrap(),
 3193    );
 3194    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3195
 3196    // test when all cursors are not at suggested indent
 3197    // then simply move to their suggested indent location
 3198    cx.set_state(indoc! {"
 3199        const a: B = (
 3200            c(
 3201        ˇ
 3202        ˇ    )
 3203        );
 3204    "});
 3205    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3206    cx.assert_editor_state(indoc! {"
 3207        const a: B = (
 3208            c(
 3209                ˇ
 3210            ˇ)
 3211        );
 3212    "});
 3213
 3214    // test cursor already at suggested indent not moving when
 3215    // other cursors are yet to reach their suggested indents
 3216    cx.set_state(indoc! {"
 3217        ˇ
 3218        const a: B = (
 3219            c(
 3220                d(
 3221        ˇ
 3222                )
 3223        ˇ
 3224        ˇ    )
 3225        );
 3226    "});
 3227    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3228    cx.assert_editor_state(indoc! {"
 3229        ˇ
 3230        const a: B = (
 3231            c(
 3232                d(
 3233                    ˇ
 3234                )
 3235                ˇ
 3236            ˇ)
 3237        );
 3238    "});
 3239    // test when all cursors are at suggested indent then tab is inserted
 3240    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3241    cx.assert_editor_state(indoc! {"
 3242            ˇ
 3243        const a: B = (
 3244            c(
 3245                d(
 3246                        ˇ
 3247                )
 3248                    ˇ
 3249                ˇ)
 3250        );
 3251    "});
 3252
 3253    // test when current indent is less than suggested indent,
 3254    // we adjust line to match suggested indent and move cursor to it
 3255    //
 3256    // when no other cursor is at word boundary, all of them should move
 3257    cx.set_state(indoc! {"
 3258        const a: B = (
 3259            c(
 3260                d(
 3261        ˇ
 3262        ˇ   )
 3263        ˇ   )
 3264        );
 3265    "});
 3266    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3267    cx.assert_editor_state(indoc! {"
 3268        const a: B = (
 3269            c(
 3270                d(
 3271                    ˇ
 3272                ˇ)
 3273            ˇ)
 3274        );
 3275    "});
 3276
 3277    // test when current indent is less than suggested indent,
 3278    // we adjust line to match suggested indent and move cursor to it
 3279    //
 3280    // when some other cursor is at word boundary, it should not move
 3281    cx.set_state(indoc! {"
 3282        const a: B = (
 3283            c(
 3284                d(
 3285        ˇ
 3286        ˇ   )
 3287           ˇ)
 3288        );
 3289    "});
 3290    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3291    cx.assert_editor_state(indoc! {"
 3292        const a: B = (
 3293            c(
 3294                d(
 3295                    ˇ
 3296                ˇ)
 3297            ˇ)
 3298        );
 3299    "});
 3300
 3301    // test when current indent is more than suggested indent,
 3302    // we just move cursor to current indent instead of suggested indent
 3303    //
 3304    // when no other cursor is at word boundary, all of them should move
 3305    cx.set_state(indoc! {"
 3306        const a: B = (
 3307            c(
 3308                d(
 3309        ˇ
 3310        ˇ                )
 3311        ˇ   )
 3312        );
 3313    "});
 3314    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3315    cx.assert_editor_state(indoc! {"
 3316        const a: B = (
 3317            c(
 3318                d(
 3319                    ˇ
 3320                        ˇ)
 3321            ˇ)
 3322        );
 3323    "});
 3324    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3325    cx.assert_editor_state(indoc! {"
 3326        const a: B = (
 3327            c(
 3328                d(
 3329                        ˇ
 3330                            ˇ)
 3331                ˇ)
 3332        );
 3333    "});
 3334
 3335    // test when current indent is more than suggested indent,
 3336    // we just move cursor to current indent instead of suggested indent
 3337    //
 3338    // when some other cursor is at word boundary, it doesn't move
 3339    cx.set_state(indoc! {"
 3340        const a: B = (
 3341            c(
 3342                d(
 3343        ˇ
 3344        ˇ                )
 3345            ˇ)
 3346        );
 3347    "});
 3348    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3349    cx.assert_editor_state(indoc! {"
 3350        const a: B = (
 3351            c(
 3352                d(
 3353                    ˇ
 3354                        ˇ)
 3355            ˇ)
 3356        );
 3357    "});
 3358
 3359    // handle auto-indent when there are multiple cursors on the same line
 3360    cx.set_state(indoc! {"
 3361        const a: B = (
 3362            c(
 3363        ˇ    ˇ
 3364        ˇ    )
 3365        );
 3366    "});
 3367    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3368    cx.assert_editor_state(indoc! {"
 3369        const a: B = (
 3370            c(
 3371                ˇ
 3372            ˇ)
 3373        );
 3374    "});
 3375}
 3376
 3377#[gpui::test]
 3378async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 3379    init_test(cx, |settings| {
 3380        settings.defaults.tab_size = NonZeroU32::new(3)
 3381    });
 3382
 3383    let mut cx = EditorTestContext::new(cx).await;
 3384    cx.set_state(indoc! {"
 3385         ˇ
 3386        \t ˇ
 3387        \t  ˇ
 3388        \t   ˇ
 3389         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 3390    "});
 3391
 3392    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3393    cx.assert_editor_state(indoc! {"
 3394           ˇ
 3395        \t   ˇ
 3396        \t   ˇ
 3397        \t      ˇ
 3398         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 3399    "});
 3400}
 3401
 3402#[gpui::test]
 3403async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 3404    init_test(cx, |settings| {
 3405        settings.defaults.tab_size = NonZeroU32::new(4)
 3406    });
 3407
 3408    let language = Arc::new(
 3409        Language::new(
 3410            LanguageConfig::default(),
 3411            Some(tree_sitter_rust::LANGUAGE.into()),
 3412        )
 3413        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 3414        .unwrap(),
 3415    );
 3416
 3417    let mut cx = EditorTestContext::new(cx).await;
 3418    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3419    cx.set_state(indoc! {"
 3420        fn a() {
 3421            if b {
 3422        \t ˇc
 3423            }
 3424        }
 3425    "});
 3426
 3427    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3428    cx.assert_editor_state(indoc! {"
 3429        fn a() {
 3430            if b {
 3431                ˇc
 3432            }
 3433        }
 3434    "});
 3435}
 3436
 3437#[gpui::test]
 3438async fn test_indent_outdent(cx: &mut TestAppContext) {
 3439    init_test(cx, |settings| {
 3440        settings.defaults.tab_size = NonZeroU32::new(4);
 3441    });
 3442
 3443    let mut cx = EditorTestContext::new(cx).await;
 3444
 3445    cx.set_state(indoc! {"
 3446          «oneˇ» «twoˇ»
 3447        three
 3448         four
 3449    "});
 3450    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3451    cx.assert_editor_state(indoc! {"
 3452            «oneˇ» «twoˇ»
 3453        three
 3454         four
 3455    "});
 3456
 3457    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3458    cx.assert_editor_state(indoc! {"
 3459        «oneˇ» «twoˇ»
 3460        three
 3461         four
 3462    "});
 3463
 3464    // select across line ending
 3465    cx.set_state(indoc! {"
 3466        one two
 3467        t«hree
 3468        ˇ» four
 3469    "});
 3470    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3471    cx.assert_editor_state(indoc! {"
 3472        one two
 3473            t«hree
 3474        ˇ» four
 3475    "});
 3476
 3477    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3478    cx.assert_editor_state(indoc! {"
 3479        one two
 3480        t«hree
 3481        ˇ» four
 3482    "});
 3483
 3484    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3485    cx.set_state(indoc! {"
 3486        one two
 3487        ˇthree
 3488            four
 3489    "});
 3490    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3491    cx.assert_editor_state(indoc! {"
 3492        one two
 3493            ˇthree
 3494            four
 3495    "});
 3496
 3497    cx.set_state(indoc! {"
 3498        one two
 3499        ˇ    three
 3500            four
 3501    "});
 3502    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3503    cx.assert_editor_state(indoc! {"
 3504        one two
 3505        ˇthree
 3506            four
 3507    "});
 3508}
 3509
 3510#[gpui::test]
 3511async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3512    // This is a regression test for issue #33761
 3513    init_test(cx, |_| {});
 3514
 3515    let mut cx = EditorTestContext::new(cx).await;
 3516    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3517    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3518
 3519    cx.set_state(
 3520        r#"ˇ#     ingress:
 3521ˇ#         api:
 3522ˇ#             enabled: false
 3523ˇ#             pathType: Prefix
 3524ˇ#           console:
 3525ˇ#               enabled: false
 3526ˇ#               pathType: Prefix
 3527"#,
 3528    );
 3529
 3530    // Press tab to indent all lines
 3531    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3532
 3533    cx.assert_editor_state(
 3534        r#"    ˇ#     ingress:
 3535    ˇ#         api:
 3536    ˇ#             enabled: false
 3537    ˇ#             pathType: Prefix
 3538    ˇ#           console:
 3539    ˇ#               enabled: false
 3540    ˇ#               pathType: Prefix
 3541"#,
 3542    );
 3543}
 3544
 3545#[gpui::test]
 3546async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3547    // This is a test to make sure our fix for issue #33761 didn't break anything
 3548    init_test(cx, |_| {});
 3549
 3550    let mut cx = EditorTestContext::new(cx).await;
 3551    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3552    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3553
 3554    cx.set_state(
 3555        r#"ˇingress:
 3556ˇ  api:
 3557ˇ    enabled: false
 3558ˇ    pathType: Prefix
 3559"#,
 3560    );
 3561
 3562    // Press tab to indent all lines
 3563    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3564
 3565    cx.assert_editor_state(
 3566        r#"ˇingress:
 3567    ˇapi:
 3568        ˇenabled: false
 3569        ˇpathType: Prefix
 3570"#,
 3571    );
 3572}
 3573
 3574#[gpui::test]
 3575async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 3576    init_test(cx, |settings| {
 3577        settings.defaults.hard_tabs = Some(true);
 3578    });
 3579
 3580    let mut cx = EditorTestContext::new(cx).await;
 3581
 3582    // select two ranges on one line
 3583    cx.set_state(indoc! {"
 3584        «oneˇ» «twoˇ»
 3585        three
 3586        four
 3587    "});
 3588    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3589    cx.assert_editor_state(indoc! {"
 3590        \t«oneˇ» «twoˇ»
 3591        three
 3592        four
 3593    "});
 3594    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3595    cx.assert_editor_state(indoc! {"
 3596        \t\t«oneˇ» «twoˇ»
 3597        three
 3598        four
 3599    "});
 3600    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3601    cx.assert_editor_state(indoc! {"
 3602        \t«oneˇ» «twoˇ»
 3603        three
 3604        four
 3605    "});
 3606    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3607    cx.assert_editor_state(indoc! {"
 3608        «oneˇ» «twoˇ»
 3609        three
 3610        four
 3611    "});
 3612
 3613    // select across a line ending
 3614    cx.set_state(indoc! {"
 3615        one two
 3616        t«hree
 3617        ˇ»four
 3618    "});
 3619    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3620    cx.assert_editor_state(indoc! {"
 3621        one two
 3622        \tt«hree
 3623        ˇ»four
 3624    "});
 3625    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3626    cx.assert_editor_state(indoc! {"
 3627        one two
 3628        \t\tt«hree
 3629        ˇ»four
 3630    "});
 3631    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3632    cx.assert_editor_state(indoc! {"
 3633        one two
 3634        \tt«hree
 3635        ˇ»four
 3636    "});
 3637    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3638    cx.assert_editor_state(indoc! {"
 3639        one two
 3640        t«hree
 3641        ˇ»four
 3642    "});
 3643
 3644    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3645    cx.set_state(indoc! {"
 3646        one two
 3647        ˇthree
 3648        four
 3649    "});
 3650    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3651    cx.assert_editor_state(indoc! {"
 3652        one two
 3653        ˇthree
 3654        four
 3655    "});
 3656    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3657    cx.assert_editor_state(indoc! {"
 3658        one two
 3659        \tˇthree
 3660        four
 3661    "});
 3662    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3663    cx.assert_editor_state(indoc! {"
 3664        one two
 3665        ˇthree
 3666        four
 3667    "});
 3668}
 3669
 3670#[gpui::test]
 3671fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 3672    init_test(cx, |settings| {
 3673        settings.languages.0.extend([
 3674            (
 3675                "TOML".into(),
 3676                LanguageSettingsContent {
 3677                    tab_size: NonZeroU32::new(2),
 3678                    ..Default::default()
 3679                },
 3680            ),
 3681            (
 3682                "Rust".into(),
 3683                LanguageSettingsContent {
 3684                    tab_size: NonZeroU32::new(4),
 3685                    ..Default::default()
 3686                },
 3687            ),
 3688        ]);
 3689    });
 3690
 3691    let toml_language = Arc::new(Language::new(
 3692        LanguageConfig {
 3693            name: "TOML".into(),
 3694            ..Default::default()
 3695        },
 3696        None,
 3697    ));
 3698    let rust_language = Arc::new(Language::new(
 3699        LanguageConfig {
 3700            name: "Rust".into(),
 3701            ..Default::default()
 3702        },
 3703        None,
 3704    ));
 3705
 3706    let toml_buffer =
 3707        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 3708    let rust_buffer =
 3709        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 3710    let multibuffer = cx.new(|cx| {
 3711        let mut multibuffer = MultiBuffer::new(ReadWrite);
 3712        multibuffer.push_excerpts(
 3713            toml_buffer.clone(),
 3714            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 3715            cx,
 3716        );
 3717        multibuffer.push_excerpts(
 3718            rust_buffer.clone(),
 3719            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 3720            cx,
 3721        );
 3722        multibuffer
 3723    });
 3724
 3725    cx.add_window(|window, cx| {
 3726        let mut editor = build_editor(multibuffer, window, cx);
 3727
 3728        assert_eq!(
 3729            editor.text(cx),
 3730            indoc! {"
 3731                a = 1
 3732                b = 2
 3733
 3734                const c: usize = 3;
 3735            "}
 3736        );
 3737
 3738        select_ranges(
 3739            &mut editor,
 3740            indoc! {"
 3741                «aˇ» = 1
 3742                b = 2
 3743
 3744                «const c:ˇ» usize = 3;
 3745            "},
 3746            window,
 3747            cx,
 3748        );
 3749
 3750        editor.tab(&Tab, window, cx);
 3751        assert_text_with_selections(
 3752            &mut editor,
 3753            indoc! {"
 3754                  «aˇ» = 1
 3755                b = 2
 3756
 3757                    «const c:ˇ» usize = 3;
 3758            "},
 3759            cx,
 3760        );
 3761        editor.backtab(&Backtab, window, cx);
 3762        assert_text_with_selections(
 3763            &mut editor,
 3764            indoc! {"
 3765                «aˇ» = 1
 3766                b = 2
 3767
 3768                «const c:ˇ» usize = 3;
 3769            "},
 3770            cx,
 3771        );
 3772
 3773        editor
 3774    });
 3775}
 3776
 3777#[gpui::test]
 3778async fn test_backspace(cx: &mut TestAppContext) {
 3779    init_test(cx, |_| {});
 3780
 3781    let mut cx = EditorTestContext::new(cx).await;
 3782
 3783    // Basic backspace
 3784    cx.set_state(indoc! {"
 3785        onˇe two three
 3786        fou«rˇ» five six
 3787        seven «ˇeight nine
 3788        »ten
 3789    "});
 3790    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3791    cx.assert_editor_state(indoc! {"
 3792        oˇe two three
 3793        fouˇ five six
 3794        seven ˇten
 3795    "});
 3796
 3797    // Test backspace inside and around indents
 3798    cx.set_state(indoc! {"
 3799        zero
 3800            ˇone
 3801                ˇtwo
 3802            ˇ ˇ ˇ  three
 3803        ˇ  ˇ  four
 3804    "});
 3805    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3806    cx.assert_editor_state(indoc! {"
 3807        zero
 3808        ˇone
 3809            ˇtwo
 3810        ˇ  threeˇ  four
 3811    "});
 3812}
 3813
 3814#[gpui::test]
 3815async fn test_delete(cx: &mut TestAppContext) {
 3816    init_test(cx, |_| {});
 3817
 3818    let mut cx = EditorTestContext::new(cx).await;
 3819    cx.set_state(indoc! {"
 3820        onˇe two three
 3821        fou«rˇ» five six
 3822        seven «ˇeight nine
 3823        »ten
 3824    "});
 3825    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 3826    cx.assert_editor_state(indoc! {"
 3827        onˇ two three
 3828        fouˇ five six
 3829        seven ˇten
 3830    "});
 3831}
 3832
 3833#[gpui::test]
 3834fn test_delete_line(cx: &mut TestAppContext) {
 3835    init_test(cx, |_| {});
 3836
 3837    let editor = cx.add_window(|window, cx| {
 3838        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3839        build_editor(buffer, window, cx)
 3840    });
 3841    _ = editor.update(cx, |editor, window, cx| {
 3842        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3843            s.select_display_ranges([
 3844                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 3845                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 3846                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 3847            ])
 3848        });
 3849        editor.delete_line(&DeleteLine, window, cx);
 3850        assert_eq!(editor.display_text(cx), "ghi");
 3851        assert_eq!(
 3852            editor.selections.display_ranges(cx),
 3853            vec![
 3854                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 3855                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
 3856            ]
 3857        );
 3858    });
 3859
 3860    let editor = cx.add_window(|window, cx| {
 3861        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3862        build_editor(buffer, window, cx)
 3863    });
 3864    _ = editor.update(cx, |editor, window, cx| {
 3865        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3866            s.select_display_ranges([
 3867                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 3868            ])
 3869        });
 3870        editor.delete_line(&DeleteLine, window, cx);
 3871        assert_eq!(editor.display_text(cx), "ghi\n");
 3872        assert_eq!(
 3873            editor.selections.display_ranges(cx),
 3874            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 3875        );
 3876    });
 3877}
 3878
 3879#[gpui::test]
 3880fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 3881    init_test(cx, |_| {});
 3882
 3883    cx.add_window(|window, cx| {
 3884        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3885        let mut editor = build_editor(buffer.clone(), window, cx);
 3886        let buffer = buffer.read(cx).as_singleton().unwrap();
 3887
 3888        assert_eq!(
 3889            editor.selections.ranges::<Point>(cx),
 3890            &[Point::new(0, 0)..Point::new(0, 0)]
 3891        );
 3892
 3893        // When on single line, replace newline at end by space
 3894        editor.join_lines(&JoinLines, window, cx);
 3895        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3896        assert_eq!(
 3897            editor.selections.ranges::<Point>(cx),
 3898            &[Point::new(0, 3)..Point::new(0, 3)]
 3899        );
 3900
 3901        // When multiple lines are selected, remove newlines that are spanned by the selection
 3902        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3903            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 3904        });
 3905        editor.join_lines(&JoinLines, window, cx);
 3906        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 3907        assert_eq!(
 3908            editor.selections.ranges::<Point>(cx),
 3909            &[Point::new(0, 11)..Point::new(0, 11)]
 3910        );
 3911
 3912        // Undo should be transactional
 3913        editor.undo(&Undo, window, cx);
 3914        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3915        assert_eq!(
 3916            editor.selections.ranges::<Point>(cx),
 3917            &[Point::new(0, 5)..Point::new(2, 2)]
 3918        );
 3919
 3920        // When joining an empty line don't insert a space
 3921        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3922            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 3923        });
 3924        editor.join_lines(&JoinLines, window, cx);
 3925        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 3926        assert_eq!(
 3927            editor.selections.ranges::<Point>(cx),
 3928            [Point::new(2, 3)..Point::new(2, 3)]
 3929        );
 3930
 3931        // We can remove trailing newlines
 3932        editor.join_lines(&JoinLines, window, cx);
 3933        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3934        assert_eq!(
 3935            editor.selections.ranges::<Point>(cx),
 3936            [Point::new(2, 3)..Point::new(2, 3)]
 3937        );
 3938
 3939        // We don't blow up on the last line
 3940        editor.join_lines(&JoinLines, window, cx);
 3941        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3942        assert_eq!(
 3943            editor.selections.ranges::<Point>(cx),
 3944            [Point::new(2, 3)..Point::new(2, 3)]
 3945        );
 3946
 3947        // reset to test indentation
 3948        editor.buffer.update(cx, |buffer, cx| {
 3949            buffer.edit(
 3950                [
 3951                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 3952                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 3953                ],
 3954                None,
 3955                cx,
 3956            )
 3957        });
 3958
 3959        // We remove any leading spaces
 3960        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 3961        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3962            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 3963        });
 3964        editor.join_lines(&JoinLines, window, cx);
 3965        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 3966
 3967        // We don't insert a space for a line containing only spaces
 3968        editor.join_lines(&JoinLines, window, cx);
 3969        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 3970
 3971        // We ignore any leading tabs
 3972        editor.join_lines(&JoinLines, window, cx);
 3973        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 3974
 3975        editor
 3976    });
 3977}
 3978
 3979#[gpui::test]
 3980fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 3981    init_test(cx, |_| {});
 3982
 3983    cx.add_window(|window, cx| {
 3984        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3985        let mut editor = build_editor(buffer.clone(), window, cx);
 3986        let buffer = buffer.read(cx).as_singleton().unwrap();
 3987
 3988        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3989            s.select_ranges([
 3990                Point::new(0, 2)..Point::new(1, 1),
 3991                Point::new(1, 2)..Point::new(1, 2),
 3992                Point::new(3, 1)..Point::new(3, 2),
 3993            ])
 3994        });
 3995
 3996        editor.join_lines(&JoinLines, window, cx);
 3997        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 3998
 3999        assert_eq!(
 4000            editor.selections.ranges::<Point>(cx),
 4001            [
 4002                Point::new(0, 7)..Point::new(0, 7),
 4003                Point::new(1, 3)..Point::new(1, 3)
 4004            ]
 4005        );
 4006        editor
 4007    });
 4008}
 4009
 4010#[gpui::test]
 4011async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 4012    init_test(cx, |_| {});
 4013
 4014    let mut cx = EditorTestContext::new(cx).await;
 4015
 4016    let diff_base = r#"
 4017        Line 0
 4018        Line 1
 4019        Line 2
 4020        Line 3
 4021        "#
 4022    .unindent();
 4023
 4024    cx.set_state(
 4025        &r#"
 4026        ˇLine 0
 4027        Line 1
 4028        Line 2
 4029        Line 3
 4030        "#
 4031        .unindent(),
 4032    );
 4033
 4034    cx.set_head_text(&diff_base);
 4035    executor.run_until_parked();
 4036
 4037    // Join lines
 4038    cx.update_editor(|editor, window, cx| {
 4039        editor.join_lines(&JoinLines, window, cx);
 4040    });
 4041    executor.run_until_parked();
 4042
 4043    cx.assert_editor_state(
 4044        &r#"
 4045        Line 0ˇ Line 1
 4046        Line 2
 4047        Line 3
 4048        "#
 4049        .unindent(),
 4050    );
 4051    // Join again
 4052    cx.update_editor(|editor, window, cx| {
 4053        editor.join_lines(&JoinLines, window, cx);
 4054    });
 4055    executor.run_until_parked();
 4056
 4057    cx.assert_editor_state(
 4058        &r#"
 4059        Line 0 Line 1ˇ Line 2
 4060        Line 3
 4061        "#
 4062        .unindent(),
 4063    );
 4064}
 4065
 4066#[gpui::test]
 4067async fn test_custom_newlines_cause_no_false_positive_diffs(
 4068    executor: BackgroundExecutor,
 4069    cx: &mut TestAppContext,
 4070) {
 4071    init_test(cx, |_| {});
 4072    let mut cx = EditorTestContext::new(cx).await;
 4073    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 4074    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 4075    executor.run_until_parked();
 4076
 4077    cx.update_editor(|editor, window, cx| {
 4078        let snapshot = editor.snapshot(window, cx);
 4079        assert_eq!(
 4080            snapshot
 4081                .buffer_snapshot
 4082                .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
 4083                .collect::<Vec<_>>(),
 4084            Vec::new(),
 4085            "Should not have any diffs for files with custom newlines"
 4086        );
 4087    });
 4088}
 4089
 4090#[gpui::test]
 4091async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 4092    init_test(cx, |_| {});
 4093
 4094    let mut cx = EditorTestContext::new(cx).await;
 4095
 4096    // Test sort_lines_case_insensitive()
 4097    cx.set_state(indoc! {"
 4098        «z
 4099        y
 4100        x
 4101        Z
 4102        Y
 4103        Xˇ»
 4104    "});
 4105    cx.update_editor(|e, window, cx| {
 4106        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4107    });
 4108    cx.assert_editor_state(indoc! {"
 4109        «x
 4110        X
 4111        y
 4112        Y
 4113        z
 4114        Zˇ»
 4115    "});
 4116
 4117    // Test sort_lines_by_length()
 4118    //
 4119    // Demonstrates:
 4120    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 4121    // - sort is stable
 4122    cx.set_state(indoc! {"
 4123        «123
 4124        æ
 4125        12
 4126 4127        1
 4128        æˇ»
 4129    "});
 4130    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 4131    cx.assert_editor_state(indoc! {"
 4132        «æ
 4133 4134        1
 4135        æ
 4136        12
 4137        123ˇ»
 4138    "});
 4139
 4140    // Test reverse_lines()
 4141    cx.set_state(indoc! {"
 4142        «5
 4143        4
 4144        3
 4145        2
 4146        1ˇ»
 4147    "});
 4148    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4149    cx.assert_editor_state(indoc! {"
 4150        «1
 4151        2
 4152        3
 4153        4
 4154        5ˇ»
 4155    "});
 4156
 4157    // Skip testing shuffle_line()
 4158
 4159    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4160    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4161
 4162    // Don't manipulate when cursor is on single line, but expand the selection
 4163    cx.set_state(indoc! {"
 4164        ddˇdd
 4165        ccc
 4166        bb
 4167        a
 4168    "});
 4169    cx.update_editor(|e, window, cx| {
 4170        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4171    });
 4172    cx.assert_editor_state(indoc! {"
 4173        «ddddˇ»
 4174        ccc
 4175        bb
 4176        a
 4177    "});
 4178
 4179    // Basic manipulate case
 4180    // Start selection moves to column 0
 4181    // End of selection shrinks to fit shorter line
 4182    cx.set_state(indoc! {"
 4183        dd«d
 4184        ccc
 4185        bb
 4186        aaaaaˇ»
 4187    "});
 4188    cx.update_editor(|e, window, cx| {
 4189        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4190    });
 4191    cx.assert_editor_state(indoc! {"
 4192        «aaaaa
 4193        bb
 4194        ccc
 4195        dddˇ»
 4196    "});
 4197
 4198    // Manipulate case with newlines
 4199    cx.set_state(indoc! {"
 4200        dd«d
 4201        ccc
 4202
 4203        bb
 4204        aaaaa
 4205
 4206        ˇ»
 4207    "});
 4208    cx.update_editor(|e, window, cx| {
 4209        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4210    });
 4211    cx.assert_editor_state(indoc! {"
 4212        «
 4213
 4214        aaaaa
 4215        bb
 4216        ccc
 4217        dddˇ»
 4218
 4219    "});
 4220
 4221    // Adding new line
 4222    cx.set_state(indoc! {"
 4223        aa«a
 4224        bbˇ»b
 4225    "});
 4226    cx.update_editor(|e, window, cx| {
 4227        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4228    });
 4229    cx.assert_editor_state(indoc! {"
 4230        «aaa
 4231        bbb
 4232        added_lineˇ»
 4233    "});
 4234
 4235    // Removing line
 4236    cx.set_state(indoc! {"
 4237        aa«a
 4238        bbbˇ»
 4239    "});
 4240    cx.update_editor(|e, window, cx| {
 4241        e.manipulate_immutable_lines(window, cx, |lines| {
 4242            lines.pop();
 4243        })
 4244    });
 4245    cx.assert_editor_state(indoc! {"
 4246        «aaaˇ»
 4247    "});
 4248
 4249    // Removing all lines
 4250    cx.set_state(indoc! {"
 4251        aa«a
 4252        bbbˇ»
 4253    "});
 4254    cx.update_editor(|e, window, cx| {
 4255        e.manipulate_immutable_lines(window, cx, |lines| {
 4256            lines.drain(..);
 4257        })
 4258    });
 4259    cx.assert_editor_state(indoc! {"
 4260        ˇ
 4261    "});
 4262}
 4263
 4264#[gpui::test]
 4265async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4266    init_test(cx, |_| {});
 4267
 4268    let mut cx = EditorTestContext::new(cx).await;
 4269
 4270    // Consider continuous selection as single selection
 4271    cx.set_state(indoc! {"
 4272        Aaa«aa
 4273        cˇ»c«c
 4274        bb
 4275        aaaˇ»aa
 4276    "});
 4277    cx.update_editor(|e, window, cx| {
 4278        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4279    });
 4280    cx.assert_editor_state(indoc! {"
 4281        «Aaaaa
 4282        ccc
 4283        bb
 4284        aaaaaˇ»
 4285    "});
 4286
 4287    cx.set_state(indoc! {"
 4288        Aaa«aa
 4289        cˇ»c«c
 4290        bb
 4291        aaaˇ»aa
 4292    "});
 4293    cx.update_editor(|e, window, cx| {
 4294        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4295    });
 4296    cx.assert_editor_state(indoc! {"
 4297        «Aaaaa
 4298        ccc
 4299        bbˇ»
 4300    "});
 4301
 4302    // Consider non continuous selection as distinct dedup operations
 4303    cx.set_state(indoc! {"
 4304        «aaaaa
 4305        bb
 4306        aaaaa
 4307        aaaaaˇ»
 4308
 4309        aaa«aaˇ»
 4310    "});
 4311    cx.update_editor(|e, window, cx| {
 4312        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4313    });
 4314    cx.assert_editor_state(indoc! {"
 4315        «aaaaa
 4316        bbˇ»
 4317
 4318        «aaaaaˇ»
 4319    "});
 4320}
 4321
 4322#[gpui::test]
 4323async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4324    init_test(cx, |_| {});
 4325
 4326    let mut cx = EditorTestContext::new(cx).await;
 4327
 4328    cx.set_state(indoc! {"
 4329        «Aaa
 4330        aAa
 4331        Aaaˇ»
 4332    "});
 4333    cx.update_editor(|e, window, cx| {
 4334        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4335    });
 4336    cx.assert_editor_state(indoc! {"
 4337        «Aaa
 4338        aAaˇ»
 4339    "});
 4340
 4341    cx.set_state(indoc! {"
 4342        «Aaa
 4343        aAa
 4344        aaAˇ»
 4345    "});
 4346    cx.update_editor(|e, window, cx| {
 4347        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4348    });
 4349    cx.assert_editor_state(indoc! {"
 4350        «Aaaˇ»
 4351    "});
 4352}
 4353
 4354#[gpui::test]
 4355async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 4356    init_test(cx, |_| {});
 4357
 4358    let mut cx = EditorTestContext::new(cx).await;
 4359
 4360    // Manipulate with multiple selections on a single line
 4361    cx.set_state(indoc! {"
 4362        dd«dd
 4363        cˇ»c«c
 4364        bb
 4365        aaaˇ»aa
 4366    "});
 4367    cx.update_editor(|e, window, cx| {
 4368        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4369    });
 4370    cx.assert_editor_state(indoc! {"
 4371        «aaaaa
 4372        bb
 4373        ccc
 4374        ddddˇ»
 4375    "});
 4376
 4377    // Manipulate with multiple disjoin selections
 4378    cx.set_state(indoc! {"
 4379 4380        4
 4381        3
 4382        2
 4383        1ˇ»
 4384
 4385        dd«dd
 4386        ccc
 4387        bb
 4388        aaaˇ»aa
 4389    "});
 4390    cx.update_editor(|e, window, cx| {
 4391        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4392    });
 4393    cx.assert_editor_state(indoc! {"
 4394        «1
 4395        2
 4396        3
 4397        4
 4398        5ˇ»
 4399
 4400        «aaaaa
 4401        bb
 4402        ccc
 4403        ddddˇ»
 4404    "});
 4405
 4406    // Adding lines on each selection
 4407    cx.set_state(indoc! {"
 4408 4409        1ˇ»
 4410
 4411        bb«bb
 4412        aaaˇ»aa
 4413    "});
 4414    cx.update_editor(|e, window, cx| {
 4415        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 4416    });
 4417    cx.assert_editor_state(indoc! {"
 4418        «2
 4419        1
 4420        added lineˇ»
 4421
 4422        «bbbb
 4423        aaaaa
 4424        added lineˇ»
 4425    "});
 4426
 4427    // Removing lines on each selection
 4428    cx.set_state(indoc! {"
 4429 4430        1ˇ»
 4431
 4432        bb«bb
 4433        aaaˇ»aa
 4434    "});
 4435    cx.update_editor(|e, window, cx| {
 4436        e.manipulate_immutable_lines(window, cx, |lines| {
 4437            lines.pop();
 4438        })
 4439    });
 4440    cx.assert_editor_state(indoc! {"
 4441        «2ˇ»
 4442
 4443        «bbbbˇ»
 4444    "});
 4445}
 4446
 4447#[gpui::test]
 4448async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 4449    init_test(cx, |settings| {
 4450        settings.defaults.tab_size = NonZeroU32::new(3)
 4451    });
 4452
 4453    let mut cx = EditorTestContext::new(cx).await;
 4454
 4455    // MULTI SELECTION
 4456    // Ln.1 "«" tests empty lines
 4457    // Ln.9 tests just leading whitespace
 4458    cx.set_state(indoc! {"
 4459        «
 4460        abc                 // No indentationˇ»
 4461        «\tabc              // 1 tabˇ»
 4462        \t\tabc «      ˇ»   // 2 tabs
 4463        \t ab«c             // Tab followed by space
 4464         \tabc              // Space followed by tab (3 spaces should be the result)
 4465        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4466           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 4467        \t
 4468        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4469    "});
 4470    cx.update_editor(|e, window, cx| {
 4471        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4472    });
 4473    cx.assert_editor_state(
 4474        indoc! {"
 4475            «
 4476            abc                 // No indentation
 4477               abc              // 1 tab
 4478                  abc          // 2 tabs
 4479                abc             // Tab followed by space
 4480               abc              // Space followed by tab (3 spaces should be the result)
 4481                           abc   // Mixed indentation (tab conversion depends on the column)
 4482               abc         // Already space indented
 4483               ·
 4484               abc\tdef          // Only the leading tab is manipulatedˇ»
 4485        "}
 4486        .replace("·", "")
 4487        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4488    );
 4489
 4490    // Test on just a few lines, the others should remain unchanged
 4491    // Only lines (3, 5, 10, 11) should change
 4492    cx.set_state(
 4493        indoc! {"
 4494            ·
 4495            abc                 // No indentation
 4496            \tabcˇ               // 1 tab
 4497            \t\tabc             // 2 tabs
 4498            \t abcˇ              // Tab followed by space
 4499             \tabc              // Space followed by tab (3 spaces should be the result)
 4500            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4501               abc              // Already space indented
 4502            «\t
 4503            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4504        "}
 4505        .replace("·", "")
 4506        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4507    );
 4508    cx.update_editor(|e, window, cx| {
 4509        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4510    });
 4511    cx.assert_editor_state(
 4512        indoc! {"
 4513            ·
 4514            abc                 // No indentation
 4515            «   abc               // 1 tabˇ»
 4516            \t\tabc             // 2 tabs
 4517            «    abc              // Tab followed by spaceˇ»
 4518             \tabc              // Space followed by tab (3 spaces should be the result)
 4519            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4520               abc              // Already space indented
 4521            «   ·
 4522               abc\tdef          // Only the leading tab is manipulatedˇ»
 4523        "}
 4524        .replace("·", "")
 4525        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4526    );
 4527
 4528    // SINGLE SELECTION
 4529    // Ln.1 "«" tests empty lines
 4530    // Ln.9 tests just leading whitespace
 4531    cx.set_state(indoc! {"
 4532        «
 4533        abc                 // No indentation
 4534        \tabc               // 1 tab
 4535        \t\tabc             // 2 tabs
 4536        \t abc              // Tab followed by space
 4537         \tabc              // Space followed by tab (3 spaces should be the result)
 4538        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4539           abc              // Already space indented
 4540        \t
 4541        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4542    "});
 4543    cx.update_editor(|e, window, cx| {
 4544        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4545    });
 4546    cx.assert_editor_state(
 4547        indoc! {"
 4548            «
 4549            abc                 // No indentation
 4550               abc               // 1 tab
 4551                  abc             // 2 tabs
 4552                abc              // Tab followed by space
 4553               abc              // Space followed by tab (3 spaces should be the result)
 4554                           abc   // Mixed indentation (tab conversion depends on the column)
 4555               abc              // Already space indented
 4556               ·
 4557               abc\tdef          // Only the leading tab is manipulatedˇ»
 4558        "}
 4559        .replace("·", "")
 4560        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4561    );
 4562}
 4563
 4564#[gpui::test]
 4565async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 4566    init_test(cx, |settings| {
 4567        settings.defaults.tab_size = NonZeroU32::new(3)
 4568    });
 4569
 4570    let mut cx = EditorTestContext::new(cx).await;
 4571
 4572    // MULTI SELECTION
 4573    // Ln.1 "«" tests empty lines
 4574    // Ln.11 tests just leading whitespace
 4575    cx.set_state(indoc! {"
 4576        «
 4577        abˇ»ˇc                 // No indentation
 4578         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 4579          abc  «             // 2 spaces (< 3 so dont convert)
 4580           abc              // 3 spaces (convert)
 4581             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 4582        «\tˇ»\t«\tˇ»abc           // Already tab indented
 4583        «\t abc              // Tab followed by space
 4584         \tabc              // Space followed by tab (should be consumed due to tab)
 4585        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4586           \tˇ»  «\t
 4587           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 4588    "});
 4589    cx.update_editor(|e, window, cx| {
 4590        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4591    });
 4592    cx.assert_editor_state(indoc! {"
 4593        «
 4594        abc                 // No indentation
 4595         abc                // 1 space (< 3 so dont convert)
 4596          abc               // 2 spaces (< 3 so dont convert)
 4597        \tabc              // 3 spaces (convert)
 4598        \t  abc            // 5 spaces (1 tab + 2 spaces)
 4599        \t\t\tabc           // Already tab indented
 4600        \t abc              // Tab followed by space
 4601        \tabc              // Space followed by tab (should be consumed due to tab)
 4602        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4603        \t\t\t
 4604        \tabc   \t         // Only the leading spaces should be convertedˇ»
 4605    "});
 4606
 4607    // Test on just a few lines, the other should remain unchanged
 4608    // Only lines (4, 8, 11, 12) should change
 4609    cx.set_state(
 4610        indoc! {"
 4611            ·
 4612            abc                 // No indentation
 4613             abc                // 1 space (< 3 so dont convert)
 4614              abc               // 2 spaces (< 3 so dont convert)
 4615            «   abc              // 3 spaces (convert)ˇ»
 4616                 abc            // 5 spaces (1 tab + 2 spaces)
 4617            \t\t\tabc           // Already tab indented
 4618            \t abc              // Tab followed by space
 4619             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 4620               \t\t  \tabc      // Mixed indentation
 4621            \t \t  \t   \tabc   // Mixed indentation
 4622               \t  \tˇ
 4623            «   abc   \t         // Only the leading spaces should be convertedˇ»
 4624        "}
 4625        .replace("·", "")
 4626        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4627    );
 4628    cx.update_editor(|e, window, cx| {
 4629        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4630    });
 4631    cx.assert_editor_state(
 4632        indoc! {"
 4633            ·
 4634            abc                 // No indentation
 4635             abc                // 1 space (< 3 so dont convert)
 4636              abc               // 2 spaces (< 3 so dont convert)
 4637            «\tabc              // 3 spaces (convert)ˇ»
 4638                 abc            // 5 spaces (1 tab + 2 spaces)
 4639            \t\t\tabc           // Already tab indented
 4640            \t abc              // Tab followed by space
 4641            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 4642               \t\t  \tabc      // Mixed indentation
 4643            \t \t  \t   \tabc   // Mixed indentation
 4644            «\t\t\t
 4645            \tabc   \t         // Only the leading spaces should be convertedˇ»
 4646        "}
 4647        .replace("·", "")
 4648        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4649    );
 4650
 4651    // SINGLE SELECTION
 4652    // Ln.1 "«" tests empty lines
 4653    // Ln.11 tests just leading whitespace
 4654    cx.set_state(indoc! {"
 4655        «
 4656        abc                 // No indentation
 4657         abc                // 1 space (< 3 so dont convert)
 4658          abc               // 2 spaces (< 3 so dont convert)
 4659           abc              // 3 spaces (convert)
 4660             abc            // 5 spaces (1 tab + 2 spaces)
 4661        \t\t\tabc           // Already tab indented
 4662        \t abc              // Tab followed by space
 4663         \tabc              // Space followed by tab (should be consumed due to tab)
 4664        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4665           \t  \t
 4666           abc   \t         // Only the leading spaces should be convertedˇ»
 4667    "});
 4668    cx.update_editor(|e, window, cx| {
 4669        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4670    });
 4671    cx.assert_editor_state(indoc! {"
 4672        «
 4673        abc                 // No indentation
 4674         abc                // 1 space (< 3 so dont convert)
 4675          abc               // 2 spaces (< 3 so dont convert)
 4676        \tabc              // 3 spaces (convert)
 4677        \t  abc            // 5 spaces (1 tab + 2 spaces)
 4678        \t\t\tabc           // Already tab indented
 4679        \t abc              // Tab followed by space
 4680        \tabc              // Space followed by tab (should be consumed due to tab)
 4681        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4682        \t\t\t
 4683        \tabc   \t         // Only the leading spaces should be convertedˇ»
 4684    "});
 4685}
 4686
 4687#[gpui::test]
 4688async fn test_toggle_case(cx: &mut TestAppContext) {
 4689    init_test(cx, |_| {});
 4690
 4691    let mut cx = EditorTestContext::new(cx).await;
 4692
 4693    // If all lower case -> upper case
 4694    cx.set_state(indoc! {"
 4695        «hello worldˇ»
 4696    "});
 4697    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4698    cx.assert_editor_state(indoc! {"
 4699        «HELLO WORLDˇ»
 4700    "});
 4701
 4702    // If all upper case -> lower case
 4703    cx.set_state(indoc! {"
 4704        «HELLO WORLDˇ»
 4705    "});
 4706    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4707    cx.assert_editor_state(indoc! {"
 4708        «hello worldˇ»
 4709    "});
 4710
 4711    // If any upper case characters are identified -> lower case
 4712    // This matches JetBrains IDEs
 4713    cx.set_state(indoc! {"
 4714        «hEllo worldˇ»
 4715    "});
 4716    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4717    cx.assert_editor_state(indoc! {"
 4718        «hello worldˇ»
 4719    "});
 4720}
 4721
 4722#[gpui::test]
 4723async fn test_manipulate_text(cx: &mut TestAppContext) {
 4724    init_test(cx, |_| {});
 4725
 4726    let mut cx = EditorTestContext::new(cx).await;
 4727
 4728    // Test convert_to_upper_case()
 4729    cx.set_state(indoc! {"
 4730        «hello worldˇ»
 4731    "});
 4732    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4733    cx.assert_editor_state(indoc! {"
 4734        «HELLO WORLDˇ»
 4735    "});
 4736
 4737    // Test convert_to_lower_case()
 4738    cx.set_state(indoc! {"
 4739        «HELLO WORLDˇ»
 4740    "});
 4741    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 4742    cx.assert_editor_state(indoc! {"
 4743        «hello worldˇ»
 4744    "});
 4745
 4746    // Test multiple line, single selection case
 4747    cx.set_state(indoc! {"
 4748        «The quick brown
 4749        fox jumps over
 4750        the lazy dogˇ»
 4751    "});
 4752    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 4753    cx.assert_editor_state(indoc! {"
 4754        «The Quick Brown
 4755        Fox Jumps Over
 4756        The Lazy Dogˇ»
 4757    "});
 4758
 4759    // Test multiple line, single selection case
 4760    cx.set_state(indoc! {"
 4761        «The quick brown
 4762        fox jumps over
 4763        the lazy dogˇ»
 4764    "});
 4765    cx.update_editor(|e, window, cx| {
 4766        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 4767    });
 4768    cx.assert_editor_state(indoc! {"
 4769        «TheQuickBrown
 4770        FoxJumpsOver
 4771        TheLazyDogˇ»
 4772    "});
 4773
 4774    // From here on out, test more complex cases of manipulate_text()
 4775
 4776    // Test no selection case - should affect words cursors are in
 4777    // Cursor at beginning, middle, and end of word
 4778    cx.set_state(indoc! {"
 4779        ˇhello big beauˇtiful worldˇ
 4780    "});
 4781    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4782    cx.assert_editor_state(indoc! {"
 4783        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 4784    "});
 4785
 4786    // Test multiple selections on a single line and across multiple lines
 4787    cx.set_state(indoc! {"
 4788        «Theˇ» quick «brown
 4789        foxˇ» jumps «overˇ»
 4790        the «lazyˇ» dog
 4791    "});
 4792    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4793    cx.assert_editor_state(indoc! {"
 4794        «THEˇ» quick «BROWN
 4795        FOXˇ» jumps «OVERˇ»
 4796        the «LAZYˇ» dog
 4797    "});
 4798
 4799    // Test case where text length grows
 4800    cx.set_state(indoc! {"
 4801        «tschüߡ»
 4802    "});
 4803    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4804    cx.assert_editor_state(indoc! {"
 4805        «TSCHÜSSˇ»
 4806    "});
 4807
 4808    // Test to make sure we don't crash when text shrinks
 4809    cx.set_state(indoc! {"
 4810        aaa_bbbˇ
 4811    "});
 4812    cx.update_editor(|e, window, cx| {
 4813        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4814    });
 4815    cx.assert_editor_state(indoc! {"
 4816        «aaaBbbˇ»
 4817    "});
 4818
 4819    // Test to make sure we all aware of the fact that each word can grow and shrink
 4820    // Final selections should be aware of this fact
 4821    cx.set_state(indoc! {"
 4822        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 4823    "});
 4824    cx.update_editor(|e, window, cx| {
 4825        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4826    });
 4827    cx.assert_editor_state(indoc! {"
 4828        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 4829    "});
 4830
 4831    cx.set_state(indoc! {"
 4832        «hElLo, WoRld!ˇ»
 4833    "});
 4834    cx.update_editor(|e, window, cx| {
 4835        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 4836    });
 4837    cx.assert_editor_state(indoc! {"
 4838        «HeLlO, wOrLD!ˇ»
 4839    "});
 4840}
 4841
 4842#[gpui::test]
 4843fn test_duplicate_line(cx: &mut TestAppContext) {
 4844    init_test(cx, |_| {});
 4845
 4846    let editor = cx.add_window(|window, cx| {
 4847        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4848        build_editor(buffer, window, cx)
 4849    });
 4850    _ = editor.update(cx, |editor, window, cx| {
 4851        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4852            s.select_display_ranges([
 4853                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4854                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4855                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4856                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4857            ])
 4858        });
 4859        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4860        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4861        assert_eq!(
 4862            editor.selections.display_ranges(cx),
 4863            vec![
 4864                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4865                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 4866                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4867                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4868            ]
 4869        );
 4870    });
 4871
 4872    let editor = cx.add_window(|window, cx| {
 4873        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4874        build_editor(buffer, window, cx)
 4875    });
 4876    _ = editor.update(cx, |editor, window, cx| {
 4877        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4878            s.select_display_ranges([
 4879                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4880                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4881            ])
 4882        });
 4883        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4884        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4885        assert_eq!(
 4886            editor.selections.display_ranges(cx),
 4887            vec![
 4888                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 4889                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 4890            ]
 4891        );
 4892    });
 4893
 4894    // With `move_upwards` the selections stay in place, except for
 4895    // the lines inserted above them
 4896    let editor = cx.add_window(|window, cx| {
 4897        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4898        build_editor(buffer, window, cx)
 4899    });
 4900    _ = editor.update(cx, |editor, window, cx| {
 4901        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4902            s.select_display_ranges([
 4903                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4904                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4905                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4906                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4907            ])
 4908        });
 4909        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4910        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4911        assert_eq!(
 4912            editor.selections.display_ranges(cx),
 4913            vec![
 4914                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4915                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4916                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 4917                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4918            ]
 4919        );
 4920    });
 4921
 4922    let editor = cx.add_window(|window, cx| {
 4923        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4924        build_editor(buffer, window, cx)
 4925    });
 4926    _ = editor.update(cx, |editor, window, cx| {
 4927        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4928            s.select_display_ranges([
 4929                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4930                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4931            ])
 4932        });
 4933        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4934        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4935        assert_eq!(
 4936            editor.selections.display_ranges(cx),
 4937            vec![
 4938                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4939                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4940            ]
 4941        );
 4942    });
 4943
 4944    let editor = cx.add_window(|window, cx| {
 4945        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4946        build_editor(buffer, window, cx)
 4947    });
 4948    _ = editor.update(cx, |editor, window, cx| {
 4949        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4950            s.select_display_ranges([
 4951                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4952                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4953            ])
 4954        });
 4955        editor.duplicate_selection(&DuplicateSelection, window, cx);
 4956        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 4957        assert_eq!(
 4958            editor.selections.display_ranges(cx),
 4959            vec![
 4960                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4961                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 4962            ]
 4963        );
 4964    });
 4965}
 4966
 4967#[gpui::test]
 4968fn test_move_line_up_down(cx: &mut TestAppContext) {
 4969    init_test(cx, |_| {});
 4970
 4971    let editor = cx.add_window(|window, cx| {
 4972        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 4973        build_editor(buffer, window, cx)
 4974    });
 4975    _ = editor.update(cx, |editor, window, cx| {
 4976        editor.fold_creases(
 4977            vec![
 4978                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 4979                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 4980                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 4981            ],
 4982            true,
 4983            window,
 4984            cx,
 4985        );
 4986        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4987            s.select_display_ranges([
 4988                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4989                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4990                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4991                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 4992            ])
 4993        });
 4994        assert_eq!(
 4995            editor.display_text(cx),
 4996            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 4997        );
 4998
 4999        editor.move_line_up(&MoveLineUp, window, cx);
 5000        assert_eq!(
 5001            editor.display_text(cx),
 5002            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 5003        );
 5004        assert_eq!(
 5005            editor.selections.display_ranges(cx),
 5006            vec![
 5007                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5008                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5009                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5010                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5011            ]
 5012        );
 5013    });
 5014
 5015    _ = editor.update(cx, |editor, window, cx| {
 5016        editor.move_line_down(&MoveLineDown, window, cx);
 5017        assert_eq!(
 5018            editor.display_text(cx),
 5019            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 5020        );
 5021        assert_eq!(
 5022            editor.selections.display_ranges(cx),
 5023            vec![
 5024                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5025                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5026                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5027                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5028            ]
 5029        );
 5030    });
 5031
 5032    _ = editor.update(cx, |editor, window, cx| {
 5033        editor.move_line_down(&MoveLineDown, window, cx);
 5034        assert_eq!(
 5035            editor.display_text(cx),
 5036            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 5037        );
 5038        assert_eq!(
 5039            editor.selections.display_ranges(cx),
 5040            vec![
 5041                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5042                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5043                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5044                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5045            ]
 5046        );
 5047    });
 5048
 5049    _ = editor.update(cx, |editor, window, cx| {
 5050        editor.move_line_up(&MoveLineUp, window, cx);
 5051        assert_eq!(
 5052            editor.display_text(cx),
 5053            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 5054        );
 5055        assert_eq!(
 5056            editor.selections.display_ranges(cx),
 5057            vec![
 5058                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5059                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5060                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5061                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5062            ]
 5063        );
 5064    });
 5065}
 5066
 5067#[gpui::test]
 5068fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 5069    init_test(cx, |_| {});
 5070
 5071    let editor = cx.add_window(|window, cx| {
 5072        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5073        build_editor(buffer, window, cx)
 5074    });
 5075    _ = editor.update(cx, |editor, window, cx| {
 5076        let snapshot = editor.buffer.read(cx).snapshot(cx);
 5077        editor.insert_blocks(
 5078            [BlockProperties {
 5079                style: BlockStyle::Fixed,
 5080                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 5081                height: Some(1),
 5082                render: Arc::new(|_| div().into_any()),
 5083                priority: 0,
 5084            }],
 5085            Some(Autoscroll::fit()),
 5086            cx,
 5087        );
 5088        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5089            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 5090        });
 5091        editor.move_line_down(&MoveLineDown, window, cx);
 5092    });
 5093}
 5094
 5095#[gpui::test]
 5096async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 5097    init_test(cx, |_| {});
 5098
 5099    let mut cx = EditorTestContext::new(cx).await;
 5100    cx.set_state(
 5101        &"
 5102            ˇzero
 5103            one
 5104            two
 5105            three
 5106            four
 5107            five
 5108        "
 5109        .unindent(),
 5110    );
 5111
 5112    // Create a four-line block that replaces three lines of text.
 5113    cx.update_editor(|editor, window, cx| {
 5114        let snapshot = editor.snapshot(window, cx);
 5115        let snapshot = &snapshot.buffer_snapshot;
 5116        let placement = BlockPlacement::Replace(
 5117            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 5118        );
 5119        editor.insert_blocks(
 5120            [BlockProperties {
 5121                placement,
 5122                height: Some(4),
 5123                style: BlockStyle::Sticky,
 5124                render: Arc::new(|_| gpui::div().into_any_element()),
 5125                priority: 0,
 5126            }],
 5127            None,
 5128            cx,
 5129        );
 5130    });
 5131
 5132    // Move down so that the cursor touches the block.
 5133    cx.update_editor(|editor, window, cx| {
 5134        editor.move_down(&Default::default(), window, cx);
 5135    });
 5136    cx.assert_editor_state(
 5137        &"
 5138            zero
 5139            «one
 5140            two
 5141            threeˇ»
 5142            four
 5143            five
 5144        "
 5145        .unindent(),
 5146    );
 5147
 5148    // Move down past the block.
 5149    cx.update_editor(|editor, window, cx| {
 5150        editor.move_down(&Default::default(), window, cx);
 5151    });
 5152    cx.assert_editor_state(
 5153        &"
 5154            zero
 5155            one
 5156            two
 5157            three
 5158            ˇfour
 5159            five
 5160        "
 5161        .unindent(),
 5162    );
 5163}
 5164
 5165#[gpui::test]
 5166fn test_transpose(cx: &mut TestAppContext) {
 5167    init_test(cx, |_| {});
 5168
 5169    _ = cx.add_window(|window, cx| {
 5170        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 5171        editor.set_style(EditorStyle::default(), window, cx);
 5172        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5173            s.select_ranges([1..1])
 5174        });
 5175        editor.transpose(&Default::default(), window, cx);
 5176        assert_eq!(editor.text(cx), "bac");
 5177        assert_eq!(editor.selections.ranges(cx), [2..2]);
 5178
 5179        editor.transpose(&Default::default(), window, cx);
 5180        assert_eq!(editor.text(cx), "bca");
 5181        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5182
 5183        editor.transpose(&Default::default(), window, cx);
 5184        assert_eq!(editor.text(cx), "bac");
 5185        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5186
 5187        editor
 5188    });
 5189
 5190    _ = cx.add_window(|window, cx| {
 5191        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5192        editor.set_style(EditorStyle::default(), window, cx);
 5193        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5194            s.select_ranges([3..3])
 5195        });
 5196        editor.transpose(&Default::default(), window, cx);
 5197        assert_eq!(editor.text(cx), "acb\nde");
 5198        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5199
 5200        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5201            s.select_ranges([4..4])
 5202        });
 5203        editor.transpose(&Default::default(), window, cx);
 5204        assert_eq!(editor.text(cx), "acbd\ne");
 5205        assert_eq!(editor.selections.ranges(cx), [5..5]);
 5206
 5207        editor.transpose(&Default::default(), window, cx);
 5208        assert_eq!(editor.text(cx), "acbde\n");
 5209        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5210
 5211        editor.transpose(&Default::default(), window, cx);
 5212        assert_eq!(editor.text(cx), "acbd\ne");
 5213        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5214
 5215        editor
 5216    });
 5217
 5218    _ = cx.add_window(|window, cx| {
 5219        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5220        editor.set_style(EditorStyle::default(), window, cx);
 5221        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5222            s.select_ranges([1..1, 2..2, 4..4])
 5223        });
 5224        editor.transpose(&Default::default(), window, cx);
 5225        assert_eq!(editor.text(cx), "bacd\ne");
 5226        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 5227
 5228        editor.transpose(&Default::default(), window, cx);
 5229        assert_eq!(editor.text(cx), "bcade\n");
 5230        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 5231
 5232        editor.transpose(&Default::default(), window, cx);
 5233        assert_eq!(editor.text(cx), "bcda\ne");
 5234        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5235
 5236        editor.transpose(&Default::default(), window, cx);
 5237        assert_eq!(editor.text(cx), "bcade\n");
 5238        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5239
 5240        editor.transpose(&Default::default(), window, cx);
 5241        assert_eq!(editor.text(cx), "bcaed\n");
 5242        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 5243
 5244        editor
 5245    });
 5246
 5247    _ = cx.add_window(|window, cx| {
 5248        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 5249        editor.set_style(EditorStyle::default(), window, cx);
 5250        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5251            s.select_ranges([4..4])
 5252        });
 5253        editor.transpose(&Default::default(), window, cx);
 5254        assert_eq!(editor.text(cx), "🏀🍐✋");
 5255        assert_eq!(editor.selections.ranges(cx), [8..8]);
 5256
 5257        editor.transpose(&Default::default(), window, cx);
 5258        assert_eq!(editor.text(cx), "🏀✋🍐");
 5259        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5260
 5261        editor.transpose(&Default::default(), window, cx);
 5262        assert_eq!(editor.text(cx), "🏀🍐✋");
 5263        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5264
 5265        editor
 5266    });
 5267}
 5268
 5269#[gpui::test]
 5270async fn test_rewrap(cx: &mut TestAppContext) {
 5271    init_test(cx, |settings| {
 5272        settings.languages.0.extend([
 5273            (
 5274                "Markdown".into(),
 5275                LanguageSettingsContent {
 5276                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5277                    preferred_line_length: Some(40),
 5278                    ..Default::default()
 5279                },
 5280            ),
 5281            (
 5282                "Plain Text".into(),
 5283                LanguageSettingsContent {
 5284                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5285                    preferred_line_length: Some(40),
 5286                    ..Default::default()
 5287                },
 5288            ),
 5289            (
 5290                "C++".into(),
 5291                LanguageSettingsContent {
 5292                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5293                    preferred_line_length: Some(40),
 5294                    ..Default::default()
 5295                },
 5296            ),
 5297            (
 5298                "Python".into(),
 5299                LanguageSettingsContent {
 5300                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5301                    preferred_line_length: Some(40),
 5302                    ..Default::default()
 5303                },
 5304            ),
 5305            (
 5306                "Rust".into(),
 5307                LanguageSettingsContent {
 5308                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5309                    preferred_line_length: Some(40),
 5310                    ..Default::default()
 5311                },
 5312            ),
 5313        ])
 5314    });
 5315
 5316    let mut cx = EditorTestContext::new(cx).await;
 5317
 5318    let cpp_language = Arc::new(Language::new(
 5319        LanguageConfig {
 5320            name: "C++".into(),
 5321            line_comments: vec!["// ".into()],
 5322            ..LanguageConfig::default()
 5323        },
 5324        None,
 5325    ));
 5326    let python_language = Arc::new(Language::new(
 5327        LanguageConfig {
 5328            name: "Python".into(),
 5329            line_comments: vec!["# ".into()],
 5330            ..LanguageConfig::default()
 5331        },
 5332        None,
 5333    ));
 5334    let markdown_language = Arc::new(Language::new(
 5335        LanguageConfig {
 5336            name: "Markdown".into(),
 5337            rewrap_prefixes: vec![
 5338                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 5339                regex::Regex::new("[-*+]\\s+").unwrap(),
 5340            ],
 5341            ..LanguageConfig::default()
 5342        },
 5343        None,
 5344    ));
 5345    let rust_language = Arc::new(Language::new(
 5346        LanguageConfig {
 5347            name: "Rust".into(),
 5348            line_comments: vec!["// ".into(), "/// ".into()],
 5349            ..LanguageConfig::default()
 5350        },
 5351        Some(tree_sitter_rust::LANGUAGE.into()),
 5352    ));
 5353
 5354    let plaintext_language = Arc::new(Language::new(
 5355        LanguageConfig {
 5356            name: "Plain Text".into(),
 5357            ..LanguageConfig::default()
 5358        },
 5359        None,
 5360    ));
 5361
 5362    // Test basic rewrapping of a long line with a cursor
 5363    assert_rewrap(
 5364        indoc! {"
 5365            // ˇThis is a long comment that needs to be wrapped.
 5366        "},
 5367        indoc! {"
 5368            // ˇThis is a long comment that needs to
 5369            // be wrapped.
 5370        "},
 5371        cpp_language.clone(),
 5372        &mut cx,
 5373    );
 5374
 5375    // Test rewrapping a full selection
 5376    assert_rewrap(
 5377        indoc! {"
 5378            «// This selected long comment needs to be wrapped.ˇ»"
 5379        },
 5380        indoc! {"
 5381            «// This selected long comment needs to
 5382            // be wrapped.ˇ»"
 5383        },
 5384        cpp_language.clone(),
 5385        &mut cx,
 5386    );
 5387
 5388    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 5389    assert_rewrap(
 5390        indoc! {"
 5391            // ˇThis is the first line.
 5392            // Thisˇ is the second line.
 5393            // This is the thirdˇ line, all part of one paragraph.
 5394         "},
 5395        indoc! {"
 5396            // ˇThis is the first line. Thisˇ is the
 5397            // second line. This is the thirdˇ line,
 5398            // all part of one paragraph.
 5399         "},
 5400        cpp_language.clone(),
 5401        &mut cx,
 5402    );
 5403
 5404    // Test multiple cursors in different paragraphs trigger separate rewraps
 5405    assert_rewrap(
 5406        indoc! {"
 5407            // ˇThis is the first paragraph, first line.
 5408            // ˇThis is the first paragraph, second line.
 5409
 5410            // ˇThis is the second paragraph, first line.
 5411            // ˇThis is the second paragraph, second line.
 5412        "},
 5413        indoc! {"
 5414            // ˇThis is the first paragraph, first
 5415            // line. ˇThis is the first paragraph,
 5416            // second line.
 5417
 5418            // ˇThis is the second paragraph, first
 5419            // line. ˇThis is the second paragraph,
 5420            // second line.
 5421        "},
 5422        cpp_language.clone(),
 5423        &mut cx,
 5424    );
 5425
 5426    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 5427    assert_rewrap(
 5428        indoc! {"
 5429            «// A regular long long comment to be wrapped.
 5430            /// A documentation long comment to be wrapped.ˇ»
 5431          "},
 5432        indoc! {"
 5433            «// A regular long long comment to be
 5434            // wrapped.
 5435            /// A documentation long comment to be
 5436            /// wrapped.ˇ»
 5437          "},
 5438        rust_language.clone(),
 5439        &mut cx,
 5440    );
 5441
 5442    // Test that change in indentation level trigger seperate rewraps
 5443    assert_rewrap(
 5444        indoc! {"
 5445            fn foo() {
 5446                «// This is a long comment at the base indent.
 5447                    // This is a long comment at the next indent.ˇ»
 5448            }
 5449        "},
 5450        indoc! {"
 5451            fn foo() {
 5452                «// This is a long comment at the
 5453                // base indent.
 5454                    // This is a long comment at the
 5455                    // next indent.ˇ»
 5456            }
 5457        "},
 5458        rust_language.clone(),
 5459        &mut cx,
 5460    );
 5461
 5462    // Test that different comment prefix characters (e.g., '#') are handled correctly
 5463    assert_rewrap(
 5464        indoc! {"
 5465            # ˇThis is a long comment using a pound sign.
 5466        "},
 5467        indoc! {"
 5468            # ˇThis is a long comment using a pound
 5469            # sign.
 5470        "},
 5471        python_language.clone(),
 5472        &mut cx,
 5473    );
 5474
 5475    // Test rewrapping only affects comments, not code even when selected
 5476    assert_rewrap(
 5477        indoc! {"
 5478            «/// This doc comment is long and should be wrapped.
 5479            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 5480        "},
 5481        indoc! {"
 5482            «/// This doc comment is long and should
 5483            /// be wrapped.
 5484            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 5485        "},
 5486        rust_language.clone(),
 5487        &mut cx,
 5488    );
 5489
 5490    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 5491    assert_rewrap(
 5492        indoc! {"
 5493            # Header
 5494
 5495            A long long long line of markdown text to wrap.ˇ
 5496         "},
 5497        indoc! {"
 5498            # Header
 5499
 5500            A long long long line of markdown text
 5501            to wrap.ˇ
 5502         "},
 5503        markdown_language.clone(),
 5504        &mut cx,
 5505    );
 5506
 5507    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 5508    assert_rewrap(
 5509        indoc! {"
 5510            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 5511            2. This is a numbered list item that is very long and needs to be wrapped properly.
 5512            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 5513        "},
 5514        indoc! {"
 5515            «1. This is a numbered list item that is
 5516               very long and needs to be wrapped
 5517               properly.
 5518            2. This is a numbered list item that is
 5519               very long and needs to be wrapped
 5520               properly.
 5521            - This is an unordered list item that is
 5522              also very long and should not merge
 5523              with the numbered item.ˇ»
 5524        "},
 5525        markdown_language.clone(),
 5526        &mut cx,
 5527    );
 5528
 5529    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 5530    assert_rewrap(
 5531        indoc! {"
 5532            «1. This is a numbered list item that is
 5533            very long and needs to be wrapped
 5534            properly.
 5535            2. This is a numbered list item that is
 5536            very long and needs to be wrapped
 5537            properly.
 5538            - This is an unordered list item that is
 5539            also very long and should not merge with
 5540            the numbered item.ˇ»
 5541        "},
 5542        indoc! {"
 5543            «1. This is a numbered list item that is
 5544               very long and needs to be wrapped
 5545               properly.
 5546            2. This is a numbered list item that is
 5547               very long and needs to be wrapped
 5548               properly.
 5549            - This is an unordered list item that is
 5550              also very long and should not merge
 5551              with the numbered item.ˇ»
 5552        "},
 5553        markdown_language.clone(),
 5554        &mut cx,
 5555    );
 5556
 5557    // Test that rewrapping maintain indents even when they already exists.
 5558    assert_rewrap(
 5559        indoc! {"
 5560            «1. This is a numbered list
 5561               item that is very long and needs to be wrapped properly.
 5562            2. This is a numbered list
 5563               item that is very long and needs to be wrapped properly.
 5564            - This is an unordered list item that is also very long and
 5565              should not merge with the numbered item.ˇ»
 5566        "},
 5567        indoc! {"
 5568            «1. This is a numbered list item that is
 5569               very long and needs to be wrapped
 5570               properly.
 5571            2. This is a numbered list item that is
 5572               very long and needs to be wrapped
 5573               properly.
 5574            - This is an unordered list item that is
 5575              also very long and should not merge
 5576              with the numbered item.ˇ»
 5577        "},
 5578        markdown_language.clone(),
 5579        &mut cx,
 5580    );
 5581
 5582    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 5583    assert_rewrap(
 5584        indoc! {"
 5585            ˇThis is a very long line of plain text that will be wrapped.
 5586        "},
 5587        indoc! {"
 5588            ˇThis is a very long line of plain text
 5589            that will be wrapped.
 5590        "},
 5591        plaintext_language.clone(),
 5592        &mut cx,
 5593    );
 5594
 5595    // Test that non-commented code acts as a paragraph boundary within a selection
 5596    assert_rewrap(
 5597        indoc! {"
 5598               «// This is the first long comment block to be wrapped.
 5599               fn my_func(a: u32);
 5600               // This is the second long comment block to be wrapped.ˇ»
 5601           "},
 5602        indoc! {"
 5603               «// This is the first long comment block
 5604               // to be wrapped.
 5605               fn my_func(a: u32);
 5606               // This is the second long comment block
 5607               // to be wrapped.ˇ»
 5608           "},
 5609        rust_language.clone(),
 5610        &mut cx,
 5611    );
 5612
 5613    // Test rewrapping multiple selections, including ones with blank lines or tabs
 5614    assert_rewrap(
 5615        indoc! {"
 5616            «ˇThis is a very long line that will be wrapped.
 5617
 5618            This is another paragraph in the same selection.»
 5619
 5620            «\tThis is a very long indented line that will be wrapped.ˇ»
 5621         "},
 5622        indoc! {"
 5623            «ˇThis is a very long line that will be
 5624            wrapped.
 5625
 5626            This is another paragraph in the same
 5627            selection.»
 5628
 5629            «\tThis is a very long indented line
 5630            \tthat will be wrapped.ˇ»
 5631         "},
 5632        plaintext_language.clone(),
 5633        &mut cx,
 5634    );
 5635
 5636    // Test that an empty comment line acts as a paragraph boundary
 5637    assert_rewrap(
 5638        indoc! {"
 5639            // ˇThis is a long comment that will be wrapped.
 5640            //
 5641            // And this is another long comment that will also be wrapped.ˇ
 5642         "},
 5643        indoc! {"
 5644            // ˇThis is a long comment that will be
 5645            // wrapped.
 5646            //
 5647            // And this is another long comment that
 5648            // will also be wrapped.ˇ
 5649         "},
 5650        cpp_language,
 5651        &mut cx,
 5652    );
 5653
 5654    #[track_caller]
 5655    fn assert_rewrap(
 5656        unwrapped_text: &str,
 5657        wrapped_text: &str,
 5658        language: Arc<Language>,
 5659        cx: &mut EditorTestContext,
 5660    ) {
 5661        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 5662        cx.set_state(unwrapped_text);
 5663        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 5664        cx.assert_editor_state(wrapped_text);
 5665    }
 5666}
 5667
 5668#[gpui::test]
 5669async fn test_hard_wrap(cx: &mut TestAppContext) {
 5670    init_test(cx, |_| {});
 5671    let mut cx = EditorTestContext::new(cx).await;
 5672
 5673    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 5674    cx.update_editor(|editor, _, cx| {
 5675        editor.set_hard_wrap(Some(14), cx);
 5676    });
 5677
 5678    cx.set_state(indoc!(
 5679        "
 5680        one two three ˇ
 5681        "
 5682    ));
 5683    cx.simulate_input("four");
 5684    cx.run_until_parked();
 5685
 5686    cx.assert_editor_state(indoc!(
 5687        "
 5688        one two three
 5689        fourˇ
 5690        "
 5691    ));
 5692
 5693    cx.update_editor(|editor, window, cx| {
 5694        editor.newline(&Default::default(), window, cx);
 5695    });
 5696    cx.run_until_parked();
 5697    cx.assert_editor_state(indoc!(
 5698        "
 5699        one two three
 5700        four
 5701        ˇ
 5702        "
 5703    ));
 5704
 5705    cx.simulate_input("five");
 5706    cx.run_until_parked();
 5707    cx.assert_editor_state(indoc!(
 5708        "
 5709        one two three
 5710        four
 5711        fiveˇ
 5712        "
 5713    ));
 5714
 5715    cx.update_editor(|editor, window, cx| {
 5716        editor.newline(&Default::default(), window, cx);
 5717    });
 5718    cx.run_until_parked();
 5719    cx.simulate_input("# ");
 5720    cx.run_until_parked();
 5721    cx.assert_editor_state(indoc!(
 5722        "
 5723        one two three
 5724        four
 5725        five
 5726        # ˇ
 5727        "
 5728    ));
 5729
 5730    cx.update_editor(|editor, window, cx| {
 5731        editor.newline(&Default::default(), window, cx);
 5732    });
 5733    cx.run_until_parked();
 5734    cx.assert_editor_state(indoc!(
 5735        "
 5736        one two three
 5737        four
 5738        five
 5739        #\x20
 5740 5741        "
 5742    ));
 5743
 5744    cx.simulate_input(" 6");
 5745    cx.run_until_parked();
 5746    cx.assert_editor_state(indoc!(
 5747        "
 5748        one two three
 5749        four
 5750        five
 5751        #
 5752        # 6ˇ
 5753        "
 5754    ));
 5755}
 5756
 5757#[gpui::test]
 5758async fn test_clipboard(cx: &mut TestAppContext) {
 5759    init_test(cx, |_| {});
 5760
 5761    let mut cx = EditorTestContext::new(cx).await;
 5762
 5763    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 5764    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5765    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 5766
 5767    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 5768    cx.set_state("two ˇfour ˇsix ˇ");
 5769    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5770    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 5771
 5772    // Paste again but with only two cursors. Since the number of cursors doesn't
 5773    // match the number of slices in the clipboard, the entire clipboard text
 5774    // is pasted at each cursor.
 5775    cx.set_state("ˇtwo one✅ four three six five ˇ");
 5776    cx.update_editor(|e, window, cx| {
 5777        e.handle_input("( ", window, cx);
 5778        e.paste(&Paste, window, cx);
 5779        e.handle_input(") ", window, cx);
 5780    });
 5781    cx.assert_editor_state(
 5782        &([
 5783            "( one✅ ",
 5784            "three ",
 5785            "five ) ˇtwo one✅ four three six five ( one✅ ",
 5786            "three ",
 5787            "five ) ˇ",
 5788        ]
 5789        .join("\n")),
 5790    );
 5791
 5792    // Cut with three selections, one of which is full-line.
 5793    cx.set_state(indoc! {"
 5794        1«2ˇ»3
 5795        4ˇ567
 5796        «8ˇ»9"});
 5797    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5798    cx.assert_editor_state(indoc! {"
 5799        1ˇ3
 5800        ˇ9"});
 5801
 5802    // Paste with three selections, noticing how the copied selection that was full-line
 5803    // gets inserted before the second cursor.
 5804    cx.set_state(indoc! {"
 5805        1ˇ3
 5806 5807        «oˇ»ne"});
 5808    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5809    cx.assert_editor_state(indoc! {"
 5810        12ˇ3
 5811        4567
 5812 5813        8ˇne"});
 5814
 5815    // Copy with a single cursor only, which writes the whole line into the clipboard.
 5816    cx.set_state(indoc! {"
 5817        The quick brown
 5818        fox juˇmps over
 5819        the lazy dog"});
 5820    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5821    assert_eq!(
 5822        cx.read_from_clipboard()
 5823            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5824        Some("fox jumps over\n".to_string())
 5825    );
 5826
 5827    // Paste with three selections, noticing how the copied full-line selection is inserted
 5828    // before the empty selections but replaces the selection that is non-empty.
 5829    cx.set_state(indoc! {"
 5830        Tˇhe quick brown
 5831        «foˇ»x jumps over
 5832        tˇhe lazy dog"});
 5833    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5834    cx.assert_editor_state(indoc! {"
 5835        fox jumps over
 5836        Tˇhe quick brown
 5837        fox jumps over
 5838        ˇx jumps over
 5839        fox jumps over
 5840        tˇhe lazy dog"});
 5841}
 5842
 5843#[gpui::test]
 5844async fn test_copy_trim(cx: &mut TestAppContext) {
 5845    init_test(cx, |_| {});
 5846
 5847    let mut cx = EditorTestContext::new(cx).await;
 5848    cx.set_state(
 5849        r#"            «for selection in selections.iter() {
 5850            let mut start = selection.start;
 5851            let mut end = selection.end;
 5852            let is_entire_line = selection.is_empty();
 5853            if is_entire_line {
 5854                start = Point::new(start.row, 0);ˇ»
 5855                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5856            }
 5857        "#,
 5858    );
 5859    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5860    assert_eq!(
 5861        cx.read_from_clipboard()
 5862            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5863        Some(
 5864            "for selection in selections.iter() {
 5865            let mut start = selection.start;
 5866            let mut end = selection.end;
 5867            let is_entire_line = selection.is_empty();
 5868            if is_entire_line {
 5869                start = Point::new(start.row, 0);"
 5870                .to_string()
 5871        ),
 5872        "Regular copying preserves all indentation selected",
 5873    );
 5874    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5875    assert_eq!(
 5876        cx.read_from_clipboard()
 5877            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5878        Some(
 5879            "for selection in selections.iter() {
 5880let mut start = selection.start;
 5881let mut end = selection.end;
 5882let is_entire_line = selection.is_empty();
 5883if is_entire_line {
 5884    start = Point::new(start.row, 0);"
 5885                .to_string()
 5886        ),
 5887        "Copying with stripping should strip all leading whitespaces"
 5888    );
 5889
 5890    cx.set_state(
 5891        r#"       «     for selection in selections.iter() {
 5892            let mut start = selection.start;
 5893            let mut end = selection.end;
 5894            let is_entire_line = selection.is_empty();
 5895            if is_entire_line {
 5896                start = Point::new(start.row, 0);ˇ»
 5897                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5898            }
 5899        "#,
 5900    );
 5901    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5902    assert_eq!(
 5903        cx.read_from_clipboard()
 5904            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5905        Some(
 5906            "     for selection in selections.iter() {
 5907            let mut start = selection.start;
 5908            let mut end = selection.end;
 5909            let is_entire_line = selection.is_empty();
 5910            if is_entire_line {
 5911                start = Point::new(start.row, 0);"
 5912                .to_string()
 5913        ),
 5914        "Regular copying preserves all indentation selected",
 5915    );
 5916    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5917    assert_eq!(
 5918        cx.read_from_clipboard()
 5919            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5920        Some(
 5921            "for selection in selections.iter() {
 5922let mut start = selection.start;
 5923let mut end = selection.end;
 5924let is_entire_line = selection.is_empty();
 5925if is_entire_line {
 5926    start = Point::new(start.row, 0);"
 5927                .to_string()
 5928        ),
 5929        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 5930    );
 5931
 5932    cx.set_state(
 5933        r#"       «ˇ     for selection in selections.iter() {
 5934            let mut start = selection.start;
 5935            let mut end = selection.end;
 5936            let is_entire_line = selection.is_empty();
 5937            if is_entire_line {
 5938                start = Point::new(start.row, 0);»
 5939                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5940            }
 5941        "#,
 5942    );
 5943    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5944    assert_eq!(
 5945        cx.read_from_clipboard()
 5946            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5947        Some(
 5948            "     for selection in selections.iter() {
 5949            let mut start = selection.start;
 5950            let mut end = selection.end;
 5951            let is_entire_line = selection.is_empty();
 5952            if is_entire_line {
 5953                start = Point::new(start.row, 0);"
 5954                .to_string()
 5955        ),
 5956        "Regular copying for reverse selection works the same",
 5957    );
 5958    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5959    assert_eq!(
 5960        cx.read_from_clipboard()
 5961            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5962        Some(
 5963            "for selection in selections.iter() {
 5964let mut start = selection.start;
 5965let mut end = selection.end;
 5966let is_entire_line = selection.is_empty();
 5967if is_entire_line {
 5968    start = Point::new(start.row, 0);"
 5969                .to_string()
 5970        ),
 5971        "Copying with stripping for reverse selection works the same"
 5972    );
 5973
 5974    cx.set_state(
 5975        r#"            for selection «in selections.iter() {
 5976            let mut start = selection.start;
 5977            let mut end = selection.end;
 5978            let is_entire_line = selection.is_empty();
 5979            if is_entire_line {
 5980                start = Point::new(start.row, 0);ˇ»
 5981                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5982            }
 5983        "#,
 5984    );
 5985    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5986    assert_eq!(
 5987        cx.read_from_clipboard()
 5988            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5989        Some(
 5990            "in selections.iter() {
 5991            let mut start = selection.start;
 5992            let mut end = selection.end;
 5993            let is_entire_line = selection.is_empty();
 5994            if is_entire_line {
 5995                start = Point::new(start.row, 0);"
 5996                .to_string()
 5997        ),
 5998        "When selecting past the indent, the copying works as usual",
 5999    );
 6000    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6001    assert_eq!(
 6002        cx.read_from_clipboard()
 6003            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6004        Some(
 6005            "in selections.iter() {
 6006            let mut start = selection.start;
 6007            let mut end = selection.end;
 6008            let is_entire_line = selection.is_empty();
 6009            if is_entire_line {
 6010                start = Point::new(start.row, 0);"
 6011                .to_string()
 6012        ),
 6013        "When selecting past the indent, nothing is trimmed"
 6014    );
 6015
 6016    cx.set_state(
 6017        r#"            «for selection in selections.iter() {
 6018            let mut start = selection.start;
 6019
 6020            let mut end = selection.end;
 6021            let is_entire_line = selection.is_empty();
 6022            if is_entire_line {
 6023                start = Point::new(start.row, 0);
 6024ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6025            }
 6026        "#,
 6027    );
 6028    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6029    assert_eq!(
 6030        cx.read_from_clipboard()
 6031            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6032        Some(
 6033            "for selection in selections.iter() {
 6034let mut start = selection.start;
 6035
 6036let mut end = selection.end;
 6037let is_entire_line = selection.is_empty();
 6038if is_entire_line {
 6039    start = Point::new(start.row, 0);
 6040"
 6041            .to_string()
 6042        ),
 6043        "Copying with stripping should ignore empty lines"
 6044    );
 6045}
 6046
 6047#[gpui::test]
 6048async fn test_paste_multiline(cx: &mut TestAppContext) {
 6049    init_test(cx, |_| {});
 6050
 6051    let mut cx = EditorTestContext::new(cx).await;
 6052    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 6053
 6054    // Cut an indented block, without the leading whitespace.
 6055    cx.set_state(indoc! {"
 6056        const a: B = (
 6057            c(),
 6058            «d(
 6059                e,
 6060                f
 6061            )ˇ»
 6062        );
 6063    "});
 6064    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6065    cx.assert_editor_state(indoc! {"
 6066        const a: B = (
 6067            c(),
 6068            ˇ
 6069        );
 6070    "});
 6071
 6072    // Paste it at the same position.
 6073    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6074    cx.assert_editor_state(indoc! {"
 6075        const a: B = (
 6076            c(),
 6077            d(
 6078                e,
 6079                f
 6080 6081        );
 6082    "});
 6083
 6084    // Paste it at a line with a lower indent level.
 6085    cx.set_state(indoc! {"
 6086        ˇ
 6087        const a: B = (
 6088            c(),
 6089        );
 6090    "});
 6091    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6092    cx.assert_editor_state(indoc! {"
 6093        d(
 6094            e,
 6095            f
 6096 6097        const a: B = (
 6098            c(),
 6099        );
 6100    "});
 6101
 6102    // Cut an indented block, with the leading whitespace.
 6103    cx.set_state(indoc! {"
 6104        const a: B = (
 6105            c(),
 6106        «    d(
 6107                e,
 6108                f
 6109            )
 6110        ˇ»);
 6111    "});
 6112    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6113    cx.assert_editor_state(indoc! {"
 6114        const a: B = (
 6115            c(),
 6116        ˇ);
 6117    "});
 6118
 6119    // Paste it at the same position.
 6120    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6121    cx.assert_editor_state(indoc! {"
 6122        const a: B = (
 6123            c(),
 6124            d(
 6125                e,
 6126                f
 6127            )
 6128        ˇ);
 6129    "});
 6130
 6131    // Paste it at a line with a higher indent level.
 6132    cx.set_state(indoc! {"
 6133        const a: B = (
 6134            c(),
 6135            d(
 6136                e,
 6137 6138            )
 6139        );
 6140    "});
 6141    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6142    cx.assert_editor_state(indoc! {"
 6143        const a: B = (
 6144            c(),
 6145            d(
 6146                e,
 6147                f    d(
 6148                    e,
 6149                    f
 6150                )
 6151        ˇ
 6152            )
 6153        );
 6154    "});
 6155
 6156    // Copy an indented block, starting mid-line
 6157    cx.set_state(indoc! {"
 6158        const a: B = (
 6159            c(),
 6160            somethin«g(
 6161                e,
 6162                f
 6163            )ˇ»
 6164        );
 6165    "});
 6166    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6167
 6168    // Paste it on a line with a lower indent level
 6169    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 6170    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6171    cx.assert_editor_state(indoc! {"
 6172        const a: B = (
 6173            c(),
 6174            something(
 6175                e,
 6176                f
 6177            )
 6178        );
 6179        g(
 6180            e,
 6181            f
 6182"});
 6183}
 6184
 6185#[gpui::test]
 6186async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 6187    init_test(cx, |_| {});
 6188
 6189    cx.write_to_clipboard(ClipboardItem::new_string(
 6190        "    d(\n        e\n    );\n".into(),
 6191    ));
 6192
 6193    let mut cx = EditorTestContext::new(cx).await;
 6194    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 6195
 6196    cx.set_state(indoc! {"
 6197        fn a() {
 6198            b();
 6199            if c() {
 6200                ˇ
 6201            }
 6202        }
 6203    "});
 6204
 6205    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6206    cx.assert_editor_state(indoc! {"
 6207        fn a() {
 6208            b();
 6209            if c() {
 6210                d(
 6211                    e
 6212                );
 6213        ˇ
 6214            }
 6215        }
 6216    "});
 6217
 6218    cx.set_state(indoc! {"
 6219        fn a() {
 6220            b();
 6221            ˇ
 6222        }
 6223    "});
 6224
 6225    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6226    cx.assert_editor_state(indoc! {"
 6227        fn a() {
 6228            b();
 6229            d(
 6230                e
 6231            );
 6232        ˇ
 6233        }
 6234    "});
 6235}
 6236
 6237#[gpui::test]
 6238fn test_select_all(cx: &mut TestAppContext) {
 6239    init_test(cx, |_| {});
 6240
 6241    let editor = cx.add_window(|window, cx| {
 6242        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 6243        build_editor(buffer, window, cx)
 6244    });
 6245    _ = editor.update(cx, |editor, window, cx| {
 6246        editor.select_all(&SelectAll, window, cx);
 6247        assert_eq!(
 6248            editor.selections.display_ranges(cx),
 6249            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 6250        );
 6251    });
 6252}
 6253
 6254#[gpui::test]
 6255fn test_select_line(cx: &mut TestAppContext) {
 6256    init_test(cx, |_| {});
 6257
 6258    let editor = cx.add_window(|window, cx| {
 6259        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 6260        build_editor(buffer, window, cx)
 6261    });
 6262    _ = editor.update(cx, |editor, window, cx| {
 6263        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6264            s.select_display_ranges([
 6265                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6266                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6267                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6268                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 6269            ])
 6270        });
 6271        editor.select_line(&SelectLine, window, cx);
 6272        assert_eq!(
 6273            editor.selections.display_ranges(cx),
 6274            vec![
 6275                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 6276                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 6277            ]
 6278        );
 6279    });
 6280
 6281    _ = editor.update(cx, |editor, window, cx| {
 6282        editor.select_line(&SelectLine, window, cx);
 6283        assert_eq!(
 6284            editor.selections.display_ranges(cx),
 6285            vec![
 6286                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 6287                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 6288            ]
 6289        );
 6290    });
 6291
 6292    _ = editor.update(cx, |editor, window, cx| {
 6293        editor.select_line(&SelectLine, window, cx);
 6294        assert_eq!(
 6295            editor.selections.display_ranges(cx),
 6296            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 6297        );
 6298    });
 6299}
 6300
 6301#[gpui::test]
 6302async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 6303    init_test(cx, |_| {});
 6304    let mut cx = EditorTestContext::new(cx).await;
 6305
 6306    #[track_caller]
 6307    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 6308        cx.set_state(initial_state);
 6309        cx.update_editor(|e, window, cx| {
 6310            e.split_selection_into_lines(&SplitSelectionIntoLines, window, cx)
 6311        });
 6312        cx.assert_editor_state(expected_state);
 6313    }
 6314
 6315    // Selection starts and ends at the middle of lines, left-to-right
 6316    test(
 6317        &mut cx,
 6318        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 6319        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 6320    );
 6321    // Same thing, right-to-left
 6322    test(
 6323        &mut cx,
 6324        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 6325        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 6326    );
 6327
 6328    // Whole buffer, left-to-right, last line *doesn't* end with newline
 6329    test(
 6330        &mut cx,
 6331        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 6332        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 6333    );
 6334    // Same thing, right-to-left
 6335    test(
 6336        &mut cx,
 6337        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 6338        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 6339    );
 6340
 6341    // Whole buffer, left-to-right, last line ends with newline
 6342    test(
 6343        &mut cx,
 6344        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 6345        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 6346    );
 6347    // Same thing, right-to-left
 6348    test(
 6349        &mut cx,
 6350        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 6351        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 6352    );
 6353
 6354    // Starts at the end of a line, ends at the start of another
 6355    test(
 6356        &mut cx,
 6357        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 6358        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 6359    );
 6360}
 6361
 6362#[gpui::test]
 6363async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 6364    init_test(cx, |_| {});
 6365
 6366    let editor = cx.add_window(|window, cx| {
 6367        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 6368        build_editor(buffer, window, cx)
 6369    });
 6370
 6371    // setup
 6372    _ = editor.update(cx, |editor, window, cx| {
 6373        editor.fold_creases(
 6374            vec![
 6375                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 6376                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 6377                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 6378            ],
 6379            true,
 6380            window,
 6381            cx,
 6382        );
 6383        assert_eq!(
 6384            editor.display_text(cx),
 6385            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 6386        );
 6387    });
 6388
 6389    _ = editor.update(cx, |editor, window, cx| {
 6390        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6391            s.select_display_ranges([
 6392                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6393                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6394                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6395                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 6396            ])
 6397        });
 6398        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 6399        assert_eq!(
 6400            editor.display_text(cx),
 6401            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 6402        );
 6403    });
 6404    EditorTestContext::for_editor(editor, cx)
 6405        .await
 6406        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 6407
 6408    _ = editor.update(cx, |editor, window, cx| {
 6409        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6410            s.select_display_ranges([
 6411                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 6412            ])
 6413        });
 6414        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 6415        assert_eq!(
 6416            editor.display_text(cx),
 6417            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 6418        );
 6419        assert_eq!(
 6420            editor.selections.display_ranges(cx),
 6421            [
 6422                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 6423                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 6424                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 6425                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 6426                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 6427                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 6428                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 6429            ]
 6430        );
 6431    });
 6432    EditorTestContext::for_editor(editor, cx)
 6433        .await
 6434        .assert_editor_state(
 6435            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 6436        );
 6437}
 6438
 6439#[gpui::test]
 6440async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 6441    init_test(cx, |_| {});
 6442
 6443    let mut cx = EditorTestContext::new(cx).await;
 6444
 6445    cx.set_state(indoc!(
 6446        r#"abc
 6447           defˇghi
 6448
 6449           jk
 6450           nlmo
 6451           "#
 6452    ));
 6453
 6454    cx.update_editor(|editor, window, cx| {
 6455        editor.add_selection_above(&Default::default(), window, cx);
 6456    });
 6457
 6458    cx.assert_editor_state(indoc!(
 6459        r#"abcˇ
 6460           defˇghi
 6461
 6462           jk
 6463           nlmo
 6464           "#
 6465    ));
 6466
 6467    cx.update_editor(|editor, window, cx| {
 6468        editor.add_selection_above(&Default::default(), window, cx);
 6469    });
 6470
 6471    cx.assert_editor_state(indoc!(
 6472        r#"abcˇ
 6473            defˇghi
 6474
 6475            jk
 6476            nlmo
 6477            "#
 6478    ));
 6479
 6480    cx.update_editor(|editor, window, cx| {
 6481        editor.add_selection_below(&Default::default(), window, cx);
 6482    });
 6483
 6484    cx.assert_editor_state(indoc!(
 6485        r#"abc
 6486           defˇghi
 6487
 6488           jk
 6489           nlmo
 6490           "#
 6491    ));
 6492
 6493    cx.update_editor(|editor, window, cx| {
 6494        editor.undo_selection(&Default::default(), window, cx);
 6495    });
 6496
 6497    cx.assert_editor_state(indoc!(
 6498        r#"abcˇ
 6499           defˇghi
 6500
 6501           jk
 6502           nlmo
 6503           "#
 6504    ));
 6505
 6506    cx.update_editor(|editor, window, cx| {
 6507        editor.redo_selection(&Default::default(), window, cx);
 6508    });
 6509
 6510    cx.assert_editor_state(indoc!(
 6511        r#"abc
 6512           defˇghi
 6513
 6514           jk
 6515           nlmo
 6516           "#
 6517    ));
 6518
 6519    cx.update_editor(|editor, window, cx| {
 6520        editor.add_selection_below(&Default::default(), window, cx);
 6521    });
 6522
 6523    cx.assert_editor_state(indoc!(
 6524        r#"abc
 6525           defˇghi
 6526           ˇ
 6527           jk
 6528           nlmo
 6529           "#
 6530    ));
 6531
 6532    cx.update_editor(|editor, window, cx| {
 6533        editor.add_selection_below(&Default::default(), window, cx);
 6534    });
 6535
 6536    cx.assert_editor_state(indoc!(
 6537        r#"abc
 6538           defˇghi
 6539           ˇ
 6540           jkˇ
 6541           nlmo
 6542           "#
 6543    ));
 6544
 6545    cx.update_editor(|editor, window, cx| {
 6546        editor.add_selection_below(&Default::default(), window, cx);
 6547    });
 6548
 6549    cx.assert_editor_state(indoc!(
 6550        r#"abc
 6551           defˇghi
 6552           ˇ
 6553           jkˇ
 6554           nlmˇo
 6555           "#
 6556    ));
 6557
 6558    cx.update_editor(|editor, window, cx| {
 6559        editor.add_selection_below(&Default::default(), window, cx);
 6560    });
 6561
 6562    cx.assert_editor_state(indoc!(
 6563        r#"abc
 6564           defˇghi
 6565           ˇ
 6566           jkˇ
 6567           nlmˇo
 6568           ˇ"#
 6569    ));
 6570
 6571    // change selections
 6572    cx.set_state(indoc!(
 6573        r#"abc
 6574           def«ˇg»hi
 6575
 6576           jk
 6577           nlmo
 6578           "#
 6579    ));
 6580
 6581    cx.update_editor(|editor, window, cx| {
 6582        editor.add_selection_below(&Default::default(), window, cx);
 6583    });
 6584
 6585    cx.assert_editor_state(indoc!(
 6586        r#"abc
 6587           def«ˇg»hi
 6588
 6589           jk
 6590           nlm«ˇo»
 6591           "#
 6592    ));
 6593
 6594    cx.update_editor(|editor, window, cx| {
 6595        editor.add_selection_below(&Default::default(), window, cx);
 6596    });
 6597
 6598    cx.assert_editor_state(indoc!(
 6599        r#"abc
 6600           def«ˇg»hi
 6601
 6602           jk
 6603           nlm«ˇo»
 6604           "#
 6605    ));
 6606
 6607    cx.update_editor(|editor, window, cx| {
 6608        editor.add_selection_above(&Default::default(), window, cx);
 6609    });
 6610
 6611    cx.assert_editor_state(indoc!(
 6612        r#"abc
 6613           def«ˇg»hi
 6614
 6615           jk
 6616           nlmo
 6617           "#
 6618    ));
 6619
 6620    cx.update_editor(|editor, window, cx| {
 6621        editor.add_selection_above(&Default::default(), window, cx);
 6622    });
 6623
 6624    cx.assert_editor_state(indoc!(
 6625        r#"abc
 6626           def«ˇg»hi
 6627
 6628           jk
 6629           nlmo
 6630           "#
 6631    ));
 6632
 6633    // Change selections again
 6634    cx.set_state(indoc!(
 6635        r#"a«bc
 6636           defgˇ»hi
 6637
 6638           jk
 6639           nlmo
 6640           "#
 6641    ));
 6642
 6643    cx.update_editor(|editor, window, cx| {
 6644        editor.add_selection_below(&Default::default(), window, cx);
 6645    });
 6646
 6647    cx.assert_editor_state(indoc!(
 6648        r#"a«bcˇ»
 6649           d«efgˇ»hi
 6650
 6651           j«kˇ»
 6652           nlmo
 6653           "#
 6654    ));
 6655
 6656    cx.update_editor(|editor, window, cx| {
 6657        editor.add_selection_below(&Default::default(), window, cx);
 6658    });
 6659    cx.assert_editor_state(indoc!(
 6660        r#"a«bcˇ»
 6661           d«efgˇ»hi
 6662
 6663           j«kˇ»
 6664           n«lmoˇ»
 6665           "#
 6666    ));
 6667    cx.update_editor(|editor, window, cx| {
 6668        editor.add_selection_above(&Default::default(), window, cx);
 6669    });
 6670
 6671    cx.assert_editor_state(indoc!(
 6672        r#"a«bcˇ»
 6673           d«efgˇ»hi
 6674
 6675           j«kˇ»
 6676           nlmo
 6677           "#
 6678    ));
 6679
 6680    // Change selections again
 6681    cx.set_state(indoc!(
 6682        r#"abc
 6683           d«ˇefghi
 6684
 6685           jk
 6686           nlm»o
 6687           "#
 6688    ));
 6689
 6690    cx.update_editor(|editor, window, cx| {
 6691        editor.add_selection_above(&Default::default(), window, cx);
 6692    });
 6693
 6694    cx.assert_editor_state(indoc!(
 6695        r#"a«ˇbc»
 6696           d«ˇef»ghi
 6697
 6698           j«ˇk»
 6699           n«ˇlm»o
 6700           "#
 6701    ));
 6702
 6703    cx.update_editor(|editor, window, cx| {
 6704        editor.add_selection_below(&Default::default(), window, cx);
 6705    });
 6706
 6707    cx.assert_editor_state(indoc!(
 6708        r#"abc
 6709           d«ˇef»ghi
 6710
 6711           j«ˇk»
 6712           n«ˇlm»o
 6713           "#
 6714    ));
 6715}
 6716
 6717#[gpui::test]
 6718async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 6719    init_test(cx, |_| {});
 6720    let mut cx = EditorTestContext::new(cx).await;
 6721
 6722    cx.set_state(indoc!(
 6723        r#"line onˇe
 6724           liˇne two
 6725           line three
 6726           line four"#
 6727    ));
 6728
 6729    cx.update_editor(|editor, window, cx| {
 6730        editor.add_selection_below(&Default::default(), window, cx);
 6731    });
 6732
 6733    // test multiple cursors expand in the same direction
 6734    cx.assert_editor_state(indoc!(
 6735        r#"line onˇe
 6736           liˇne twˇo
 6737           liˇne three
 6738           line four"#
 6739    ));
 6740
 6741    cx.update_editor(|editor, window, cx| {
 6742        editor.add_selection_below(&Default::default(), window, cx);
 6743    });
 6744
 6745    cx.update_editor(|editor, window, cx| {
 6746        editor.add_selection_below(&Default::default(), window, cx);
 6747    });
 6748
 6749    // test multiple cursors expand below overflow
 6750    cx.assert_editor_state(indoc!(
 6751        r#"line onˇe
 6752           liˇne twˇo
 6753           liˇne thˇree
 6754           liˇne foˇur"#
 6755    ));
 6756
 6757    cx.update_editor(|editor, window, cx| {
 6758        editor.add_selection_above(&Default::default(), window, cx);
 6759    });
 6760
 6761    // test multiple cursors retrieves back correctly
 6762    cx.assert_editor_state(indoc!(
 6763        r#"line onˇe
 6764           liˇne twˇo
 6765           liˇne thˇree
 6766           line four"#
 6767    ));
 6768
 6769    cx.update_editor(|editor, window, cx| {
 6770        editor.add_selection_above(&Default::default(), window, cx);
 6771    });
 6772
 6773    cx.update_editor(|editor, window, cx| {
 6774        editor.add_selection_above(&Default::default(), window, cx);
 6775    });
 6776
 6777    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 6778    cx.assert_editor_state(indoc!(
 6779        r#"liˇne onˇe
 6780           liˇne two
 6781           line three
 6782           line four"#
 6783    ));
 6784
 6785    cx.update_editor(|editor, window, cx| {
 6786        editor.undo_selection(&Default::default(), window, cx);
 6787    });
 6788
 6789    // test undo
 6790    cx.assert_editor_state(indoc!(
 6791        r#"line onˇe
 6792           liˇne twˇo
 6793           line three
 6794           line four"#
 6795    ));
 6796
 6797    cx.update_editor(|editor, window, cx| {
 6798        editor.redo_selection(&Default::default(), window, cx);
 6799    });
 6800
 6801    // test redo
 6802    cx.assert_editor_state(indoc!(
 6803        r#"liˇne onˇe
 6804           liˇne two
 6805           line three
 6806           line four"#
 6807    ));
 6808
 6809    cx.set_state(indoc!(
 6810        r#"abcd
 6811           ef«ghˇ»
 6812           ijkl
 6813           «mˇ»nop"#
 6814    ));
 6815
 6816    cx.update_editor(|editor, window, cx| {
 6817        editor.add_selection_above(&Default::default(), window, cx);
 6818    });
 6819
 6820    // test multiple selections expand in the same direction
 6821    cx.assert_editor_state(indoc!(
 6822        r#"ab«cdˇ»
 6823           ef«ghˇ»
 6824           «iˇ»jkl
 6825           «mˇ»nop"#
 6826    ));
 6827
 6828    cx.update_editor(|editor, window, cx| {
 6829        editor.add_selection_above(&Default::default(), window, cx);
 6830    });
 6831
 6832    // test multiple selection upward overflow
 6833    cx.assert_editor_state(indoc!(
 6834        r#"ab«cdˇ»
 6835           «eˇ»f«ghˇ»
 6836           «iˇ»jkl
 6837           «mˇ»nop"#
 6838    ));
 6839
 6840    cx.update_editor(|editor, window, cx| {
 6841        editor.add_selection_below(&Default::default(), window, cx);
 6842    });
 6843
 6844    // test multiple selection retrieves back correctly
 6845    cx.assert_editor_state(indoc!(
 6846        r#"abcd
 6847           ef«ghˇ»
 6848           «iˇ»jkl
 6849           «mˇ»nop"#
 6850    ));
 6851
 6852    cx.update_editor(|editor, window, cx| {
 6853        editor.add_selection_below(&Default::default(), window, cx);
 6854    });
 6855
 6856    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 6857    cx.assert_editor_state(indoc!(
 6858        r#"abcd
 6859           ef«ghˇ»
 6860           ij«klˇ»
 6861           «mˇ»nop"#
 6862    ));
 6863
 6864    cx.update_editor(|editor, window, cx| {
 6865        editor.undo_selection(&Default::default(), window, cx);
 6866    });
 6867
 6868    // test undo
 6869    cx.assert_editor_state(indoc!(
 6870        r#"abcd
 6871           ef«ghˇ»
 6872           «iˇ»jkl
 6873           «mˇ»nop"#
 6874    ));
 6875
 6876    cx.update_editor(|editor, window, cx| {
 6877        editor.redo_selection(&Default::default(), window, cx);
 6878    });
 6879
 6880    // test redo
 6881    cx.assert_editor_state(indoc!(
 6882        r#"abcd
 6883           ef«ghˇ»
 6884           ij«klˇ»
 6885           «mˇ»nop"#
 6886    ));
 6887}
 6888
 6889#[gpui::test]
 6890async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 6891    init_test(cx, |_| {});
 6892    let mut cx = EditorTestContext::new(cx).await;
 6893
 6894    cx.set_state(indoc!(
 6895        r#"line onˇe
 6896           liˇne two
 6897           line three
 6898           line four"#
 6899    ));
 6900
 6901    cx.update_editor(|editor, window, cx| {
 6902        editor.add_selection_below(&Default::default(), window, cx);
 6903        editor.add_selection_below(&Default::default(), window, cx);
 6904        editor.add_selection_below(&Default::default(), window, cx);
 6905    });
 6906
 6907    // initial state with two multi cursor groups
 6908    cx.assert_editor_state(indoc!(
 6909        r#"line onˇe
 6910           liˇne twˇo
 6911           liˇne thˇree
 6912           liˇne foˇur"#
 6913    ));
 6914
 6915    // add single cursor in middle - simulate opt click
 6916    cx.update_editor(|editor, window, cx| {
 6917        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 6918        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 6919        editor.end_selection(window, cx);
 6920    });
 6921
 6922    cx.assert_editor_state(indoc!(
 6923        r#"line onˇe
 6924           liˇne twˇo
 6925           liˇneˇ thˇree
 6926           liˇne foˇur"#
 6927    ));
 6928
 6929    cx.update_editor(|editor, window, cx| {
 6930        editor.add_selection_above(&Default::default(), window, cx);
 6931    });
 6932
 6933    // test new added selection expands above and existing selection shrinks
 6934    cx.assert_editor_state(indoc!(
 6935        r#"line onˇe
 6936           liˇneˇ twˇo
 6937           liˇneˇ thˇree
 6938           line four"#
 6939    ));
 6940
 6941    cx.update_editor(|editor, window, cx| {
 6942        editor.add_selection_above(&Default::default(), window, cx);
 6943    });
 6944
 6945    // test new added selection expands above and existing selection shrinks
 6946    cx.assert_editor_state(indoc!(
 6947        r#"lineˇ onˇe
 6948           liˇneˇ twˇo
 6949           lineˇ three
 6950           line four"#
 6951    ));
 6952
 6953    // intial state with two selection groups
 6954    cx.set_state(indoc!(
 6955        r#"abcd
 6956           ef«ghˇ»
 6957           ijkl
 6958           «mˇ»nop"#
 6959    ));
 6960
 6961    cx.update_editor(|editor, window, cx| {
 6962        editor.add_selection_above(&Default::default(), window, cx);
 6963        editor.add_selection_above(&Default::default(), window, cx);
 6964    });
 6965
 6966    cx.assert_editor_state(indoc!(
 6967        r#"ab«cdˇ»
 6968           «eˇ»f«ghˇ»
 6969           «iˇ»jkl
 6970           «mˇ»nop"#
 6971    ));
 6972
 6973    // add single selection in middle - simulate opt drag
 6974    cx.update_editor(|editor, window, cx| {
 6975        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 6976        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 6977        editor.update_selection(
 6978            DisplayPoint::new(DisplayRow(2), 4),
 6979            0,
 6980            gpui::Point::<f32>::default(),
 6981            window,
 6982            cx,
 6983        );
 6984        editor.end_selection(window, cx);
 6985    });
 6986
 6987    cx.assert_editor_state(indoc!(
 6988        r#"ab«cdˇ»
 6989           «eˇ»f«ghˇ»
 6990           «iˇ»jk«lˇ»
 6991           «mˇ»nop"#
 6992    ));
 6993
 6994    cx.update_editor(|editor, window, cx| {
 6995        editor.add_selection_below(&Default::default(), window, cx);
 6996    });
 6997
 6998    // test new added selection expands below, others shrinks from above
 6999    cx.assert_editor_state(indoc!(
 7000        r#"abcd
 7001           ef«ghˇ»
 7002           «iˇ»jk«lˇ»
 7003           «mˇ»no«pˇ»"#
 7004    ));
 7005}
 7006
 7007#[gpui::test]
 7008async fn test_select_next(cx: &mut TestAppContext) {
 7009    init_test(cx, |_| {});
 7010
 7011    let mut cx = EditorTestContext::new(cx).await;
 7012    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7013
 7014    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7015        .unwrap();
 7016    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7017
 7018    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7019        .unwrap();
 7020    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 7021
 7022    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7023    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7024
 7025    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7026    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 7027
 7028    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7029        .unwrap();
 7030    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7031
 7032    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7033        .unwrap();
 7034    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7035
 7036    // Test selection direction should be preserved
 7037    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 7038
 7039    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7040        .unwrap();
 7041    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 7042}
 7043
 7044#[gpui::test]
 7045async fn test_select_all_matches(cx: &mut TestAppContext) {
 7046    init_test(cx, |_| {});
 7047
 7048    let mut cx = EditorTestContext::new(cx).await;
 7049
 7050    // Test caret-only selections
 7051    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7052    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7053        .unwrap();
 7054    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7055
 7056    // Test left-to-right selections
 7057    cx.set_state("abc\n«abcˇ»\nabc");
 7058    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7059        .unwrap();
 7060    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 7061
 7062    // Test right-to-left selections
 7063    cx.set_state("abc\n«ˇabc»\nabc");
 7064    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7065        .unwrap();
 7066    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 7067
 7068    // Test selecting whitespace with caret selection
 7069    cx.set_state("abc\nˇ   abc\nabc");
 7070    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7071        .unwrap();
 7072    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 7073
 7074    // Test selecting whitespace with left-to-right selection
 7075    cx.set_state("abc\n«ˇ  »abc\nabc");
 7076    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7077        .unwrap();
 7078    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 7079
 7080    // Test no matches with right-to-left selection
 7081    cx.set_state("abc\n«  ˇ»abc\nabc");
 7082    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7083        .unwrap();
 7084    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 7085
 7086    // Test with a single word and clip_at_line_ends=true (#29823)
 7087    cx.set_state("aˇbc");
 7088    cx.update_editor(|e, window, cx| {
 7089        e.set_clip_at_line_ends(true, cx);
 7090        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 7091        e.set_clip_at_line_ends(false, cx);
 7092    });
 7093    cx.assert_editor_state("«abcˇ»");
 7094}
 7095
 7096#[gpui::test]
 7097async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 7098    init_test(cx, |_| {});
 7099
 7100    let mut cx = EditorTestContext::new(cx).await;
 7101
 7102    let large_body_1 = "\nd".repeat(200);
 7103    let large_body_2 = "\ne".repeat(200);
 7104
 7105    cx.set_state(&format!(
 7106        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 7107    ));
 7108    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 7109        let scroll_position = editor.scroll_position(cx);
 7110        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 7111        scroll_position
 7112    });
 7113
 7114    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7115        .unwrap();
 7116    cx.assert_editor_state(&format!(
 7117        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 7118    ));
 7119    let scroll_position_after_selection =
 7120        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 7121    assert_eq!(
 7122        initial_scroll_position, scroll_position_after_selection,
 7123        "Scroll position should not change after selecting all matches"
 7124    );
 7125}
 7126
 7127#[gpui::test]
 7128async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 7129    init_test(cx, |_| {});
 7130
 7131    let mut cx = EditorLspTestContext::new_rust(
 7132        lsp::ServerCapabilities {
 7133            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 7134            ..Default::default()
 7135        },
 7136        cx,
 7137    )
 7138    .await;
 7139
 7140    cx.set_state(indoc! {"
 7141        line 1
 7142        line 2
 7143        linˇe 3
 7144        line 4
 7145        line 5
 7146    "});
 7147
 7148    // Make an edit
 7149    cx.update_editor(|editor, window, cx| {
 7150        editor.handle_input("X", window, cx);
 7151    });
 7152
 7153    // Move cursor to a different position
 7154    cx.update_editor(|editor, window, cx| {
 7155        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7156            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 7157        });
 7158    });
 7159
 7160    cx.assert_editor_state(indoc! {"
 7161        line 1
 7162        line 2
 7163        linXe 3
 7164        line 4
 7165        liˇne 5
 7166    "});
 7167
 7168    cx.lsp
 7169        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 7170            Ok(Some(vec![lsp::TextEdit::new(
 7171                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 7172                "PREFIX ".to_string(),
 7173            )]))
 7174        });
 7175
 7176    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 7177        .unwrap()
 7178        .await
 7179        .unwrap();
 7180
 7181    cx.assert_editor_state(indoc! {"
 7182        PREFIX line 1
 7183        line 2
 7184        linXe 3
 7185        line 4
 7186        liˇne 5
 7187    "});
 7188
 7189    // Undo formatting
 7190    cx.update_editor(|editor, window, cx| {
 7191        editor.undo(&Default::default(), window, cx);
 7192    });
 7193
 7194    // Verify cursor moved back to position after edit
 7195    cx.assert_editor_state(indoc! {"
 7196        line 1
 7197        line 2
 7198        linXˇe 3
 7199        line 4
 7200        line 5
 7201    "});
 7202}
 7203
 7204#[gpui::test]
 7205async fn test_undo_inline_completion_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 7206    init_test(cx, |_| {});
 7207
 7208    let mut cx = EditorTestContext::new(cx).await;
 7209
 7210    let provider = cx.new(|_| FakeInlineCompletionProvider::default());
 7211    cx.update_editor(|editor, window, cx| {
 7212        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 7213    });
 7214
 7215    cx.set_state(indoc! {"
 7216        line 1
 7217        line 2
 7218        linˇe 3
 7219        line 4
 7220        line 5
 7221        line 6
 7222        line 7
 7223        line 8
 7224        line 9
 7225        line 10
 7226    "});
 7227
 7228    let snapshot = cx.buffer_snapshot();
 7229    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 7230
 7231    cx.update(|_, cx| {
 7232        provider.update(cx, |provider, _| {
 7233            provider.set_inline_completion(Some(inline_completion::InlineCompletion {
 7234                id: None,
 7235                edits: vec![(edit_position..edit_position, "X".into())],
 7236                edit_preview: None,
 7237            }))
 7238        })
 7239    });
 7240
 7241    cx.update_editor(|editor, window, cx| editor.update_visible_inline_completion(window, cx));
 7242    cx.update_editor(|editor, window, cx| {
 7243        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 7244    });
 7245
 7246    cx.assert_editor_state(indoc! {"
 7247        line 1
 7248        line 2
 7249        lineXˇ 3
 7250        line 4
 7251        line 5
 7252        line 6
 7253        line 7
 7254        line 8
 7255        line 9
 7256        line 10
 7257    "});
 7258
 7259    cx.update_editor(|editor, window, cx| {
 7260        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7261            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 7262        });
 7263    });
 7264
 7265    cx.assert_editor_state(indoc! {"
 7266        line 1
 7267        line 2
 7268        lineX 3
 7269        line 4
 7270        line 5
 7271        line 6
 7272        line 7
 7273        line 8
 7274        line 9
 7275        liˇne 10
 7276    "});
 7277
 7278    cx.update_editor(|editor, window, cx| {
 7279        editor.undo(&Default::default(), window, cx);
 7280    });
 7281
 7282    cx.assert_editor_state(indoc! {"
 7283        line 1
 7284        line 2
 7285        lineˇ 3
 7286        line 4
 7287        line 5
 7288        line 6
 7289        line 7
 7290        line 8
 7291        line 9
 7292        line 10
 7293    "});
 7294}
 7295
 7296#[gpui::test]
 7297async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 7298    init_test(cx, |_| {});
 7299
 7300    let mut cx = EditorTestContext::new(cx).await;
 7301    cx.set_state(
 7302        r#"let foo = 2;
 7303lˇet foo = 2;
 7304let fooˇ = 2;
 7305let foo = 2;
 7306let foo = ˇ2;"#,
 7307    );
 7308
 7309    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7310        .unwrap();
 7311    cx.assert_editor_state(
 7312        r#"let foo = 2;
 7313«letˇ» foo = 2;
 7314let «fooˇ» = 2;
 7315let foo = 2;
 7316let foo = «2ˇ»;"#,
 7317    );
 7318
 7319    // noop for multiple selections with different contents
 7320    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7321        .unwrap();
 7322    cx.assert_editor_state(
 7323        r#"let foo = 2;
 7324«letˇ» foo = 2;
 7325let «fooˇ» = 2;
 7326let foo = 2;
 7327let foo = «2ˇ»;"#,
 7328    );
 7329
 7330    // Test last selection direction should be preserved
 7331    cx.set_state(
 7332        r#"let foo = 2;
 7333let foo = 2;
 7334let «fooˇ» = 2;
 7335let «ˇfoo» = 2;
 7336let foo = 2;"#,
 7337    );
 7338
 7339    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7340        .unwrap();
 7341    cx.assert_editor_state(
 7342        r#"let foo = 2;
 7343let foo = 2;
 7344let «fooˇ» = 2;
 7345let «ˇfoo» = 2;
 7346let «ˇfoo» = 2;"#,
 7347    );
 7348}
 7349
 7350#[gpui::test]
 7351async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 7352    init_test(cx, |_| {});
 7353
 7354    let mut cx =
 7355        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 7356
 7357    cx.assert_editor_state(indoc! {"
 7358        ˇbbb
 7359        ccc
 7360
 7361        bbb
 7362        ccc
 7363        "});
 7364    cx.dispatch_action(SelectPrevious::default());
 7365    cx.assert_editor_state(indoc! {"
 7366                «bbbˇ»
 7367                ccc
 7368
 7369                bbb
 7370                ccc
 7371                "});
 7372    cx.dispatch_action(SelectPrevious::default());
 7373    cx.assert_editor_state(indoc! {"
 7374                «bbbˇ»
 7375                ccc
 7376
 7377                «bbbˇ»
 7378                ccc
 7379                "});
 7380}
 7381
 7382#[gpui::test]
 7383async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 7384    init_test(cx, |_| {});
 7385
 7386    let mut cx = EditorTestContext::new(cx).await;
 7387    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7388
 7389    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7390        .unwrap();
 7391    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7392
 7393    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7394        .unwrap();
 7395    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 7396
 7397    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7398    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7399
 7400    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7401    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 7402
 7403    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7404        .unwrap();
 7405    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 7406
 7407    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7408        .unwrap();
 7409    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7410}
 7411
 7412#[gpui::test]
 7413async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 7414    init_test(cx, |_| {});
 7415
 7416    let mut cx = EditorTestContext::new(cx).await;
 7417    cx.set_state("");
 7418
 7419    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7420        .unwrap();
 7421    cx.assert_editor_state("«aˇ»");
 7422    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7423        .unwrap();
 7424    cx.assert_editor_state("«aˇ»");
 7425}
 7426
 7427#[gpui::test]
 7428async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 7429    init_test(cx, |_| {});
 7430
 7431    let mut cx = EditorTestContext::new(cx).await;
 7432    cx.set_state(
 7433        r#"let foo = 2;
 7434lˇet foo = 2;
 7435let fooˇ = 2;
 7436let foo = 2;
 7437let foo = ˇ2;"#,
 7438    );
 7439
 7440    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7441        .unwrap();
 7442    cx.assert_editor_state(
 7443        r#"let foo = 2;
 7444«letˇ» foo = 2;
 7445let «fooˇ» = 2;
 7446let foo = 2;
 7447let foo = «2ˇ»;"#,
 7448    );
 7449
 7450    // noop for multiple selections with different contents
 7451    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7452        .unwrap();
 7453    cx.assert_editor_state(
 7454        r#"let foo = 2;
 7455«letˇ» foo = 2;
 7456let «fooˇ» = 2;
 7457let foo = 2;
 7458let foo = «2ˇ»;"#,
 7459    );
 7460}
 7461
 7462#[gpui::test]
 7463async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 7464    init_test(cx, |_| {});
 7465
 7466    let mut cx = EditorTestContext::new(cx).await;
 7467    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 7468
 7469    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7470        .unwrap();
 7471    // selection direction is preserved
 7472    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 7473
 7474    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7475        .unwrap();
 7476    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7477
 7478    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7479    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 7480
 7481    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7482    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7483
 7484    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7485        .unwrap();
 7486    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 7487
 7488    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7489        .unwrap();
 7490    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 7491}
 7492
 7493#[gpui::test]
 7494async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 7495    init_test(cx, |_| {});
 7496
 7497    let language = Arc::new(Language::new(
 7498        LanguageConfig::default(),
 7499        Some(tree_sitter_rust::LANGUAGE.into()),
 7500    ));
 7501
 7502    let text = r#"
 7503        use mod1::mod2::{mod3, mod4};
 7504
 7505        fn fn_1(param1: bool, param2: &str) {
 7506            let var1 = "text";
 7507        }
 7508    "#
 7509    .unindent();
 7510
 7511    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7512    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7513    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7514
 7515    editor
 7516        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7517        .await;
 7518
 7519    editor.update_in(cx, |editor, window, cx| {
 7520        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7521            s.select_display_ranges([
 7522                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 7523                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 7524                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 7525            ]);
 7526        });
 7527        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7528    });
 7529    editor.update(cx, |editor, cx| {
 7530        assert_text_with_selections(
 7531            editor,
 7532            indoc! {r#"
 7533                use mod1::mod2::{mod3, «mod4ˇ»};
 7534
 7535                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7536                    let var1 = "«ˇtext»";
 7537                }
 7538            "#},
 7539            cx,
 7540        );
 7541    });
 7542
 7543    editor.update_in(cx, |editor, window, cx| {
 7544        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7545    });
 7546    editor.update(cx, |editor, cx| {
 7547        assert_text_with_selections(
 7548            editor,
 7549            indoc! {r#"
 7550                use mod1::mod2::«{mod3, mod4}ˇ»;
 7551
 7552                «ˇfn fn_1(param1: bool, param2: &str) {
 7553                    let var1 = "text";
 7554 7555            "#},
 7556            cx,
 7557        );
 7558    });
 7559
 7560    editor.update_in(cx, |editor, window, cx| {
 7561        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7562    });
 7563    assert_eq!(
 7564        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7565        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7566    );
 7567
 7568    // Trying to expand the selected syntax node one more time has no effect.
 7569    editor.update_in(cx, |editor, window, cx| {
 7570        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7571    });
 7572    assert_eq!(
 7573        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7574        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7575    );
 7576
 7577    editor.update_in(cx, |editor, window, cx| {
 7578        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7579    });
 7580    editor.update(cx, |editor, cx| {
 7581        assert_text_with_selections(
 7582            editor,
 7583            indoc! {r#"
 7584                use mod1::mod2::«{mod3, mod4}ˇ»;
 7585
 7586                «ˇfn fn_1(param1: bool, param2: &str) {
 7587                    let var1 = "text";
 7588 7589            "#},
 7590            cx,
 7591        );
 7592    });
 7593
 7594    editor.update_in(cx, |editor, window, cx| {
 7595        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7596    });
 7597    editor.update(cx, |editor, cx| {
 7598        assert_text_with_selections(
 7599            editor,
 7600            indoc! {r#"
 7601                use mod1::mod2::{mod3, «mod4ˇ»};
 7602
 7603                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7604                    let var1 = "«ˇtext»";
 7605                }
 7606            "#},
 7607            cx,
 7608        );
 7609    });
 7610
 7611    editor.update_in(cx, |editor, window, cx| {
 7612        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7613    });
 7614    editor.update(cx, |editor, cx| {
 7615        assert_text_with_selections(
 7616            editor,
 7617            indoc! {r#"
 7618                use mod1::mod2::{mod3, mo«ˇ»d4};
 7619
 7620                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7621                    let var1 = "te«ˇ»xt";
 7622                }
 7623            "#},
 7624            cx,
 7625        );
 7626    });
 7627
 7628    // Trying to shrink the selected syntax node one more time has no effect.
 7629    editor.update_in(cx, |editor, window, cx| {
 7630        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7631    });
 7632    editor.update_in(cx, |editor, _, cx| {
 7633        assert_text_with_selections(
 7634            editor,
 7635            indoc! {r#"
 7636                use mod1::mod2::{mod3, mo«ˇ»d4};
 7637
 7638                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7639                    let var1 = "te«ˇ»xt";
 7640                }
 7641            "#},
 7642            cx,
 7643        );
 7644    });
 7645
 7646    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 7647    // a fold.
 7648    editor.update_in(cx, |editor, window, cx| {
 7649        editor.fold_creases(
 7650            vec![
 7651                Crease::simple(
 7652                    Point::new(0, 21)..Point::new(0, 24),
 7653                    FoldPlaceholder::test(),
 7654                ),
 7655                Crease::simple(
 7656                    Point::new(3, 20)..Point::new(3, 22),
 7657                    FoldPlaceholder::test(),
 7658                ),
 7659            ],
 7660            true,
 7661            window,
 7662            cx,
 7663        );
 7664        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7665    });
 7666    editor.update(cx, |editor, cx| {
 7667        assert_text_with_selections(
 7668            editor,
 7669            indoc! {r#"
 7670                use mod1::mod2::«{mod3, mod4}ˇ»;
 7671
 7672                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7673                    let var1 = "«ˇtext»";
 7674                }
 7675            "#},
 7676            cx,
 7677        );
 7678    });
 7679}
 7680
 7681#[gpui::test]
 7682async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 7683    init_test(cx, |_| {});
 7684
 7685    let language = Arc::new(Language::new(
 7686        LanguageConfig::default(),
 7687        Some(tree_sitter_rust::LANGUAGE.into()),
 7688    ));
 7689
 7690    let text = "let a = 2;";
 7691
 7692    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7693    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7694    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7695
 7696    editor
 7697        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7698        .await;
 7699
 7700    // Test case 1: Cursor at end of word
 7701    editor.update_in(cx, |editor, window, cx| {
 7702        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7703            s.select_display_ranges([
 7704                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 7705            ]);
 7706        });
 7707    });
 7708    editor.update(cx, |editor, cx| {
 7709        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 7710    });
 7711    editor.update_in(cx, |editor, window, cx| {
 7712        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7713    });
 7714    editor.update(cx, |editor, cx| {
 7715        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 7716    });
 7717    editor.update_in(cx, |editor, window, cx| {
 7718        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7719    });
 7720    editor.update(cx, |editor, cx| {
 7721        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7722    });
 7723
 7724    // Test case 2: Cursor at end of statement
 7725    editor.update_in(cx, |editor, window, cx| {
 7726        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7727            s.select_display_ranges([
 7728                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 7729            ]);
 7730        });
 7731    });
 7732    editor.update(cx, |editor, cx| {
 7733        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 7734    });
 7735    editor.update_in(cx, |editor, window, cx| {
 7736        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7737    });
 7738    editor.update(cx, |editor, cx| {
 7739        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7740    });
 7741}
 7742
 7743#[gpui::test]
 7744async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 7745    init_test(cx, |_| {});
 7746
 7747    let language = Arc::new(Language::new(
 7748        LanguageConfig::default(),
 7749        Some(tree_sitter_rust::LANGUAGE.into()),
 7750    ));
 7751
 7752    let text = r#"
 7753        use mod1::mod2::{mod3, mod4};
 7754
 7755        fn fn_1(param1: bool, param2: &str) {
 7756            let var1 = "hello world";
 7757        }
 7758    "#
 7759    .unindent();
 7760
 7761    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7762    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7763    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7764
 7765    editor
 7766        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7767        .await;
 7768
 7769    // Test 1: Cursor on a letter of a string word
 7770    editor.update_in(cx, |editor, window, cx| {
 7771        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7772            s.select_display_ranges([
 7773                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 7774            ]);
 7775        });
 7776    });
 7777    editor.update_in(cx, |editor, window, cx| {
 7778        assert_text_with_selections(
 7779            editor,
 7780            indoc! {r#"
 7781                use mod1::mod2::{mod3, mod4};
 7782
 7783                fn fn_1(param1: bool, param2: &str) {
 7784                    let var1 = "hˇello world";
 7785                }
 7786            "#},
 7787            cx,
 7788        );
 7789        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7790        assert_text_with_selections(
 7791            editor,
 7792            indoc! {r#"
 7793                use mod1::mod2::{mod3, mod4};
 7794
 7795                fn fn_1(param1: bool, param2: &str) {
 7796                    let var1 = "«ˇhello» world";
 7797                }
 7798            "#},
 7799            cx,
 7800        );
 7801    });
 7802
 7803    // Test 2: Partial selection within a word
 7804    editor.update_in(cx, |editor, window, cx| {
 7805        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7806            s.select_display_ranges([
 7807                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 7808            ]);
 7809        });
 7810    });
 7811    editor.update_in(cx, |editor, window, cx| {
 7812        assert_text_with_selections(
 7813            editor,
 7814            indoc! {r#"
 7815                use mod1::mod2::{mod3, mod4};
 7816
 7817                fn fn_1(param1: bool, param2: &str) {
 7818                    let var1 = "h«elˇ»lo world";
 7819                }
 7820            "#},
 7821            cx,
 7822        );
 7823        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7824        assert_text_with_selections(
 7825            editor,
 7826            indoc! {r#"
 7827                use mod1::mod2::{mod3, mod4};
 7828
 7829                fn fn_1(param1: bool, param2: &str) {
 7830                    let var1 = "«ˇhello» world";
 7831                }
 7832            "#},
 7833            cx,
 7834        );
 7835    });
 7836
 7837    // Test 3: Complete word already selected
 7838    editor.update_in(cx, |editor, window, cx| {
 7839        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7840            s.select_display_ranges([
 7841                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 7842            ]);
 7843        });
 7844    });
 7845    editor.update_in(cx, |editor, window, cx| {
 7846        assert_text_with_selections(
 7847            editor,
 7848            indoc! {r#"
 7849                use mod1::mod2::{mod3, mod4};
 7850
 7851                fn fn_1(param1: bool, param2: &str) {
 7852                    let var1 = "«helloˇ» world";
 7853                }
 7854            "#},
 7855            cx,
 7856        );
 7857        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7858        assert_text_with_selections(
 7859            editor,
 7860            indoc! {r#"
 7861                use mod1::mod2::{mod3, mod4};
 7862
 7863                fn fn_1(param1: bool, param2: &str) {
 7864                    let var1 = "«hello worldˇ»";
 7865                }
 7866            "#},
 7867            cx,
 7868        );
 7869    });
 7870
 7871    // Test 4: Selection spanning across words
 7872    editor.update_in(cx, |editor, window, cx| {
 7873        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7874            s.select_display_ranges([
 7875                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 7876            ]);
 7877        });
 7878    });
 7879    editor.update_in(cx, |editor, window, cx| {
 7880        assert_text_with_selections(
 7881            editor,
 7882            indoc! {r#"
 7883                use mod1::mod2::{mod3, mod4};
 7884
 7885                fn fn_1(param1: bool, param2: &str) {
 7886                    let var1 = "hel«lo woˇ»rld";
 7887                }
 7888            "#},
 7889            cx,
 7890        );
 7891        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7892        assert_text_with_selections(
 7893            editor,
 7894            indoc! {r#"
 7895                use mod1::mod2::{mod3, mod4};
 7896
 7897                fn fn_1(param1: bool, param2: &str) {
 7898                    let var1 = "«ˇhello world»";
 7899                }
 7900            "#},
 7901            cx,
 7902        );
 7903    });
 7904
 7905    // Test 5: Expansion beyond string
 7906    editor.update_in(cx, |editor, window, cx| {
 7907        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7908        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7909        assert_text_with_selections(
 7910            editor,
 7911            indoc! {r#"
 7912                use mod1::mod2::{mod3, mod4};
 7913
 7914                fn fn_1(param1: bool, param2: &str) {
 7915                    «ˇlet var1 = "hello world";»
 7916                }
 7917            "#},
 7918            cx,
 7919        );
 7920    });
 7921}
 7922
 7923#[gpui::test]
 7924async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 7925    init_test(cx, |_| {});
 7926
 7927    let base_text = r#"
 7928        impl A {
 7929            // this is an uncommitted comment
 7930
 7931            fn b() {
 7932                c();
 7933            }
 7934
 7935            // this is another uncommitted comment
 7936
 7937            fn d() {
 7938                // e
 7939                // f
 7940            }
 7941        }
 7942
 7943        fn g() {
 7944            // h
 7945        }
 7946    "#
 7947    .unindent();
 7948
 7949    let text = r#"
 7950        ˇimpl A {
 7951
 7952            fn b() {
 7953                c();
 7954            }
 7955
 7956            fn d() {
 7957                // e
 7958                // f
 7959            }
 7960        }
 7961
 7962        fn g() {
 7963            // h
 7964        }
 7965    "#
 7966    .unindent();
 7967
 7968    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 7969    cx.set_state(&text);
 7970    cx.set_head_text(&base_text);
 7971    cx.update_editor(|editor, window, cx| {
 7972        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 7973    });
 7974
 7975    cx.assert_state_with_diff(
 7976        "
 7977        ˇimpl A {
 7978      -     // this is an uncommitted comment
 7979
 7980            fn b() {
 7981                c();
 7982            }
 7983
 7984      -     // this is another uncommitted comment
 7985      -
 7986            fn d() {
 7987                // e
 7988                // f
 7989            }
 7990        }
 7991
 7992        fn g() {
 7993            // h
 7994        }
 7995    "
 7996        .unindent(),
 7997    );
 7998
 7999    let expected_display_text = "
 8000        impl A {
 8001            // this is an uncommitted comment
 8002
 8003            fn b() {
 8004 8005            }
 8006
 8007            // this is another uncommitted comment
 8008
 8009            fn d() {
 8010 8011            }
 8012        }
 8013
 8014        fn g() {
 8015 8016        }
 8017        "
 8018    .unindent();
 8019
 8020    cx.update_editor(|editor, window, cx| {
 8021        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 8022        assert_eq!(editor.display_text(cx), expected_display_text);
 8023    });
 8024}
 8025
 8026#[gpui::test]
 8027async fn test_autoindent(cx: &mut TestAppContext) {
 8028    init_test(cx, |_| {});
 8029
 8030    let language = Arc::new(
 8031        Language::new(
 8032            LanguageConfig {
 8033                brackets: BracketPairConfig {
 8034                    pairs: vec![
 8035                        BracketPair {
 8036                            start: "{".to_string(),
 8037                            end: "}".to_string(),
 8038                            close: false,
 8039                            surround: false,
 8040                            newline: true,
 8041                        },
 8042                        BracketPair {
 8043                            start: "(".to_string(),
 8044                            end: ")".to_string(),
 8045                            close: false,
 8046                            surround: false,
 8047                            newline: true,
 8048                        },
 8049                    ],
 8050                    ..Default::default()
 8051                },
 8052                ..Default::default()
 8053            },
 8054            Some(tree_sitter_rust::LANGUAGE.into()),
 8055        )
 8056        .with_indents_query(
 8057            r#"
 8058                (_ "(" ")" @end) @indent
 8059                (_ "{" "}" @end) @indent
 8060            "#,
 8061        )
 8062        .unwrap(),
 8063    );
 8064
 8065    let text = "fn a() {}";
 8066
 8067    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8068    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8069    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8070    editor
 8071        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8072        .await;
 8073
 8074    editor.update_in(cx, |editor, window, cx| {
 8075        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8076            s.select_ranges([5..5, 8..8, 9..9])
 8077        });
 8078        editor.newline(&Newline, window, cx);
 8079        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 8080        assert_eq!(
 8081            editor.selections.ranges(cx),
 8082            &[
 8083                Point::new(1, 4)..Point::new(1, 4),
 8084                Point::new(3, 4)..Point::new(3, 4),
 8085                Point::new(5, 0)..Point::new(5, 0)
 8086            ]
 8087        );
 8088    });
 8089}
 8090
 8091#[gpui::test]
 8092async fn test_autoindent_selections(cx: &mut TestAppContext) {
 8093    init_test(cx, |_| {});
 8094
 8095    {
 8096        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 8097        cx.set_state(indoc! {"
 8098            impl A {
 8099
 8100                fn b() {}
 8101
 8102            «fn c() {
 8103
 8104            }ˇ»
 8105            }
 8106        "});
 8107
 8108        cx.update_editor(|editor, window, cx| {
 8109            editor.autoindent(&Default::default(), window, cx);
 8110        });
 8111
 8112        cx.assert_editor_state(indoc! {"
 8113            impl A {
 8114
 8115                fn b() {}
 8116
 8117                «fn c() {
 8118
 8119                }ˇ»
 8120            }
 8121        "});
 8122    }
 8123
 8124    {
 8125        let mut cx = EditorTestContext::new_multibuffer(
 8126            cx,
 8127            [indoc! { "
 8128                impl A {
 8129                «
 8130                // a
 8131                fn b(){}
 8132                »
 8133                «
 8134                    }
 8135                    fn c(){}
 8136                »
 8137            "}],
 8138        );
 8139
 8140        let buffer = cx.update_editor(|editor, _, cx| {
 8141            let buffer = editor.buffer().update(cx, |buffer, _| {
 8142                buffer.all_buffers().iter().next().unwrap().clone()
 8143            });
 8144            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 8145            buffer
 8146        });
 8147
 8148        cx.run_until_parked();
 8149        cx.update_editor(|editor, window, cx| {
 8150            editor.select_all(&Default::default(), window, cx);
 8151            editor.autoindent(&Default::default(), window, cx)
 8152        });
 8153        cx.run_until_parked();
 8154
 8155        cx.update(|_, cx| {
 8156            assert_eq!(
 8157                buffer.read(cx).text(),
 8158                indoc! { "
 8159                    impl A {
 8160
 8161                        // a
 8162                        fn b(){}
 8163
 8164
 8165                    }
 8166                    fn c(){}
 8167
 8168                " }
 8169            )
 8170        });
 8171    }
 8172}
 8173
 8174#[gpui::test]
 8175async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 8176    init_test(cx, |_| {});
 8177
 8178    let mut cx = EditorTestContext::new(cx).await;
 8179
 8180    let language = Arc::new(Language::new(
 8181        LanguageConfig {
 8182            brackets: BracketPairConfig {
 8183                pairs: vec![
 8184                    BracketPair {
 8185                        start: "{".to_string(),
 8186                        end: "}".to_string(),
 8187                        close: true,
 8188                        surround: true,
 8189                        newline: true,
 8190                    },
 8191                    BracketPair {
 8192                        start: "(".to_string(),
 8193                        end: ")".to_string(),
 8194                        close: true,
 8195                        surround: true,
 8196                        newline: true,
 8197                    },
 8198                    BracketPair {
 8199                        start: "/*".to_string(),
 8200                        end: " */".to_string(),
 8201                        close: true,
 8202                        surround: true,
 8203                        newline: true,
 8204                    },
 8205                    BracketPair {
 8206                        start: "[".to_string(),
 8207                        end: "]".to_string(),
 8208                        close: false,
 8209                        surround: false,
 8210                        newline: true,
 8211                    },
 8212                    BracketPair {
 8213                        start: "\"".to_string(),
 8214                        end: "\"".to_string(),
 8215                        close: true,
 8216                        surround: true,
 8217                        newline: false,
 8218                    },
 8219                    BracketPair {
 8220                        start: "<".to_string(),
 8221                        end: ">".to_string(),
 8222                        close: false,
 8223                        surround: true,
 8224                        newline: true,
 8225                    },
 8226                ],
 8227                ..Default::default()
 8228            },
 8229            autoclose_before: "})]".to_string(),
 8230            ..Default::default()
 8231        },
 8232        Some(tree_sitter_rust::LANGUAGE.into()),
 8233    ));
 8234
 8235    cx.language_registry().add(language.clone());
 8236    cx.update_buffer(|buffer, cx| {
 8237        buffer.set_language(Some(language), cx);
 8238    });
 8239
 8240    cx.set_state(
 8241        &r#"
 8242            🏀ˇ
 8243            εˇ
 8244            ❤️ˇ
 8245        "#
 8246        .unindent(),
 8247    );
 8248
 8249    // autoclose multiple nested brackets at multiple cursors
 8250    cx.update_editor(|editor, window, cx| {
 8251        editor.handle_input("{", window, cx);
 8252        editor.handle_input("{", window, cx);
 8253        editor.handle_input("{", window, cx);
 8254    });
 8255    cx.assert_editor_state(
 8256        &"
 8257            🏀{{{ˇ}}}
 8258            ε{{{ˇ}}}
 8259            ❤️{{{ˇ}}}
 8260        "
 8261        .unindent(),
 8262    );
 8263
 8264    // insert a different closing bracket
 8265    cx.update_editor(|editor, window, cx| {
 8266        editor.handle_input(")", window, cx);
 8267    });
 8268    cx.assert_editor_state(
 8269        &"
 8270            🏀{{{)ˇ}}}
 8271            ε{{{)ˇ}}}
 8272            ❤️{{{)ˇ}}}
 8273        "
 8274        .unindent(),
 8275    );
 8276
 8277    // skip over the auto-closed brackets when typing a closing bracket
 8278    cx.update_editor(|editor, window, cx| {
 8279        editor.move_right(&MoveRight, window, cx);
 8280        editor.handle_input("}", window, cx);
 8281        editor.handle_input("}", window, cx);
 8282        editor.handle_input("}", window, cx);
 8283    });
 8284    cx.assert_editor_state(
 8285        &"
 8286            🏀{{{)}}}}ˇ
 8287            ε{{{)}}}}ˇ
 8288            ❤️{{{)}}}}ˇ
 8289        "
 8290        .unindent(),
 8291    );
 8292
 8293    // autoclose multi-character pairs
 8294    cx.set_state(
 8295        &"
 8296            ˇ
 8297            ˇ
 8298        "
 8299        .unindent(),
 8300    );
 8301    cx.update_editor(|editor, window, cx| {
 8302        editor.handle_input("/", window, cx);
 8303        editor.handle_input("*", window, cx);
 8304    });
 8305    cx.assert_editor_state(
 8306        &"
 8307            /*ˇ */
 8308            /*ˇ */
 8309        "
 8310        .unindent(),
 8311    );
 8312
 8313    // one cursor autocloses a multi-character pair, one cursor
 8314    // does not autoclose.
 8315    cx.set_state(
 8316        &"
 8317 8318            ˇ
 8319        "
 8320        .unindent(),
 8321    );
 8322    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 8323    cx.assert_editor_state(
 8324        &"
 8325            /*ˇ */
 8326 8327        "
 8328        .unindent(),
 8329    );
 8330
 8331    // Don't autoclose if the next character isn't whitespace and isn't
 8332    // listed in the language's "autoclose_before" section.
 8333    cx.set_state("ˇa b");
 8334    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8335    cx.assert_editor_state("{ˇa b");
 8336
 8337    // Don't autoclose if `close` is false for the bracket pair
 8338    cx.set_state("ˇ");
 8339    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 8340    cx.assert_editor_state("");
 8341
 8342    // Surround with brackets if text is selected
 8343    cx.set_state("«aˇ» b");
 8344    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8345    cx.assert_editor_state("{«aˇ»} b");
 8346
 8347    // Autoclose when not immediately after a word character
 8348    cx.set_state("a ˇ");
 8349    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8350    cx.assert_editor_state("a \"ˇ\"");
 8351
 8352    // Autoclose pair where the start and end characters are the same
 8353    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8354    cx.assert_editor_state("a \"\"ˇ");
 8355
 8356    // Don't autoclose when immediately after a word character
 8357    cx.set_state("");
 8358    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8359    cx.assert_editor_state("a\"ˇ");
 8360
 8361    // Do autoclose when after a non-word character
 8362    cx.set_state("");
 8363    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8364    cx.assert_editor_state("{\"ˇ\"");
 8365
 8366    // Non identical pairs autoclose regardless of preceding character
 8367    cx.set_state("");
 8368    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8369    cx.assert_editor_state("a{ˇ}");
 8370
 8371    // Don't autoclose pair if autoclose is disabled
 8372    cx.set_state("ˇ");
 8373    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 8374    cx.assert_editor_state("");
 8375
 8376    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 8377    cx.set_state("«aˇ» b");
 8378    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 8379    cx.assert_editor_state("<«aˇ»> b");
 8380}
 8381
 8382#[gpui::test]
 8383async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 8384    init_test(cx, |settings| {
 8385        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 8386    });
 8387
 8388    let mut cx = EditorTestContext::new(cx).await;
 8389
 8390    let language = Arc::new(Language::new(
 8391        LanguageConfig {
 8392            brackets: BracketPairConfig {
 8393                pairs: vec![
 8394                    BracketPair {
 8395                        start: "{".to_string(),
 8396                        end: "}".to_string(),
 8397                        close: true,
 8398                        surround: true,
 8399                        newline: true,
 8400                    },
 8401                    BracketPair {
 8402                        start: "(".to_string(),
 8403                        end: ")".to_string(),
 8404                        close: true,
 8405                        surround: true,
 8406                        newline: true,
 8407                    },
 8408                    BracketPair {
 8409                        start: "[".to_string(),
 8410                        end: "]".to_string(),
 8411                        close: false,
 8412                        surround: false,
 8413                        newline: true,
 8414                    },
 8415                ],
 8416                ..Default::default()
 8417            },
 8418            autoclose_before: "})]".to_string(),
 8419            ..Default::default()
 8420        },
 8421        Some(tree_sitter_rust::LANGUAGE.into()),
 8422    ));
 8423
 8424    cx.language_registry().add(language.clone());
 8425    cx.update_buffer(|buffer, cx| {
 8426        buffer.set_language(Some(language), cx);
 8427    });
 8428
 8429    cx.set_state(
 8430        &"
 8431            ˇ
 8432            ˇ
 8433            ˇ
 8434        "
 8435        .unindent(),
 8436    );
 8437
 8438    // ensure only matching closing brackets are skipped over
 8439    cx.update_editor(|editor, window, cx| {
 8440        editor.handle_input("}", window, cx);
 8441        editor.move_left(&MoveLeft, window, cx);
 8442        editor.handle_input(")", window, cx);
 8443        editor.move_left(&MoveLeft, window, cx);
 8444    });
 8445    cx.assert_editor_state(
 8446        &"
 8447            ˇ)}
 8448            ˇ)}
 8449            ˇ)}
 8450        "
 8451        .unindent(),
 8452    );
 8453
 8454    // skip-over closing brackets at multiple cursors
 8455    cx.update_editor(|editor, window, cx| {
 8456        editor.handle_input(")", window, cx);
 8457        editor.handle_input("}", window, cx);
 8458    });
 8459    cx.assert_editor_state(
 8460        &"
 8461            )}ˇ
 8462            )}ˇ
 8463            )}ˇ
 8464        "
 8465        .unindent(),
 8466    );
 8467
 8468    // ignore non-close brackets
 8469    cx.update_editor(|editor, window, cx| {
 8470        editor.handle_input("]", window, cx);
 8471        editor.move_left(&MoveLeft, window, cx);
 8472        editor.handle_input("]", window, cx);
 8473    });
 8474    cx.assert_editor_state(
 8475        &"
 8476            )}]ˇ]
 8477            )}]ˇ]
 8478            )}]ˇ]
 8479        "
 8480        .unindent(),
 8481    );
 8482}
 8483
 8484#[gpui::test]
 8485async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
 8486    init_test(cx, |_| {});
 8487
 8488    let mut cx = EditorTestContext::new(cx).await;
 8489
 8490    let html_language = Arc::new(
 8491        Language::new(
 8492            LanguageConfig {
 8493                name: "HTML".into(),
 8494                brackets: BracketPairConfig {
 8495                    pairs: vec![
 8496                        BracketPair {
 8497                            start: "<".into(),
 8498                            end: ">".into(),
 8499                            close: true,
 8500                            ..Default::default()
 8501                        },
 8502                        BracketPair {
 8503                            start: "{".into(),
 8504                            end: "}".into(),
 8505                            close: true,
 8506                            ..Default::default()
 8507                        },
 8508                        BracketPair {
 8509                            start: "(".into(),
 8510                            end: ")".into(),
 8511                            close: true,
 8512                            ..Default::default()
 8513                        },
 8514                    ],
 8515                    ..Default::default()
 8516                },
 8517                autoclose_before: "})]>".into(),
 8518                ..Default::default()
 8519            },
 8520            Some(tree_sitter_html::LANGUAGE.into()),
 8521        )
 8522        .with_injection_query(
 8523            r#"
 8524            (script_element
 8525                (raw_text) @injection.content
 8526                (#set! injection.language "javascript"))
 8527            "#,
 8528        )
 8529        .unwrap(),
 8530    );
 8531
 8532    let javascript_language = Arc::new(Language::new(
 8533        LanguageConfig {
 8534            name: "JavaScript".into(),
 8535            brackets: BracketPairConfig {
 8536                pairs: vec![
 8537                    BracketPair {
 8538                        start: "/*".into(),
 8539                        end: " */".into(),
 8540                        close: true,
 8541                        ..Default::default()
 8542                    },
 8543                    BracketPair {
 8544                        start: "{".into(),
 8545                        end: "}".into(),
 8546                        close: true,
 8547                        ..Default::default()
 8548                    },
 8549                    BracketPair {
 8550                        start: "(".into(),
 8551                        end: ")".into(),
 8552                        close: true,
 8553                        ..Default::default()
 8554                    },
 8555                ],
 8556                ..Default::default()
 8557            },
 8558            autoclose_before: "})]>".into(),
 8559            ..Default::default()
 8560        },
 8561        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 8562    ));
 8563
 8564    cx.language_registry().add(html_language.clone());
 8565    cx.language_registry().add(javascript_language.clone());
 8566
 8567    cx.update_buffer(|buffer, cx| {
 8568        buffer.set_language(Some(html_language), cx);
 8569    });
 8570
 8571    cx.set_state(
 8572        &r#"
 8573            <body>ˇ
 8574                <script>
 8575                    var x = 1;ˇ
 8576                </script>
 8577            </body>ˇ
 8578        "#
 8579        .unindent(),
 8580    );
 8581
 8582    // Precondition: different languages are active at different locations.
 8583    cx.update_editor(|editor, window, cx| {
 8584        let snapshot = editor.snapshot(window, cx);
 8585        let cursors = editor.selections.ranges::<usize>(cx);
 8586        let languages = cursors
 8587            .iter()
 8588            .map(|c| snapshot.language_at(c.start).unwrap().name())
 8589            .collect::<Vec<_>>();
 8590        assert_eq!(
 8591            languages,
 8592            &["HTML".into(), "JavaScript".into(), "HTML".into()]
 8593        );
 8594    });
 8595
 8596    // Angle brackets autoclose in HTML, but not JavaScript.
 8597    cx.update_editor(|editor, window, cx| {
 8598        editor.handle_input("<", window, cx);
 8599        editor.handle_input("a", window, cx);
 8600    });
 8601    cx.assert_editor_state(
 8602        &r#"
 8603            <body><aˇ>
 8604                <script>
 8605                    var x = 1;<aˇ
 8606                </script>
 8607            </body><aˇ>
 8608        "#
 8609        .unindent(),
 8610    );
 8611
 8612    // Curly braces and parens autoclose in both HTML and JavaScript.
 8613    cx.update_editor(|editor, window, cx| {
 8614        editor.handle_input(" b=", window, cx);
 8615        editor.handle_input("{", window, cx);
 8616        editor.handle_input("c", window, cx);
 8617        editor.handle_input("(", window, cx);
 8618    });
 8619    cx.assert_editor_state(
 8620        &r#"
 8621            <body><a b={c(ˇ)}>
 8622                <script>
 8623                    var x = 1;<a b={c(ˇ)}
 8624                </script>
 8625            </body><a b={c(ˇ)}>
 8626        "#
 8627        .unindent(),
 8628    );
 8629
 8630    // Brackets that were already autoclosed are skipped.
 8631    cx.update_editor(|editor, window, cx| {
 8632        editor.handle_input(")", window, cx);
 8633        editor.handle_input("d", window, cx);
 8634        editor.handle_input("}", window, cx);
 8635    });
 8636    cx.assert_editor_state(
 8637        &r#"
 8638            <body><a b={c()d}ˇ>
 8639                <script>
 8640                    var x = 1;<a b={c()d}ˇ
 8641                </script>
 8642            </body><a b={c()d}ˇ>
 8643        "#
 8644        .unindent(),
 8645    );
 8646    cx.update_editor(|editor, window, cx| {
 8647        editor.handle_input(">", window, cx);
 8648    });
 8649    cx.assert_editor_state(
 8650        &r#"
 8651            <body><a b={c()d}>ˇ
 8652                <script>
 8653                    var x = 1;<a b={c()d}>ˇ
 8654                </script>
 8655            </body><a b={c()d}>ˇ
 8656        "#
 8657        .unindent(),
 8658    );
 8659
 8660    // Reset
 8661    cx.set_state(
 8662        &r#"
 8663            <body>ˇ
 8664                <script>
 8665                    var x = 1;ˇ
 8666                </script>
 8667            </body>ˇ
 8668        "#
 8669        .unindent(),
 8670    );
 8671
 8672    cx.update_editor(|editor, window, cx| {
 8673        editor.handle_input("<", window, cx);
 8674    });
 8675    cx.assert_editor_state(
 8676        &r#"
 8677            <body><ˇ>
 8678                <script>
 8679                    var x = 1;<ˇ
 8680                </script>
 8681            </body><ˇ>
 8682        "#
 8683        .unindent(),
 8684    );
 8685
 8686    // When backspacing, the closing angle brackets are removed.
 8687    cx.update_editor(|editor, window, cx| {
 8688        editor.backspace(&Backspace, window, cx);
 8689    });
 8690    cx.assert_editor_state(
 8691        &r#"
 8692            <body>ˇ
 8693                <script>
 8694                    var x = 1;ˇ
 8695                </script>
 8696            </body>ˇ
 8697        "#
 8698        .unindent(),
 8699    );
 8700
 8701    // Block comments autoclose in JavaScript, but not HTML.
 8702    cx.update_editor(|editor, window, cx| {
 8703        editor.handle_input("/", window, cx);
 8704        editor.handle_input("*", window, cx);
 8705    });
 8706    cx.assert_editor_state(
 8707        &r#"
 8708            <body>/*ˇ
 8709                <script>
 8710                    var x = 1;/*ˇ */
 8711                </script>
 8712            </body>/*ˇ
 8713        "#
 8714        .unindent(),
 8715    );
 8716}
 8717
 8718#[gpui::test]
 8719async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
 8720    init_test(cx, |_| {});
 8721
 8722    let mut cx = EditorTestContext::new(cx).await;
 8723
 8724    let rust_language = Arc::new(
 8725        Language::new(
 8726            LanguageConfig {
 8727                name: "Rust".into(),
 8728                brackets: serde_json::from_value(json!([
 8729                    { "start": "{", "end": "}", "close": true, "newline": true },
 8730                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
 8731                ]))
 8732                .unwrap(),
 8733                autoclose_before: "})]>".into(),
 8734                ..Default::default()
 8735            },
 8736            Some(tree_sitter_rust::LANGUAGE.into()),
 8737        )
 8738        .with_override_query("(string_literal) @string")
 8739        .unwrap(),
 8740    );
 8741
 8742    cx.language_registry().add(rust_language.clone());
 8743    cx.update_buffer(|buffer, cx| {
 8744        buffer.set_language(Some(rust_language), cx);
 8745    });
 8746
 8747    cx.set_state(
 8748        &r#"
 8749            let x = ˇ
 8750        "#
 8751        .unindent(),
 8752    );
 8753
 8754    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
 8755    cx.update_editor(|editor, window, cx| {
 8756        editor.handle_input("\"", window, cx);
 8757    });
 8758    cx.assert_editor_state(
 8759        &r#"
 8760            let x = "ˇ"
 8761        "#
 8762        .unindent(),
 8763    );
 8764
 8765    // Inserting another quotation mark. The cursor moves across the existing
 8766    // automatically-inserted quotation mark.
 8767    cx.update_editor(|editor, window, cx| {
 8768        editor.handle_input("\"", window, cx);
 8769    });
 8770    cx.assert_editor_state(
 8771        &r#"
 8772            let x = ""ˇ
 8773        "#
 8774        .unindent(),
 8775    );
 8776
 8777    // Reset
 8778    cx.set_state(
 8779        &r#"
 8780            let x = ˇ
 8781        "#
 8782        .unindent(),
 8783    );
 8784
 8785    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
 8786    cx.update_editor(|editor, window, cx| {
 8787        editor.handle_input("\"", window, cx);
 8788        editor.handle_input(" ", window, cx);
 8789        editor.move_left(&Default::default(), window, cx);
 8790        editor.handle_input("\\", window, cx);
 8791        editor.handle_input("\"", window, cx);
 8792    });
 8793    cx.assert_editor_state(
 8794        &r#"
 8795            let x = "\"ˇ "
 8796        "#
 8797        .unindent(),
 8798    );
 8799
 8800    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
 8801    // mark. Nothing is inserted.
 8802    cx.update_editor(|editor, window, cx| {
 8803        editor.move_right(&Default::default(), window, cx);
 8804        editor.handle_input("\"", window, cx);
 8805    });
 8806    cx.assert_editor_state(
 8807        &r#"
 8808            let x = "\" "ˇ
 8809        "#
 8810        .unindent(),
 8811    );
 8812}
 8813
 8814#[gpui::test]
 8815async fn test_surround_with_pair(cx: &mut TestAppContext) {
 8816    init_test(cx, |_| {});
 8817
 8818    let language = Arc::new(Language::new(
 8819        LanguageConfig {
 8820            brackets: BracketPairConfig {
 8821                pairs: vec![
 8822                    BracketPair {
 8823                        start: "{".to_string(),
 8824                        end: "}".to_string(),
 8825                        close: true,
 8826                        surround: true,
 8827                        newline: true,
 8828                    },
 8829                    BracketPair {
 8830                        start: "/* ".to_string(),
 8831                        end: "*/".to_string(),
 8832                        close: true,
 8833                        surround: true,
 8834                        ..Default::default()
 8835                    },
 8836                ],
 8837                ..Default::default()
 8838            },
 8839            ..Default::default()
 8840        },
 8841        Some(tree_sitter_rust::LANGUAGE.into()),
 8842    ));
 8843
 8844    let text = r#"
 8845        a
 8846        b
 8847        c
 8848    "#
 8849    .unindent();
 8850
 8851    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8852    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8853    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8854    editor
 8855        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8856        .await;
 8857
 8858    editor.update_in(cx, |editor, window, cx| {
 8859        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8860            s.select_display_ranges([
 8861                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8862                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8863                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
 8864            ])
 8865        });
 8866
 8867        editor.handle_input("{", window, cx);
 8868        editor.handle_input("{", window, cx);
 8869        editor.handle_input("{", window, cx);
 8870        assert_eq!(
 8871            editor.text(cx),
 8872            "
 8873                {{{a}}}
 8874                {{{b}}}
 8875                {{{c}}}
 8876            "
 8877            .unindent()
 8878        );
 8879        assert_eq!(
 8880            editor.selections.display_ranges(cx),
 8881            [
 8882                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
 8883                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
 8884                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
 8885            ]
 8886        );
 8887
 8888        editor.undo(&Undo, window, cx);
 8889        editor.undo(&Undo, window, cx);
 8890        editor.undo(&Undo, window, cx);
 8891        assert_eq!(
 8892            editor.text(cx),
 8893            "
 8894                a
 8895                b
 8896                c
 8897            "
 8898            .unindent()
 8899        );
 8900        assert_eq!(
 8901            editor.selections.display_ranges(cx),
 8902            [
 8903                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8904                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8905                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 8906            ]
 8907        );
 8908
 8909        // Ensure inserting the first character of a multi-byte bracket pair
 8910        // doesn't surround the selections with the bracket.
 8911        editor.handle_input("/", window, cx);
 8912        assert_eq!(
 8913            editor.text(cx),
 8914            "
 8915                /
 8916                /
 8917                /
 8918            "
 8919            .unindent()
 8920        );
 8921        assert_eq!(
 8922            editor.selections.display_ranges(cx),
 8923            [
 8924                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 8925                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 8926                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 8927            ]
 8928        );
 8929
 8930        editor.undo(&Undo, window, cx);
 8931        assert_eq!(
 8932            editor.text(cx),
 8933            "
 8934                a
 8935                b
 8936                c
 8937            "
 8938            .unindent()
 8939        );
 8940        assert_eq!(
 8941            editor.selections.display_ranges(cx),
 8942            [
 8943                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8944                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8945                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 8946            ]
 8947        );
 8948
 8949        // Ensure inserting the last character of a multi-byte bracket pair
 8950        // doesn't surround the selections with the bracket.
 8951        editor.handle_input("*", window, cx);
 8952        assert_eq!(
 8953            editor.text(cx),
 8954            "
 8955                *
 8956                *
 8957                *
 8958            "
 8959            .unindent()
 8960        );
 8961        assert_eq!(
 8962            editor.selections.display_ranges(cx),
 8963            [
 8964                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 8965                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 8966                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 8967            ]
 8968        );
 8969    });
 8970}
 8971
 8972#[gpui::test]
 8973async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
 8974    init_test(cx, |_| {});
 8975
 8976    let language = Arc::new(Language::new(
 8977        LanguageConfig {
 8978            brackets: BracketPairConfig {
 8979                pairs: vec![BracketPair {
 8980                    start: "{".to_string(),
 8981                    end: "}".to_string(),
 8982                    close: true,
 8983                    surround: true,
 8984                    newline: true,
 8985                }],
 8986                ..Default::default()
 8987            },
 8988            autoclose_before: "}".to_string(),
 8989            ..Default::default()
 8990        },
 8991        Some(tree_sitter_rust::LANGUAGE.into()),
 8992    ));
 8993
 8994    let text = r#"
 8995        a
 8996        b
 8997        c
 8998    "#
 8999    .unindent();
 9000
 9001    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9002    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9003    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9004    editor
 9005        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9006        .await;
 9007
 9008    editor.update_in(cx, |editor, window, cx| {
 9009        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9010            s.select_ranges([
 9011                Point::new(0, 1)..Point::new(0, 1),
 9012                Point::new(1, 1)..Point::new(1, 1),
 9013                Point::new(2, 1)..Point::new(2, 1),
 9014            ])
 9015        });
 9016
 9017        editor.handle_input("{", window, cx);
 9018        editor.handle_input("{", window, cx);
 9019        editor.handle_input("_", window, cx);
 9020        assert_eq!(
 9021            editor.text(cx),
 9022            "
 9023                a{{_}}
 9024                b{{_}}
 9025                c{{_}}
 9026            "
 9027            .unindent()
 9028        );
 9029        assert_eq!(
 9030            editor.selections.ranges::<Point>(cx),
 9031            [
 9032                Point::new(0, 4)..Point::new(0, 4),
 9033                Point::new(1, 4)..Point::new(1, 4),
 9034                Point::new(2, 4)..Point::new(2, 4)
 9035            ]
 9036        );
 9037
 9038        editor.backspace(&Default::default(), window, cx);
 9039        editor.backspace(&Default::default(), window, cx);
 9040        assert_eq!(
 9041            editor.text(cx),
 9042            "
 9043                a{}
 9044                b{}
 9045                c{}
 9046            "
 9047            .unindent()
 9048        );
 9049        assert_eq!(
 9050            editor.selections.ranges::<Point>(cx),
 9051            [
 9052                Point::new(0, 2)..Point::new(0, 2),
 9053                Point::new(1, 2)..Point::new(1, 2),
 9054                Point::new(2, 2)..Point::new(2, 2)
 9055            ]
 9056        );
 9057
 9058        editor.delete_to_previous_word_start(&Default::default(), window, cx);
 9059        assert_eq!(
 9060            editor.text(cx),
 9061            "
 9062                a
 9063                b
 9064                c
 9065            "
 9066            .unindent()
 9067        );
 9068        assert_eq!(
 9069            editor.selections.ranges::<Point>(cx),
 9070            [
 9071                Point::new(0, 1)..Point::new(0, 1),
 9072                Point::new(1, 1)..Point::new(1, 1),
 9073                Point::new(2, 1)..Point::new(2, 1)
 9074            ]
 9075        );
 9076    });
 9077}
 9078
 9079#[gpui::test]
 9080async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
 9081    init_test(cx, |settings| {
 9082        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 9083    });
 9084
 9085    let mut cx = EditorTestContext::new(cx).await;
 9086
 9087    let language = Arc::new(Language::new(
 9088        LanguageConfig {
 9089            brackets: BracketPairConfig {
 9090                pairs: vec![
 9091                    BracketPair {
 9092                        start: "{".to_string(),
 9093                        end: "}".to_string(),
 9094                        close: true,
 9095                        surround: true,
 9096                        newline: true,
 9097                    },
 9098                    BracketPair {
 9099                        start: "(".to_string(),
 9100                        end: ")".to_string(),
 9101                        close: true,
 9102                        surround: true,
 9103                        newline: true,
 9104                    },
 9105                    BracketPair {
 9106                        start: "[".to_string(),
 9107                        end: "]".to_string(),
 9108                        close: false,
 9109                        surround: true,
 9110                        newline: true,
 9111                    },
 9112                ],
 9113                ..Default::default()
 9114            },
 9115            autoclose_before: "})]".to_string(),
 9116            ..Default::default()
 9117        },
 9118        Some(tree_sitter_rust::LANGUAGE.into()),
 9119    ));
 9120
 9121    cx.language_registry().add(language.clone());
 9122    cx.update_buffer(|buffer, cx| {
 9123        buffer.set_language(Some(language), cx);
 9124    });
 9125
 9126    cx.set_state(
 9127        &"
 9128            {(ˇ)}
 9129            [[ˇ]]
 9130            {(ˇ)}
 9131        "
 9132        .unindent(),
 9133    );
 9134
 9135    cx.update_editor(|editor, window, cx| {
 9136        editor.backspace(&Default::default(), window, cx);
 9137        editor.backspace(&Default::default(), window, cx);
 9138    });
 9139
 9140    cx.assert_editor_state(
 9141        &"
 9142            ˇ
 9143            ˇ]]
 9144            ˇ
 9145        "
 9146        .unindent(),
 9147    );
 9148
 9149    cx.update_editor(|editor, window, cx| {
 9150        editor.handle_input("{", window, cx);
 9151        editor.handle_input("{", window, cx);
 9152        editor.move_right(&MoveRight, window, cx);
 9153        editor.move_right(&MoveRight, window, cx);
 9154        editor.move_left(&MoveLeft, window, cx);
 9155        editor.move_left(&MoveLeft, window, cx);
 9156        editor.backspace(&Default::default(), window, cx);
 9157    });
 9158
 9159    cx.assert_editor_state(
 9160        &"
 9161            {ˇ}
 9162            {ˇ}]]
 9163            {ˇ}
 9164        "
 9165        .unindent(),
 9166    );
 9167
 9168    cx.update_editor(|editor, window, cx| {
 9169        editor.backspace(&Default::default(), window, cx);
 9170    });
 9171
 9172    cx.assert_editor_state(
 9173        &"
 9174            ˇ
 9175            ˇ]]
 9176            ˇ
 9177        "
 9178        .unindent(),
 9179    );
 9180}
 9181
 9182#[gpui::test]
 9183async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
 9184    init_test(cx, |_| {});
 9185
 9186    let language = Arc::new(Language::new(
 9187        LanguageConfig::default(),
 9188        Some(tree_sitter_rust::LANGUAGE.into()),
 9189    ));
 9190
 9191    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
 9192    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9193    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9194    editor
 9195        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9196        .await;
 9197
 9198    editor.update_in(cx, |editor, window, cx| {
 9199        editor.set_auto_replace_emoji_shortcode(true);
 9200
 9201        editor.handle_input("Hello ", window, cx);
 9202        editor.handle_input(":wave", window, cx);
 9203        assert_eq!(editor.text(cx), "Hello :wave".unindent());
 9204
 9205        editor.handle_input(":", window, cx);
 9206        assert_eq!(editor.text(cx), "Hello 👋".unindent());
 9207
 9208        editor.handle_input(" :smile", window, cx);
 9209        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
 9210
 9211        editor.handle_input(":", window, cx);
 9212        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
 9213
 9214        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
 9215        editor.handle_input(":wave", window, cx);
 9216        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
 9217
 9218        editor.handle_input(":", window, cx);
 9219        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
 9220
 9221        editor.handle_input(":1", window, cx);
 9222        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
 9223
 9224        editor.handle_input(":", window, cx);
 9225        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
 9226
 9227        // Ensure shortcode does not get replaced when it is part of a word
 9228        editor.handle_input(" Test:wave", window, cx);
 9229        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
 9230
 9231        editor.handle_input(":", window, cx);
 9232        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
 9233
 9234        editor.set_auto_replace_emoji_shortcode(false);
 9235
 9236        // Ensure shortcode does not get replaced when auto replace is off
 9237        editor.handle_input(" :wave", window, cx);
 9238        assert_eq!(
 9239            editor.text(cx),
 9240            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
 9241        );
 9242
 9243        editor.handle_input(":", window, cx);
 9244        assert_eq!(
 9245            editor.text(cx),
 9246            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
 9247        );
 9248    });
 9249}
 9250
 9251#[gpui::test]
 9252async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
 9253    init_test(cx, |_| {});
 9254
 9255    let (text, insertion_ranges) = marked_text_ranges(
 9256        indoc! {"
 9257            ˇ
 9258        "},
 9259        false,
 9260    );
 9261
 9262    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
 9263    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9264
 9265    _ = editor.update_in(cx, |editor, window, cx| {
 9266        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
 9267
 9268        editor
 9269            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9270            .unwrap();
 9271
 9272        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
 9273            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
 9274            assert_eq!(editor.text(cx), expected_text);
 9275            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
 9276        }
 9277
 9278        assert(
 9279            editor,
 9280            cx,
 9281            indoc! {"
 9282            type «» =•
 9283            "},
 9284        );
 9285
 9286        assert!(editor.context_menu_visible(), "There should be a matches");
 9287    });
 9288}
 9289
 9290#[gpui::test]
 9291async fn test_snippets(cx: &mut TestAppContext) {
 9292    init_test(cx, |_| {});
 9293
 9294    let mut cx = EditorTestContext::new(cx).await;
 9295
 9296    cx.set_state(indoc! {"
 9297        a.ˇ b
 9298        a.ˇ b
 9299        a.ˇ b
 9300    "});
 9301
 9302    cx.update_editor(|editor, window, cx| {
 9303        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
 9304        let insertion_ranges = editor
 9305            .selections
 9306            .all(cx)
 9307            .iter()
 9308            .map(|s| s.range().clone())
 9309            .collect::<Vec<_>>();
 9310        editor
 9311            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9312            .unwrap();
 9313    });
 9314
 9315    cx.assert_editor_state(indoc! {"
 9316        a.f(«oneˇ», two, «threeˇ») b
 9317        a.f(«oneˇ», two, «threeˇ») b
 9318        a.f(«oneˇ», two, «threeˇ») b
 9319    "});
 9320
 9321    // Can't move earlier than the first tab stop
 9322    cx.update_editor(|editor, window, cx| {
 9323        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 9324    });
 9325    cx.assert_editor_state(indoc! {"
 9326        a.f(«oneˇ», two, «threeˇ») b
 9327        a.f(«oneˇ», two, «threeˇ») b
 9328        a.f(«oneˇ», two, «threeˇ») b
 9329    "});
 9330
 9331    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9332    cx.assert_editor_state(indoc! {"
 9333        a.f(one, «twoˇ», three) b
 9334        a.f(one, «twoˇ», three) b
 9335        a.f(one, «twoˇ», three) b
 9336    "});
 9337
 9338    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
 9339    cx.assert_editor_state(indoc! {"
 9340        a.f(«oneˇ», two, «threeˇ») b
 9341        a.f(«oneˇ», two, «threeˇ») b
 9342        a.f(«oneˇ», two, «threeˇ») b
 9343    "});
 9344
 9345    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9346    cx.assert_editor_state(indoc! {"
 9347        a.f(one, «twoˇ», three) b
 9348        a.f(one, «twoˇ», three) b
 9349        a.f(one, «twoˇ», three) b
 9350    "});
 9351    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9352    cx.assert_editor_state(indoc! {"
 9353        a.f(one, two, three)ˇ b
 9354        a.f(one, two, three)ˇ b
 9355        a.f(one, two, three)ˇ b
 9356    "});
 9357
 9358    // As soon as the last tab stop is reached, snippet state is gone
 9359    cx.update_editor(|editor, window, cx| {
 9360        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 9361    });
 9362    cx.assert_editor_state(indoc! {"
 9363        a.f(one, two, three)ˇ b
 9364        a.f(one, two, three)ˇ b
 9365        a.f(one, two, three)ˇ b
 9366    "});
 9367}
 9368
 9369#[gpui::test]
 9370async fn test_snippet_indentation(cx: &mut TestAppContext) {
 9371    init_test(cx, |_| {});
 9372
 9373    let mut cx = EditorTestContext::new(cx).await;
 9374
 9375    cx.update_editor(|editor, window, cx| {
 9376        let snippet = Snippet::parse(indoc! {"
 9377            /*
 9378             * Multiline comment with leading indentation
 9379             *
 9380             * $1
 9381             */
 9382            $0"})
 9383        .unwrap();
 9384        let insertion_ranges = editor
 9385            .selections
 9386            .all(cx)
 9387            .iter()
 9388            .map(|s| s.range().clone())
 9389            .collect::<Vec<_>>();
 9390        editor
 9391            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9392            .unwrap();
 9393    });
 9394
 9395    cx.assert_editor_state(indoc! {"
 9396        /*
 9397         * Multiline comment with leading indentation
 9398         *
 9399         * ˇ
 9400         */
 9401    "});
 9402
 9403    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9404    cx.assert_editor_state(indoc! {"
 9405        /*
 9406         * Multiline comment with leading indentation
 9407         *
 9408         *•
 9409         */
 9410        ˇ"});
 9411}
 9412
 9413#[gpui::test]
 9414async fn test_document_format_during_save(cx: &mut TestAppContext) {
 9415    init_test(cx, |_| {});
 9416
 9417    let fs = FakeFs::new(cx.executor());
 9418    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9419
 9420    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
 9421
 9422    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9423    language_registry.add(rust_lang());
 9424    let mut fake_servers = language_registry.register_fake_lsp(
 9425        "Rust",
 9426        FakeLspAdapter {
 9427            capabilities: lsp::ServerCapabilities {
 9428                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9429                ..Default::default()
 9430            },
 9431            ..Default::default()
 9432        },
 9433    );
 9434
 9435    let buffer = project
 9436        .update(cx, |project, cx| {
 9437            project.open_local_buffer(path!("/file.rs"), cx)
 9438        })
 9439        .await
 9440        .unwrap();
 9441
 9442    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9443    let (editor, cx) = cx.add_window_view(|window, cx| {
 9444        build_editor_with_project(project.clone(), buffer, window, cx)
 9445    });
 9446    editor.update_in(cx, |editor, window, cx| {
 9447        editor.set_text("one\ntwo\nthree\n", window, cx)
 9448    });
 9449    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9450
 9451    cx.executor().start_waiting();
 9452    let fake_server = fake_servers.next().await.unwrap();
 9453
 9454    {
 9455        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9456            move |params, _| async move {
 9457                assert_eq!(
 9458                    params.text_document.uri,
 9459                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9460                );
 9461                assert_eq!(params.options.tab_size, 4);
 9462                Ok(Some(vec![lsp::TextEdit::new(
 9463                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9464                    ", ".to_string(),
 9465                )]))
 9466            },
 9467        );
 9468        let save = editor
 9469            .update_in(cx, |editor, window, cx| {
 9470                editor.save(
 9471                    SaveOptions {
 9472                        format: true,
 9473                        autosave: false,
 9474                    },
 9475                    project.clone(),
 9476                    window,
 9477                    cx,
 9478                )
 9479            })
 9480            .unwrap();
 9481        cx.executor().start_waiting();
 9482        save.await;
 9483
 9484        assert_eq!(
 9485            editor.update(cx, |editor, cx| editor.text(cx)),
 9486            "one, two\nthree\n"
 9487        );
 9488        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9489    }
 9490
 9491    {
 9492        editor.update_in(cx, |editor, window, cx| {
 9493            editor.set_text("one\ntwo\nthree\n", window, cx)
 9494        });
 9495        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9496
 9497        // Ensure we can still save even if formatting hangs.
 9498        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9499            move |params, _| async move {
 9500                assert_eq!(
 9501                    params.text_document.uri,
 9502                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9503                );
 9504                futures::future::pending::<()>().await;
 9505                unreachable!()
 9506            },
 9507        );
 9508        let save = editor
 9509            .update_in(cx, |editor, window, cx| {
 9510                editor.save(
 9511                    SaveOptions {
 9512                        format: true,
 9513                        autosave: false,
 9514                    },
 9515                    project.clone(),
 9516                    window,
 9517                    cx,
 9518                )
 9519            })
 9520            .unwrap();
 9521        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9522        cx.executor().start_waiting();
 9523        save.await;
 9524        assert_eq!(
 9525            editor.update(cx, |editor, cx| editor.text(cx)),
 9526            "one\ntwo\nthree\n"
 9527        );
 9528    }
 9529
 9530    // Set rust language override and assert overridden tabsize is sent to language server
 9531    update_test_language_settings(cx, |settings| {
 9532        settings.languages.0.insert(
 9533            "Rust".into(),
 9534            LanguageSettingsContent {
 9535                tab_size: NonZeroU32::new(8),
 9536                ..Default::default()
 9537            },
 9538        );
 9539    });
 9540
 9541    {
 9542        editor.update_in(cx, |editor, window, cx| {
 9543            editor.set_text("somehting_new\n", window, cx)
 9544        });
 9545        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9546        let _formatting_request_signal = fake_server
 9547            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9548                assert_eq!(
 9549                    params.text_document.uri,
 9550                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9551                );
 9552                assert_eq!(params.options.tab_size, 8);
 9553                Ok(Some(vec![]))
 9554            });
 9555        let save = editor
 9556            .update_in(cx, |editor, window, cx| {
 9557                editor.save(
 9558                    SaveOptions {
 9559                        format: true,
 9560                        autosave: false,
 9561                    },
 9562                    project.clone(),
 9563                    window,
 9564                    cx,
 9565                )
 9566            })
 9567            .unwrap();
 9568        cx.executor().start_waiting();
 9569        save.await;
 9570    }
 9571}
 9572
 9573#[gpui::test]
 9574async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
 9575    init_test(cx, |settings| {
 9576        settings.defaults.ensure_final_newline_on_save = Some(false);
 9577    });
 9578
 9579    let fs = FakeFs::new(cx.executor());
 9580    fs.insert_file(path!("/file.txt"), "foo".into()).await;
 9581
 9582    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
 9583
 9584    let buffer = project
 9585        .update(cx, |project, cx| {
 9586            project.open_local_buffer(path!("/file.txt"), cx)
 9587        })
 9588        .await
 9589        .unwrap();
 9590
 9591    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9592    let (editor, cx) = cx.add_window_view(|window, cx| {
 9593        build_editor_with_project(project.clone(), buffer, window, cx)
 9594    });
 9595    editor.update_in(cx, |editor, window, cx| {
 9596        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 9597            s.select_ranges([0..0])
 9598        });
 9599    });
 9600    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9601
 9602    editor.update_in(cx, |editor, window, cx| {
 9603        editor.handle_input("\n", window, cx)
 9604    });
 9605    cx.run_until_parked();
 9606    save(&editor, &project, cx).await;
 9607    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
 9608
 9609    editor.update_in(cx, |editor, window, cx| {
 9610        editor.undo(&Default::default(), window, cx);
 9611    });
 9612    save(&editor, &project, cx).await;
 9613    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
 9614
 9615    editor.update_in(cx, |editor, window, cx| {
 9616        editor.redo(&Default::default(), window, cx);
 9617    });
 9618    cx.run_until_parked();
 9619    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
 9620
 9621    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
 9622        let save = editor
 9623            .update_in(cx, |editor, window, cx| {
 9624                editor.save(
 9625                    SaveOptions {
 9626                        format: true,
 9627                        autosave: false,
 9628                    },
 9629                    project.clone(),
 9630                    window,
 9631                    cx,
 9632                )
 9633            })
 9634            .unwrap();
 9635        cx.executor().start_waiting();
 9636        save.await;
 9637        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9638    }
 9639}
 9640
 9641#[gpui::test]
 9642async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
 9643    init_test(cx, |_| {});
 9644
 9645    let cols = 4;
 9646    let rows = 10;
 9647    let sample_text_1 = sample_text(rows, cols, 'a');
 9648    assert_eq!(
 9649        sample_text_1,
 9650        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
 9651    );
 9652    let sample_text_2 = sample_text(rows, cols, 'l');
 9653    assert_eq!(
 9654        sample_text_2,
 9655        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
 9656    );
 9657    let sample_text_3 = sample_text(rows, cols, 'v');
 9658    assert_eq!(
 9659        sample_text_3,
 9660        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
 9661    );
 9662
 9663    let fs = FakeFs::new(cx.executor());
 9664    fs.insert_tree(
 9665        path!("/a"),
 9666        json!({
 9667            "main.rs": sample_text_1,
 9668            "other.rs": sample_text_2,
 9669            "lib.rs": sample_text_3,
 9670        }),
 9671    )
 9672    .await;
 9673
 9674    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
 9675    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9676    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 9677
 9678    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9679    language_registry.add(rust_lang());
 9680    let mut fake_servers = language_registry.register_fake_lsp(
 9681        "Rust",
 9682        FakeLspAdapter {
 9683            capabilities: lsp::ServerCapabilities {
 9684                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9685                ..Default::default()
 9686            },
 9687            ..Default::default()
 9688        },
 9689    );
 9690
 9691    let worktree = project.update(cx, |project, cx| {
 9692        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
 9693        assert_eq!(worktrees.len(), 1);
 9694        worktrees.pop().unwrap()
 9695    });
 9696    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 9697
 9698    let buffer_1 = project
 9699        .update(cx, |project, cx| {
 9700            project.open_buffer((worktree_id, "main.rs"), cx)
 9701        })
 9702        .await
 9703        .unwrap();
 9704    let buffer_2 = project
 9705        .update(cx, |project, cx| {
 9706            project.open_buffer((worktree_id, "other.rs"), cx)
 9707        })
 9708        .await
 9709        .unwrap();
 9710    let buffer_3 = project
 9711        .update(cx, |project, cx| {
 9712            project.open_buffer((worktree_id, "lib.rs"), cx)
 9713        })
 9714        .await
 9715        .unwrap();
 9716
 9717    let multi_buffer = cx.new(|cx| {
 9718        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 9719        multi_buffer.push_excerpts(
 9720            buffer_1.clone(),
 9721            [
 9722                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9723                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9724                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9725            ],
 9726            cx,
 9727        );
 9728        multi_buffer.push_excerpts(
 9729            buffer_2.clone(),
 9730            [
 9731                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9732                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9733                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9734            ],
 9735            cx,
 9736        );
 9737        multi_buffer.push_excerpts(
 9738            buffer_3.clone(),
 9739            [
 9740                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9741                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9742                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9743            ],
 9744            cx,
 9745        );
 9746        multi_buffer
 9747    });
 9748    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
 9749        Editor::new(
 9750            EditorMode::full(),
 9751            multi_buffer,
 9752            Some(project.clone()),
 9753            window,
 9754            cx,
 9755        )
 9756    });
 9757
 9758    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 9759        editor.change_selections(
 9760            SelectionEffects::scroll(Autoscroll::Next),
 9761            window,
 9762            cx,
 9763            |s| s.select_ranges(Some(1..2)),
 9764        );
 9765        editor.insert("|one|two|three|", window, cx);
 9766    });
 9767    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 9768    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 9769        editor.change_selections(
 9770            SelectionEffects::scroll(Autoscroll::Next),
 9771            window,
 9772            cx,
 9773            |s| s.select_ranges(Some(60..70)),
 9774        );
 9775        editor.insert("|four|five|six|", window, cx);
 9776    });
 9777    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 9778
 9779    // First two buffers should be edited, but not the third one.
 9780    assert_eq!(
 9781        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 9782        "a|one|two|three|aa\nbbbb\ncccc\n\nffff\ngggg\n\njjjj\nllll\nmmmm\nnnnn|four|five|six|\nr\n\nuuuu\nvvvv\nwwww\nxxxx\n\n{{{{\n||||\n\n\u{7f}\u{7f}\u{7f}\u{7f}",
 9783    );
 9784    buffer_1.update(cx, |buffer, _| {
 9785        assert!(buffer.is_dirty());
 9786        assert_eq!(
 9787            buffer.text(),
 9788            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
 9789        )
 9790    });
 9791    buffer_2.update(cx, |buffer, _| {
 9792        assert!(buffer.is_dirty());
 9793        assert_eq!(
 9794            buffer.text(),
 9795            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
 9796        )
 9797    });
 9798    buffer_3.update(cx, |buffer, _| {
 9799        assert!(!buffer.is_dirty());
 9800        assert_eq!(buffer.text(), sample_text_3,)
 9801    });
 9802    cx.executor().run_until_parked();
 9803
 9804    cx.executor().start_waiting();
 9805    let save = multi_buffer_editor
 9806        .update_in(cx, |editor, window, cx| {
 9807            editor.save(
 9808                SaveOptions {
 9809                    format: true,
 9810                    autosave: false,
 9811                },
 9812                project.clone(),
 9813                window,
 9814                cx,
 9815            )
 9816        })
 9817        .unwrap();
 9818
 9819    let fake_server = fake_servers.next().await.unwrap();
 9820    fake_server
 9821        .server
 9822        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9823            Ok(Some(vec![lsp::TextEdit::new(
 9824                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9825                format!("[{} formatted]", params.text_document.uri),
 9826            )]))
 9827        })
 9828        .detach();
 9829    save.await;
 9830
 9831    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
 9832    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
 9833    assert_eq!(
 9834        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 9835        uri!(
 9836            "a|o[file:///a/main.rs formatted]bbbb\ncccc\n\nffff\ngggg\n\njjjj\n\nlll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|\nr\n\nuuuu\n\nvvvv\nwwww\nxxxx\n\n{{{{\n||||\n\n\u{7f}\u{7f}\u{7f}\u{7f}"
 9837        ),
 9838    );
 9839    buffer_1.update(cx, |buffer, _| {
 9840        assert!(!buffer.is_dirty());
 9841        assert_eq!(
 9842            buffer.text(),
 9843            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
 9844        )
 9845    });
 9846    buffer_2.update(cx, |buffer, _| {
 9847        assert!(!buffer.is_dirty());
 9848        assert_eq!(
 9849            buffer.text(),
 9850            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
 9851        )
 9852    });
 9853    buffer_3.update(cx, |buffer, _| {
 9854        assert!(!buffer.is_dirty());
 9855        assert_eq!(buffer.text(), sample_text_3,)
 9856    });
 9857}
 9858
 9859#[gpui::test]
 9860async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
 9861    init_test(cx, |_| {});
 9862
 9863    let fs = FakeFs::new(cx.executor());
 9864    fs.insert_tree(
 9865        path!("/dir"),
 9866        json!({
 9867            "file1.rs": "fn main() { println!(\"hello\"); }",
 9868            "file2.rs": "fn test() { println!(\"test\"); }",
 9869            "file3.rs": "fn other() { println!(\"other\"); }\n",
 9870        }),
 9871    )
 9872    .await;
 9873
 9874    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
 9875    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9876    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 9877
 9878    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9879    language_registry.add(rust_lang());
 9880
 9881    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
 9882    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 9883
 9884    // Open three buffers
 9885    let buffer_1 = project
 9886        .update(cx, |project, cx| {
 9887            project.open_buffer((worktree_id, "file1.rs"), cx)
 9888        })
 9889        .await
 9890        .unwrap();
 9891    let buffer_2 = project
 9892        .update(cx, |project, cx| {
 9893            project.open_buffer((worktree_id, "file2.rs"), cx)
 9894        })
 9895        .await
 9896        .unwrap();
 9897    let buffer_3 = project
 9898        .update(cx, |project, cx| {
 9899            project.open_buffer((worktree_id, "file3.rs"), cx)
 9900        })
 9901        .await
 9902        .unwrap();
 9903
 9904    // Create a multi-buffer with all three buffers
 9905    let multi_buffer = cx.new(|cx| {
 9906        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 9907        multi_buffer.push_excerpts(
 9908            buffer_1.clone(),
 9909            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9910            cx,
 9911        );
 9912        multi_buffer.push_excerpts(
 9913            buffer_2.clone(),
 9914            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9915            cx,
 9916        );
 9917        multi_buffer.push_excerpts(
 9918            buffer_3.clone(),
 9919            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9920            cx,
 9921        );
 9922        multi_buffer
 9923    });
 9924
 9925    let editor = cx.new_window_entity(|window, cx| {
 9926        Editor::new(
 9927            EditorMode::full(),
 9928            multi_buffer,
 9929            Some(project.clone()),
 9930            window,
 9931            cx,
 9932        )
 9933    });
 9934
 9935    // Edit only the first buffer
 9936    editor.update_in(cx, |editor, window, cx| {
 9937        editor.change_selections(
 9938            SelectionEffects::scroll(Autoscroll::Next),
 9939            window,
 9940            cx,
 9941            |s| s.select_ranges(Some(10..10)),
 9942        );
 9943        editor.insert("// edited", window, cx);
 9944    });
 9945
 9946    // Verify that only buffer 1 is dirty
 9947    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
 9948    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9949    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9950
 9951    // Get write counts after file creation (files were created with initial content)
 9952    // We expect each file to have been written once during creation
 9953    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
 9954    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
 9955    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
 9956
 9957    // Perform autosave
 9958    let save_task = editor.update_in(cx, |editor, window, cx| {
 9959        editor.save(
 9960            SaveOptions {
 9961                format: true,
 9962                autosave: true,
 9963            },
 9964            project.clone(),
 9965            window,
 9966            cx,
 9967        )
 9968    });
 9969    save_task.await.unwrap();
 9970
 9971    // Only the dirty buffer should have been saved
 9972    assert_eq!(
 9973        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
 9974        1,
 9975        "Buffer 1 was dirty, so it should have been written once during autosave"
 9976    );
 9977    assert_eq!(
 9978        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
 9979        0,
 9980        "Buffer 2 was clean, so it should not have been written during autosave"
 9981    );
 9982    assert_eq!(
 9983        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
 9984        0,
 9985        "Buffer 3 was clean, so it should not have been written during autosave"
 9986    );
 9987
 9988    // Verify buffer states after autosave
 9989    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9990    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9991    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9992
 9993    // Now perform a manual save (format = true)
 9994    let save_task = editor.update_in(cx, |editor, window, cx| {
 9995        editor.save(
 9996            SaveOptions {
 9997                format: true,
 9998                autosave: false,
 9999            },
10000            project.clone(),
10001            window,
10002            cx,
10003        )
10004    });
10005    save_task.await.unwrap();
10006
10007    // During manual save, clean buffers don't get written to disk
10008    // They just get did_save called for language server notifications
10009    assert_eq!(
10010        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
10011        1,
10012        "Buffer 1 should only have been written once total (during autosave, not manual save)"
10013    );
10014    assert_eq!(
10015        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
10016        0,
10017        "Buffer 2 should not have been written at all"
10018    );
10019    assert_eq!(
10020        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
10021        0,
10022        "Buffer 3 should not have been written at all"
10023    );
10024}
10025
10026async fn setup_range_format_test(
10027    cx: &mut TestAppContext,
10028) -> (
10029    Entity<Project>,
10030    Entity<Editor>,
10031    &mut gpui::VisualTestContext,
10032    lsp::FakeLanguageServer,
10033) {
10034    init_test(cx, |_| {});
10035
10036    let fs = FakeFs::new(cx.executor());
10037    fs.insert_file(path!("/file.rs"), Default::default()).await;
10038
10039    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10040
10041    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10042    language_registry.add(rust_lang());
10043    let mut fake_servers = language_registry.register_fake_lsp(
10044        "Rust",
10045        FakeLspAdapter {
10046            capabilities: lsp::ServerCapabilities {
10047                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
10048                ..lsp::ServerCapabilities::default()
10049            },
10050            ..FakeLspAdapter::default()
10051        },
10052    );
10053
10054    let buffer = project
10055        .update(cx, |project, cx| {
10056            project.open_local_buffer(path!("/file.rs"), cx)
10057        })
10058        .await
10059        .unwrap();
10060
10061    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10062    let (editor, cx) = cx.add_window_view(|window, cx| {
10063        build_editor_with_project(project.clone(), buffer, window, cx)
10064    });
10065
10066    cx.executor().start_waiting();
10067    let fake_server = fake_servers.next().await.unwrap();
10068
10069    (project, editor, cx, fake_server)
10070}
10071
10072#[gpui::test]
10073async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
10074    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
10075
10076    editor.update_in(cx, |editor, window, cx| {
10077        editor.set_text("one\ntwo\nthree\n", window, cx)
10078    });
10079    assert!(cx.read(|cx| editor.is_dirty(cx)));
10080
10081    let save = editor
10082        .update_in(cx, |editor, window, cx| {
10083            editor.save(
10084                SaveOptions {
10085                    format: true,
10086                    autosave: false,
10087                },
10088                project.clone(),
10089                window,
10090                cx,
10091            )
10092        })
10093        .unwrap();
10094    fake_server
10095        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
10096            assert_eq!(
10097                params.text_document.uri,
10098                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10099            );
10100            assert_eq!(params.options.tab_size, 4);
10101            Ok(Some(vec![lsp::TextEdit::new(
10102                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10103                ", ".to_string(),
10104            )]))
10105        })
10106        .next()
10107        .await;
10108    cx.executor().start_waiting();
10109    save.await;
10110    assert_eq!(
10111        editor.update(cx, |editor, cx| editor.text(cx)),
10112        "one, two\nthree\n"
10113    );
10114    assert!(!cx.read(|cx| editor.is_dirty(cx)));
10115}
10116
10117#[gpui::test]
10118async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
10119    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
10120
10121    editor.update_in(cx, |editor, window, cx| {
10122        editor.set_text("one\ntwo\nthree\n", window, cx)
10123    });
10124    assert!(cx.read(|cx| editor.is_dirty(cx)));
10125
10126    // Test that save still works when formatting hangs
10127    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
10128        move |params, _| async move {
10129            assert_eq!(
10130                params.text_document.uri,
10131                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10132            );
10133            futures::future::pending::<()>().await;
10134            unreachable!()
10135        },
10136    );
10137    let save = editor
10138        .update_in(cx, |editor, window, cx| {
10139            editor.save(
10140                SaveOptions {
10141                    format: true,
10142                    autosave: false,
10143                },
10144                project.clone(),
10145                window,
10146                cx,
10147            )
10148        })
10149        .unwrap();
10150    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10151    cx.executor().start_waiting();
10152    save.await;
10153    assert_eq!(
10154        editor.update(cx, |editor, cx| editor.text(cx)),
10155        "one\ntwo\nthree\n"
10156    );
10157    assert!(!cx.read(|cx| editor.is_dirty(cx)));
10158}
10159
10160#[gpui::test]
10161async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
10162    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
10163
10164    // Buffer starts clean, no formatting should be requested
10165    let save = editor
10166        .update_in(cx, |editor, window, cx| {
10167            editor.save(
10168                SaveOptions {
10169                    format: false,
10170                    autosave: false,
10171                },
10172                project.clone(),
10173                window,
10174                cx,
10175            )
10176        })
10177        .unwrap();
10178    let _pending_format_request = fake_server
10179        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
10180            panic!("Should not be invoked");
10181        })
10182        .next();
10183    cx.executor().start_waiting();
10184    save.await;
10185    cx.run_until_parked();
10186}
10187
10188#[gpui::test]
10189async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
10190    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
10191
10192    // Set Rust language override and assert overridden tabsize is sent to language server
10193    update_test_language_settings(cx, |settings| {
10194        settings.languages.0.insert(
10195            "Rust".into(),
10196            LanguageSettingsContent {
10197                tab_size: NonZeroU32::new(8),
10198                ..Default::default()
10199            },
10200        );
10201    });
10202
10203    editor.update_in(cx, |editor, window, cx| {
10204        editor.set_text("something_new\n", window, cx)
10205    });
10206    assert!(cx.read(|cx| editor.is_dirty(cx)));
10207    let save = editor
10208        .update_in(cx, |editor, window, cx| {
10209            editor.save(
10210                SaveOptions {
10211                    format: true,
10212                    autosave: false,
10213                },
10214                project.clone(),
10215                window,
10216                cx,
10217            )
10218        })
10219        .unwrap();
10220    fake_server
10221        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
10222            assert_eq!(
10223                params.text_document.uri,
10224                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10225            );
10226            assert_eq!(params.options.tab_size, 8);
10227            Ok(Some(Vec::new()))
10228        })
10229        .next()
10230        .await;
10231    save.await;
10232}
10233
10234#[gpui::test]
10235async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
10236    init_test(cx, |settings| {
10237        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
10238            Formatter::LanguageServer { name: None },
10239        )))
10240    });
10241
10242    let fs = FakeFs::new(cx.executor());
10243    fs.insert_file(path!("/file.rs"), Default::default()).await;
10244
10245    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10246
10247    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10248    language_registry.add(Arc::new(Language::new(
10249        LanguageConfig {
10250            name: "Rust".into(),
10251            matcher: LanguageMatcher {
10252                path_suffixes: vec!["rs".to_string()],
10253                ..Default::default()
10254            },
10255            ..LanguageConfig::default()
10256        },
10257        Some(tree_sitter_rust::LANGUAGE.into()),
10258    )));
10259    update_test_language_settings(cx, |settings| {
10260        // Enable Prettier formatting for the same buffer, and ensure
10261        // LSP is called instead of Prettier.
10262        settings.defaults.prettier = Some(PrettierSettings {
10263            allowed: true,
10264            ..PrettierSettings::default()
10265        });
10266    });
10267    let mut fake_servers = language_registry.register_fake_lsp(
10268        "Rust",
10269        FakeLspAdapter {
10270            capabilities: lsp::ServerCapabilities {
10271                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10272                ..Default::default()
10273            },
10274            ..Default::default()
10275        },
10276    );
10277
10278    let buffer = project
10279        .update(cx, |project, cx| {
10280            project.open_local_buffer(path!("/file.rs"), cx)
10281        })
10282        .await
10283        .unwrap();
10284
10285    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10286    let (editor, cx) = cx.add_window_view(|window, cx| {
10287        build_editor_with_project(project.clone(), buffer, window, cx)
10288    });
10289    editor.update_in(cx, |editor, window, cx| {
10290        editor.set_text("one\ntwo\nthree\n", window, cx)
10291    });
10292
10293    cx.executor().start_waiting();
10294    let fake_server = fake_servers.next().await.unwrap();
10295
10296    let format = editor
10297        .update_in(cx, |editor, window, cx| {
10298            editor.perform_format(
10299                project.clone(),
10300                FormatTrigger::Manual,
10301                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10302                window,
10303                cx,
10304            )
10305        })
10306        .unwrap();
10307    fake_server
10308        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
10309            assert_eq!(
10310                params.text_document.uri,
10311                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10312            );
10313            assert_eq!(params.options.tab_size, 4);
10314            Ok(Some(vec![lsp::TextEdit::new(
10315                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10316                ", ".to_string(),
10317            )]))
10318        })
10319        .next()
10320        .await;
10321    cx.executor().start_waiting();
10322    format.await;
10323    assert_eq!(
10324        editor.update(cx, |editor, cx| editor.text(cx)),
10325        "one, two\nthree\n"
10326    );
10327
10328    editor.update_in(cx, |editor, window, cx| {
10329        editor.set_text("one\ntwo\nthree\n", window, cx)
10330    });
10331    // Ensure we don't lock if formatting hangs.
10332    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10333        move |params, _| async move {
10334            assert_eq!(
10335                params.text_document.uri,
10336                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10337            );
10338            futures::future::pending::<()>().await;
10339            unreachable!()
10340        },
10341    );
10342    let format = editor
10343        .update_in(cx, |editor, window, cx| {
10344            editor.perform_format(
10345                project,
10346                FormatTrigger::Manual,
10347                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10348                window,
10349                cx,
10350            )
10351        })
10352        .unwrap();
10353    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10354    cx.executor().start_waiting();
10355    format.await;
10356    assert_eq!(
10357        editor.update(cx, |editor, cx| editor.text(cx)),
10358        "one\ntwo\nthree\n"
10359    );
10360}
10361
10362#[gpui::test]
10363async fn test_multiple_formatters(cx: &mut TestAppContext) {
10364    init_test(cx, |settings| {
10365        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
10366        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
10367            Formatter::LanguageServer { name: None },
10368            Formatter::CodeActions(
10369                [
10370                    ("code-action-1".into(), true),
10371                    ("code-action-2".into(), true),
10372                ]
10373                .into_iter()
10374                .collect(),
10375            ),
10376        ])))
10377    });
10378
10379    let fs = FakeFs::new(cx.executor());
10380    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
10381        .await;
10382
10383    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10384    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10385    language_registry.add(rust_lang());
10386
10387    let mut fake_servers = language_registry.register_fake_lsp(
10388        "Rust",
10389        FakeLspAdapter {
10390            capabilities: lsp::ServerCapabilities {
10391                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10392                execute_command_provider: Some(lsp::ExecuteCommandOptions {
10393                    commands: vec!["the-command-for-code-action-1".into()],
10394                    ..Default::default()
10395                }),
10396                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10397                ..Default::default()
10398            },
10399            ..Default::default()
10400        },
10401    );
10402
10403    let buffer = project
10404        .update(cx, |project, cx| {
10405            project.open_local_buffer(path!("/file.rs"), cx)
10406        })
10407        .await
10408        .unwrap();
10409
10410    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10411    let (editor, cx) = cx.add_window_view(|window, cx| {
10412        build_editor_with_project(project.clone(), buffer, window, cx)
10413    });
10414
10415    cx.executor().start_waiting();
10416
10417    let fake_server = fake_servers.next().await.unwrap();
10418    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10419        move |_params, _| async move {
10420            Ok(Some(vec![lsp::TextEdit::new(
10421                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10422                "applied-formatting\n".to_string(),
10423            )]))
10424        },
10425    );
10426    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
10427        move |params, _| async move {
10428            assert_eq!(
10429                params.context.only,
10430                Some(vec!["code-action-1".into(), "code-action-2".into()])
10431            );
10432            let uri = lsp::Url::from_file_path(path!("/file.rs")).unwrap();
10433            Ok(Some(vec![
10434                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10435                    kind: Some("code-action-1".into()),
10436                    edit: Some(lsp::WorkspaceEdit::new(
10437                        [(
10438                            uri.clone(),
10439                            vec![lsp::TextEdit::new(
10440                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10441                                "applied-code-action-1-edit\n".to_string(),
10442                            )],
10443                        )]
10444                        .into_iter()
10445                        .collect(),
10446                    )),
10447                    command: Some(lsp::Command {
10448                        command: "the-command-for-code-action-1".into(),
10449                        ..Default::default()
10450                    }),
10451                    ..Default::default()
10452                }),
10453                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10454                    kind: Some("code-action-2".into()),
10455                    edit: Some(lsp::WorkspaceEdit::new(
10456                        [(
10457                            uri.clone(),
10458                            vec![lsp::TextEdit::new(
10459                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10460                                "applied-code-action-2-edit\n".to_string(),
10461                            )],
10462                        )]
10463                        .into_iter()
10464                        .collect(),
10465                    )),
10466                    ..Default::default()
10467                }),
10468            ]))
10469        },
10470    );
10471
10472    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
10473        move |params, _| async move { Ok(params) }
10474    });
10475
10476    let command_lock = Arc::new(futures::lock::Mutex::new(()));
10477    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
10478        let fake = fake_server.clone();
10479        let lock = command_lock.clone();
10480        move |params, _| {
10481            assert_eq!(params.command, "the-command-for-code-action-1");
10482            let fake = fake.clone();
10483            let lock = lock.clone();
10484            async move {
10485                lock.lock().await;
10486                fake.server
10487                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
10488                        label: None,
10489                        edit: lsp::WorkspaceEdit {
10490                            changes: Some(
10491                                [(
10492                                    lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
10493                                    vec![lsp::TextEdit {
10494                                        range: lsp::Range::new(
10495                                            lsp::Position::new(0, 0),
10496                                            lsp::Position::new(0, 0),
10497                                        ),
10498                                        new_text: "applied-code-action-1-command\n".into(),
10499                                    }],
10500                                )]
10501                                .into_iter()
10502                                .collect(),
10503                            ),
10504                            ..Default::default()
10505                        },
10506                    })
10507                    .await
10508                    .into_response()
10509                    .unwrap();
10510                Ok(Some(json!(null)))
10511            }
10512        }
10513    });
10514
10515    cx.executor().start_waiting();
10516    editor
10517        .update_in(cx, |editor, window, cx| {
10518            editor.perform_format(
10519                project.clone(),
10520                FormatTrigger::Manual,
10521                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10522                window,
10523                cx,
10524            )
10525        })
10526        .unwrap()
10527        .await;
10528    editor.update(cx, |editor, cx| {
10529        assert_eq!(
10530            editor.text(cx),
10531            r#"
10532                applied-code-action-2-edit
10533                applied-code-action-1-command
10534                applied-code-action-1-edit
10535                applied-formatting
10536                one
10537                two
10538                three
10539            "#
10540            .unindent()
10541        );
10542    });
10543
10544    editor.update_in(cx, |editor, window, cx| {
10545        editor.undo(&Default::default(), window, cx);
10546        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10547    });
10548
10549    // Perform a manual edit while waiting for an LSP command
10550    // that's being run as part of a formatting code action.
10551    let lock_guard = command_lock.lock().await;
10552    let format = editor
10553        .update_in(cx, |editor, window, cx| {
10554            editor.perform_format(
10555                project.clone(),
10556                FormatTrigger::Manual,
10557                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10558                window,
10559                cx,
10560            )
10561        })
10562        .unwrap();
10563    cx.run_until_parked();
10564    editor.update(cx, |editor, cx| {
10565        assert_eq!(
10566            editor.text(cx),
10567            r#"
10568                applied-code-action-1-edit
10569                applied-formatting
10570                one
10571                two
10572                three
10573            "#
10574            .unindent()
10575        );
10576
10577        editor.buffer.update(cx, |buffer, cx| {
10578            let ix = buffer.len(cx);
10579            buffer.edit([(ix..ix, "edited\n")], None, cx);
10580        });
10581    });
10582
10583    // Allow the LSP command to proceed. Because the buffer was edited,
10584    // the second code action will not be run.
10585    drop(lock_guard);
10586    format.await;
10587    editor.update_in(cx, |editor, window, cx| {
10588        assert_eq!(
10589            editor.text(cx),
10590            r#"
10591                applied-code-action-1-command
10592                applied-code-action-1-edit
10593                applied-formatting
10594                one
10595                two
10596                three
10597                edited
10598            "#
10599            .unindent()
10600        );
10601
10602        // The manual edit is undone first, because it is the last thing the user did
10603        // (even though the command completed afterwards).
10604        editor.undo(&Default::default(), window, cx);
10605        assert_eq!(
10606            editor.text(cx),
10607            r#"
10608                applied-code-action-1-command
10609                applied-code-action-1-edit
10610                applied-formatting
10611                one
10612                two
10613                three
10614            "#
10615            .unindent()
10616        );
10617
10618        // All the formatting (including the command, which completed after the manual edit)
10619        // is undone together.
10620        editor.undo(&Default::default(), window, cx);
10621        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10622    });
10623}
10624
10625#[gpui::test]
10626async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
10627    init_test(cx, |settings| {
10628        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
10629            Formatter::LanguageServer { name: None },
10630        ])))
10631    });
10632
10633    let fs = FakeFs::new(cx.executor());
10634    fs.insert_file(path!("/file.ts"), Default::default()).await;
10635
10636    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10637
10638    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10639    language_registry.add(Arc::new(Language::new(
10640        LanguageConfig {
10641            name: "TypeScript".into(),
10642            matcher: LanguageMatcher {
10643                path_suffixes: vec!["ts".to_string()],
10644                ..Default::default()
10645            },
10646            ..LanguageConfig::default()
10647        },
10648        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
10649    )));
10650    update_test_language_settings(cx, |settings| {
10651        settings.defaults.prettier = Some(PrettierSettings {
10652            allowed: true,
10653            ..PrettierSettings::default()
10654        });
10655    });
10656    let mut fake_servers = language_registry.register_fake_lsp(
10657        "TypeScript",
10658        FakeLspAdapter {
10659            capabilities: lsp::ServerCapabilities {
10660                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10661                ..Default::default()
10662            },
10663            ..Default::default()
10664        },
10665    );
10666
10667    let buffer = project
10668        .update(cx, |project, cx| {
10669            project.open_local_buffer(path!("/file.ts"), cx)
10670        })
10671        .await
10672        .unwrap();
10673
10674    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10675    let (editor, cx) = cx.add_window_view(|window, cx| {
10676        build_editor_with_project(project.clone(), buffer, window, cx)
10677    });
10678    editor.update_in(cx, |editor, window, cx| {
10679        editor.set_text(
10680            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
10681            window,
10682            cx,
10683        )
10684    });
10685
10686    cx.executor().start_waiting();
10687    let fake_server = fake_servers.next().await.unwrap();
10688
10689    let format = editor
10690        .update_in(cx, |editor, window, cx| {
10691            editor.perform_code_action_kind(
10692                project.clone(),
10693                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
10694                window,
10695                cx,
10696            )
10697        })
10698        .unwrap();
10699    fake_server
10700        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
10701            assert_eq!(
10702                params.text_document.uri,
10703                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
10704            );
10705            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
10706                lsp::CodeAction {
10707                    title: "Organize Imports".to_string(),
10708                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
10709                    edit: Some(lsp::WorkspaceEdit {
10710                        changes: Some(
10711                            [(
10712                                params.text_document.uri.clone(),
10713                                vec![lsp::TextEdit::new(
10714                                    lsp::Range::new(
10715                                        lsp::Position::new(1, 0),
10716                                        lsp::Position::new(2, 0),
10717                                    ),
10718                                    "".to_string(),
10719                                )],
10720                            )]
10721                            .into_iter()
10722                            .collect(),
10723                        ),
10724                        ..Default::default()
10725                    }),
10726                    ..Default::default()
10727                },
10728            )]))
10729        })
10730        .next()
10731        .await;
10732    cx.executor().start_waiting();
10733    format.await;
10734    assert_eq!(
10735        editor.update(cx, |editor, cx| editor.text(cx)),
10736        "import { a } from 'module';\n\nconst x = a;\n"
10737    );
10738
10739    editor.update_in(cx, |editor, window, cx| {
10740        editor.set_text(
10741            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
10742            window,
10743            cx,
10744        )
10745    });
10746    // Ensure we don't lock if code action hangs.
10747    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
10748        move |params, _| async move {
10749            assert_eq!(
10750                params.text_document.uri,
10751                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
10752            );
10753            futures::future::pending::<()>().await;
10754            unreachable!()
10755        },
10756    );
10757    let format = editor
10758        .update_in(cx, |editor, window, cx| {
10759            editor.perform_code_action_kind(
10760                project,
10761                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
10762                window,
10763                cx,
10764            )
10765        })
10766        .unwrap();
10767    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
10768    cx.executor().start_waiting();
10769    format.await;
10770    assert_eq!(
10771        editor.update(cx, |editor, cx| editor.text(cx)),
10772        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
10773    );
10774}
10775
10776#[gpui::test]
10777async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
10778    init_test(cx, |_| {});
10779
10780    let mut cx = EditorLspTestContext::new_rust(
10781        lsp::ServerCapabilities {
10782            document_formatting_provider: Some(lsp::OneOf::Left(true)),
10783            ..Default::default()
10784        },
10785        cx,
10786    )
10787    .await;
10788
10789    cx.set_state(indoc! {"
10790        one.twoˇ
10791    "});
10792
10793    // The format request takes a long time. When it completes, it inserts
10794    // a newline and an indent before the `.`
10795    cx.lsp
10796        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
10797            let executor = cx.background_executor().clone();
10798            async move {
10799                executor.timer(Duration::from_millis(100)).await;
10800                Ok(Some(vec![lsp::TextEdit {
10801                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
10802                    new_text: "\n    ".into(),
10803                }]))
10804            }
10805        });
10806
10807    // Submit a format request.
10808    let format_1 = cx
10809        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10810        .unwrap();
10811    cx.executor().run_until_parked();
10812
10813    // Submit a second format request.
10814    let format_2 = cx
10815        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10816        .unwrap();
10817    cx.executor().run_until_parked();
10818
10819    // Wait for both format requests to complete
10820    cx.executor().advance_clock(Duration::from_millis(200));
10821    cx.executor().start_waiting();
10822    format_1.await.unwrap();
10823    cx.executor().start_waiting();
10824    format_2.await.unwrap();
10825
10826    // The formatting edits only happens once.
10827    cx.assert_editor_state(indoc! {"
10828        one
10829            .twoˇ
10830    "});
10831}
10832
10833#[gpui::test]
10834async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
10835    init_test(cx, |settings| {
10836        settings.defaults.formatter = Some(SelectedFormatter::Auto)
10837    });
10838
10839    let mut cx = EditorLspTestContext::new_rust(
10840        lsp::ServerCapabilities {
10841            document_formatting_provider: Some(lsp::OneOf::Left(true)),
10842            ..Default::default()
10843        },
10844        cx,
10845    )
10846    .await;
10847
10848    // Set up a buffer white some trailing whitespace and no trailing newline.
10849    cx.set_state(
10850        &[
10851            "one ",   //
10852            "twoˇ",   //
10853            "three ", //
10854            "four",   //
10855        ]
10856        .join("\n"),
10857    );
10858
10859    // Submit a format request.
10860    let format = cx
10861        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10862        .unwrap();
10863
10864    // Record which buffer changes have been sent to the language server
10865    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
10866    cx.lsp
10867        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
10868            let buffer_changes = buffer_changes.clone();
10869            move |params, _| {
10870                buffer_changes.lock().extend(
10871                    params
10872                        .content_changes
10873                        .into_iter()
10874                        .map(|e| (e.range.unwrap(), e.text)),
10875                );
10876            }
10877        });
10878
10879    // Handle formatting requests to the language server.
10880    cx.lsp
10881        .set_request_handler::<lsp::request::Formatting, _, _>({
10882            let buffer_changes = buffer_changes.clone();
10883            move |_, _| {
10884                // When formatting is requested, trailing whitespace has already been stripped,
10885                // and the trailing newline has already been added.
10886                assert_eq!(
10887                    &buffer_changes.lock()[1..],
10888                    &[
10889                        (
10890                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
10891                            "".into()
10892                        ),
10893                        (
10894                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
10895                            "".into()
10896                        ),
10897                        (
10898                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
10899                            "\n".into()
10900                        ),
10901                    ]
10902                );
10903
10904                // Insert blank lines between each line of the buffer.
10905                async move {
10906                    Ok(Some(vec![
10907                        lsp::TextEdit {
10908                            range: lsp::Range::new(
10909                                lsp::Position::new(1, 0),
10910                                lsp::Position::new(1, 0),
10911                            ),
10912                            new_text: "\n".into(),
10913                        },
10914                        lsp::TextEdit {
10915                            range: lsp::Range::new(
10916                                lsp::Position::new(2, 0),
10917                                lsp::Position::new(2, 0),
10918                            ),
10919                            new_text: "\n".into(),
10920                        },
10921                    ]))
10922                }
10923            }
10924        });
10925
10926    // After formatting the buffer, the trailing whitespace is stripped,
10927    // a newline is appended, and the edits provided by the language server
10928    // have been applied.
10929    format.await.unwrap();
10930    cx.assert_editor_state(
10931        &[
10932            "one",   //
10933            "",      //
10934            "twoˇ",  //
10935            "",      //
10936            "three", //
10937            "four",  //
10938            "",      //
10939        ]
10940        .join("\n"),
10941    );
10942
10943    // Undoing the formatting undoes the trailing whitespace removal, the
10944    // trailing newline, and the LSP edits.
10945    cx.update_buffer(|buffer, cx| buffer.undo(cx));
10946    cx.assert_editor_state(
10947        &[
10948            "one ",   //
10949            "twoˇ",   //
10950            "three ", //
10951            "four",   //
10952        ]
10953        .join("\n"),
10954    );
10955}
10956
10957#[gpui::test]
10958async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
10959    cx: &mut TestAppContext,
10960) {
10961    init_test(cx, |_| {});
10962
10963    cx.update(|cx| {
10964        cx.update_global::<SettingsStore, _>(|settings, cx| {
10965            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10966                settings.auto_signature_help = Some(true);
10967            });
10968        });
10969    });
10970
10971    let mut cx = EditorLspTestContext::new_rust(
10972        lsp::ServerCapabilities {
10973            signature_help_provider: Some(lsp::SignatureHelpOptions {
10974                ..Default::default()
10975            }),
10976            ..Default::default()
10977        },
10978        cx,
10979    )
10980    .await;
10981
10982    let language = Language::new(
10983        LanguageConfig {
10984            name: "Rust".into(),
10985            brackets: BracketPairConfig {
10986                pairs: vec![
10987                    BracketPair {
10988                        start: "{".to_string(),
10989                        end: "}".to_string(),
10990                        close: true,
10991                        surround: true,
10992                        newline: true,
10993                    },
10994                    BracketPair {
10995                        start: "(".to_string(),
10996                        end: ")".to_string(),
10997                        close: true,
10998                        surround: true,
10999                        newline: true,
11000                    },
11001                    BracketPair {
11002                        start: "/*".to_string(),
11003                        end: " */".to_string(),
11004                        close: true,
11005                        surround: true,
11006                        newline: true,
11007                    },
11008                    BracketPair {
11009                        start: "[".to_string(),
11010                        end: "]".to_string(),
11011                        close: false,
11012                        surround: false,
11013                        newline: true,
11014                    },
11015                    BracketPair {
11016                        start: "\"".to_string(),
11017                        end: "\"".to_string(),
11018                        close: true,
11019                        surround: true,
11020                        newline: false,
11021                    },
11022                    BracketPair {
11023                        start: "<".to_string(),
11024                        end: ">".to_string(),
11025                        close: false,
11026                        surround: true,
11027                        newline: true,
11028                    },
11029                ],
11030                ..Default::default()
11031            },
11032            autoclose_before: "})]".to_string(),
11033            ..Default::default()
11034        },
11035        Some(tree_sitter_rust::LANGUAGE.into()),
11036    );
11037    let language = Arc::new(language);
11038
11039    cx.language_registry().add(language.clone());
11040    cx.update_buffer(|buffer, cx| {
11041        buffer.set_language(Some(language), cx);
11042    });
11043
11044    cx.set_state(
11045        &r#"
11046            fn main() {
11047                sampleˇ
11048            }
11049        "#
11050        .unindent(),
11051    );
11052
11053    cx.update_editor(|editor, window, cx| {
11054        editor.handle_input("(", window, cx);
11055    });
11056    cx.assert_editor_state(
11057        &"
11058            fn main() {
11059                sample(ˇ)
11060            }
11061        "
11062        .unindent(),
11063    );
11064
11065    let mocked_response = lsp::SignatureHelp {
11066        signatures: vec![lsp::SignatureInformation {
11067            label: "fn sample(param1: u8, param2: u8)".to_string(),
11068            documentation: None,
11069            parameters: Some(vec![
11070                lsp::ParameterInformation {
11071                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11072                    documentation: None,
11073                },
11074                lsp::ParameterInformation {
11075                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11076                    documentation: None,
11077                },
11078            ]),
11079            active_parameter: None,
11080        }],
11081        active_signature: Some(0),
11082        active_parameter: Some(0),
11083    };
11084    handle_signature_help_request(&mut cx, mocked_response).await;
11085
11086    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11087        .await;
11088
11089    cx.editor(|editor, _, _| {
11090        let signature_help_state = editor.signature_help_state.popover().cloned();
11091        let signature = signature_help_state.unwrap();
11092        assert_eq!(
11093            signature.signatures[signature.current_signature].label,
11094            "fn sample(param1: u8, param2: u8)"
11095        );
11096    });
11097}
11098
11099#[gpui::test]
11100async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
11101    init_test(cx, |_| {});
11102
11103    cx.update(|cx| {
11104        cx.update_global::<SettingsStore, _>(|settings, cx| {
11105            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11106                settings.auto_signature_help = Some(false);
11107                settings.show_signature_help_after_edits = Some(false);
11108            });
11109        });
11110    });
11111
11112    let mut cx = EditorLspTestContext::new_rust(
11113        lsp::ServerCapabilities {
11114            signature_help_provider: Some(lsp::SignatureHelpOptions {
11115                ..Default::default()
11116            }),
11117            ..Default::default()
11118        },
11119        cx,
11120    )
11121    .await;
11122
11123    let language = Language::new(
11124        LanguageConfig {
11125            name: "Rust".into(),
11126            brackets: BracketPairConfig {
11127                pairs: vec![
11128                    BracketPair {
11129                        start: "{".to_string(),
11130                        end: "}".to_string(),
11131                        close: true,
11132                        surround: true,
11133                        newline: true,
11134                    },
11135                    BracketPair {
11136                        start: "(".to_string(),
11137                        end: ")".to_string(),
11138                        close: true,
11139                        surround: true,
11140                        newline: true,
11141                    },
11142                    BracketPair {
11143                        start: "/*".to_string(),
11144                        end: " */".to_string(),
11145                        close: true,
11146                        surround: true,
11147                        newline: true,
11148                    },
11149                    BracketPair {
11150                        start: "[".to_string(),
11151                        end: "]".to_string(),
11152                        close: false,
11153                        surround: false,
11154                        newline: true,
11155                    },
11156                    BracketPair {
11157                        start: "\"".to_string(),
11158                        end: "\"".to_string(),
11159                        close: true,
11160                        surround: true,
11161                        newline: false,
11162                    },
11163                    BracketPair {
11164                        start: "<".to_string(),
11165                        end: ">".to_string(),
11166                        close: false,
11167                        surround: true,
11168                        newline: true,
11169                    },
11170                ],
11171                ..Default::default()
11172            },
11173            autoclose_before: "})]".to_string(),
11174            ..Default::default()
11175        },
11176        Some(tree_sitter_rust::LANGUAGE.into()),
11177    );
11178    let language = Arc::new(language);
11179
11180    cx.language_registry().add(language.clone());
11181    cx.update_buffer(|buffer, cx| {
11182        buffer.set_language(Some(language), cx);
11183    });
11184
11185    // Ensure that signature_help is not called when no signature help is enabled.
11186    cx.set_state(
11187        &r#"
11188            fn main() {
11189                sampleˇ
11190            }
11191        "#
11192        .unindent(),
11193    );
11194    cx.update_editor(|editor, window, cx| {
11195        editor.handle_input("(", window, cx);
11196    });
11197    cx.assert_editor_state(
11198        &"
11199            fn main() {
11200                sample(ˇ)
11201            }
11202        "
11203        .unindent(),
11204    );
11205    cx.editor(|editor, _, _| {
11206        assert!(editor.signature_help_state.task().is_none());
11207    });
11208
11209    let mocked_response = lsp::SignatureHelp {
11210        signatures: vec![lsp::SignatureInformation {
11211            label: "fn sample(param1: u8, param2: u8)".to_string(),
11212            documentation: None,
11213            parameters: Some(vec![
11214                lsp::ParameterInformation {
11215                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11216                    documentation: None,
11217                },
11218                lsp::ParameterInformation {
11219                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11220                    documentation: None,
11221                },
11222            ]),
11223            active_parameter: None,
11224        }],
11225        active_signature: Some(0),
11226        active_parameter: Some(0),
11227    };
11228
11229    // Ensure that signature_help is called when enabled afte edits
11230    cx.update(|_, cx| {
11231        cx.update_global::<SettingsStore, _>(|settings, cx| {
11232            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11233                settings.auto_signature_help = Some(false);
11234                settings.show_signature_help_after_edits = Some(true);
11235            });
11236        });
11237    });
11238    cx.set_state(
11239        &r#"
11240            fn main() {
11241                sampleˇ
11242            }
11243        "#
11244        .unindent(),
11245    );
11246    cx.update_editor(|editor, window, cx| {
11247        editor.handle_input("(", window, cx);
11248    });
11249    cx.assert_editor_state(
11250        &"
11251            fn main() {
11252                sample(ˇ)
11253            }
11254        "
11255        .unindent(),
11256    );
11257    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11258    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11259        .await;
11260    cx.update_editor(|editor, _, _| {
11261        let signature_help_state = editor.signature_help_state.popover().cloned();
11262        assert!(signature_help_state.is_some());
11263        let signature = signature_help_state.unwrap();
11264        assert_eq!(
11265            signature.signatures[signature.current_signature].label,
11266            "fn sample(param1: u8, param2: u8)"
11267        );
11268        editor.signature_help_state = SignatureHelpState::default();
11269    });
11270
11271    // Ensure that signature_help is called when auto signature help override is enabled
11272    cx.update(|_, cx| {
11273        cx.update_global::<SettingsStore, _>(|settings, cx| {
11274            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11275                settings.auto_signature_help = Some(true);
11276                settings.show_signature_help_after_edits = Some(false);
11277            });
11278        });
11279    });
11280    cx.set_state(
11281        &r#"
11282            fn main() {
11283                sampleˇ
11284            }
11285        "#
11286        .unindent(),
11287    );
11288    cx.update_editor(|editor, window, cx| {
11289        editor.handle_input("(", window, cx);
11290    });
11291    cx.assert_editor_state(
11292        &"
11293            fn main() {
11294                sample(ˇ)
11295            }
11296        "
11297        .unindent(),
11298    );
11299    handle_signature_help_request(&mut cx, mocked_response).await;
11300    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11301        .await;
11302    cx.editor(|editor, _, _| {
11303        let signature_help_state = editor.signature_help_state.popover().cloned();
11304        assert!(signature_help_state.is_some());
11305        let signature = signature_help_state.unwrap();
11306        assert_eq!(
11307            signature.signatures[signature.current_signature].label,
11308            "fn sample(param1: u8, param2: u8)"
11309        );
11310    });
11311}
11312
11313#[gpui::test]
11314async fn test_signature_help(cx: &mut TestAppContext) {
11315    init_test(cx, |_| {});
11316    cx.update(|cx| {
11317        cx.update_global::<SettingsStore, _>(|settings, cx| {
11318            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11319                settings.auto_signature_help = Some(true);
11320            });
11321        });
11322    });
11323
11324    let mut cx = EditorLspTestContext::new_rust(
11325        lsp::ServerCapabilities {
11326            signature_help_provider: Some(lsp::SignatureHelpOptions {
11327                ..Default::default()
11328            }),
11329            ..Default::default()
11330        },
11331        cx,
11332    )
11333    .await;
11334
11335    // A test that directly calls `show_signature_help`
11336    cx.update_editor(|editor, window, cx| {
11337        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11338    });
11339
11340    let mocked_response = lsp::SignatureHelp {
11341        signatures: vec![lsp::SignatureInformation {
11342            label: "fn sample(param1: u8, param2: u8)".to_string(),
11343            documentation: None,
11344            parameters: Some(vec![
11345                lsp::ParameterInformation {
11346                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11347                    documentation: None,
11348                },
11349                lsp::ParameterInformation {
11350                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11351                    documentation: None,
11352                },
11353            ]),
11354            active_parameter: None,
11355        }],
11356        active_signature: Some(0),
11357        active_parameter: Some(0),
11358    };
11359    handle_signature_help_request(&mut cx, mocked_response).await;
11360
11361    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11362        .await;
11363
11364    cx.editor(|editor, _, _| {
11365        let signature_help_state = editor.signature_help_state.popover().cloned();
11366        assert!(signature_help_state.is_some());
11367        let signature = signature_help_state.unwrap();
11368        assert_eq!(
11369            signature.signatures[signature.current_signature].label,
11370            "fn sample(param1: u8, param2: u8)"
11371        );
11372    });
11373
11374    // When exiting outside from inside the brackets, `signature_help` is closed.
11375    cx.set_state(indoc! {"
11376        fn main() {
11377            sample(ˇ);
11378        }
11379
11380        fn sample(param1: u8, param2: u8) {}
11381    "});
11382
11383    cx.update_editor(|editor, window, cx| {
11384        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11385            s.select_ranges([0..0])
11386        });
11387    });
11388
11389    let mocked_response = lsp::SignatureHelp {
11390        signatures: Vec::new(),
11391        active_signature: None,
11392        active_parameter: None,
11393    };
11394    handle_signature_help_request(&mut cx, mocked_response).await;
11395
11396    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11397        .await;
11398
11399    cx.editor(|editor, _, _| {
11400        assert!(!editor.signature_help_state.is_shown());
11401    });
11402
11403    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
11404    cx.set_state(indoc! {"
11405        fn main() {
11406            sample(ˇ);
11407        }
11408
11409        fn sample(param1: u8, param2: u8) {}
11410    "});
11411
11412    let mocked_response = lsp::SignatureHelp {
11413        signatures: vec![lsp::SignatureInformation {
11414            label: "fn sample(param1: u8, param2: u8)".to_string(),
11415            documentation: None,
11416            parameters: Some(vec![
11417                lsp::ParameterInformation {
11418                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11419                    documentation: None,
11420                },
11421                lsp::ParameterInformation {
11422                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11423                    documentation: None,
11424                },
11425            ]),
11426            active_parameter: None,
11427        }],
11428        active_signature: Some(0),
11429        active_parameter: Some(0),
11430    };
11431    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11432    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11433        .await;
11434    cx.editor(|editor, _, _| {
11435        assert!(editor.signature_help_state.is_shown());
11436    });
11437
11438    // Restore the popover with more parameter input
11439    cx.set_state(indoc! {"
11440        fn main() {
11441            sample(param1, param2ˇ);
11442        }
11443
11444        fn sample(param1: u8, param2: u8) {}
11445    "});
11446
11447    let mocked_response = lsp::SignatureHelp {
11448        signatures: vec![lsp::SignatureInformation {
11449            label: "fn sample(param1: u8, param2: u8)".to_string(),
11450            documentation: None,
11451            parameters: Some(vec![
11452                lsp::ParameterInformation {
11453                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11454                    documentation: None,
11455                },
11456                lsp::ParameterInformation {
11457                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11458                    documentation: None,
11459                },
11460            ]),
11461            active_parameter: None,
11462        }],
11463        active_signature: Some(0),
11464        active_parameter: Some(1),
11465    };
11466    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11467    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11468        .await;
11469
11470    // When selecting a range, the popover is gone.
11471    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
11472    cx.update_editor(|editor, window, cx| {
11473        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11474            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11475        })
11476    });
11477    cx.assert_editor_state(indoc! {"
11478        fn main() {
11479            sample(param1, «ˇparam2»);
11480        }
11481
11482        fn sample(param1: u8, param2: u8) {}
11483    "});
11484    cx.editor(|editor, _, _| {
11485        assert!(!editor.signature_help_state.is_shown());
11486    });
11487
11488    // When unselecting again, the popover is back if within the brackets.
11489    cx.update_editor(|editor, window, cx| {
11490        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11491            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11492        })
11493    });
11494    cx.assert_editor_state(indoc! {"
11495        fn main() {
11496            sample(param1, ˇparam2);
11497        }
11498
11499        fn sample(param1: u8, param2: u8) {}
11500    "});
11501    handle_signature_help_request(&mut cx, mocked_response).await;
11502    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11503        .await;
11504    cx.editor(|editor, _, _| {
11505        assert!(editor.signature_help_state.is_shown());
11506    });
11507
11508    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
11509    cx.update_editor(|editor, window, cx| {
11510        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11511            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
11512            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11513        })
11514    });
11515    cx.assert_editor_state(indoc! {"
11516        fn main() {
11517            sample(param1, ˇparam2);
11518        }
11519
11520        fn sample(param1: u8, param2: u8) {}
11521    "});
11522
11523    let mocked_response = lsp::SignatureHelp {
11524        signatures: vec![lsp::SignatureInformation {
11525            label: "fn sample(param1: u8, param2: u8)".to_string(),
11526            documentation: None,
11527            parameters: Some(vec![
11528                lsp::ParameterInformation {
11529                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11530                    documentation: None,
11531                },
11532                lsp::ParameterInformation {
11533                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11534                    documentation: None,
11535                },
11536            ]),
11537            active_parameter: None,
11538        }],
11539        active_signature: Some(0),
11540        active_parameter: Some(1),
11541    };
11542    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11543    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11544        .await;
11545    cx.update_editor(|editor, _, cx| {
11546        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
11547    });
11548    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11549        .await;
11550    cx.update_editor(|editor, window, cx| {
11551        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11552            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11553        })
11554    });
11555    cx.assert_editor_state(indoc! {"
11556        fn main() {
11557            sample(param1, «ˇparam2»);
11558        }
11559
11560        fn sample(param1: u8, param2: u8) {}
11561    "});
11562    cx.update_editor(|editor, window, cx| {
11563        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11564            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11565        })
11566    });
11567    cx.assert_editor_state(indoc! {"
11568        fn main() {
11569            sample(param1, ˇparam2);
11570        }
11571
11572        fn sample(param1: u8, param2: u8) {}
11573    "});
11574    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
11575        .await;
11576}
11577
11578#[gpui::test]
11579async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
11580    init_test(cx, |_| {});
11581
11582    let mut cx = EditorLspTestContext::new_rust(
11583        lsp::ServerCapabilities {
11584            signature_help_provider: Some(lsp::SignatureHelpOptions {
11585                ..Default::default()
11586            }),
11587            ..Default::default()
11588        },
11589        cx,
11590    )
11591    .await;
11592
11593    cx.set_state(indoc! {"
11594        fn main() {
11595            overloadedˇ
11596        }
11597    "});
11598
11599    cx.update_editor(|editor, window, cx| {
11600        editor.handle_input("(", window, cx);
11601        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11602    });
11603
11604    // Mock response with 3 signatures
11605    let mocked_response = lsp::SignatureHelp {
11606        signatures: vec![
11607            lsp::SignatureInformation {
11608                label: "fn overloaded(x: i32)".to_string(),
11609                documentation: None,
11610                parameters: Some(vec![lsp::ParameterInformation {
11611                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11612                    documentation: None,
11613                }]),
11614                active_parameter: None,
11615            },
11616            lsp::SignatureInformation {
11617                label: "fn overloaded(x: i32, y: i32)".to_string(),
11618                documentation: None,
11619                parameters: Some(vec![
11620                    lsp::ParameterInformation {
11621                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11622                        documentation: None,
11623                    },
11624                    lsp::ParameterInformation {
11625                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
11626                        documentation: None,
11627                    },
11628                ]),
11629                active_parameter: None,
11630            },
11631            lsp::SignatureInformation {
11632                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
11633                documentation: None,
11634                parameters: Some(vec![
11635                    lsp::ParameterInformation {
11636                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11637                        documentation: None,
11638                    },
11639                    lsp::ParameterInformation {
11640                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
11641                        documentation: None,
11642                    },
11643                    lsp::ParameterInformation {
11644                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
11645                        documentation: None,
11646                    },
11647                ]),
11648                active_parameter: None,
11649            },
11650        ],
11651        active_signature: Some(1),
11652        active_parameter: Some(0),
11653    };
11654    handle_signature_help_request(&mut cx, mocked_response).await;
11655
11656    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11657        .await;
11658
11659    // Verify we have multiple signatures and the right one is selected
11660    cx.editor(|editor, _, _| {
11661        let popover = editor.signature_help_state.popover().cloned().unwrap();
11662        assert_eq!(popover.signatures.len(), 3);
11663        // active_signature was 1, so that should be the current
11664        assert_eq!(popover.current_signature, 1);
11665        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
11666        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
11667        assert_eq!(
11668            popover.signatures[2].label,
11669            "fn overloaded(x: i32, y: i32, z: i32)"
11670        );
11671    });
11672
11673    // Test navigation functionality
11674    cx.update_editor(|editor, window, cx| {
11675        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
11676    });
11677
11678    cx.editor(|editor, _, _| {
11679        let popover = editor.signature_help_state.popover().cloned().unwrap();
11680        assert_eq!(popover.current_signature, 2);
11681    });
11682
11683    // Test wrap around
11684    cx.update_editor(|editor, window, cx| {
11685        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
11686    });
11687
11688    cx.editor(|editor, _, _| {
11689        let popover = editor.signature_help_state.popover().cloned().unwrap();
11690        assert_eq!(popover.current_signature, 0);
11691    });
11692
11693    // Test previous navigation
11694    cx.update_editor(|editor, window, cx| {
11695        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
11696    });
11697
11698    cx.editor(|editor, _, _| {
11699        let popover = editor.signature_help_state.popover().cloned().unwrap();
11700        assert_eq!(popover.current_signature, 2);
11701    });
11702}
11703
11704#[gpui::test]
11705async fn test_completion_mode(cx: &mut TestAppContext) {
11706    init_test(cx, |_| {});
11707    let mut cx = EditorLspTestContext::new_rust(
11708        lsp::ServerCapabilities {
11709            completion_provider: Some(lsp::CompletionOptions {
11710                resolve_provider: Some(true),
11711                ..Default::default()
11712            }),
11713            ..Default::default()
11714        },
11715        cx,
11716    )
11717    .await;
11718
11719    struct Run {
11720        run_description: &'static str,
11721        initial_state: String,
11722        buffer_marked_text: String,
11723        completion_label: &'static str,
11724        completion_text: &'static str,
11725        expected_with_insert_mode: String,
11726        expected_with_replace_mode: String,
11727        expected_with_replace_subsequence_mode: String,
11728        expected_with_replace_suffix_mode: String,
11729    }
11730
11731    let runs = [
11732        Run {
11733            run_description: "Start of word matches completion text",
11734            initial_state: "before ediˇ after".into(),
11735            buffer_marked_text: "before <edi|> after".into(),
11736            completion_label: "editor",
11737            completion_text: "editor",
11738            expected_with_insert_mode: "before editorˇ after".into(),
11739            expected_with_replace_mode: "before editorˇ after".into(),
11740            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11741            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11742        },
11743        Run {
11744            run_description: "Accept same text at the middle of the word",
11745            initial_state: "before ediˇtor after".into(),
11746            buffer_marked_text: "before <edi|tor> after".into(),
11747            completion_label: "editor",
11748            completion_text: "editor",
11749            expected_with_insert_mode: "before editorˇtor after".into(),
11750            expected_with_replace_mode: "before editorˇ after".into(),
11751            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11752            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11753        },
11754        Run {
11755            run_description: "End of word matches completion text -- cursor at end",
11756            initial_state: "before torˇ after".into(),
11757            buffer_marked_text: "before <tor|> after".into(),
11758            completion_label: "editor",
11759            completion_text: "editor",
11760            expected_with_insert_mode: "before editorˇ after".into(),
11761            expected_with_replace_mode: "before editorˇ after".into(),
11762            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11763            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11764        },
11765        Run {
11766            run_description: "End of word matches completion text -- cursor at start",
11767            initial_state: "before ˇtor after".into(),
11768            buffer_marked_text: "before <|tor> after".into(),
11769            completion_label: "editor",
11770            completion_text: "editor",
11771            expected_with_insert_mode: "before editorˇtor after".into(),
11772            expected_with_replace_mode: "before editorˇ after".into(),
11773            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11774            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11775        },
11776        Run {
11777            run_description: "Prepend text containing whitespace",
11778            initial_state: "pˇfield: bool".into(),
11779            buffer_marked_text: "<p|field>: bool".into(),
11780            completion_label: "pub ",
11781            completion_text: "pub ",
11782            expected_with_insert_mode: "pub ˇfield: bool".into(),
11783            expected_with_replace_mode: "pub ˇ: bool".into(),
11784            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
11785            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
11786        },
11787        Run {
11788            run_description: "Add element to start of list",
11789            initial_state: "[element_ˇelement_2]".into(),
11790            buffer_marked_text: "[<element_|element_2>]".into(),
11791            completion_label: "element_1",
11792            completion_text: "element_1",
11793            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
11794            expected_with_replace_mode: "[element_1ˇ]".into(),
11795            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
11796            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
11797        },
11798        Run {
11799            run_description: "Add element to start of list -- first and second elements are equal",
11800            initial_state: "[elˇelement]".into(),
11801            buffer_marked_text: "[<el|element>]".into(),
11802            completion_label: "element",
11803            completion_text: "element",
11804            expected_with_insert_mode: "[elementˇelement]".into(),
11805            expected_with_replace_mode: "[elementˇ]".into(),
11806            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
11807            expected_with_replace_suffix_mode: "[elementˇ]".into(),
11808        },
11809        Run {
11810            run_description: "Ends with matching suffix",
11811            initial_state: "SubˇError".into(),
11812            buffer_marked_text: "<Sub|Error>".into(),
11813            completion_label: "SubscriptionError",
11814            completion_text: "SubscriptionError",
11815            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
11816            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11817            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11818            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
11819        },
11820        Run {
11821            run_description: "Suffix is a subsequence -- contiguous",
11822            initial_state: "SubˇErr".into(),
11823            buffer_marked_text: "<Sub|Err>".into(),
11824            completion_label: "SubscriptionError",
11825            completion_text: "SubscriptionError",
11826            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
11827            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11828            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11829            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
11830        },
11831        Run {
11832            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
11833            initial_state: "Suˇscrirr".into(),
11834            buffer_marked_text: "<Su|scrirr>".into(),
11835            completion_label: "SubscriptionError",
11836            completion_text: "SubscriptionError",
11837            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
11838            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11839            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11840            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
11841        },
11842        Run {
11843            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
11844            initial_state: "foo(indˇix)".into(),
11845            buffer_marked_text: "foo(<ind|ix>)".into(),
11846            completion_label: "node_index",
11847            completion_text: "node_index",
11848            expected_with_insert_mode: "foo(node_indexˇix)".into(),
11849            expected_with_replace_mode: "foo(node_indexˇ)".into(),
11850            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
11851            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
11852        },
11853        Run {
11854            run_description: "Replace range ends before cursor - should extend to cursor",
11855            initial_state: "before editˇo after".into(),
11856            buffer_marked_text: "before <{ed}>it|o after".into(),
11857            completion_label: "editor",
11858            completion_text: "editor",
11859            expected_with_insert_mode: "before editorˇo after".into(),
11860            expected_with_replace_mode: "before editorˇo after".into(),
11861            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
11862            expected_with_replace_suffix_mode: "before editorˇo after".into(),
11863        },
11864        Run {
11865            run_description: "Uses label for suffix matching",
11866            initial_state: "before ediˇtor after".into(),
11867            buffer_marked_text: "before <edi|tor> after".into(),
11868            completion_label: "editor",
11869            completion_text: "editor()",
11870            expected_with_insert_mode: "before editor()ˇtor after".into(),
11871            expected_with_replace_mode: "before editor()ˇ after".into(),
11872            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
11873            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
11874        },
11875        Run {
11876            run_description: "Case insensitive subsequence and suffix matching",
11877            initial_state: "before EDiˇtoR after".into(),
11878            buffer_marked_text: "before <EDi|toR> after".into(),
11879            completion_label: "editor",
11880            completion_text: "editor",
11881            expected_with_insert_mode: "before editorˇtoR after".into(),
11882            expected_with_replace_mode: "before editorˇ after".into(),
11883            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11884            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11885        },
11886    ];
11887
11888    for run in runs {
11889        let run_variations = [
11890            (LspInsertMode::Insert, run.expected_with_insert_mode),
11891            (LspInsertMode::Replace, run.expected_with_replace_mode),
11892            (
11893                LspInsertMode::ReplaceSubsequence,
11894                run.expected_with_replace_subsequence_mode,
11895            ),
11896            (
11897                LspInsertMode::ReplaceSuffix,
11898                run.expected_with_replace_suffix_mode,
11899            ),
11900        ];
11901
11902        for (lsp_insert_mode, expected_text) in run_variations {
11903            eprintln!(
11904                "run = {:?}, mode = {lsp_insert_mode:.?}",
11905                run.run_description,
11906            );
11907
11908            update_test_language_settings(&mut cx, |settings| {
11909                settings.defaults.completions = Some(CompletionSettings {
11910                    lsp_insert_mode,
11911                    words: WordsCompletionMode::Disabled,
11912                    lsp: true,
11913                    lsp_fetch_timeout_ms: 0,
11914                });
11915            });
11916
11917            cx.set_state(&run.initial_state);
11918            cx.update_editor(|editor, window, cx| {
11919                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11920            });
11921
11922            let counter = Arc::new(AtomicUsize::new(0));
11923            handle_completion_request_with_insert_and_replace(
11924                &mut cx,
11925                &run.buffer_marked_text,
11926                vec![(run.completion_label, run.completion_text)],
11927                counter.clone(),
11928            )
11929            .await;
11930            cx.condition(|editor, _| editor.context_menu_visible())
11931                .await;
11932            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11933
11934            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11935                editor
11936                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
11937                    .unwrap()
11938            });
11939            cx.assert_editor_state(&expected_text);
11940            handle_resolve_completion_request(&mut cx, None).await;
11941            apply_additional_edits.await.unwrap();
11942        }
11943    }
11944}
11945
11946#[gpui::test]
11947async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
11948    init_test(cx, |_| {});
11949    let mut cx = EditorLspTestContext::new_rust(
11950        lsp::ServerCapabilities {
11951            completion_provider: Some(lsp::CompletionOptions {
11952                resolve_provider: Some(true),
11953                ..Default::default()
11954            }),
11955            ..Default::default()
11956        },
11957        cx,
11958    )
11959    .await;
11960
11961    let initial_state = "SubˇError";
11962    let buffer_marked_text = "<Sub|Error>";
11963    let completion_text = "SubscriptionError";
11964    let expected_with_insert_mode = "SubscriptionErrorˇError";
11965    let expected_with_replace_mode = "SubscriptionErrorˇ";
11966
11967    update_test_language_settings(&mut cx, |settings| {
11968        settings.defaults.completions = Some(CompletionSettings {
11969            words: WordsCompletionMode::Disabled,
11970            // set the opposite here to ensure that the action is overriding the default behavior
11971            lsp_insert_mode: LspInsertMode::Insert,
11972            lsp: true,
11973            lsp_fetch_timeout_ms: 0,
11974        });
11975    });
11976
11977    cx.set_state(initial_state);
11978    cx.update_editor(|editor, window, cx| {
11979        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11980    });
11981
11982    let counter = Arc::new(AtomicUsize::new(0));
11983    handle_completion_request_with_insert_and_replace(
11984        &mut cx,
11985        &buffer_marked_text,
11986        vec![(completion_text, completion_text)],
11987        counter.clone(),
11988    )
11989    .await;
11990    cx.condition(|editor, _| editor.context_menu_visible())
11991        .await;
11992    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11993
11994    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11995        editor
11996            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11997            .unwrap()
11998    });
11999    cx.assert_editor_state(&expected_with_replace_mode);
12000    handle_resolve_completion_request(&mut cx, None).await;
12001    apply_additional_edits.await.unwrap();
12002
12003    update_test_language_settings(&mut cx, |settings| {
12004        settings.defaults.completions = Some(CompletionSettings {
12005            words: WordsCompletionMode::Disabled,
12006            // set the opposite here to ensure that the action is overriding the default behavior
12007            lsp_insert_mode: LspInsertMode::Replace,
12008            lsp: true,
12009            lsp_fetch_timeout_ms: 0,
12010        });
12011    });
12012
12013    cx.set_state(initial_state);
12014    cx.update_editor(|editor, window, cx| {
12015        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12016    });
12017    handle_completion_request_with_insert_and_replace(
12018        &mut cx,
12019        &buffer_marked_text,
12020        vec![(completion_text, completion_text)],
12021        counter.clone(),
12022    )
12023    .await;
12024    cx.condition(|editor, _| editor.context_menu_visible())
12025        .await;
12026    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12027
12028    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12029        editor
12030            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
12031            .unwrap()
12032    });
12033    cx.assert_editor_state(&expected_with_insert_mode);
12034    handle_resolve_completion_request(&mut cx, None).await;
12035    apply_additional_edits.await.unwrap();
12036}
12037
12038#[gpui::test]
12039async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
12040    init_test(cx, |_| {});
12041    let mut cx = EditorLspTestContext::new_rust(
12042        lsp::ServerCapabilities {
12043            completion_provider: Some(lsp::CompletionOptions {
12044                resolve_provider: Some(true),
12045                ..Default::default()
12046            }),
12047            ..Default::default()
12048        },
12049        cx,
12050    )
12051    .await;
12052
12053    // scenario: surrounding text matches completion text
12054    let completion_text = "to_offset";
12055    let initial_state = indoc! {"
12056        1. buf.to_offˇsuffix
12057        2. buf.to_offˇsuf
12058        3. buf.to_offˇfix
12059        4. buf.to_offˇ
12060        5. into_offˇensive
12061        6. ˇsuffix
12062        7. let ˇ //
12063        8. aaˇzz
12064        9. buf.to_off«zzzzzˇ»suffix
12065        10. buf.«ˇzzzzz»suffix
12066        11. to_off«ˇzzzzz»
12067
12068        buf.to_offˇsuffix  // newest cursor
12069    "};
12070    let completion_marked_buffer = indoc! {"
12071        1. buf.to_offsuffix
12072        2. buf.to_offsuf
12073        3. buf.to_offfix
12074        4. buf.to_off
12075        5. into_offensive
12076        6. suffix
12077        7. let  //
12078        8. aazz
12079        9. buf.to_offzzzzzsuffix
12080        10. buf.zzzzzsuffix
12081        11. to_offzzzzz
12082
12083        buf.<to_off|suffix>  // newest cursor
12084    "};
12085    let expected = indoc! {"
12086        1. buf.to_offsetˇ
12087        2. buf.to_offsetˇsuf
12088        3. buf.to_offsetˇfix
12089        4. buf.to_offsetˇ
12090        5. into_offsetˇensive
12091        6. to_offsetˇsuffix
12092        7. let to_offsetˇ //
12093        8. aato_offsetˇzz
12094        9. buf.to_offsetˇ
12095        10. buf.to_offsetˇsuffix
12096        11. to_offsetˇ
12097
12098        buf.to_offsetˇ  // newest cursor
12099    "};
12100    cx.set_state(initial_state);
12101    cx.update_editor(|editor, window, cx| {
12102        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12103    });
12104    handle_completion_request_with_insert_and_replace(
12105        &mut cx,
12106        completion_marked_buffer,
12107        vec![(completion_text, completion_text)],
12108        Arc::new(AtomicUsize::new(0)),
12109    )
12110    .await;
12111    cx.condition(|editor, _| editor.context_menu_visible())
12112        .await;
12113    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12114        editor
12115            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12116            .unwrap()
12117    });
12118    cx.assert_editor_state(expected);
12119    handle_resolve_completion_request(&mut cx, None).await;
12120    apply_additional_edits.await.unwrap();
12121
12122    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
12123    let completion_text = "foo_and_bar";
12124    let initial_state = indoc! {"
12125        1. ooanbˇ
12126        2. zooanbˇ
12127        3. ooanbˇz
12128        4. zooanbˇz
12129        5. ooanˇ
12130        6. oanbˇ
12131
12132        ooanbˇ
12133    "};
12134    let completion_marked_buffer = indoc! {"
12135        1. ooanb
12136        2. zooanb
12137        3. ooanbz
12138        4. zooanbz
12139        5. ooan
12140        6. oanb
12141
12142        <ooanb|>
12143    "};
12144    let expected = indoc! {"
12145        1. foo_and_barˇ
12146        2. zfoo_and_barˇ
12147        3. foo_and_barˇz
12148        4. zfoo_and_barˇz
12149        5. ooanfoo_and_barˇ
12150        6. oanbfoo_and_barˇ
12151
12152        foo_and_barˇ
12153    "};
12154    cx.set_state(initial_state);
12155    cx.update_editor(|editor, window, cx| {
12156        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12157    });
12158    handle_completion_request_with_insert_and_replace(
12159        &mut cx,
12160        completion_marked_buffer,
12161        vec![(completion_text, completion_text)],
12162        Arc::new(AtomicUsize::new(0)),
12163    )
12164    .await;
12165    cx.condition(|editor, _| editor.context_menu_visible())
12166        .await;
12167    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12168        editor
12169            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12170            .unwrap()
12171    });
12172    cx.assert_editor_state(expected);
12173    handle_resolve_completion_request(&mut cx, None).await;
12174    apply_additional_edits.await.unwrap();
12175
12176    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
12177    // (expects the same as if it was inserted at the end)
12178    let completion_text = "foo_and_bar";
12179    let initial_state = indoc! {"
12180        1. ooˇanb
12181        2. zooˇanb
12182        3. ooˇanbz
12183        4. zooˇanbz
12184
12185        ooˇanb
12186    "};
12187    let completion_marked_buffer = indoc! {"
12188        1. ooanb
12189        2. zooanb
12190        3. ooanbz
12191        4. zooanbz
12192
12193        <oo|anb>
12194    "};
12195    let expected = indoc! {"
12196        1. foo_and_barˇ
12197        2. zfoo_and_barˇ
12198        3. foo_and_barˇz
12199        4. zfoo_and_barˇz
12200
12201        foo_and_barˇ
12202    "};
12203    cx.set_state(initial_state);
12204    cx.update_editor(|editor, window, cx| {
12205        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12206    });
12207    handle_completion_request_with_insert_and_replace(
12208        &mut cx,
12209        completion_marked_buffer,
12210        vec![(completion_text, completion_text)],
12211        Arc::new(AtomicUsize::new(0)),
12212    )
12213    .await;
12214    cx.condition(|editor, _| editor.context_menu_visible())
12215        .await;
12216    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12217        editor
12218            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12219            .unwrap()
12220    });
12221    cx.assert_editor_state(expected);
12222    handle_resolve_completion_request(&mut cx, None).await;
12223    apply_additional_edits.await.unwrap();
12224}
12225
12226// This used to crash
12227#[gpui::test]
12228async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
12229    init_test(cx, |_| {});
12230
12231    let buffer_text = indoc! {"
12232        fn main() {
12233            10.satu;
12234
12235            //
12236            // separate cursors so they open in different excerpts (manually reproducible)
12237            //
12238
12239            10.satu20;
12240        }
12241    "};
12242    let multibuffer_text_with_selections = indoc! {"
12243        fn main() {
12244            10.satuˇ;
12245
12246            //
12247
12248            //
12249
12250            10.satuˇ20;
12251        }
12252    "};
12253    let expected_multibuffer = indoc! {"
12254        fn main() {
12255            10.saturating_sub()ˇ;
12256
12257            //
12258
12259            //
12260
12261            10.saturating_sub()ˇ;
12262        }
12263    "};
12264
12265    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
12266    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
12267
12268    let fs = FakeFs::new(cx.executor());
12269    fs.insert_tree(
12270        path!("/a"),
12271        json!({
12272            "main.rs": buffer_text,
12273        }),
12274    )
12275    .await;
12276
12277    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12278    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12279    language_registry.add(rust_lang());
12280    let mut fake_servers = language_registry.register_fake_lsp(
12281        "Rust",
12282        FakeLspAdapter {
12283            capabilities: lsp::ServerCapabilities {
12284                completion_provider: Some(lsp::CompletionOptions {
12285                    resolve_provider: None,
12286                    ..lsp::CompletionOptions::default()
12287                }),
12288                ..lsp::ServerCapabilities::default()
12289            },
12290            ..FakeLspAdapter::default()
12291        },
12292    );
12293    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12294    let cx = &mut VisualTestContext::from_window(*workspace, cx);
12295    let buffer = project
12296        .update(cx, |project, cx| {
12297            project.open_local_buffer(path!("/a/main.rs"), cx)
12298        })
12299        .await
12300        .unwrap();
12301
12302    let multi_buffer = cx.new(|cx| {
12303        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
12304        multi_buffer.push_excerpts(
12305            buffer.clone(),
12306            [ExcerptRange::new(0..first_excerpt_end)],
12307            cx,
12308        );
12309        multi_buffer.push_excerpts(
12310            buffer.clone(),
12311            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
12312            cx,
12313        );
12314        multi_buffer
12315    });
12316
12317    let editor = workspace
12318        .update(cx, |_, window, cx| {
12319            cx.new(|cx| {
12320                Editor::new(
12321                    EditorMode::Full {
12322                        scale_ui_elements_with_buffer_font_size: false,
12323                        show_active_line_background: false,
12324                        sized_by_content: false,
12325                    },
12326                    multi_buffer.clone(),
12327                    Some(project.clone()),
12328                    window,
12329                    cx,
12330                )
12331            })
12332        })
12333        .unwrap();
12334
12335    let pane = workspace
12336        .update(cx, |workspace, _, _| workspace.active_pane().clone())
12337        .unwrap();
12338    pane.update_in(cx, |pane, window, cx| {
12339        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
12340    });
12341
12342    let fake_server = fake_servers.next().await.unwrap();
12343
12344    editor.update_in(cx, |editor, window, cx| {
12345        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12346            s.select_ranges([
12347                Point::new(1, 11)..Point::new(1, 11),
12348                Point::new(7, 11)..Point::new(7, 11),
12349            ])
12350        });
12351
12352        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
12353    });
12354
12355    editor.update_in(cx, |editor, window, cx| {
12356        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12357    });
12358
12359    fake_server
12360        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
12361            let completion_item = lsp::CompletionItem {
12362                label: "saturating_sub()".into(),
12363                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
12364                    lsp::InsertReplaceEdit {
12365                        new_text: "saturating_sub()".to_owned(),
12366                        insert: lsp::Range::new(
12367                            lsp::Position::new(7, 7),
12368                            lsp::Position::new(7, 11),
12369                        ),
12370                        replace: lsp::Range::new(
12371                            lsp::Position::new(7, 7),
12372                            lsp::Position::new(7, 13),
12373                        ),
12374                    },
12375                )),
12376                ..lsp::CompletionItem::default()
12377            };
12378
12379            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
12380        })
12381        .next()
12382        .await
12383        .unwrap();
12384
12385    cx.condition(&editor, |editor, _| editor.context_menu_visible())
12386        .await;
12387
12388    editor
12389        .update_in(cx, |editor, window, cx| {
12390            editor
12391                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12392                .unwrap()
12393        })
12394        .await
12395        .unwrap();
12396
12397    editor.update(cx, |editor, cx| {
12398        assert_text_with_selections(editor, expected_multibuffer, cx);
12399    })
12400}
12401
12402#[gpui::test]
12403async fn test_completion(cx: &mut TestAppContext) {
12404    init_test(cx, |_| {});
12405
12406    let mut cx = EditorLspTestContext::new_rust(
12407        lsp::ServerCapabilities {
12408            completion_provider: Some(lsp::CompletionOptions {
12409                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12410                resolve_provider: Some(true),
12411                ..Default::default()
12412            }),
12413            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12414            ..Default::default()
12415        },
12416        cx,
12417    )
12418    .await;
12419    let counter = Arc::new(AtomicUsize::new(0));
12420
12421    cx.set_state(indoc! {"
12422        oneˇ
12423        two
12424        three
12425    "});
12426    cx.simulate_keystroke(".");
12427    handle_completion_request(
12428        indoc! {"
12429            one.|<>
12430            two
12431            three
12432        "},
12433        vec!["first_completion", "second_completion"],
12434        true,
12435        counter.clone(),
12436        &mut cx,
12437    )
12438    .await;
12439    cx.condition(|editor, _| editor.context_menu_visible())
12440        .await;
12441    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12442
12443    let _handler = handle_signature_help_request(
12444        &mut cx,
12445        lsp::SignatureHelp {
12446            signatures: vec![lsp::SignatureInformation {
12447                label: "test signature".to_string(),
12448                documentation: None,
12449                parameters: Some(vec![lsp::ParameterInformation {
12450                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
12451                    documentation: None,
12452                }]),
12453                active_parameter: None,
12454            }],
12455            active_signature: None,
12456            active_parameter: None,
12457        },
12458    );
12459    cx.update_editor(|editor, window, cx| {
12460        assert!(
12461            !editor.signature_help_state.is_shown(),
12462            "No signature help was called for"
12463        );
12464        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12465    });
12466    cx.run_until_parked();
12467    cx.update_editor(|editor, _, _| {
12468        assert!(
12469            !editor.signature_help_state.is_shown(),
12470            "No signature help should be shown when completions menu is open"
12471        );
12472    });
12473
12474    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12475        editor.context_menu_next(&Default::default(), window, cx);
12476        editor
12477            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12478            .unwrap()
12479    });
12480    cx.assert_editor_state(indoc! {"
12481        one.second_completionˇ
12482        two
12483        three
12484    "});
12485
12486    handle_resolve_completion_request(
12487        &mut cx,
12488        Some(vec![
12489            (
12490                //This overlaps with the primary completion edit which is
12491                //misbehavior from the LSP spec, test that we filter it out
12492                indoc! {"
12493                    one.second_ˇcompletion
12494                    two
12495                    threeˇ
12496                "},
12497                "overlapping additional edit",
12498            ),
12499            (
12500                indoc! {"
12501                    one.second_completion
12502                    two
12503                    threeˇ
12504                "},
12505                "\nadditional edit",
12506            ),
12507        ]),
12508    )
12509    .await;
12510    apply_additional_edits.await.unwrap();
12511    cx.assert_editor_state(indoc! {"
12512        one.second_completionˇ
12513        two
12514        three
12515        additional edit
12516    "});
12517
12518    cx.set_state(indoc! {"
12519        one.second_completion
12520        twoˇ
12521        threeˇ
12522        additional edit
12523    "});
12524    cx.simulate_keystroke(" ");
12525    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12526    cx.simulate_keystroke("s");
12527    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12528
12529    cx.assert_editor_state(indoc! {"
12530        one.second_completion
12531        two sˇ
12532        three sˇ
12533        additional edit
12534    "});
12535    handle_completion_request(
12536        indoc! {"
12537            one.second_completion
12538            two s
12539            three <s|>
12540            additional edit
12541        "},
12542        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
12543        true,
12544        counter.clone(),
12545        &mut cx,
12546    )
12547    .await;
12548    cx.condition(|editor, _| editor.context_menu_visible())
12549        .await;
12550    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12551
12552    cx.simulate_keystroke("i");
12553
12554    handle_completion_request(
12555        indoc! {"
12556            one.second_completion
12557            two si
12558            three <si|>
12559            additional edit
12560        "},
12561        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
12562        true,
12563        counter.clone(),
12564        &mut cx,
12565    )
12566    .await;
12567    cx.condition(|editor, _| editor.context_menu_visible())
12568        .await;
12569    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12570
12571    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12572        editor
12573            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12574            .unwrap()
12575    });
12576    cx.assert_editor_state(indoc! {"
12577        one.second_completion
12578        two sixth_completionˇ
12579        three sixth_completionˇ
12580        additional edit
12581    "});
12582
12583    apply_additional_edits.await.unwrap();
12584
12585    update_test_language_settings(&mut cx, |settings| {
12586        settings.defaults.show_completions_on_input = Some(false);
12587    });
12588    cx.set_state("editorˇ");
12589    cx.simulate_keystroke(".");
12590    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12591    cx.simulate_keystrokes("c l o");
12592    cx.assert_editor_state("editor.cloˇ");
12593    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12594    cx.update_editor(|editor, window, cx| {
12595        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12596    });
12597    handle_completion_request(
12598        "editor.<clo|>",
12599        vec!["close", "clobber"],
12600        true,
12601        counter.clone(),
12602        &mut cx,
12603    )
12604    .await;
12605    cx.condition(|editor, _| editor.context_menu_visible())
12606        .await;
12607    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
12608
12609    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12610        editor
12611            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12612            .unwrap()
12613    });
12614    cx.assert_editor_state("editor.clobberˇ");
12615    handle_resolve_completion_request(&mut cx, None).await;
12616    apply_additional_edits.await.unwrap();
12617}
12618
12619#[gpui::test]
12620async fn test_completion_reuse(cx: &mut TestAppContext) {
12621    init_test(cx, |_| {});
12622
12623    let mut cx = EditorLspTestContext::new_rust(
12624        lsp::ServerCapabilities {
12625            completion_provider: Some(lsp::CompletionOptions {
12626                trigger_characters: Some(vec![".".to_string()]),
12627                ..Default::default()
12628            }),
12629            ..Default::default()
12630        },
12631        cx,
12632    )
12633    .await;
12634
12635    let counter = Arc::new(AtomicUsize::new(0));
12636    cx.set_state("objˇ");
12637    cx.simulate_keystroke(".");
12638
12639    // Initial completion request returns complete results
12640    let is_incomplete = false;
12641    handle_completion_request(
12642        "obj.|<>",
12643        vec!["a", "ab", "abc"],
12644        is_incomplete,
12645        counter.clone(),
12646        &mut cx,
12647    )
12648    .await;
12649    cx.run_until_parked();
12650    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12651    cx.assert_editor_state("obj.ˇ");
12652    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12653
12654    // Type "a" - filters existing completions
12655    cx.simulate_keystroke("a");
12656    cx.run_until_parked();
12657    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12658    cx.assert_editor_state("obj.aˇ");
12659    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12660
12661    // Type "b" - filters existing completions
12662    cx.simulate_keystroke("b");
12663    cx.run_until_parked();
12664    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12665    cx.assert_editor_state("obj.abˇ");
12666    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12667
12668    // Type "c" - filters existing completions
12669    cx.simulate_keystroke("c");
12670    cx.run_until_parked();
12671    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12672    cx.assert_editor_state("obj.abcˇ");
12673    check_displayed_completions(vec!["abc"], &mut cx);
12674
12675    // Backspace to delete "c" - filters existing completions
12676    cx.update_editor(|editor, window, cx| {
12677        editor.backspace(&Backspace, window, cx);
12678    });
12679    cx.run_until_parked();
12680    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12681    cx.assert_editor_state("obj.abˇ");
12682    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12683
12684    // Moving cursor to the left dismisses menu.
12685    cx.update_editor(|editor, window, cx| {
12686        editor.move_left(&MoveLeft, window, cx);
12687    });
12688    cx.run_until_parked();
12689    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12690    cx.assert_editor_state("obj.aˇb");
12691    cx.update_editor(|editor, _, _| {
12692        assert_eq!(editor.context_menu_visible(), false);
12693    });
12694
12695    // Type "b" - new request
12696    cx.simulate_keystroke("b");
12697    let is_incomplete = false;
12698    handle_completion_request(
12699        "obj.<ab|>a",
12700        vec!["ab", "abc"],
12701        is_incomplete,
12702        counter.clone(),
12703        &mut cx,
12704    )
12705    .await;
12706    cx.run_until_parked();
12707    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12708    cx.assert_editor_state("obj.abˇb");
12709    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12710
12711    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
12712    cx.update_editor(|editor, window, cx| {
12713        editor.backspace(&Backspace, window, cx);
12714    });
12715    let is_incomplete = false;
12716    handle_completion_request(
12717        "obj.<a|>b",
12718        vec!["a", "ab", "abc"],
12719        is_incomplete,
12720        counter.clone(),
12721        &mut cx,
12722    )
12723    .await;
12724    cx.run_until_parked();
12725    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12726    cx.assert_editor_state("obj.aˇb");
12727    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12728
12729    // Backspace to delete "a" - dismisses menu.
12730    cx.update_editor(|editor, window, cx| {
12731        editor.backspace(&Backspace, window, cx);
12732    });
12733    cx.run_until_parked();
12734    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12735    cx.assert_editor_state("obj.ˇb");
12736    cx.update_editor(|editor, _, _| {
12737        assert_eq!(editor.context_menu_visible(), false);
12738    });
12739}
12740
12741#[gpui::test]
12742async fn test_word_completion(cx: &mut TestAppContext) {
12743    let lsp_fetch_timeout_ms = 10;
12744    init_test(cx, |language_settings| {
12745        language_settings.defaults.completions = Some(CompletionSettings {
12746            words: WordsCompletionMode::Fallback,
12747            lsp: true,
12748            lsp_fetch_timeout_ms: 10,
12749            lsp_insert_mode: LspInsertMode::Insert,
12750        });
12751    });
12752
12753    let mut cx = EditorLspTestContext::new_rust(
12754        lsp::ServerCapabilities {
12755            completion_provider: Some(lsp::CompletionOptions {
12756                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12757                ..lsp::CompletionOptions::default()
12758            }),
12759            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12760            ..lsp::ServerCapabilities::default()
12761        },
12762        cx,
12763    )
12764    .await;
12765
12766    let throttle_completions = Arc::new(AtomicBool::new(false));
12767
12768    let lsp_throttle_completions = throttle_completions.clone();
12769    let _completion_requests_handler =
12770        cx.lsp
12771            .server
12772            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
12773                let lsp_throttle_completions = lsp_throttle_completions.clone();
12774                let cx = cx.clone();
12775                async move {
12776                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
12777                        cx.background_executor()
12778                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
12779                            .await;
12780                    }
12781                    Ok(Some(lsp::CompletionResponse::Array(vec![
12782                        lsp::CompletionItem {
12783                            label: "first".into(),
12784                            ..lsp::CompletionItem::default()
12785                        },
12786                        lsp::CompletionItem {
12787                            label: "last".into(),
12788                            ..lsp::CompletionItem::default()
12789                        },
12790                    ])))
12791                }
12792            });
12793
12794    cx.set_state(indoc! {"
12795        oneˇ
12796        two
12797        three
12798    "});
12799    cx.simulate_keystroke(".");
12800    cx.executor().run_until_parked();
12801    cx.condition(|editor, _| editor.context_menu_visible())
12802        .await;
12803    cx.update_editor(|editor, window, cx| {
12804        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12805        {
12806            assert_eq!(
12807                completion_menu_entries(&menu),
12808                &["first", "last"],
12809                "When LSP server is fast to reply, no fallback word completions are used"
12810            );
12811        } else {
12812            panic!("expected completion menu to be open");
12813        }
12814        editor.cancel(&Cancel, window, cx);
12815    });
12816    cx.executor().run_until_parked();
12817    cx.condition(|editor, _| !editor.context_menu_visible())
12818        .await;
12819
12820    throttle_completions.store(true, atomic::Ordering::Release);
12821    cx.simulate_keystroke(".");
12822    cx.executor()
12823        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
12824    cx.executor().run_until_parked();
12825    cx.condition(|editor, _| editor.context_menu_visible())
12826        .await;
12827    cx.update_editor(|editor, _, _| {
12828        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12829        {
12830            assert_eq!(completion_menu_entries(&menu), &["one", "three", "two"],
12831                "When LSP server is slow, document words can be shown instead, if configured accordingly");
12832        } else {
12833            panic!("expected completion menu to be open");
12834        }
12835    });
12836}
12837
12838#[gpui::test]
12839async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
12840    init_test(cx, |language_settings| {
12841        language_settings.defaults.completions = Some(CompletionSettings {
12842            words: WordsCompletionMode::Enabled,
12843            lsp: true,
12844            lsp_fetch_timeout_ms: 0,
12845            lsp_insert_mode: LspInsertMode::Insert,
12846        });
12847    });
12848
12849    let mut cx = EditorLspTestContext::new_rust(
12850        lsp::ServerCapabilities {
12851            completion_provider: Some(lsp::CompletionOptions {
12852                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12853                ..lsp::CompletionOptions::default()
12854            }),
12855            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12856            ..lsp::ServerCapabilities::default()
12857        },
12858        cx,
12859    )
12860    .await;
12861
12862    let _completion_requests_handler =
12863        cx.lsp
12864            .server
12865            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12866                Ok(Some(lsp::CompletionResponse::Array(vec![
12867                    lsp::CompletionItem {
12868                        label: "first".into(),
12869                        ..lsp::CompletionItem::default()
12870                    },
12871                    lsp::CompletionItem {
12872                        label: "last".into(),
12873                        ..lsp::CompletionItem::default()
12874                    },
12875                ])))
12876            });
12877
12878    cx.set_state(indoc! {"ˇ
12879        first
12880        last
12881        second
12882    "});
12883    cx.simulate_keystroke(".");
12884    cx.executor().run_until_parked();
12885    cx.condition(|editor, _| editor.context_menu_visible())
12886        .await;
12887    cx.update_editor(|editor, _, _| {
12888        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12889        {
12890            assert_eq!(
12891                completion_menu_entries(&menu),
12892                &["first", "last", "second"],
12893                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
12894            );
12895        } else {
12896            panic!("expected completion menu to be open");
12897        }
12898    });
12899}
12900
12901#[gpui::test]
12902async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
12903    init_test(cx, |language_settings| {
12904        language_settings.defaults.completions = Some(CompletionSettings {
12905            words: WordsCompletionMode::Disabled,
12906            lsp: true,
12907            lsp_fetch_timeout_ms: 0,
12908            lsp_insert_mode: LspInsertMode::Insert,
12909        });
12910    });
12911
12912    let mut cx = EditorLspTestContext::new_rust(
12913        lsp::ServerCapabilities {
12914            completion_provider: Some(lsp::CompletionOptions {
12915                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12916                ..lsp::CompletionOptions::default()
12917            }),
12918            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12919            ..lsp::ServerCapabilities::default()
12920        },
12921        cx,
12922    )
12923    .await;
12924
12925    let _completion_requests_handler =
12926        cx.lsp
12927            .server
12928            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12929                panic!("LSP completions should not be queried when dealing with word completions")
12930            });
12931
12932    cx.set_state(indoc! {"ˇ
12933        first
12934        last
12935        second
12936    "});
12937    cx.update_editor(|editor, window, cx| {
12938        editor.show_word_completions(&ShowWordCompletions, window, cx);
12939    });
12940    cx.executor().run_until_parked();
12941    cx.condition(|editor, _| editor.context_menu_visible())
12942        .await;
12943    cx.update_editor(|editor, _, _| {
12944        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12945        {
12946            assert_eq!(
12947                completion_menu_entries(&menu),
12948                &["first", "last", "second"],
12949                "`ShowWordCompletions` action should show word completions"
12950            );
12951        } else {
12952            panic!("expected completion menu to be open");
12953        }
12954    });
12955
12956    cx.simulate_keystroke("l");
12957    cx.executor().run_until_parked();
12958    cx.condition(|editor, _| editor.context_menu_visible())
12959        .await;
12960    cx.update_editor(|editor, _, _| {
12961        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12962        {
12963            assert_eq!(
12964                completion_menu_entries(&menu),
12965                &["last"],
12966                "After showing word completions, further editing should filter them and not query the LSP"
12967            );
12968        } else {
12969            panic!("expected completion menu to be open");
12970        }
12971    });
12972}
12973
12974#[gpui::test]
12975async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
12976    init_test(cx, |language_settings| {
12977        language_settings.defaults.completions = Some(CompletionSettings {
12978            words: WordsCompletionMode::Fallback,
12979            lsp: false,
12980            lsp_fetch_timeout_ms: 0,
12981            lsp_insert_mode: LspInsertMode::Insert,
12982        });
12983    });
12984
12985    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12986
12987    cx.set_state(indoc! {"ˇ
12988        0_usize
12989        let
12990        33
12991        4.5f32
12992    "});
12993    cx.update_editor(|editor, window, cx| {
12994        editor.show_completions(&ShowCompletions::default(), window, cx);
12995    });
12996    cx.executor().run_until_parked();
12997    cx.condition(|editor, _| editor.context_menu_visible())
12998        .await;
12999    cx.update_editor(|editor, window, cx| {
13000        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13001        {
13002            assert_eq!(
13003                completion_menu_entries(&menu),
13004                &["let"],
13005                "With no digits in the completion query, no digits should be in the word completions"
13006            );
13007        } else {
13008            panic!("expected completion menu to be open");
13009        }
13010        editor.cancel(&Cancel, window, cx);
13011    });
13012
13013    cx.set_state(indoc! {"13014        0_usize
13015        let
13016        3
13017        33.35f32
13018    "});
13019    cx.update_editor(|editor, window, cx| {
13020        editor.show_completions(&ShowCompletions::default(), window, cx);
13021    });
13022    cx.executor().run_until_parked();
13023    cx.condition(|editor, _| editor.context_menu_visible())
13024        .await;
13025    cx.update_editor(|editor, _, _| {
13026        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13027        {
13028            assert_eq!(completion_menu_entries(&menu), &["33", "35f32"], "The digit is in the completion query, \
13029                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
13030        } else {
13031            panic!("expected completion menu to be open");
13032        }
13033    });
13034}
13035
13036fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
13037    let position = || lsp::Position {
13038        line: params.text_document_position.position.line,
13039        character: params.text_document_position.position.character,
13040    };
13041    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13042        range: lsp::Range {
13043            start: position(),
13044            end: position(),
13045        },
13046        new_text: text.to_string(),
13047    }))
13048}
13049
13050#[gpui::test]
13051async fn test_multiline_completion(cx: &mut TestAppContext) {
13052    init_test(cx, |_| {});
13053
13054    let fs = FakeFs::new(cx.executor());
13055    fs.insert_tree(
13056        path!("/a"),
13057        json!({
13058            "main.ts": "a",
13059        }),
13060    )
13061    .await;
13062
13063    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13064    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13065    let typescript_language = Arc::new(Language::new(
13066        LanguageConfig {
13067            name: "TypeScript".into(),
13068            matcher: LanguageMatcher {
13069                path_suffixes: vec!["ts".to_string()],
13070                ..LanguageMatcher::default()
13071            },
13072            line_comments: vec!["// ".into()],
13073            ..LanguageConfig::default()
13074        },
13075        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
13076    ));
13077    language_registry.add(typescript_language.clone());
13078    let mut fake_servers = language_registry.register_fake_lsp(
13079        "TypeScript",
13080        FakeLspAdapter {
13081            capabilities: lsp::ServerCapabilities {
13082                completion_provider: Some(lsp::CompletionOptions {
13083                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13084                    ..lsp::CompletionOptions::default()
13085                }),
13086                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13087                ..lsp::ServerCapabilities::default()
13088            },
13089            // Emulate vtsls label generation
13090            label_for_completion: Some(Box::new(|item, _| {
13091                let text = if let Some(description) = item
13092                    .label_details
13093                    .as_ref()
13094                    .and_then(|label_details| label_details.description.as_ref())
13095                {
13096                    format!("{} {}", item.label, description)
13097                } else if let Some(detail) = &item.detail {
13098                    format!("{} {}", item.label, detail)
13099                } else {
13100                    item.label.clone()
13101                };
13102                let len = text.len();
13103                Some(language::CodeLabel {
13104                    text,
13105                    runs: Vec::new(),
13106                    filter_range: 0..len,
13107                })
13108            })),
13109            ..FakeLspAdapter::default()
13110        },
13111    );
13112    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13113    let cx = &mut VisualTestContext::from_window(*workspace, cx);
13114    let worktree_id = workspace
13115        .update(cx, |workspace, _window, cx| {
13116            workspace.project().update(cx, |project, cx| {
13117                project.worktrees(cx).next().unwrap().read(cx).id()
13118            })
13119        })
13120        .unwrap();
13121    let _buffer = project
13122        .update(cx, |project, cx| {
13123            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
13124        })
13125        .await
13126        .unwrap();
13127    let editor = workspace
13128        .update(cx, |workspace, window, cx| {
13129            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
13130        })
13131        .unwrap()
13132        .await
13133        .unwrap()
13134        .downcast::<Editor>()
13135        .unwrap();
13136    let fake_server = fake_servers.next().await.unwrap();
13137
13138    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
13139    let multiline_label_2 = "a\nb\nc\n";
13140    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
13141    let multiline_description = "d\ne\nf\n";
13142    let multiline_detail_2 = "g\nh\ni\n";
13143
13144    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
13145        move |params, _| async move {
13146            Ok(Some(lsp::CompletionResponse::Array(vec![
13147                lsp::CompletionItem {
13148                    label: multiline_label.to_string(),
13149                    text_edit: gen_text_edit(&params, "new_text_1"),
13150                    ..lsp::CompletionItem::default()
13151                },
13152                lsp::CompletionItem {
13153                    label: "single line label 1".to_string(),
13154                    detail: Some(multiline_detail.to_string()),
13155                    text_edit: gen_text_edit(&params, "new_text_2"),
13156                    ..lsp::CompletionItem::default()
13157                },
13158                lsp::CompletionItem {
13159                    label: "single line label 2".to_string(),
13160                    label_details: Some(lsp::CompletionItemLabelDetails {
13161                        description: Some(multiline_description.to_string()),
13162                        detail: None,
13163                    }),
13164                    text_edit: gen_text_edit(&params, "new_text_2"),
13165                    ..lsp::CompletionItem::default()
13166                },
13167                lsp::CompletionItem {
13168                    label: multiline_label_2.to_string(),
13169                    detail: Some(multiline_detail_2.to_string()),
13170                    text_edit: gen_text_edit(&params, "new_text_3"),
13171                    ..lsp::CompletionItem::default()
13172                },
13173                lsp::CompletionItem {
13174                    label: "Label with many     spaces and \t but without newlines".to_string(),
13175                    detail: Some(
13176                        "Details with many     spaces and \t but without newlines".to_string(),
13177                    ),
13178                    text_edit: gen_text_edit(&params, "new_text_4"),
13179                    ..lsp::CompletionItem::default()
13180                },
13181            ])))
13182        },
13183    );
13184
13185    editor.update_in(cx, |editor, window, cx| {
13186        cx.focus_self(window);
13187        editor.move_to_end(&MoveToEnd, window, cx);
13188        editor.handle_input(".", window, cx);
13189    });
13190    cx.run_until_parked();
13191    completion_handle.next().await.unwrap();
13192
13193    editor.update(cx, |editor, _| {
13194        assert!(editor.context_menu_visible());
13195        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13196        {
13197            let completion_labels = menu
13198                .completions
13199                .borrow()
13200                .iter()
13201                .map(|c| c.label.text.clone())
13202                .collect::<Vec<_>>();
13203            assert_eq!(
13204                completion_labels,
13205                &[
13206                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
13207                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
13208                    "single line label 2 d e f ",
13209                    "a b c g h i ",
13210                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
13211                ],
13212                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
13213            );
13214
13215            for completion in menu
13216                .completions
13217                .borrow()
13218                .iter() {
13219                    assert_eq!(
13220                        completion.label.filter_range,
13221                        0..completion.label.text.len(),
13222                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
13223                    );
13224                }
13225        } else {
13226            panic!("expected completion menu to be open");
13227        }
13228    });
13229}
13230
13231#[gpui::test]
13232async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
13233    init_test(cx, |_| {});
13234    let mut cx = EditorLspTestContext::new_rust(
13235        lsp::ServerCapabilities {
13236            completion_provider: Some(lsp::CompletionOptions {
13237                trigger_characters: Some(vec![".".to_string()]),
13238                ..Default::default()
13239            }),
13240            ..Default::default()
13241        },
13242        cx,
13243    )
13244    .await;
13245    cx.lsp
13246        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13247            Ok(Some(lsp::CompletionResponse::Array(vec![
13248                lsp::CompletionItem {
13249                    label: "first".into(),
13250                    ..Default::default()
13251                },
13252                lsp::CompletionItem {
13253                    label: "last".into(),
13254                    ..Default::default()
13255                },
13256            ])))
13257        });
13258    cx.set_state("variableˇ");
13259    cx.simulate_keystroke(".");
13260    cx.executor().run_until_parked();
13261
13262    cx.update_editor(|editor, _, _| {
13263        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13264        {
13265            assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
13266        } else {
13267            panic!("expected completion menu to be open");
13268        }
13269    });
13270
13271    cx.update_editor(|editor, window, cx| {
13272        editor.move_page_down(&MovePageDown::default(), window, cx);
13273        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13274        {
13275            assert!(
13276                menu.selected_item == 1,
13277                "expected PageDown to select the last item from the context menu"
13278            );
13279        } else {
13280            panic!("expected completion menu to stay open after PageDown");
13281        }
13282    });
13283
13284    cx.update_editor(|editor, window, cx| {
13285        editor.move_page_up(&MovePageUp::default(), window, cx);
13286        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13287        {
13288            assert!(
13289                menu.selected_item == 0,
13290                "expected PageUp to select the first item from the context menu"
13291            );
13292        } else {
13293            panic!("expected completion menu to stay open after PageUp");
13294        }
13295    });
13296}
13297
13298#[gpui::test]
13299async fn test_as_is_completions(cx: &mut TestAppContext) {
13300    init_test(cx, |_| {});
13301    let mut cx = EditorLspTestContext::new_rust(
13302        lsp::ServerCapabilities {
13303            completion_provider: Some(lsp::CompletionOptions {
13304                ..Default::default()
13305            }),
13306            ..Default::default()
13307        },
13308        cx,
13309    )
13310    .await;
13311    cx.lsp
13312        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13313            Ok(Some(lsp::CompletionResponse::Array(vec![
13314                lsp::CompletionItem {
13315                    label: "unsafe".into(),
13316                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13317                        range: lsp::Range {
13318                            start: lsp::Position {
13319                                line: 1,
13320                                character: 2,
13321                            },
13322                            end: lsp::Position {
13323                                line: 1,
13324                                character: 3,
13325                            },
13326                        },
13327                        new_text: "unsafe".to_string(),
13328                    })),
13329                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
13330                    ..Default::default()
13331                },
13332            ])))
13333        });
13334    cx.set_state("fn a() {}\n");
13335    cx.executor().run_until_parked();
13336    cx.update_editor(|editor, window, cx| {
13337        editor.show_completions(
13338            &ShowCompletions {
13339                trigger: Some("\n".into()),
13340            },
13341            window,
13342            cx,
13343        );
13344    });
13345    cx.executor().run_until_parked();
13346
13347    cx.update_editor(|editor, window, cx| {
13348        editor.confirm_completion(&Default::default(), window, cx)
13349    });
13350    cx.executor().run_until_parked();
13351    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
13352}
13353
13354#[gpui::test]
13355async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
13356    init_test(cx, |_| {});
13357
13358    let mut cx = EditorLspTestContext::new_rust(
13359        lsp::ServerCapabilities {
13360            completion_provider: Some(lsp::CompletionOptions {
13361                trigger_characters: Some(vec![".".to_string()]),
13362                resolve_provider: Some(true),
13363                ..Default::default()
13364            }),
13365            ..Default::default()
13366        },
13367        cx,
13368    )
13369    .await;
13370
13371    cx.set_state("fn main() { let a = 2ˇ; }");
13372    cx.simulate_keystroke(".");
13373    let completion_item = lsp::CompletionItem {
13374        label: "Some".into(),
13375        kind: Some(lsp::CompletionItemKind::SNIPPET),
13376        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
13377        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
13378            kind: lsp::MarkupKind::Markdown,
13379            value: "```rust\nSome(2)\n```".to_string(),
13380        })),
13381        deprecated: Some(false),
13382        sort_text: Some("Some".to_string()),
13383        filter_text: Some("Some".to_string()),
13384        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
13385        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13386            range: lsp::Range {
13387                start: lsp::Position {
13388                    line: 0,
13389                    character: 22,
13390                },
13391                end: lsp::Position {
13392                    line: 0,
13393                    character: 22,
13394                },
13395            },
13396            new_text: "Some(2)".to_string(),
13397        })),
13398        additional_text_edits: Some(vec![lsp::TextEdit {
13399            range: lsp::Range {
13400                start: lsp::Position {
13401                    line: 0,
13402                    character: 20,
13403                },
13404                end: lsp::Position {
13405                    line: 0,
13406                    character: 22,
13407                },
13408            },
13409            new_text: "".to_string(),
13410        }]),
13411        ..Default::default()
13412    };
13413
13414    let closure_completion_item = completion_item.clone();
13415    let counter = Arc::new(AtomicUsize::new(0));
13416    let counter_clone = counter.clone();
13417    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
13418        let task_completion_item = closure_completion_item.clone();
13419        counter_clone.fetch_add(1, atomic::Ordering::Release);
13420        async move {
13421            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
13422                is_incomplete: true,
13423                item_defaults: None,
13424                items: vec![task_completion_item],
13425            })))
13426        }
13427    });
13428
13429    cx.condition(|editor, _| editor.context_menu_visible())
13430        .await;
13431    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
13432    assert!(request.next().await.is_some());
13433    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13434
13435    cx.simulate_keystrokes("S o m");
13436    cx.condition(|editor, _| editor.context_menu_visible())
13437        .await;
13438    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
13439    assert!(request.next().await.is_some());
13440    assert!(request.next().await.is_some());
13441    assert!(request.next().await.is_some());
13442    request.close();
13443    assert!(request.next().await.is_none());
13444    assert_eq!(
13445        counter.load(atomic::Ordering::Acquire),
13446        4,
13447        "With the completions menu open, only one LSP request should happen per input"
13448    );
13449}
13450
13451#[gpui::test]
13452async fn test_toggle_comment(cx: &mut TestAppContext) {
13453    init_test(cx, |_| {});
13454    let mut cx = EditorTestContext::new(cx).await;
13455    let language = Arc::new(Language::new(
13456        LanguageConfig {
13457            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
13458            ..Default::default()
13459        },
13460        Some(tree_sitter_rust::LANGUAGE.into()),
13461    ));
13462    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
13463
13464    // If multiple selections intersect a line, the line is only toggled once.
13465    cx.set_state(indoc! {"
13466        fn a() {
13467            «//b();
13468            ˇ»// «c();
13469            //ˇ»  d();
13470        }
13471    "});
13472
13473    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13474
13475    cx.assert_editor_state(indoc! {"
13476        fn a() {
13477            «b();
13478            c();
13479            ˇ» d();
13480        }
13481    "});
13482
13483    // The comment prefix is inserted at the same column for every line in a
13484    // selection.
13485    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13486
13487    cx.assert_editor_state(indoc! {"
13488        fn a() {
13489            // «b();
13490            // c();
13491            ˇ»//  d();
13492        }
13493    "});
13494
13495    // If a selection ends at the beginning of a line, that line is not toggled.
13496    cx.set_selections_state(indoc! {"
13497        fn a() {
13498            // b();
13499            «// c();
13500        ˇ»    //  d();
13501        }
13502    "});
13503
13504    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13505
13506    cx.assert_editor_state(indoc! {"
13507        fn a() {
13508            // b();
13509            «c();
13510        ˇ»    //  d();
13511        }
13512    "});
13513
13514    // If a selection span a single line and is empty, the line is toggled.
13515    cx.set_state(indoc! {"
13516        fn a() {
13517            a();
13518            b();
13519        ˇ
13520        }
13521    "});
13522
13523    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13524
13525    cx.assert_editor_state(indoc! {"
13526        fn a() {
13527            a();
13528            b();
13529        //•ˇ
13530        }
13531    "});
13532
13533    // If a selection span multiple lines, empty lines are not toggled.
13534    cx.set_state(indoc! {"
13535        fn a() {
13536            «a();
13537
13538            c();ˇ»
13539        }
13540    "});
13541
13542    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13543
13544    cx.assert_editor_state(indoc! {"
13545        fn a() {
13546            // «a();
13547
13548            // c();ˇ»
13549        }
13550    "});
13551
13552    // If a selection includes multiple comment prefixes, all lines are uncommented.
13553    cx.set_state(indoc! {"
13554        fn a() {
13555            «// a();
13556            /// b();
13557            //! c();ˇ»
13558        }
13559    "});
13560
13561    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13562
13563    cx.assert_editor_state(indoc! {"
13564        fn a() {
13565            «a();
13566            b();
13567            c();ˇ»
13568        }
13569    "});
13570}
13571
13572#[gpui::test]
13573async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
13574    init_test(cx, |_| {});
13575    let mut cx = EditorTestContext::new(cx).await;
13576    let language = Arc::new(Language::new(
13577        LanguageConfig {
13578            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
13579            ..Default::default()
13580        },
13581        Some(tree_sitter_rust::LANGUAGE.into()),
13582    ));
13583    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
13584
13585    let toggle_comments = &ToggleComments {
13586        advance_downwards: false,
13587        ignore_indent: true,
13588    };
13589
13590    // If multiple selections intersect a line, the line is only toggled once.
13591    cx.set_state(indoc! {"
13592        fn a() {
13593        //    «b();
13594        //    c();
13595        //    ˇ» d();
13596        }
13597    "});
13598
13599    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13600
13601    cx.assert_editor_state(indoc! {"
13602        fn a() {
13603            «b();
13604            c();
13605            ˇ» d();
13606        }
13607    "});
13608
13609    // The comment prefix is inserted at the beginning of each line
13610    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13611
13612    cx.assert_editor_state(indoc! {"
13613        fn a() {
13614        //    «b();
13615        //    c();
13616        //    ˇ» d();
13617        }
13618    "});
13619
13620    // If a selection ends at the beginning of a line, that line is not toggled.
13621    cx.set_selections_state(indoc! {"
13622        fn a() {
13623        //    b();
13624        //    «c();
13625        ˇ»//     d();
13626        }
13627    "});
13628
13629    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13630
13631    cx.assert_editor_state(indoc! {"
13632        fn a() {
13633        //    b();
13634            «c();
13635        ˇ»//     d();
13636        }
13637    "});
13638
13639    // If a selection span a single line and is empty, the line is toggled.
13640    cx.set_state(indoc! {"
13641        fn a() {
13642            a();
13643            b();
13644        ˇ
13645        }
13646    "});
13647
13648    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13649
13650    cx.assert_editor_state(indoc! {"
13651        fn a() {
13652            a();
13653            b();
13654        //ˇ
13655        }
13656    "});
13657
13658    // If a selection span multiple lines, empty lines are not toggled.
13659    cx.set_state(indoc! {"
13660        fn a() {
13661            «a();
13662
13663            c();ˇ»
13664        }
13665    "});
13666
13667    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13668
13669    cx.assert_editor_state(indoc! {"
13670        fn a() {
13671        //    «a();
13672
13673        //    c();ˇ»
13674        }
13675    "});
13676
13677    // If a selection includes multiple comment prefixes, all lines are uncommented.
13678    cx.set_state(indoc! {"
13679        fn a() {
13680        //    «a();
13681        ///    b();
13682        //!    c();ˇ»
13683        }
13684    "});
13685
13686    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13687
13688    cx.assert_editor_state(indoc! {"
13689        fn a() {
13690            «a();
13691            b();
13692            c();ˇ»
13693        }
13694    "});
13695}
13696
13697#[gpui::test]
13698async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
13699    init_test(cx, |_| {});
13700
13701    let language = Arc::new(Language::new(
13702        LanguageConfig {
13703            line_comments: vec!["// ".into()],
13704            ..Default::default()
13705        },
13706        Some(tree_sitter_rust::LANGUAGE.into()),
13707    ));
13708
13709    let mut cx = EditorTestContext::new(cx).await;
13710
13711    cx.language_registry().add(language.clone());
13712    cx.update_buffer(|buffer, cx| {
13713        buffer.set_language(Some(language), cx);
13714    });
13715
13716    let toggle_comments = &ToggleComments {
13717        advance_downwards: true,
13718        ignore_indent: false,
13719    };
13720
13721    // Single cursor on one line -> advance
13722    // Cursor moves horizontally 3 characters as well on non-blank line
13723    cx.set_state(indoc!(
13724        "fn a() {
13725             ˇdog();
13726             cat();
13727        }"
13728    ));
13729    cx.update_editor(|editor, window, cx| {
13730        editor.toggle_comments(toggle_comments, window, cx);
13731    });
13732    cx.assert_editor_state(indoc!(
13733        "fn a() {
13734             // dog();
13735             catˇ();
13736        }"
13737    ));
13738
13739    // Single selection on one line -> don't advance
13740    cx.set_state(indoc!(
13741        "fn a() {
13742             «dog()ˇ»;
13743             cat();
13744        }"
13745    ));
13746    cx.update_editor(|editor, window, cx| {
13747        editor.toggle_comments(toggle_comments, window, cx);
13748    });
13749    cx.assert_editor_state(indoc!(
13750        "fn a() {
13751             // «dog()ˇ»;
13752             cat();
13753        }"
13754    ));
13755
13756    // Multiple cursors on one line -> advance
13757    cx.set_state(indoc!(
13758        "fn a() {
13759             ˇdˇog();
13760             cat();
13761        }"
13762    ));
13763    cx.update_editor(|editor, window, cx| {
13764        editor.toggle_comments(toggle_comments, window, cx);
13765    });
13766    cx.assert_editor_state(indoc!(
13767        "fn a() {
13768             // dog();
13769             catˇ(ˇ);
13770        }"
13771    ));
13772
13773    // Multiple cursors on one line, with selection -> don't advance
13774    cx.set_state(indoc!(
13775        "fn a() {
13776             ˇdˇog«()ˇ»;
13777             cat();
13778        }"
13779    ));
13780    cx.update_editor(|editor, window, cx| {
13781        editor.toggle_comments(toggle_comments, window, cx);
13782    });
13783    cx.assert_editor_state(indoc!(
13784        "fn a() {
13785             // ˇdˇog«()ˇ»;
13786             cat();
13787        }"
13788    ));
13789
13790    // Single cursor on one line -> advance
13791    // Cursor moves to column 0 on blank line
13792    cx.set_state(indoc!(
13793        "fn a() {
13794             ˇdog();
13795
13796             cat();
13797        }"
13798    ));
13799    cx.update_editor(|editor, window, cx| {
13800        editor.toggle_comments(toggle_comments, window, cx);
13801    });
13802    cx.assert_editor_state(indoc!(
13803        "fn a() {
13804             // dog();
13805        ˇ
13806             cat();
13807        }"
13808    ));
13809
13810    // Single cursor on one line -> advance
13811    // Cursor starts and ends at column 0
13812    cx.set_state(indoc!(
13813        "fn a() {
13814         ˇ    dog();
13815             cat();
13816        }"
13817    ));
13818    cx.update_editor(|editor, window, cx| {
13819        editor.toggle_comments(toggle_comments, window, cx);
13820    });
13821    cx.assert_editor_state(indoc!(
13822        "fn a() {
13823             // dog();
13824         ˇ    cat();
13825        }"
13826    ));
13827}
13828
13829#[gpui::test]
13830async fn test_toggle_block_comment(cx: &mut TestAppContext) {
13831    init_test(cx, |_| {});
13832
13833    let mut cx = EditorTestContext::new(cx).await;
13834
13835    let html_language = Arc::new(
13836        Language::new(
13837            LanguageConfig {
13838                name: "HTML".into(),
13839                block_comment: Some(("<!-- ".into(), " -->".into())),
13840                ..Default::default()
13841            },
13842            Some(tree_sitter_html::LANGUAGE.into()),
13843        )
13844        .with_injection_query(
13845            r#"
13846            (script_element
13847                (raw_text) @injection.content
13848                (#set! injection.language "javascript"))
13849            "#,
13850        )
13851        .unwrap(),
13852    );
13853
13854    let javascript_language = Arc::new(Language::new(
13855        LanguageConfig {
13856            name: "JavaScript".into(),
13857            line_comments: vec!["// ".into()],
13858            ..Default::default()
13859        },
13860        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
13861    ));
13862
13863    cx.language_registry().add(html_language.clone());
13864    cx.language_registry().add(javascript_language.clone());
13865    cx.update_buffer(|buffer, cx| {
13866        buffer.set_language(Some(html_language), cx);
13867    });
13868
13869    // Toggle comments for empty selections
13870    cx.set_state(
13871        &r#"
13872            <p>A</p>ˇ
13873            <p>B</p>ˇ
13874            <p>C</p>ˇ
13875        "#
13876        .unindent(),
13877    );
13878    cx.update_editor(|editor, window, cx| {
13879        editor.toggle_comments(&ToggleComments::default(), window, cx)
13880    });
13881    cx.assert_editor_state(
13882        &r#"
13883            <!-- <p>A</p>ˇ -->
13884            <!-- <p>B</p>ˇ -->
13885            <!-- <p>C</p>ˇ -->
13886        "#
13887        .unindent(),
13888    );
13889    cx.update_editor(|editor, window, cx| {
13890        editor.toggle_comments(&ToggleComments::default(), window, cx)
13891    });
13892    cx.assert_editor_state(
13893        &r#"
13894            <p>A</p>ˇ
13895            <p>B</p>ˇ
13896            <p>C</p>ˇ
13897        "#
13898        .unindent(),
13899    );
13900
13901    // Toggle comments for mixture of empty and non-empty selections, where
13902    // multiple selections occupy a given line.
13903    cx.set_state(
13904        &r#"
13905            <p>A«</p>
13906            <p>ˇ»B</p>ˇ
13907            <p>C«</p>
13908            <p>ˇ»D</p>ˇ
13909        "#
13910        .unindent(),
13911    );
13912
13913    cx.update_editor(|editor, window, cx| {
13914        editor.toggle_comments(&ToggleComments::default(), window, cx)
13915    });
13916    cx.assert_editor_state(
13917        &r#"
13918            <!-- <p>A«</p>
13919            <p>ˇ»B</p>ˇ -->
13920            <!-- <p>C«</p>
13921            <p>ˇ»D</p>ˇ -->
13922        "#
13923        .unindent(),
13924    );
13925    cx.update_editor(|editor, window, cx| {
13926        editor.toggle_comments(&ToggleComments::default(), window, cx)
13927    });
13928    cx.assert_editor_state(
13929        &r#"
13930            <p>A«</p>
13931            <p>ˇ»B</p>ˇ
13932            <p>C«</p>
13933            <p>ˇ»D</p>ˇ
13934        "#
13935        .unindent(),
13936    );
13937
13938    // Toggle comments when different languages are active for different
13939    // selections.
13940    cx.set_state(
13941        &r#"
13942            ˇ<script>
13943                ˇvar x = new Y();
13944            ˇ</script>
13945        "#
13946        .unindent(),
13947    );
13948    cx.executor().run_until_parked();
13949    cx.update_editor(|editor, window, cx| {
13950        editor.toggle_comments(&ToggleComments::default(), window, cx)
13951    });
13952    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
13953    // Uncommenting and commenting from this position brings in even more wrong artifacts.
13954    cx.assert_editor_state(
13955        &r#"
13956            <!-- ˇ<script> -->
13957                // ˇvar x = new Y();
13958            <!-- ˇ</script> -->
13959        "#
13960        .unindent(),
13961    );
13962}
13963
13964#[gpui::test]
13965fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
13966    init_test(cx, |_| {});
13967
13968    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
13969    let multibuffer = cx.new(|cx| {
13970        let mut multibuffer = MultiBuffer::new(ReadWrite);
13971        multibuffer.push_excerpts(
13972            buffer.clone(),
13973            [
13974                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
13975                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
13976            ],
13977            cx,
13978        );
13979        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
13980        multibuffer
13981    });
13982
13983    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
13984    editor.update_in(cx, |editor, window, cx| {
13985        assert_eq!(editor.text(cx), "aaaa\nbbbb");
13986        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13987            s.select_ranges([
13988                Point::new(0, 0)..Point::new(0, 0),
13989                Point::new(1, 0)..Point::new(1, 0),
13990            ])
13991        });
13992
13993        editor.handle_input("X", window, cx);
13994        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
13995        assert_eq!(
13996            editor.selections.ranges(cx),
13997            [
13998                Point::new(0, 1)..Point::new(0, 1),
13999                Point::new(1, 1)..Point::new(1, 1),
14000            ]
14001        );
14002
14003        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
14004        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14005            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
14006        });
14007        editor.backspace(&Default::default(), window, cx);
14008        assert_eq!(editor.text(cx), "Xa\nbbb");
14009        assert_eq!(
14010            editor.selections.ranges(cx),
14011            [Point::new(1, 0)..Point::new(1, 0)]
14012        );
14013
14014        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14015            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
14016        });
14017        editor.backspace(&Default::default(), window, cx);
14018        assert_eq!(editor.text(cx), "X\nbb");
14019        assert_eq!(
14020            editor.selections.ranges(cx),
14021            [Point::new(0, 1)..Point::new(0, 1)]
14022        );
14023    });
14024}
14025
14026#[gpui::test]
14027fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
14028    init_test(cx, |_| {});
14029
14030    let markers = vec![('[', ']').into(), ('(', ')').into()];
14031    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
14032        indoc! {"
14033            [aaaa
14034            (bbbb]
14035            cccc)",
14036        },
14037        markers.clone(),
14038    );
14039    let excerpt_ranges = markers.into_iter().map(|marker| {
14040        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
14041        ExcerptRange::new(context.clone())
14042    });
14043    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
14044    let multibuffer = cx.new(|cx| {
14045        let mut multibuffer = MultiBuffer::new(ReadWrite);
14046        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
14047        multibuffer
14048    });
14049
14050    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
14051    editor.update_in(cx, |editor, window, cx| {
14052        let (expected_text, selection_ranges) = marked_text_ranges(
14053            indoc! {"
14054                aaaa
14055                bˇbbb
14056                bˇbbˇb
14057                cccc"
14058            },
14059            true,
14060        );
14061        assert_eq!(editor.text(cx), expected_text);
14062        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14063            s.select_ranges(selection_ranges)
14064        });
14065
14066        editor.handle_input("X", window, cx);
14067
14068        let (expected_text, expected_selections) = marked_text_ranges(
14069            indoc! {"
14070                aaaa
14071                bXˇbbXb
14072                bXˇbbXˇb
14073                cccc"
14074            },
14075            false,
14076        );
14077        assert_eq!(editor.text(cx), expected_text);
14078        assert_eq!(editor.selections.ranges(cx), expected_selections);
14079
14080        editor.newline(&Newline, window, cx);
14081        let (expected_text, expected_selections) = marked_text_ranges(
14082            indoc! {"
14083                aaaa
14084                bX
14085                ˇbbX
14086                b
14087                bX
14088                ˇbbX
14089                ˇb
14090                cccc"
14091            },
14092            false,
14093        );
14094        assert_eq!(editor.text(cx), expected_text);
14095        assert_eq!(editor.selections.ranges(cx), expected_selections);
14096    });
14097}
14098
14099#[gpui::test]
14100fn test_refresh_selections(cx: &mut TestAppContext) {
14101    init_test(cx, |_| {});
14102
14103    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
14104    let mut excerpt1_id = None;
14105    let multibuffer = cx.new(|cx| {
14106        let mut multibuffer = MultiBuffer::new(ReadWrite);
14107        excerpt1_id = multibuffer
14108            .push_excerpts(
14109                buffer.clone(),
14110                [
14111                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
14112                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
14113                ],
14114                cx,
14115            )
14116            .into_iter()
14117            .next();
14118        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
14119        multibuffer
14120    });
14121
14122    let editor = cx.add_window(|window, cx| {
14123        let mut editor = build_editor(multibuffer.clone(), window, cx);
14124        let snapshot = editor.snapshot(window, cx);
14125        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14126            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
14127        });
14128        editor.begin_selection(
14129            Point::new(2, 1).to_display_point(&snapshot),
14130            true,
14131            1,
14132            window,
14133            cx,
14134        );
14135        assert_eq!(
14136            editor.selections.ranges(cx),
14137            [
14138                Point::new(1, 3)..Point::new(1, 3),
14139                Point::new(2, 1)..Point::new(2, 1),
14140            ]
14141        );
14142        editor
14143    });
14144
14145    // Refreshing selections is a no-op when excerpts haven't changed.
14146    _ = editor.update(cx, |editor, window, cx| {
14147        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14148        assert_eq!(
14149            editor.selections.ranges(cx),
14150            [
14151                Point::new(1, 3)..Point::new(1, 3),
14152                Point::new(2, 1)..Point::new(2, 1),
14153            ]
14154        );
14155    });
14156
14157    multibuffer.update(cx, |multibuffer, cx| {
14158        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
14159    });
14160    _ = editor.update(cx, |editor, window, cx| {
14161        // Removing an excerpt causes the first selection to become degenerate.
14162        assert_eq!(
14163            editor.selections.ranges(cx),
14164            [
14165                Point::new(0, 0)..Point::new(0, 0),
14166                Point::new(0, 1)..Point::new(0, 1)
14167            ]
14168        );
14169
14170        // Refreshing selections will relocate the first selection to the original buffer
14171        // location.
14172        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14173        assert_eq!(
14174            editor.selections.ranges(cx),
14175            [
14176                Point::new(0, 1)..Point::new(0, 1),
14177                Point::new(0, 3)..Point::new(0, 3)
14178            ]
14179        );
14180        assert!(editor.selections.pending_anchor().is_some());
14181    });
14182}
14183
14184#[gpui::test]
14185fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
14186    init_test(cx, |_| {});
14187
14188    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
14189    let mut excerpt1_id = None;
14190    let multibuffer = cx.new(|cx| {
14191        let mut multibuffer = MultiBuffer::new(ReadWrite);
14192        excerpt1_id = multibuffer
14193            .push_excerpts(
14194                buffer.clone(),
14195                [
14196                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
14197                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
14198                ],
14199                cx,
14200            )
14201            .into_iter()
14202            .next();
14203        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
14204        multibuffer
14205    });
14206
14207    let editor = cx.add_window(|window, cx| {
14208        let mut editor = build_editor(multibuffer.clone(), window, cx);
14209        let snapshot = editor.snapshot(window, cx);
14210        editor.begin_selection(
14211            Point::new(1, 3).to_display_point(&snapshot),
14212            false,
14213            1,
14214            window,
14215            cx,
14216        );
14217        assert_eq!(
14218            editor.selections.ranges(cx),
14219            [Point::new(1, 3)..Point::new(1, 3)]
14220        );
14221        editor
14222    });
14223
14224    multibuffer.update(cx, |multibuffer, cx| {
14225        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
14226    });
14227    _ = editor.update(cx, |editor, window, cx| {
14228        assert_eq!(
14229            editor.selections.ranges(cx),
14230            [Point::new(0, 0)..Point::new(0, 0)]
14231        );
14232
14233        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
14234        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14235        assert_eq!(
14236            editor.selections.ranges(cx),
14237            [Point::new(0, 3)..Point::new(0, 3)]
14238        );
14239        assert!(editor.selections.pending_anchor().is_some());
14240    });
14241}
14242
14243#[gpui::test]
14244async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
14245    init_test(cx, |_| {});
14246
14247    let language = Arc::new(
14248        Language::new(
14249            LanguageConfig {
14250                brackets: BracketPairConfig {
14251                    pairs: vec![
14252                        BracketPair {
14253                            start: "{".to_string(),
14254                            end: "}".to_string(),
14255                            close: true,
14256                            surround: true,
14257                            newline: true,
14258                        },
14259                        BracketPair {
14260                            start: "/* ".to_string(),
14261                            end: " */".to_string(),
14262                            close: true,
14263                            surround: true,
14264                            newline: true,
14265                        },
14266                    ],
14267                    ..Default::default()
14268                },
14269                ..Default::default()
14270            },
14271            Some(tree_sitter_rust::LANGUAGE.into()),
14272        )
14273        .with_indents_query("")
14274        .unwrap(),
14275    );
14276
14277    let text = concat!(
14278        "{   }\n",     //
14279        "  x\n",       //
14280        "  /*   */\n", //
14281        "x\n",         //
14282        "{{} }\n",     //
14283    );
14284
14285    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
14286    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
14287    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
14288    editor
14289        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
14290        .await;
14291
14292    editor.update_in(cx, |editor, window, cx| {
14293        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14294            s.select_display_ranges([
14295                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
14296                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
14297                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
14298            ])
14299        });
14300        editor.newline(&Newline, window, cx);
14301
14302        assert_eq!(
14303            editor.buffer().read(cx).read(cx).text(),
14304            concat!(
14305                "{ \n",    // Suppress rustfmt
14306                "\n",      //
14307                "}\n",     //
14308                "  x\n",   //
14309                "  /* \n", //
14310                "  \n",    //
14311                "  */\n",  //
14312                "x\n",     //
14313                "{{} \n",  //
14314                "}\n",     //
14315            )
14316        );
14317    });
14318}
14319
14320#[gpui::test]
14321fn test_highlighted_ranges(cx: &mut TestAppContext) {
14322    init_test(cx, |_| {});
14323
14324    let editor = cx.add_window(|window, cx| {
14325        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
14326        build_editor(buffer.clone(), window, cx)
14327    });
14328
14329    _ = editor.update(cx, |editor, window, cx| {
14330        struct Type1;
14331        struct Type2;
14332
14333        let buffer = editor.buffer.read(cx).snapshot(cx);
14334
14335        let anchor_range =
14336            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
14337
14338        editor.highlight_background::<Type1>(
14339            &[
14340                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
14341                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
14342                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
14343                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
14344            ],
14345            |_| Hsla::red(),
14346            cx,
14347        );
14348        editor.highlight_background::<Type2>(
14349            &[
14350                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
14351                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
14352                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
14353                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
14354            ],
14355            |_| Hsla::green(),
14356            cx,
14357        );
14358
14359        let snapshot = editor.snapshot(window, cx);
14360        let mut highlighted_ranges = editor.background_highlights_in_range(
14361            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
14362            &snapshot,
14363            cx.theme(),
14364        );
14365        // Enforce a consistent ordering based on color without relying on the ordering of the
14366        // highlight's `TypeId` which is non-executor.
14367        highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
14368        assert_eq!(
14369            highlighted_ranges,
14370            &[
14371                (
14372                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
14373                    Hsla::red(),
14374                ),
14375                (
14376                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
14377                    Hsla::red(),
14378                ),
14379                (
14380                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
14381                    Hsla::green(),
14382                ),
14383                (
14384                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
14385                    Hsla::green(),
14386                ),
14387            ]
14388        );
14389        assert_eq!(
14390            editor.background_highlights_in_range(
14391                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
14392                &snapshot,
14393                cx.theme(),
14394            ),
14395            &[(
14396                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
14397                Hsla::red(),
14398            )]
14399        );
14400    });
14401}
14402
14403#[gpui::test]
14404async fn test_following(cx: &mut TestAppContext) {
14405    init_test(cx, |_| {});
14406
14407    let fs = FakeFs::new(cx.executor());
14408    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
14409
14410    let buffer = project.update(cx, |project, cx| {
14411        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
14412        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
14413    });
14414    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
14415    let follower = cx.update(|cx| {
14416        cx.open_window(
14417            WindowOptions {
14418                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
14419                    gpui::Point::new(px(0.), px(0.)),
14420                    gpui::Point::new(px(10.), px(80.)),
14421                ))),
14422                ..Default::default()
14423            },
14424            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
14425        )
14426        .unwrap()
14427    });
14428
14429    let is_still_following = Rc::new(RefCell::new(true));
14430    let follower_edit_event_count = Rc::new(RefCell::new(0));
14431    let pending_update = Rc::new(RefCell::new(None));
14432    let leader_entity = leader.root(cx).unwrap();
14433    let follower_entity = follower.root(cx).unwrap();
14434    _ = follower.update(cx, {
14435        let update = pending_update.clone();
14436        let is_still_following = is_still_following.clone();
14437        let follower_edit_event_count = follower_edit_event_count.clone();
14438        |_, window, cx| {
14439            cx.subscribe_in(
14440                &leader_entity,
14441                window,
14442                move |_, leader, event, window, cx| {
14443                    leader.read(cx).add_event_to_update_proto(
14444                        event,
14445                        &mut update.borrow_mut(),
14446                        window,
14447                        cx,
14448                    );
14449                },
14450            )
14451            .detach();
14452
14453            cx.subscribe_in(
14454                &follower_entity,
14455                window,
14456                move |_, _, event: &EditorEvent, _window, _cx| {
14457                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
14458                        *is_still_following.borrow_mut() = false;
14459                    }
14460
14461                    if let EditorEvent::BufferEdited = event {
14462                        *follower_edit_event_count.borrow_mut() += 1;
14463                    }
14464                },
14465            )
14466            .detach();
14467        }
14468    });
14469
14470    // Update the selections only
14471    _ = leader.update(cx, |leader, window, cx| {
14472        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14473            s.select_ranges([1..1])
14474        });
14475    });
14476    follower
14477        .update(cx, |follower, window, cx| {
14478            follower.apply_update_proto(
14479                &project,
14480                pending_update.borrow_mut().take().unwrap(),
14481                window,
14482                cx,
14483            )
14484        })
14485        .unwrap()
14486        .await
14487        .unwrap();
14488    _ = follower.update(cx, |follower, _, cx| {
14489        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
14490    });
14491    assert!(*is_still_following.borrow());
14492    assert_eq!(*follower_edit_event_count.borrow(), 0);
14493
14494    // Update the scroll position only
14495    _ = leader.update(cx, |leader, window, cx| {
14496        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
14497    });
14498    follower
14499        .update(cx, |follower, window, cx| {
14500            follower.apply_update_proto(
14501                &project,
14502                pending_update.borrow_mut().take().unwrap(),
14503                window,
14504                cx,
14505            )
14506        })
14507        .unwrap()
14508        .await
14509        .unwrap();
14510    assert_eq!(
14511        follower
14512            .update(cx, |follower, _, cx| follower.scroll_position(cx))
14513            .unwrap(),
14514        gpui::Point::new(1.5, 3.5)
14515    );
14516    assert!(*is_still_following.borrow());
14517    assert_eq!(*follower_edit_event_count.borrow(), 0);
14518
14519    // Update the selections and scroll position. The follower's scroll position is updated
14520    // via autoscroll, not via the leader's exact scroll position.
14521    _ = leader.update(cx, |leader, window, cx| {
14522        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14523            s.select_ranges([0..0])
14524        });
14525        leader.request_autoscroll(Autoscroll::newest(), cx);
14526        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
14527    });
14528    follower
14529        .update(cx, |follower, window, cx| {
14530            follower.apply_update_proto(
14531                &project,
14532                pending_update.borrow_mut().take().unwrap(),
14533                window,
14534                cx,
14535            )
14536        })
14537        .unwrap()
14538        .await
14539        .unwrap();
14540    _ = follower.update(cx, |follower, _, cx| {
14541        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
14542        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
14543    });
14544    assert!(*is_still_following.borrow());
14545
14546    // Creating a pending selection that precedes another selection
14547    _ = leader.update(cx, |leader, window, cx| {
14548        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14549            s.select_ranges([1..1])
14550        });
14551        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
14552    });
14553    follower
14554        .update(cx, |follower, window, cx| {
14555            follower.apply_update_proto(
14556                &project,
14557                pending_update.borrow_mut().take().unwrap(),
14558                window,
14559                cx,
14560            )
14561        })
14562        .unwrap()
14563        .await
14564        .unwrap();
14565    _ = follower.update(cx, |follower, _, cx| {
14566        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
14567    });
14568    assert!(*is_still_following.borrow());
14569
14570    // Extend the pending selection so that it surrounds another selection
14571    _ = leader.update(cx, |leader, window, cx| {
14572        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
14573    });
14574    follower
14575        .update(cx, |follower, window, cx| {
14576            follower.apply_update_proto(
14577                &project,
14578                pending_update.borrow_mut().take().unwrap(),
14579                window,
14580                cx,
14581            )
14582        })
14583        .unwrap()
14584        .await
14585        .unwrap();
14586    _ = follower.update(cx, |follower, _, cx| {
14587        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
14588    });
14589
14590    // Scrolling locally breaks the follow
14591    _ = follower.update(cx, |follower, window, cx| {
14592        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
14593        follower.set_scroll_anchor(
14594            ScrollAnchor {
14595                anchor: top_anchor,
14596                offset: gpui::Point::new(0.0, 0.5),
14597            },
14598            window,
14599            cx,
14600        );
14601    });
14602    assert!(!(*is_still_following.borrow()));
14603}
14604
14605#[gpui::test]
14606async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
14607    init_test(cx, |_| {});
14608
14609    let fs = FakeFs::new(cx.executor());
14610    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
14611    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14612    let pane = workspace
14613        .update(cx, |workspace, _, _| workspace.active_pane().clone())
14614        .unwrap();
14615
14616    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14617
14618    let leader = pane.update_in(cx, |_, window, cx| {
14619        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
14620        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
14621    });
14622
14623    // Start following the editor when it has no excerpts.
14624    let mut state_message =
14625        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
14626    let workspace_entity = workspace.root(cx).unwrap();
14627    let follower_1 = cx
14628        .update_window(*workspace.deref(), |_, window, cx| {
14629            Editor::from_state_proto(
14630                workspace_entity,
14631                ViewId {
14632                    creator: CollaboratorId::PeerId(PeerId::default()),
14633                    id: 0,
14634                },
14635                &mut state_message,
14636                window,
14637                cx,
14638            )
14639        })
14640        .unwrap()
14641        .unwrap()
14642        .await
14643        .unwrap();
14644
14645    let update_message = Rc::new(RefCell::new(None));
14646    follower_1.update_in(cx, {
14647        let update = update_message.clone();
14648        |_, window, cx| {
14649            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
14650                leader.read(cx).add_event_to_update_proto(
14651                    event,
14652                    &mut update.borrow_mut(),
14653                    window,
14654                    cx,
14655                );
14656            })
14657            .detach();
14658        }
14659    });
14660
14661    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
14662        (
14663            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
14664            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
14665        )
14666    });
14667
14668    // Insert some excerpts.
14669    leader.update(cx, |leader, cx| {
14670        leader.buffer.update(cx, |multibuffer, cx| {
14671            multibuffer.set_excerpts_for_path(
14672                PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
14673                buffer_1.clone(),
14674                vec![
14675                    Point::row_range(0..3),
14676                    Point::row_range(1..6),
14677                    Point::row_range(12..15),
14678                ],
14679                0,
14680                cx,
14681            );
14682            multibuffer.set_excerpts_for_path(
14683                PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
14684                buffer_2.clone(),
14685                vec![Point::row_range(0..6), Point::row_range(8..12)],
14686                0,
14687                cx,
14688            );
14689        });
14690    });
14691
14692    // Apply the update of adding the excerpts.
14693    follower_1
14694        .update_in(cx, |follower, window, cx| {
14695            follower.apply_update_proto(
14696                &project,
14697                update_message.borrow().clone().unwrap(),
14698                window,
14699                cx,
14700            )
14701        })
14702        .await
14703        .unwrap();
14704    assert_eq!(
14705        follower_1.update(cx, |editor, cx| editor.text(cx)),
14706        leader.update(cx, |editor, cx| editor.text(cx))
14707    );
14708    update_message.borrow_mut().take();
14709
14710    // Start following separately after it already has excerpts.
14711    let mut state_message =
14712        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
14713    let workspace_entity = workspace.root(cx).unwrap();
14714    let follower_2 = cx
14715        .update_window(*workspace.deref(), |_, window, cx| {
14716            Editor::from_state_proto(
14717                workspace_entity,
14718                ViewId {
14719                    creator: CollaboratorId::PeerId(PeerId::default()),
14720                    id: 0,
14721                },
14722                &mut state_message,
14723                window,
14724                cx,
14725            )
14726        })
14727        .unwrap()
14728        .unwrap()
14729        .await
14730        .unwrap();
14731    assert_eq!(
14732        follower_2.update(cx, |editor, cx| editor.text(cx)),
14733        leader.update(cx, |editor, cx| editor.text(cx))
14734    );
14735
14736    // Remove some excerpts.
14737    leader.update(cx, |leader, cx| {
14738        leader.buffer.update(cx, |multibuffer, cx| {
14739            let excerpt_ids = multibuffer.excerpt_ids();
14740            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
14741            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
14742        });
14743    });
14744
14745    // Apply the update of removing the excerpts.
14746    follower_1
14747        .update_in(cx, |follower, window, cx| {
14748            follower.apply_update_proto(
14749                &project,
14750                update_message.borrow().clone().unwrap(),
14751                window,
14752                cx,
14753            )
14754        })
14755        .await
14756        .unwrap();
14757    follower_2
14758        .update_in(cx, |follower, window, cx| {
14759            follower.apply_update_proto(
14760                &project,
14761                update_message.borrow().clone().unwrap(),
14762                window,
14763                cx,
14764            )
14765        })
14766        .await
14767        .unwrap();
14768    update_message.borrow_mut().take();
14769    assert_eq!(
14770        follower_1.update(cx, |editor, cx| editor.text(cx)),
14771        leader.update(cx, |editor, cx| editor.text(cx))
14772    );
14773}
14774
14775#[gpui::test]
14776async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14777    init_test(cx, |_| {});
14778
14779    let mut cx = EditorTestContext::new(cx).await;
14780    let lsp_store =
14781        cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
14782
14783    cx.set_state(indoc! {"
14784        ˇfn func(abc def: i32) -> u32 {
14785        }
14786    "});
14787
14788    cx.update(|_, cx| {
14789        lsp_store.update(cx, |lsp_store, cx| {
14790            lsp_store
14791                .update_diagnostics(
14792                    LanguageServerId(0),
14793                    lsp::PublishDiagnosticsParams {
14794                        uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
14795                        version: None,
14796                        diagnostics: vec![
14797                            lsp::Diagnostic {
14798                                range: lsp::Range::new(
14799                                    lsp::Position::new(0, 11),
14800                                    lsp::Position::new(0, 12),
14801                                ),
14802                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14803                                ..Default::default()
14804                            },
14805                            lsp::Diagnostic {
14806                                range: lsp::Range::new(
14807                                    lsp::Position::new(0, 12),
14808                                    lsp::Position::new(0, 15),
14809                                ),
14810                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14811                                ..Default::default()
14812                            },
14813                            lsp::Diagnostic {
14814                                range: lsp::Range::new(
14815                                    lsp::Position::new(0, 25),
14816                                    lsp::Position::new(0, 28),
14817                                ),
14818                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14819                                ..Default::default()
14820                            },
14821                        ],
14822                    },
14823                    None,
14824                    DiagnosticSourceKind::Pushed,
14825                    &[],
14826                    cx,
14827                )
14828                .unwrap()
14829        });
14830    });
14831
14832    executor.run_until_parked();
14833
14834    cx.update_editor(|editor, window, cx| {
14835        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
14836    });
14837
14838    cx.assert_editor_state(indoc! {"
14839        fn func(abc def: i32) -> ˇu32 {
14840        }
14841    "});
14842
14843    cx.update_editor(|editor, window, cx| {
14844        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
14845    });
14846
14847    cx.assert_editor_state(indoc! {"
14848        fn func(abc ˇdef: i32) -> u32 {
14849        }
14850    "});
14851
14852    cx.update_editor(|editor, window, cx| {
14853        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
14854    });
14855
14856    cx.assert_editor_state(indoc! {"
14857        fn func(abcˇ def: i32) -> u32 {
14858        }
14859    "});
14860
14861    cx.update_editor(|editor, window, cx| {
14862        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
14863    });
14864
14865    cx.assert_editor_state(indoc! {"
14866        fn func(abc def: i32) -> ˇu32 {
14867        }
14868    "});
14869}
14870
14871#[gpui::test]
14872async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14873    init_test(cx, |_| {});
14874
14875    let mut cx = EditorTestContext::new(cx).await;
14876
14877    let diff_base = r#"
14878        use some::mod;
14879
14880        const A: u32 = 42;
14881
14882        fn main() {
14883            println!("hello");
14884
14885            println!("world");
14886        }
14887        "#
14888    .unindent();
14889
14890    // Edits are modified, removed, modified, added
14891    cx.set_state(
14892        &r#"
14893        use some::modified;
14894
14895        ˇ
14896        fn main() {
14897            println!("hello there");
14898
14899            println!("around the");
14900            println!("world");
14901        }
14902        "#
14903        .unindent(),
14904    );
14905
14906    cx.set_head_text(&diff_base);
14907    executor.run_until_parked();
14908
14909    cx.update_editor(|editor, window, cx| {
14910        //Wrap around the bottom of the buffer
14911        for _ in 0..3 {
14912            editor.go_to_next_hunk(&GoToHunk, window, cx);
14913        }
14914    });
14915
14916    cx.assert_editor_state(
14917        &r#"
14918        ˇuse some::modified;
14919
14920
14921        fn main() {
14922            println!("hello there");
14923
14924            println!("around the");
14925            println!("world");
14926        }
14927        "#
14928        .unindent(),
14929    );
14930
14931    cx.update_editor(|editor, window, cx| {
14932        //Wrap around the top of the buffer
14933        for _ in 0..2 {
14934            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14935        }
14936    });
14937
14938    cx.assert_editor_state(
14939        &r#"
14940        use some::modified;
14941
14942
14943        fn main() {
14944        ˇ    println!("hello there");
14945
14946            println!("around the");
14947            println!("world");
14948        }
14949        "#
14950        .unindent(),
14951    );
14952
14953    cx.update_editor(|editor, window, cx| {
14954        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14955    });
14956
14957    cx.assert_editor_state(
14958        &r#"
14959        use some::modified;
14960
14961        ˇ
14962        fn main() {
14963            println!("hello there");
14964
14965            println!("around the");
14966            println!("world");
14967        }
14968        "#
14969        .unindent(),
14970    );
14971
14972    cx.update_editor(|editor, window, cx| {
14973        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14974    });
14975
14976    cx.assert_editor_state(
14977        &r#"
14978        ˇuse some::modified;
14979
14980
14981        fn main() {
14982            println!("hello there");
14983
14984            println!("around the");
14985            println!("world");
14986        }
14987        "#
14988        .unindent(),
14989    );
14990
14991    cx.update_editor(|editor, window, cx| {
14992        for _ in 0..2 {
14993            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14994        }
14995    });
14996
14997    cx.assert_editor_state(
14998        &r#"
14999        use some::modified;
15000
15001
15002        fn main() {
15003        ˇ    println!("hello there");
15004
15005            println!("around the");
15006            println!("world");
15007        }
15008        "#
15009        .unindent(),
15010    );
15011
15012    cx.update_editor(|editor, window, cx| {
15013        editor.fold(&Fold, window, cx);
15014    });
15015
15016    cx.update_editor(|editor, window, cx| {
15017        editor.go_to_next_hunk(&GoToHunk, window, cx);
15018    });
15019
15020    cx.assert_editor_state(
15021        &r#"
15022        ˇuse some::modified;
15023
15024
15025        fn main() {
15026            println!("hello there");
15027
15028            println!("around the");
15029            println!("world");
15030        }
15031        "#
15032        .unindent(),
15033    );
15034}
15035
15036#[test]
15037fn test_split_words() {
15038    fn split(text: &str) -> Vec<&str> {
15039        split_words(text).collect()
15040    }
15041
15042    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
15043    assert_eq!(split("hello_world"), &["hello_", "world"]);
15044    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
15045    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
15046    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
15047    assert_eq!(split("helloworld"), &["helloworld"]);
15048
15049    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
15050}
15051
15052#[gpui::test]
15053async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
15054    init_test(cx, |_| {});
15055
15056    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
15057    let mut assert = |before, after| {
15058        let _state_context = cx.set_state(before);
15059        cx.run_until_parked();
15060        cx.update_editor(|editor, window, cx| {
15061            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
15062        });
15063        cx.run_until_parked();
15064        cx.assert_editor_state(after);
15065    };
15066
15067    // Outside bracket jumps to outside of matching bracket
15068    assert("console.logˇ(var);", "console.log(var)ˇ;");
15069    assert("console.log(var)ˇ;", "console.logˇ(var);");
15070
15071    // Inside bracket jumps to inside of matching bracket
15072    assert("console.log(ˇvar);", "console.log(varˇ);");
15073    assert("console.log(varˇ);", "console.log(ˇvar);");
15074
15075    // When outside a bracket and inside, favor jumping to the inside bracket
15076    assert(
15077        "console.log('foo', [1, 2, 3]ˇ);",
15078        "console.log(ˇ'foo', [1, 2, 3]);",
15079    );
15080    assert(
15081        "console.log(ˇ'foo', [1, 2, 3]);",
15082        "console.log('foo', [1, 2, 3]ˇ);",
15083    );
15084
15085    // Bias forward if two options are equally likely
15086    assert(
15087        "let result = curried_fun()ˇ();",
15088        "let result = curried_fun()()ˇ;",
15089    );
15090
15091    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
15092    assert(
15093        indoc! {"
15094            function test() {
15095                console.log('test')ˇ
15096            }"},
15097        indoc! {"
15098            function test() {
15099                console.logˇ('test')
15100            }"},
15101    );
15102}
15103
15104#[gpui::test]
15105async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
15106    init_test(cx, |_| {});
15107
15108    let fs = FakeFs::new(cx.executor());
15109    fs.insert_tree(
15110        path!("/a"),
15111        json!({
15112            "main.rs": "fn main() { let a = 5; }",
15113            "other.rs": "// Test file",
15114        }),
15115    )
15116    .await;
15117    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15118
15119    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15120    language_registry.add(Arc::new(Language::new(
15121        LanguageConfig {
15122            name: "Rust".into(),
15123            matcher: LanguageMatcher {
15124                path_suffixes: vec!["rs".to_string()],
15125                ..Default::default()
15126            },
15127            brackets: BracketPairConfig {
15128                pairs: vec![BracketPair {
15129                    start: "{".to_string(),
15130                    end: "}".to_string(),
15131                    close: true,
15132                    surround: true,
15133                    newline: true,
15134                }],
15135                disabled_scopes_by_bracket_ix: Vec::new(),
15136            },
15137            ..Default::default()
15138        },
15139        Some(tree_sitter_rust::LANGUAGE.into()),
15140    )));
15141    let mut fake_servers = language_registry.register_fake_lsp(
15142        "Rust",
15143        FakeLspAdapter {
15144            capabilities: lsp::ServerCapabilities {
15145                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
15146                    first_trigger_character: "{".to_string(),
15147                    more_trigger_character: None,
15148                }),
15149                ..Default::default()
15150            },
15151            ..Default::default()
15152        },
15153    );
15154
15155    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15156
15157    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15158
15159    let worktree_id = workspace
15160        .update(cx, |workspace, _, cx| {
15161            workspace.project().update(cx, |project, cx| {
15162                project.worktrees(cx).next().unwrap().read(cx).id()
15163            })
15164        })
15165        .unwrap();
15166
15167    let buffer = project
15168        .update(cx, |project, cx| {
15169            project.open_local_buffer(path!("/a/main.rs"), cx)
15170        })
15171        .await
15172        .unwrap();
15173    let editor_handle = workspace
15174        .update(cx, |workspace, window, cx| {
15175            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
15176        })
15177        .unwrap()
15178        .await
15179        .unwrap()
15180        .downcast::<Editor>()
15181        .unwrap();
15182
15183    cx.executor().start_waiting();
15184    let fake_server = fake_servers.next().await.unwrap();
15185
15186    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
15187        |params, _| async move {
15188            assert_eq!(
15189                params.text_document_position.text_document.uri,
15190                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
15191            );
15192            assert_eq!(
15193                params.text_document_position.position,
15194                lsp::Position::new(0, 21),
15195            );
15196
15197            Ok(Some(vec![lsp::TextEdit {
15198                new_text: "]".to_string(),
15199                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15200            }]))
15201        },
15202    );
15203
15204    editor_handle.update_in(cx, |editor, window, cx| {
15205        window.focus(&editor.focus_handle(cx));
15206        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15207            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
15208        });
15209        editor.handle_input("{", window, cx);
15210    });
15211
15212    cx.executor().run_until_parked();
15213
15214    buffer.update(cx, |buffer, _| {
15215        assert_eq!(
15216            buffer.text(),
15217            "fn main() { let a = {5}; }",
15218            "No extra braces from on type formatting should appear in the buffer"
15219        )
15220    });
15221}
15222
15223#[gpui::test(iterations = 20, seeds(31))]
15224async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
15225    init_test(cx, |_| {});
15226
15227    let mut cx = EditorLspTestContext::new_rust(
15228        lsp::ServerCapabilities {
15229            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
15230                first_trigger_character: ".".to_string(),
15231                more_trigger_character: None,
15232            }),
15233            ..Default::default()
15234        },
15235        cx,
15236    )
15237    .await;
15238
15239    cx.update_buffer(|buffer, _| {
15240        // This causes autoindent to be async.
15241        buffer.set_sync_parse_timeout(Duration::ZERO)
15242    });
15243
15244    cx.set_state("fn c() {\n    d()ˇ\n}\n");
15245    cx.simulate_keystroke("\n");
15246    cx.run_until_parked();
15247
15248    let buffer_cloned =
15249        cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap().clone());
15250    let mut request =
15251        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
15252            let buffer_cloned = buffer_cloned.clone();
15253            async move {
15254                buffer_cloned.update(&mut cx, |buffer, _| {
15255                    assert_eq!(
15256                        buffer.text(),
15257                        "fn c() {\n    d()\n        .\n}\n",
15258                        "OnTypeFormatting should triggered after autoindent applied"
15259                    )
15260                })?;
15261
15262                Ok(Some(vec![]))
15263            }
15264        });
15265
15266    cx.simulate_keystroke(".");
15267    cx.run_until_parked();
15268
15269    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
15270    assert!(request.next().await.is_some());
15271    request.close();
15272    assert!(request.next().await.is_none());
15273}
15274
15275#[gpui::test]
15276async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
15277    init_test(cx, |_| {});
15278
15279    let fs = FakeFs::new(cx.executor());
15280    fs.insert_tree(
15281        path!("/a"),
15282        json!({
15283            "main.rs": "fn main() { let a = 5; }",
15284            "other.rs": "// Test file",
15285        }),
15286    )
15287    .await;
15288
15289    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15290
15291    let server_restarts = Arc::new(AtomicUsize::new(0));
15292    let closure_restarts = Arc::clone(&server_restarts);
15293    let language_server_name = "test language server";
15294    let language_name: LanguageName = "Rust".into();
15295
15296    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15297    language_registry.add(Arc::new(Language::new(
15298        LanguageConfig {
15299            name: language_name.clone(),
15300            matcher: LanguageMatcher {
15301                path_suffixes: vec!["rs".to_string()],
15302                ..Default::default()
15303            },
15304            ..Default::default()
15305        },
15306        Some(tree_sitter_rust::LANGUAGE.into()),
15307    )));
15308    let mut fake_servers = language_registry.register_fake_lsp(
15309        "Rust",
15310        FakeLspAdapter {
15311            name: language_server_name,
15312            initialization_options: Some(json!({
15313                "testOptionValue": true
15314            })),
15315            initializer: Some(Box::new(move |fake_server| {
15316                let task_restarts = Arc::clone(&closure_restarts);
15317                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
15318                    task_restarts.fetch_add(1, atomic::Ordering::Release);
15319                    futures::future::ready(Ok(()))
15320                });
15321            })),
15322            ..Default::default()
15323        },
15324    );
15325
15326    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15327    let _buffer = project
15328        .update(cx, |project, cx| {
15329            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
15330        })
15331        .await
15332        .unwrap();
15333    let _fake_server = fake_servers.next().await.unwrap();
15334    update_test_language_settings(cx, |language_settings| {
15335        language_settings.languages.0.insert(
15336            language_name.clone(),
15337            LanguageSettingsContent {
15338                tab_size: NonZeroU32::new(8),
15339                ..Default::default()
15340            },
15341        );
15342    });
15343    cx.executor().run_until_parked();
15344    assert_eq!(
15345        server_restarts.load(atomic::Ordering::Acquire),
15346        0,
15347        "Should not restart LSP server on an unrelated change"
15348    );
15349
15350    update_test_project_settings(cx, |project_settings| {
15351        project_settings.lsp.insert(
15352            "Some other server name".into(),
15353            LspSettings {
15354                binary: None,
15355                settings: None,
15356                initialization_options: Some(json!({
15357                    "some other init value": false
15358                })),
15359                enable_lsp_tasks: false,
15360            },
15361        );
15362    });
15363    cx.executor().run_until_parked();
15364    assert_eq!(
15365        server_restarts.load(atomic::Ordering::Acquire),
15366        0,
15367        "Should not restart LSP server on an unrelated LSP settings change"
15368    );
15369
15370    update_test_project_settings(cx, |project_settings| {
15371        project_settings.lsp.insert(
15372            language_server_name.into(),
15373            LspSettings {
15374                binary: None,
15375                settings: None,
15376                initialization_options: Some(json!({
15377                    "anotherInitValue": false
15378                })),
15379                enable_lsp_tasks: false,
15380            },
15381        );
15382    });
15383    cx.executor().run_until_parked();
15384    assert_eq!(
15385        server_restarts.load(atomic::Ordering::Acquire),
15386        1,
15387        "Should restart LSP server on a related LSP settings change"
15388    );
15389
15390    update_test_project_settings(cx, |project_settings| {
15391        project_settings.lsp.insert(
15392            language_server_name.into(),
15393            LspSettings {
15394                binary: None,
15395                settings: None,
15396                initialization_options: Some(json!({
15397                    "anotherInitValue": false
15398                })),
15399                enable_lsp_tasks: false,
15400            },
15401        );
15402    });
15403    cx.executor().run_until_parked();
15404    assert_eq!(
15405        server_restarts.load(atomic::Ordering::Acquire),
15406        1,
15407        "Should not restart LSP server on a related LSP settings change that is the same"
15408    );
15409
15410    update_test_project_settings(cx, |project_settings| {
15411        project_settings.lsp.insert(
15412            language_server_name.into(),
15413            LspSettings {
15414                binary: None,
15415                settings: None,
15416                initialization_options: None,
15417                enable_lsp_tasks: false,
15418            },
15419        );
15420    });
15421    cx.executor().run_until_parked();
15422    assert_eq!(
15423        server_restarts.load(atomic::Ordering::Acquire),
15424        2,
15425        "Should restart LSP server on another related LSP settings change"
15426    );
15427}
15428
15429#[gpui::test]
15430async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
15431    init_test(cx, |_| {});
15432
15433    let mut cx = EditorLspTestContext::new_rust(
15434        lsp::ServerCapabilities {
15435            completion_provider: Some(lsp::CompletionOptions {
15436                trigger_characters: Some(vec![".".to_string()]),
15437                resolve_provider: Some(true),
15438                ..Default::default()
15439            }),
15440            ..Default::default()
15441        },
15442        cx,
15443    )
15444    .await;
15445
15446    cx.set_state("fn main() { let a = 2ˇ; }");
15447    cx.simulate_keystroke(".");
15448    let completion_item = lsp::CompletionItem {
15449        label: "some".into(),
15450        kind: Some(lsp::CompletionItemKind::SNIPPET),
15451        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15452        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15453            kind: lsp::MarkupKind::Markdown,
15454            value: "```rust\nSome(2)\n```".to_string(),
15455        })),
15456        deprecated: Some(false),
15457        sort_text: Some("fffffff2".to_string()),
15458        filter_text: Some("some".to_string()),
15459        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15460        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15461            range: lsp::Range {
15462                start: lsp::Position {
15463                    line: 0,
15464                    character: 22,
15465                },
15466                end: lsp::Position {
15467                    line: 0,
15468                    character: 22,
15469                },
15470            },
15471            new_text: "Some(2)".to_string(),
15472        })),
15473        additional_text_edits: Some(vec![lsp::TextEdit {
15474            range: lsp::Range {
15475                start: lsp::Position {
15476                    line: 0,
15477                    character: 20,
15478                },
15479                end: lsp::Position {
15480                    line: 0,
15481                    character: 22,
15482                },
15483            },
15484            new_text: "".to_string(),
15485        }]),
15486        ..Default::default()
15487    };
15488
15489    let closure_completion_item = completion_item.clone();
15490    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15491        let task_completion_item = closure_completion_item.clone();
15492        async move {
15493            Ok(Some(lsp::CompletionResponse::Array(vec![
15494                task_completion_item,
15495            ])))
15496        }
15497    });
15498
15499    request.next().await;
15500
15501    cx.condition(|editor, _| editor.context_menu_visible())
15502        .await;
15503    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15504        editor
15505            .confirm_completion(&ConfirmCompletion::default(), window, cx)
15506            .unwrap()
15507    });
15508    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
15509
15510    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
15511        let task_completion_item = completion_item.clone();
15512        async move { Ok(task_completion_item) }
15513    })
15514    .next()
15515    .await
15516    .unwrap();
15517    apply_additional_edits.await.unwrap();
15518    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
15519}
15520
15521#[gpui::test]
15522async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
15523    init_test(cx, |_| {});
15524
15525    let mut cx = EditorLspTestContext::new_rust(
15526        lsp::ServerCapabilities {
15527            completion_provider: Some(lsp::CompletionOptions {
15528                trigger_characters: Some(vec![".".to_string()]),
15529                resolve_provider: Some(true),
15530                ..Default::default()
15531            }),
15532            ..Default::default()
15533        },
15534        cx,
15535    )
15536    .await;
15537
15538    cx.set_state("fn main() { let a = 2ˇ; }");
15539    cx.simulate_keystroke(".");
15540
15541    let item1 = lsp::CompletionItem {
15542        label: "method id()".to_string(),
15543        filter_text: Some("id".to_string()),
15544        detail: None,
15545        documentation: None,
15546        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15547            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15548            new_text: ".id".to_string(),
15549        })),
15550        ..lsp::CompletionItem::default()
15551    };
15552
15553    let item2 = lsp::CompletionItem {
15554        label: "other".to_string(),
15555        filter_text: Some("other".to_string()),
15556        detail: None,
15557        documentation: None,
15558        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15559            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15560            new_text: ".other".to_string(),
15561        })),
15562        ..lsp::CompletionItem::default()
15563    };
15564
15565    let item1 = item1.clone();
15566    cx.set_request_handler::<lsp::request::Completion, _, _>({
15567        let item1 = item1.clone();
15568        move |_, _, _| {
15569            let item1 = item1.clone();
15570            let item2 = item2.clone();
15571            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
15572        }
15573    })
15574    .next()
15575    .await;
15576
15577    cx.condition(|editor, _| editor.context_menu_visible())
15578        .await;
15579    cx.update_editor(|editor, _, _| {
15580        let context_menu = editor.context_menu.borrow_mut();
15581        let context_menu = context_menu
15582            .as_ref()
15583            .expect("Should have the context menu deployed");
15584        match context_menu {
15585            CodeContextMenu::Completions(completions_menu) => {
15586                let completions = completions_menu.completions.borrow_mut();
15587                assert_eq!(
15588                    completions
15589                        .iter()
15590                        .map(|completion| &completion.label.text)
15591                        .collect::<Vec<_>>(),
15592                    vec!["method id()", "other"]
15593                )
15594            }
15595            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15596        }
15597    });
15598
15599    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
15600        let item1 = item1.clone();
15601        move |_, item_to_resolve, _| {
15602            let item1 = item1.clone();
15603            async move {
15604                if item1 == item_to_resolve {
15605                    Ok(lsp::CompletionItem {
15606                        label: "method id()".to_string(),
15607                        filter_text: Some("id".to_string()),
15608                        detail: Some("Now resolved!".to_string()),
15609                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
15610                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15611                            range: lsp::Range::new(
15612                                lsp::Position::new(0, 22),
15613                                lsp::Position::new(0, 22),
15614                            ),
15615                            new_text: ".id".to_string(),
15616                        })),
15617                        ..lsp::CompletionItem::default()
15618                    })
15619                } else {
15620                    Ok(item_to_resolve)
15621                }
15622            }
15623        }
15624    })
15625    .next()
15626    .await
15627    .unwrap();
15628    cx.run_until_parked();
15629
15630    cx.update_editor(|editor, window, cx| {
15631        editor.context_menu_next(&Default::default(), window, cx);
15632    });
15633
15634    cx.update_editor(|editor, _, _| {
15635        let context_menu = editor.context_menu.borrow_mut();
15636        let context_menu = context_menu
15637            .as_ref()
15638            .expect("Should have the context menu deployed");
15639        match context_menu {
15640            CodeContextMenu::Completions(completions_menu) => {
15641                let completions = completions_menu.completions.borrow_mut();
15642                assert_eq!(
15643                    completions
15644                        .iter()
15645                        .map(|completion| &completion.label.text)
15646                        .collect::<Vec<_>>(),
15647                    vec!["method id() Now resolved!", "other"],
15648                    "Should update first completion label, but not second as the filter text did not match."
15649                );
15650            }
15651            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15652        }
15653    });
15654}
15655
15656#[gpui::test]
15657async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
15658    init_test(cx, |_| {});
15659    let mut cx = EditorLspTestContext::new_rust(
15660        lsp::ServerCapabilities {
15661            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
15662            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
15663            completion_provider: Some(lsp::CompletionOptions {
15664                resolve_provider: Some(true),
15665                ..Default::default()
15666            }),
15667            ..Default::default()
15668        },
15669        cx,
15670    )
15671    .await;
15672    cx.set_state(indoc! {"
15673        struct TestStruct {
15674            field: i32
15675        }
15676
15677        fn mainˇ() {
15678            let unused_var = 42;
15679            let test_struct = TestStruct { field: 42 };
15680        }
15681    "});
15682    let symbol_range = cx.lsp_range(indoc! {"
15683        struct TestStruct {
15684            field: i32
15685        }
15686
15687        «fn main»() {
15688            let unused_var = 42;
15689            let test_struct = TestStruct { field: 42 };
15690        }
15691    "});
15692    let mut hover_requests =
15693        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
15694            Ok(Some(lsp::Hover {
15695                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
15696                    kind: lsp::MarkupKind::Markdown,
15697                    value: "Function documentation".to_string(),
15698                }),
15699                range: Some(symbol_range),
15700            }))
15701        });
15702
15703    // Case 1: Test that code action menu hide hover popover
15704    cx.dispatch_action(Hover);
15705    hover_requests.next().await;
15706    cx.condition(|editor, _| editor.hover_state.visible()).await;
15707    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
15708        move |_, _, _| async move {
15709            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
15710                lsp::CodeAction {
15711                    title: "Remove unused variable".to_string(),
15712                    kind: Some(CodeActionKind::QUICKFIX),
15713                    edit: Some(lsp::WorkspaceEdit {
15714                        changes: Some(
15715                            [(
15716                                lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
15717                                vec![lsp::TextEdit {
15718                                    range: lsp::Range::new(
15719                                        lsp::Position::new(5, 4),
15720                                        lsp::Position::new(5, 27),
15721                                    ),
15722                                    new_text: "".to_string(),
15723                                }],
15724                            )]
15725                            .into_iter()
15726                            .collect(),
15727                        ),
15728                        ..Default::default()
15729                    }),
15730                    ..Default::default()
15731                },
15732            )]))
15733        },
15734    );
15735    cx.update_editor(|editor, window, cx| {
15736        editor.toggle_code_actions(
15737            &ToggleCodeActions {
15738                deployed_from: None,
15739                quick_launch: false,
15740            },
15741            window,
15742            cx,
15743        );
15744    });
15745    code_action_requests.next().await;
15746    cx.run_until_parked();
15747    cx.condition(|editor, _| editor.context_menu_visible())
15748        .await;
15749    cx.update_editor(|editor, _, _| {
15750        assert!(
15751            !editor.hover_state.visible(),
15752            "Hover popover should be hidden when code action menu is shown"
15753        );
15754        // Hide code actions
15755        editor.context_menu.take();
15756    });
15757
15758    // Case 2: Test that code completions hide hover popover
15759    cx.dispatch_action(Hover);
15760    hover_requests.next().await;
15761    cx.condition(|editor, _| editor.hover_state.visible()).await;
15762    let counter = Arc::new(AtomicUsize::new(0));
15763    let mut completion_requests =
15764        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15765            let counter = counter.clone();
15766            async move {
15767                counter.fetch_add(1, atomic::Ordering::Release);
15768                Ok(Some(lsp::CompletionResponse::Array(vec![
15769                    lsp::CompletionItem {
15770                        label: "main".into(),
15771                        kind: Some(lsp::CompletionItemKind::FUNCTION),
15772                        detail: Some("() -> ()".to_string()),
15773                        ..Default::default()
15774                    },
15775                    lsp::CompletionItem {
15776                        label: "TestStruct".into(),
15777                        kind: Some(lsp::CompletionItemKind::STRUCT),
15778                        detail: Some("struct TestStruct".to_string()),
15779                        ..Default::default()
15780                    },
15781                ])))
15782            }
15783        });
15784    cx.update_editor(|editor, window, cx| {
15785        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15786    });
15787    completion_requests.next().await;
15788    cx.condition(|editor, _| editor.context_menu_visible())
15789        .await;
15790    cx.update_editor(|editor, _, _| {
15791        assert!(
15792            !editor.hover_state.visible(),
15793            "Hover popover should be hidden when completion menu is shown"
15794        );
15795    });
15796}
15797
15798#[gpui::test]
15799async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
15800    init_test(cx, |_| {});
15801
15802    let mut cx = EditorLspTestContext::new_rust(
15803        lsp::ServerCapabilities {
15804            completion_provider: Some(lsp::CompletionOptions {
15805                trigger_characters: Some(vec![".".to_string()]),
15806                resolve_provider: Some(true),
15807                ..Default::default()
15808            }),
15809            ..Default::default()
15810        },
15811        cx,
15812    )
15813    .await;
15814
15815    cx.set_state("fn main() { let a = 2ˇ; }");
15816    cx.simulate_keystroke(".");
15817
15818    let unresolved_item_1 = lsp::CompletionItem {
15819        label: "id".to_string(),
15820        filter_text: Some("id".to_string()),
15821        detail: None,
15822        documentation: None,
15823        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15824            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15825            new_text: ".id".to_string(),
15826        })),
15827        ..lsp::CompletionItem::default()
15828    };
15829    let resolved_item_1 = lsp::CompletionItem {
15830        additional_text_edits: Some(vec![lsp::TextEdit {
15831            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
15832            new_text: "!!".to_string(),
15833        }]),
15834        ..unresolved_item_1.clone()
15835    };
15836    let unresolved_item_2 = lsp::CompletionItem {
15837        label: "other".to_string(),
15838        filter_text: Some("other".to_string()),
15839        detail: None,
15840        documentation: None,
15841        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15842            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15843            new_text: ".other".to_string(),
15844        })),
15845        ..lsp::CompletionItem::default()
15846    };
15847    let resolved_item_2 = lsp::CompletionItem {
15848        additional_text_edits: Some(vec![lsp::TextEdit {
15849            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
15850            new_text: "??".to_string(),
15851        }]),
15852        ..unresolved_item_2.clone()
15853    };
15854
15855    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
15856    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
15857    cx.lsp
15858        .server
15859        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
15860            let unresolved_item_1 = unresolved_item_1.clone();
15861            let resolved_item_1 = resolved_item_1.clone();
15862            let unresolved_item_2 = unresolved_item_2.clone();
15863            let resolved_item_2 = resolved_item_2.clone();
15864            let resolve_requests_1 = resolve_requests_1.clone();
15865            let resolve_requests_2 = resolve_requests_2.clone();
15866            move |unresolved_request, _| {
15867                let unresolved_item_1 = unresolved_item_1.clone();
15868                let resolved_item_1 = resolved_item_1.clone();
15869                let unresolved_item_2 = unresolved_item_2.clone();
15870                let resolved_item_2 = resolved_item_2.clone();
15871                let resolve_requests_1 = resolve_requests_1.clone();
15872                let resolve_requests_2 = resolve_requests_2.clone();
15873                async move {
15874                    if unresolved_request == unresolved_item_1 {
15875                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
15876                        Ok(resolved_item_1.clone())
15877                    } else if unresolved_request == unresolved_item_2 {
15878                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
15879                        Ok(resolved_item_2.clone())
15880                    } else {
15881                        panic!("Unexpected completion item {unresolved_request:?}")
15882                    }
15883                }
15884            }
15885        })
15886        .detach();
15887
15888    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15889        let unresolved_item_1 = unresolved_item_1.clone();
15890        let unresolved_item_2 = unresolved_item_2.clone();
15891        async move {
15892            Ok(Some(lsp::CompletionResponse::Array(vec![
15893                unresolved_item_1,
15894                unresolved_item_2,
15895            ])))
15896        }
15897    })
15898    .next()
15899    .await;
15900
15901    cx.condition(|editor, _| editor.context_menu_visible())
15902        .await;
15903    cx.update_editor(|editor, _, _| {
15904        let context_menu = editor.context_menu.borrow_mut();
15905        let context_menu = context_menu
15906            .as_ref()
15907            .expect("Should have the context menu deployed");
15908        match context_menu {
15909            CodeContextMenu::Completions(completions_menu) => {
15910                let completions = completions_menu.completions.borrow_mut();
15911                assert_eq!(
15912                    completions
15913                        .iter()
15914                        .map(|completion| &completion.label.text)
15915                        .collect::<Vec<_>>(),
15916                    vec!["id", "other"]
15917                )
15918            }
15919            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15920        }
15921    });
15922    cx.run_until_parked();
15923
15924    cx.update_editor(|editor, window, cx| {
15925        editor.context_menu_next(&ContextMenuNext, window, cx);
15926    });
15927    cx.run_until_parked();
15928    cx.update_editor(|editor, window, cx| {
15929        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
15930    });
15931    cx.run_until_parked();
15932    cx.update_editor(|editor, window, cx| {
15933        editor.context_menu_next(&ContextMenuNext, window, cx);
15934    });
15935    cx.run_until_parked();
15936    cx.update_editor(|editor, window, cx| {
15937        editor
15938            .compose_completion(&ComposeCompletion::default(), window, cx)
15939            .expect("No task returned")
15940    })
15941    .await
15942    .expect("Completion failed");
15943    cx.run_until_parked();
15944
15945    cx.update_editor(|editor, _, cx| {
15946        assert_eq!(
15947            resolve_requests_1.load(atomic::Ordering::Acquire),
15948            1,
15949            "Should always resolve once despite multiple selections"
15950        );
15951        assert_eq!(
15952            resolve_requests_2.load(atomic::Ordering::Acquire),
15953            1,
15954            "Should always resolve once after multiple selections and applying the completion"
15955        );
15956        assert_eq!(
15957            editor.text(cx),
15958            "fn main() { let a = ??.other; }",
15959            "Should use resolved data when applying the completion"
15960        );
15961    });
15962}
15963
15964#[gpui::test]
15965async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
15966    init_test(cx, |_| {});
15967
15968    let item_0 = lsp::CompletionItem {
15969        label: "abs".into(),
15970        insert_text: Some("abs".into()),
15971        data: Some(json!({ "very": "special"})),
15972        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
15973        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
15974            lsp::InsertReplaceEdit {
15975                new_text: "abs".to_string(),
15976                insert: lsp::Range::default(),
15977                replace: lsp::Range::default(),
15978            },
15979        )),
15980        ..lsp::CompletionItem::default()
15981    };
15982    let items = iter::once(item_0.clone())
15983        .chain((11..51).map(|i| lsp::CompletionItem {
15984            label: format!("item_{}", i),
15985            insert_text: Some(format!("item_{}", i)),
15986            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15987            ..lsp::CompletionItem::default()
15988        }))
15989        .collect::<Vec<_>>();
15990
15991    let default_commit_characters = vec!["?".to_string()];
15992    let default_data = json!({ "default": "data"});
15993    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
15994    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
15995    let default_edit_range = lsp::Range {
15996        start: lsp::Position {
15997            line: 0,
15998            character: 5,
15999        },
16000        end: lsp::Position {
16001            line: 0,
16002            character: 5,
16003        },
16004    };
16005
16006    let mut cx = EditorLspTestContext::new_rust(
16007        lsp::ServerCapabilities {
16008            completion_provider: Some(lsp::CompletionOptions {
16009                trigger_characters: Some(vec![".".to_string()]),
16010                resolve_provider: Some(true),
16011                ..Default::default()
16012            }),
16013            ..Default::default()
16014        },
16015        cx,
16016    )
16017    .await;
16018
16019    cx.set_state("fn main() { let a = 2ˇ; }");
16020    cx.simulate_keystroke(".");
16021
16022    let completion_data = default_data.clone();
16023    let completion_characters = default_commit_characters.clone();
16024    let completion_items = items.clone();
16025    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16026        let default_data = completion_data.clone();
16027        let default_commit_characters = completion_characters.clone();
16028        let items = completion_items.clone();
16029        async move {
16030            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16031                items,
16032                item_defaults: Some(lsp::CompletionListItemDefaults {
16033                    data: Some(default_data.clone()),
16034                    commit_characters: Some(default_commit_characters.clone()),
16035                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
16036                        default_edit_range,
16037                    )),
16038                    insert_text_format: Some(default_insert_text_format),
16039                    insert_text_mode: Some(default_insert_text_mode),
16040                }),
16041                ..lsp::CompletionList::default()
16042            })))
16043        }
16044    })
16045    .next()
16046    .await;
16047
16048    let resolved_items = Arc::new(Mutex::new(Vec::new()));
16049    cx.lsp
16050        .server
16051        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
16052            let closure_resolved_items = resolved_items.clone();
16053            move |item_to_resolve, _| {
16054                let closure_resolved_items = closure_resolved_items.clone();
16055                async move {
16056                    closure_resolved_items.lock().push(item_to_resolve.clone());
16057                    Ok(item_to_resolve)
16058                }
16059            }
16060        })
16061        .detach();
16062
16063    cx.condition(|editor, _| editor.context_menu_visible())
16064        .await;
16065    cx.run_until_parked();
16066    cx.update_editor(|editor, _, _| {
16067        let menu = editor.context_menu.borrow_mut();
16068        match menu.as_ref().expect("should have the completions menu") {
16069            CodeContextMenu::Completions(completions_menu) => {
16070                assert_eq!(
16071                    completions_menu
16072                        .entries
16073                        .borrow()
16074                        .iter()
16075                        .map(|mat| mat.string.clone())
16076                        .collect::<Vec<String>>(),
16077                    items
16078                        .iter()
16079                        .map(|completion| completion.label.clone())
16080                        .collect::<Vec<String>>()
16081                );
16082            }
16083            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
16084        }
16085    });
16086    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
16087    // with 4 from the end.
16088    assert_eq!(
16089        *resolved_items.lock(),
16090        [&items[0..16], &items[items.len() - 4..items.len()]]
16091            .concat()
16092            .iter()
16093            .cloned()
16094            .map(|mut item| {
16095                if item.data.is_none() {
16096                    item.data = Some(default_data.clone());
16097                }
16098                item
16099            })
16100            .collect::<Vec<lsp::CompletionItem>>(),
16101        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
16102    );
16103    resolved_items.lock().clear();
16104
16105    cx.update_editor(|editor, window, cx| {
16106        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
16107    });
16108    cx.run_until_parked();
16109    // Completions that have already been resolved are skipped.
16110    assert_eq!(
16111        *resolved_items.lock(),
16112        items[items.len() - 17..items.len() - 4]
16113            .iter()
16114            .cloned()
16115            .map(|mut item| {
16116                if item.data.is_none() {
16117                    item.data = Some(default_data.clone());
16118                }
16119                item
16120            })
16121            .collect::<Vec<lsp::CompletionItem>>()
16122    );
16123    resolved_items.lock().clear();
16124}
16125
16126#[gpui::test]
16127async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
16128    init_test(cx, |_| {});
16129
16130    let mut cx = EditorLspTestContext::new(
16131        Language::new(
16132            LanguageConfig {
16133                matcher: LanguageMatcher {
16134                    path_suffixes: vec!["jsx".into()],
16135                    ..Default::default()
16136                },
16137                overrides: [(
16138                    "element".into(),
16139                    LanguageConfigOverride {
16140                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
16141                        ..Default::default()
16142                    },
16143                )]
16144                .into_iter()
16145                .collect(),
16146                ..Default::default()
16147            },
16148            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
16149        )
16150        .with_override_query("(jsx_self_closing_element) @element")
16151        .unwrap(),
16152        lsp::ServerCapabilities {
16153            completion_provider: Some(lsp::CompletionOptions {
16154                trigger_characters: Some(vec![":".to_string()]),
16155                ..Default::default()
16156            }),
16157            ..Default::default()
16158        },
16159        cx,
16160    )
16161    .await;
16162
16163    cx.lsp
16164        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16165            Ok(Some(lsp::CompletionResponse::Array(vec![
16166                lsp::CompletionItem {
16167                    label: "bg-blue".into(),
16168                    ..Default::default()
16169                },
16170                lsp::CompletionItem {
16171                    label: "bg-red".into(),
16172                    ..Default::default()
16173                },
16174                lsp::CompletionItem {
16175                    label: "bg-yellow".into(),
16176                    ..Default::default()
16177                },
16178            ])))
16179        });
16180
16181    cx.set_state(r#"<p class="bgˇ" />"#);
16182
16183    // Trigger completion when typing a dash, because the dash is an extra
16184    // word character in the 'element' scope, which contains the cursor.
16185    cx.simulate_keystroke("-");
16186    cx.executor().run_until_parked();
16187    cx.update_editor(|editor, _, _| {
16188        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16189        {
16190            assert_eq!(
16191                completion_menu_entries(&menu),
16192                &["bg-blue", "bg-red", "bg-yellow"]
16193            );
16194        } else {
16195            panic!("expected completion menu to be open");
16196        }
16197    });
16198
16199    cx.simulate_keystroke("l");
16200    cx.executor().run_until_parked();
16201    cx.update_editor(|editor, _, _| {
16202        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16203        {
16204            assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
16205        } else {
16206            panic!("expected completion menu to be open");
16207        }
16208    });
16209
16210    // When filtering completions, consider the character after the '-' to
16211    // be the start of a subword.
16212    cx.set_state(r#"<p class="yelˇ" />"#);
16213    cx.simulate_keystroke("l");
16214    cx.executor().run_until_parked();
16215    cx.update_editor(|editor, _, _| {
16216        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16217        {
16218            assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
16219        } else {
16220            panic!("expected completion menu to be open");
16221        }
16222    });
16223}
16224
16225fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
16226    let entries = menu.entries.borrow();
16227    entries.iter().map(|mat| mat.string.clone()).collect()
16228}
16229
16230#[gpui::test]
16231async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
16232    init_test(cx, |settings| {
16233        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
16234            Formatter::Prettier,
16235        )))
16236    });
16237
16238    let fs = FakeFs::new(cx.executor());
16239    fs.insert_file(path!("/file.ts"), Default::default()).await;
16240
16241    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
16242    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16243
16244    language_registry.add(Arc::new(Language::new(
16245        LanguageConfig {
16246            name: "TypeScript".into(),
16247            matcher: LanguageMatcher {
16248                path_suffixes: vec!["ts".to_string()],
16249                ..Default::default()
16250            },
16251            ..Default::default()
16252        },
16253        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
16254    )));
16255    update_test_language_settings(cx, |settings| {
16256        settings.defaults.prettier = Some(PrettierSettings {
16257            allowed: true,
16258            ..PrettierSettings::default()
16259        });
16260    });
16261
16262    let test_plugin = "test_plugin";
16263    let _ = language_registry.register_fake_lsp(
16264        "TypeScript",
16265        FakeLspAdapter {
16266            prettier_plugins: vec![test_plugin],
16267            ..Default::default()
16268        },
16269    );
16270
16271    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
16272    let buffer = project
16273        .update(cx, |project, cx| {
16274            project.open_local_buffer(path!("/file.ts"), cx)
16275        })
16276        .await
16277        .unwrap();
16278
16279    let buffer_text = "one\ntwo\nthree\n";
16280    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16281    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16282    editor.update_in(cx, |editor, window, cx| {
16283        editor.set_text(buffer_text, window, cx)
16284    });
16285
16286    editor
16287        .update_in(cx, |editor, window, cx| {
16288            editor.perform_format(
16289                project.clone(),
16290                FormatTrigger::Manual,
16291                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
16292                window,
16293                cx,
16294            )
16295        })
16296        .unwrap()
16297        .await;
16298    assert_eq!(
16299        editor.update(cx, |editor, cx| editor.text(cx)),
16300        buffer_text.to_string() + prettier_format_suffix,
16301        "Test prettier formatting was not applied to the original buffer text",
16302    );
16303
16304    update_test_language_settings(cx, |settings| {
16305        settings.defaults.formatter = Some(SelectedFormatter::Auto)
16306    });
16307    let format = editor.update_in(cx, |editor, window, cx| {
16308        editor.perform_format(
16309            project.clone(),
16310            FormatTrigger::Manual,
16311            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
16312            window,
16313            cx,
16314        )
16315    });
16316    format.await.unwrap();
16317    assert_eq!(
16318        editor.update(cx, |editor, cx| editor.text(cx)),
16319        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
16320        "Autoformatting (via test prettier) was not applied to the original buffer text",
16321    );
16322}
16323
16324#[gpui::test]
16325async fn test_addition_reverts(cx: &mut TestAppContext) {
16326    init_test(cx, |_| {});
16327    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16328    let base_text = indoc! {r#"
16329        struct Row;
16330        struct Row1;
16331        struct Row2;
16332
16333        struct Row4;
16334        struct Row5;
16335        struct Row6;
16336
16337        struct Row8;
16338        struct Row9;
16339        struct Row10;"#};
16340
16341    // When addition hunks are not adjacent to carets, no hunk revert is performed
16342    assert_hunk_revert(
16343        indoc! {r#"struct Row;
16344                   struct Row1;
16345                   struct Row1.1;
16346                   struct Row1.2;
16347                   struct Row2;ˇ
16348
16349                   struct Row4;
16350                   struct Row5;
16351                   struct Row6;
16352
16353                   struct Row8;
16354                   ˇstruct Row9;
16355                   struct Row9.1;
16356                   struct Row9.2;
16357                   struct Row9.3;
16358                   struct Row10;"#},
16359        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
16360        indoc! {r#"struct Row;
16361                   struct Row1;
16362                   struct Row1.1;
16363                   struct Row1.2;
16364                   struct Row2;ˇ
16365
16366                   struct Row4;
16367                   struct Row5;
16368                   struct Row6;
16369
16370                   struct Row8;
16371                   ˇstruct Row9;
16372                   struct Row9.1;
16373                   struct Row9.2;
16374                   struct Row9.3;
16375                   struct Row10;"#},
16376        base_text,
16377        &mut cx,
16378    );
16379    // Same for selections
16380    assert_hunk_revert(
16381        indoc! {r#"struct Row;
16382                   struct Row1;
16383                   struct Row2;
16384                   struct Row2.1;
16385                   struct Row2.2;
16386                   «ˇ
16387                   struct Row4;
16388                   struct» Row5;
16389                   «struct Row6;
16390                   ˇ»
16391                   struct Row9.1;
16392                   struct Row9.2;
16393                   struct Row9.3;
16394                   struct Row8;
16395                   struct Row9;
16396                   struct Row10;"#},
16397        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
16398        indoc! {r#"struct Row;
16399                   struct Row1;
16400                   struct Row2;
16401                   struct Row2.1;
16402                   struct Row2.2;
16403                   «ˇ
16404                   struct Row4;
16405                   struct» Row5;
16406                   «struct Row6;
16407                   ˇ»
16408                   struct Row9.1;
16409                   struct Row9.2;
16410                   struct Row9.3;
16411                   struct Row8;
16412                   struct Row9;
16413                   struct Row10;"#},
16414        base_text,
16415        &mut cx,
16416    );
16417
16418    // When carets and selections intersect the addition hunks, those are reverted.
16419    // Adjacent carets got merged.
16420    assert_hunk_revert(
16421        indoc! {r#"struct Row;
16422                   ˇ// something on the top
16423                   struct Row1;
16424                   struct Row2;
16425                   struct Roˇw3.1;
16426                   struct Row2.2;
16427                   struct Row2.3;ˇ
16428
16429                   struct Row4;
16430                   struct ˇRow5.1;
16431                   struct Row5.2;
16432                   struct «Rowˇ»5.3;
16433                   struct Row5;
16434                   struct Row6;
16435                   ˇ
16436                   struct Row9.1;
16437                   struct «Rowˇ»9.2;
16438                   struct «ˇRow»9.3;
16439                   struct Row8;
16440                   struct Row9;
16441                   «ˇ// something on bottom»
16442                   struct Row10;"#},
16443        vec![
16444            DiffHunkStatusKind::Added,
16445            DiffHunkStatusKind::Added,
16446            DiffHunkStatusKind::Added,
16447            DiffHunkStatusKind::Added,
16448            DiffHunkStatusKind::Added,
16449        ],
16450        indoc! {r#"struct Row;
16451                   ˇstruct Row1;
16452                   struct Row2;
16453                   ˇ
16454                   struct Row4;
16455                   ˇstruct Row5;
16456                   struct Row6;
16457                   ˇ
16458                   ˇstruct Row8;
16459                   struct Row9;
16460                   ˇstruct Row10;"#},
16461        base_text,
16462        &mut cx,
16463    );
16464}
16465
16466#[gpui::test]
16467async fn test_modification_reverts(cx: &mut TestAppContext) {
16468    init_test(cx, |_| {});
16469    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16470    let base_text = indoc! {r#"
16471        struct Row;
16472        struct Row1;
16473        struct Row2;
16474
16475        struct Row4;
16476        struct Row5;
16477        struct Row6;
16478
16479        struct Row8;
16480        struct Row9;
16481        struct Row10;"#};
16482
16483    // Modification hunks behave the same as the addition ones.
16484    assert_hunk_revert(
16485        indoc! {r#"struct Row;
16486                   struct Row1;
16487                   struct Row33;
16488                   ˇ
16489                   struct Row4;
16490                   struct Row5;
16491                   struct Row6;
16492                   ˇ
16493                   struct Row99;
16494                   struct Row9;
16495                   struct Row10;"#},
16496        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
16497        indoc! {r#"struct Row;
16498                   struct Row1;
16499                   struct Row33;
16500                   ˇ
16501                   struct Row4;
16502                   struct Row5;
16503                   struct Row6;
16504                   ˇ
16505                   struct Row99;
16506                   struct Row9;
16507                   struct Row10;"#},
16508        base_text,
16509        &mut cx,
16510    );
16511    assert_hunk_revert(
16512        indoc! {r#"struct Row;
16513                   struct Row1;
16514                   struct Row33;
16515                   «ˇ
16516                   struct Row4;
16517                   struct» Row5;
16518                   «struct Row6;
16519                   ˇ»
16520                   struct Row99;
16521                   struct Row9;
16522                   struct Row10;"#},
16523        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
16524        indoc! {r#"struct Row;
16525                   struct Row1;
16526                   struct Row33;
16527                   «ˇ
16528                   struct Row4;
16529                   struct» Row5;
16530                   «struct Row6;
16531                   ˇ»
16532                   struct Row99;
16533                   struct Row9;
16534                   struct Row10;"#},
16535        base_text,
16536        &mut cx,
16537    );
16538
16539    assert_hunk_revert(
16540        indoc! {r#"ˇstruct Row1.1;
16541                   struct Row1;
16542                   «ˇstr»uct Row22;
16543
16544                   struct ˇRow44;
16545                   struct Row5;
16546                   struct «Rˇ»ow66;ˇ
16547
16548                   «struˇ»ct Row88;
16549                   struct Row9;
16550                   struct Row1011;ˇ"#},
16551        vec![
16552            DiffHunkStatusKind::Modified,
16553            DiffHunkStatusKind::Modified,
16554            DiffHunkStatusKind::Modified,
16555            DiffHunkStatusKind::Modified,
16556            DiffHunkStatusKind::Modified,
16557            DiffHunkStatusKind::Modified,
16558        ],
16559        indoc! {r#"struct Row;
16560                   ˇstruct Row1;
16561                   struct Row2;
16562                   ˇ
16563                   struct Row4;
16564                   ˇstruct Row5;
16565                   struct Row6;
16566                   ˇ
16567                   struct Row8;
16568                   ˇstruct Row9;
16569                   struct Row10;ˇ"#},
16570        base_text,
16571        &mut cx,
16572    );
16573}
16574
16575#[gpui::test]
16576async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
16577    init_test(cx, |_| {});
16578    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16579    let base_text = indoc! {r#"
16580        one
16581
16582        two
16583        three
16584        "#};
16585
16586    cx.set_head_text(base_text);
16587    cx.set_state("\nˇ\n");
16588    cx.executor().run_until_parked();
16589    cx.update_editor(|editor, _window, cx| {
16590        editor.expand_selected_diff_hunks(cx);
16591    });
16592    cx.executor().run_until_parked();
16593    cx.update_editor(|editor, window, cx| {
16594        editor.backspace(&Default::default(), window, cx);
16595    });
16596    cx.run_until_parked();
16597    cx.assert_state_with_diff(
16598        indoc! {r#"
16599
16600        - two
16601        - threeˇ
16602        +
16603        "#}
16604        .to_string(),
16605    );
16606}
16607
16608#[gpui::test]
16609async fn test_deletion_reverts(cx: &mut TestAppContext) {
16610    init_test(cx, |_| {});
16611    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16612    let base_text = indoc! {r#"struct Row;
16613struct Row1;
16614struct Row2;
16615
16616struct Row4;
16617struct Row5;
16618struct Row6;
16619
16620struct Row8;
16621struct Row9;
16622struct Row10;"#};
16623
16624    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
16625    assert_hunk_revert(
16626        indoc! {r#"struct Row;
16627                   struct Row2;
16628
16629                   ˇstruct Row4;
16630                   struct Row5;
16631                   struct Row6;
16632                   ˇ
16633                   struct Row8;
16634                   struct Row10;"#},
16635        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16636        indoc! {r#"struct Row;
16637                   struct Row2;
16638
16639                   ˇstruct Row4;
16640                   struct Row5;
16641                   struct Row6;
16642                   ˇ
16643                   struct Row8;
16644                   struct Row10;"#},
16645        base_text,
16646        &mut cx,
16647    );
16648    assert_hunk_revert(
16649        indoc! {r#"struct Row;
16650                   struct Row2;
16651
16652                   «ˇstruct Row4;
16653                   struct» Row5;
16654                   «struct Row6;
16655                   ˇ»
16656                   struct Row8;
16657                   struct Row10;"#},
16658        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16659        indoc! {r#"struct Row;
16660                   struct Row2;
16661
16662                   «ˇstruct Row4;
16663                   struct» Row5;
16664                   «struct Row6;
16665                   ˇ»
16666                   struct Row8;
16667                   struct Row10;"#},
16668        base_text,
16669        &mut cx,
16670    );
16671
16672    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
16673    assert_hunk_revert(
16674        indoc! {r#"struct Row;
16675                   ˇstruct Row2;
16676
16677                   struct Row4;
16678                   struct Row5;
16679                   struct Row6;
16680
16681                   struct Row8;ˇ
16682                   struct Row10;"#},
16683        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16684        indoc! {r#"struct Row;
16685                   struct Row1;
16686                   ˇstruct Row2;
16687
16688                   struct Row4;
16689                   struct Row5;
16690                   struct Row6;
16691
16692                   struct Row8;ˇ
16693                   struct Row9;
16694                   struct Row10;"#},
16695        base_text,
16696        &mut cx,
16697    );
16698    assert_hunk_revert(
16699        indoc! {r#"struct Row;
16700                   struct Row2«ˇ;
16701                   struct Row4;
16702                   struct» Row5;
16703                   «struct Row6;
16704
16705                   struct Row8;ˇ»
16706                   struct Row10;"#},
16707        vec![
16708            DiffHunkStatusKind::Deleted,
16709            DiffHunkStatusKind::Deleted,
16710            DiffHunkStatusKind::Deleted,
16711        ],
16712        indoc! {r#"struct Row;
16713                   struct Row1;
16714                   struct Row2«ˇ;
16715
16716                   struct Row4;
16717                   struct» Row5;
16718                   «struct Row6;
16719
16720                   struct Row8;ˇ»
16721                   struct Row9;
16722                   struct Row10;"#},
16723        base_text,
16724        &mut cx,
16725    );
16726}
16727
16728#[gpui::test]
16729async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
16730    init_test(cx, |_| {});
16731
16732    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
16733    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
16734    let base_text_3 =
16735        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
16736
16737    let text_1 = edit_first_char_of_every_line(base_text_1);
16738    let text_2 = edit_first_char_of_every_line(base_text_2);
16739    let text_3 = edit_first_char_of_every_line(base_text_3);
16740
16741    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
16742    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
16743    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
16744
16745    let multibuffer = cx.new(|cx| {
16746        let mut multibuffer = MultiBuffer::new(ReadWrite);
16747        multibuffer.push_excerpts(
16748            buffer_1.clone(),
16749            [
16750                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16751                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16752                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16753            ],
16754            cx,
16755        );
16756        multibuffer.push_excerpts(
16757            buffer_2.clone(),
16758            [
16759                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16760                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16761                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16762            ],
16763            cx,
16764        );
16765        multibuffer.push_excerpts(
16766            buffer_3.clone(),
16767            [
16768                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16769                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16770                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16771            ],
16772            cx,
16773        );
16774        multibuffer
16775    });
16776
16777    let fs = FakeFs::new(cx.executor());
16778    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
16779    let (editor, cx) = cx
16780        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
16781    editor.update_in(cx, |editor, _window, cx| {
16782        for (buffer, diff_base) in [
16783            (buffer_1.clone(), base_text_1),
16784            (buffer_2.clone(), base_text_2),
16785            (buffer_3.clone(), base_text_3),
16786        ] {
16787            let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
16788            editor
16789                .buffer
16790                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
16791        }
16792    });
16793    cx.executor().run_until_parked();
16794
16795    editor.update_in(cx, |editor, window, cx| {
16796        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}");
16797        editor.select_all(&SelectAll, window, cx);
16798        editor.git_restore(&Default::default(), window, cx);
16799    });
16800    cx.executor().run_until_parked();
16801
16802    // When all ranges are selected, all buffer hunks are reverted.
16803    editor.update(cx, |editor, cx| {
16804        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");
16805    });
16806    buffer_1.update(cx, |buffer, _| {
16807        assert_eq!(buffer.text(), base_text_1);
16808    });
16809    buffer_2.update(cx, |buffer, _| {
16810        assert_eq!(buffer.text(), base_text_2);
16811    });
16812    buffer_3.update(cx, |buffer, _| {
16813        assert_eq!(buffer.text(), base_text_3);
16814    });
16815
16816    editor.update_in(cx, |editor, window, cx| {
16817        editor.undo(&Default::default(), window, cx);
16818    });
16819
16820    editor.update_in(cx, |editor, window, cx| {
16821        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16822            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
16823        });
16824        editor.git_restore(&Default::default(), window, cx);
16825    });
16826
16827    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
16828    // but not affect buffer_2 and its related excerpts.
16829    editor.update(cx, |editor, cx| {
16830        assert_eq!(
16831            editor.text(cx),
16832            "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}"
16833        );
16834    });
16835    buffer_1.update(cx, |buffer, _| {
16836        assert_eq!(buffer.text(), base_text_1);
16837    });
16838    buffer_2.update(cx, |buffer, _| {
16839        assert_eq!(
16840            buffer.text(),
16841            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
16842        );
16843    });
16844    buffer_3.update(cx, |buffer, _| {
16845        assert_eq!(
16846            buffer.text(),
16847            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
16848        );
16849    });
16850
16851    fn edit_first_char_of_every_line(text: &str) -> String {
16852        text.split('\n')
16853            .map(|line| format!("X{}", &line[1..]))
16854            .collect::<Vec<_>>()
16855            .join("\n")
16856    }
16857}
16858
16859#[gpui::test]
16860async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
16861    init_test(cx, |_| {});
16862
16863    let cols = 4;
16864    let rows = 10;
16865    let sample_text_1 = sample_text(rows, cols, 'a');
16866    assert_eq!(
16867        sample_text_1,
16868        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
16869    );
16870    let sample_text_2 = sample_text(rows, cols, 'l');
16871    assert_eq!(
16872        sample_text_2,
16873        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
16874    );
16875    let sample_text_3 = sample_text(rows, cols, 'v');
16876    assert_eq!(
16877        sample_text_3,
16878        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
16879    );
16880
16881    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
16882    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
16883    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
16884
16885    let multi_buffer = cx.new(|cx| {
16886        let mut multibuffer = MultiBuffer::new(ReadWrite);
16887        multibuffer.push_excerpts(
16888            buffer_1.clone(),
16889            [
16890                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16891                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16892                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16893            ],
16894            cx,
16895        );
16896        multibuffer.push_excerpts(
16897            buffer_2.clone(),
16898            [
16899                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16900                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16901                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16902            ],
16903            cx,
16904        );
16905        multibuffer.push_excerpts(
16906            buffer_3.clone(),
16907            [
16908                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16909                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16910                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16911            ],
16912            cx,
16913        );
16914        multibuffer
16915    });
16916
16917    let fs = FakeFs::new(cx.executor());
16918    fs.insert_tree(
16919        "/a",
16920        json!({
16921            "main.rs": sample_text_1,
16922            "other.rs": sample_text_2,
16923            "lib.rs": sample_text_3,
16924        }),
16925    )
16926    .await;
16927    let project = Project::test(fs, ["/a".as_ref()], cx).await;
16928    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16929    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16930    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16931        Editor::new(
16932            EditorMode::full(),
16933            multi_buffer,
16934            Some(project.clone()),
16935            window,
16936            cx,
16937        )
16938    });
16939    let multibuffer_item_id = workspace
16940        .update(cx, |workspace, window, cx| {
16941            assert!(
16942                workspace.active_item(cx).is_none(),
16943                "active item should be None before the first item is added"
16944            );
16945            workspace.add_item_to_active_pane(
16946                Box::new(multi_buffer_editor.clone()),
16947                None,
16948                true,
16949                window,
16950                cx,
16951            );
16952            let active_item = workspace
16953                .active_item(cx)
16954                .expect("should have an active item after adding the multi buffer");
16955            assert!(
16956                !active_item.is_singleton(cx),
16957                "A multi buffer was expected to active after adding"
16958            );
16959            active_item.item_id()
16960        })
16961        .unwrap();
16962    cx.executor().run_until_parked();
16963
16964    multi_buffer_editor.update_in(cx, |editor, window, cx| {
16965        editor.change_selections(
16966            SelectionEffects::scroll(Autoscroll::Next),
16967            window,
16968            cx,
16969            |s| s.select_ranges(Some(1..2)),
16970        );
16971        editor.open_excerpts(&OpenExcerpts, window, cx);
16972    });
16973    cx.executor().run_until_parked();
16974    let first_item_id = workspace
16975        .update(cx, |workspace, window, cx| {
16976            let active_item = workspace
16977                .active_item(cx)
16978                .expect("should have an active item after navigating into the 1st buffer");
16979            let first_item_id = active_item.item_id();
16980            assert_ne!(
16981                first_item_id, multibuffer_item_id,
16982                "Should navigate into the 1st buffer and activate it"
16983            );
16984            assert!(
16985                active_item.is_singleton(cx),
16986                "New active item should be a singleton buffer"
16987            );
16988            assert_eq!(
16989                active_item
16990                    .act_as::<Editor>(cx)
16991                    .expect("should have navigated into an editor for the 1st buffer")
16992                    .read(cx)
16993                    .text(cx),
16994                sample_text_1
16995            );
16996
16997            workspace
16998                .go_back(workspace.active_pane().downgrade(), window, cx)
16999                .detach_and_log_err(cx);
17000
17001            first_item_id
17002        })
17003        .unwrap();
17004    cx.executor().run_until_parked();
17005    workspace
17006        .update(cx, |workspace, _, cx| {
17007            let active_item = workspace
17008                .active_item(cx)
17009                .expect("should have an active item after navigating back");
17010            assert_eq!(
17011                active_item.item_id(),
17012                multibuffer_item_id,
17013                "Should navigate back to the multi buffer"
17014            );
17015            assert!(!active_item.is_singleton(cx));
17016        })
17017        .unwrap();
17018
17019    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17020        editor.change_selections(
17021            SelectionEffects::scroll(Autoscroll::Next),
17022            window,
17023            cx,
17024            |s| s.select_ranges(Some(39..40)),
17025        );
17026        editor.open_excerpts(&OpenExcerpts, window, cx);
17027    });
17028    cx.executor().run_until_parked();
17029    let second_item_id = workspace
17030        .update(cx, |workspace, window, cx| {
17031            let active_item = workspace
17032                .active_item(cx)
17033                .expect("should have an active item after navigating into the 2nd buffer");
17034            let second_item_id = active_item.item_id();
17035            assert_ne!(
17036                second_item_id, multibuffer_item_id,
17037                "Should navigate away from the multibuffer"
17038            );
17039            assert_ne!(
17040                second_item_id, first_item_id,
17041                "Should navigate into the 2nd buffer and activate it"
17042            );
17043            assert!(
17044                active_item.is_singleton(cx),
17045                "New active item should be a singleton buffer"
17046            );
17047            assert_eq!(
17048                active_item
17049                    .act_as::<Editor>(cx)
17050                    .expect("should have navigated into an editor")
17051                    .read(cx)
17052                    .text(cx),
17053                sample_text_2
17054            );
17055
17056            workspace
17057                .go_back(workspace.active_pane().downgrade(), window, cx)
17058                .detach_and_log_err(cx);
17059
17060            second_item_id
17061        })
17062        .unwrap();
17063    cx.executor().run_until_parked();
17064    workspace
17065        .update(cx, |workspace, _, cx| {
17066            let active_item = workspace
17067                .active_item(cx)
17068                .expect("should have an active item after navigating back from the 2nd buffer");
17069            assert_eq!(
17070                active_item.item_id(),
17071                multibuffer_item_id,
17072                "Should navigate back from the 2nd buffer to the multi buffer"
17073            );
17074            assert!(!active_item.is_singleton(cx));
17075        })
17076        .unwrap();
17077
17078    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17079        editor.change_selections(
17080            SelectionEffects::scroll(Autoscroll::Next),
17081            window,
17082            cx,
17083            |s| s.select_ranges(Some(70..70)),
17084        );
17085        editor.open_excerpts(&OpenExcerpts, window, cx);
17086    });
17087    cx.executor().run_until_parked();
17088    workspace
17089        .update(cx, |workspace, window, cx| {
17090            let active_item = workspace
17091                .active_item(cx)
17092                .expect("should have an active item after navigating into the 3rd buffer");
17093            let third_item_id = active_item.item_id();
17094            assert_ne!(
17095                third_item_id, multibuffer_item_id,
17096                "Should navigate into the 3rd buffer and activate it"
17097            );
17098            assert_ne!(third_item_id, first_item_id);
17099            assert_ne!(third_item_id, second_item_id);
17100            assert!(
17101                active_item.is_singleton(cx),
17102                "New active item should be a singleton buffer"
17103            );
17104            assert_eq!(
17105                active_item
17106                    .act_as::<Editor>(cx)
17107                    .expect("should have navigated into an editor")
17108                    .read(cx)
17109                    .text(cx),
17110                sample_text_3
17111            );
17112
17113            workspace
17114                .go_back(workspace.active_pane().downgrade(), window, cx)
17115                .detach_and_log_err(cx);
17116        })
17117        .unwrap();
17118    cx.executor().run_until_parked();
17119    workspace
17120        .update(cx, |workspace, _, cx| {
17121            let active_item = workspace
17122                .active_item(cx)
17123                .expect("should have an active item after navigating back from the 3rd buffer");
17124            assert_eq!(
17125                active_item.item_id(),
17126                multibuffer_item_id,
17127                "Should navigate back from the 3rd buffer to the multi buffer"
17128            );
17129            assert!(!active_item.is_singleton(cx));
17130        })
17131        .unwrap();
17132}
17133
17134#[gpui::test]
17135async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17136    init_test(cx, |_| {});
17137
17138    let mut cx = EditorTestContext::new(cx).await;
17139
17140    let diff_base = r#"
17141        use some::mod;
17142
17143        const A: u32 = 42;
17144
17145        fn main() {
17146            println!("hello");
17147
17148            println!("world");
17149        }
17150        "#
17151    .unindent();
17152
17153    cx.set_state(
17154        &r#"
17155        use some::modified;
17156
17157        ˇ
17158        fn main() {
17159            println!("hello there");
17160
17161            println!("around the");
17162            println!("world");
17163        }
17164        "#
17165        .unindent(),
17166    );
17167
17168    cx.set_head_text(&diff_base);
17169    executor.run_until_parked();
17170
17171    cx.update_editor(|editor, window, cx| {
17172        editor.go_to_next_hunk(&GoToHunk, window, cx);
17173        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17174    });
17175    executor.run_until_parked();
17176    cx.assert_state_with_diff(
17177        r#"
17178          use some::modified;
17179
17180
17181          fn main() {
17182        -     println!("hello");
17183        + ˇ    println!("hello there");
17184
17185              println!("around the");
17186              println!("world");
17187          }
17188        "#
17189        .unindent(),
17190    );
17191
17192    cx.update_editor(|editor, window, cx| {
17193        for _ in 0..2 {
17194            editor.go_to_next_hunk(&GoToHunk, window, cx);
17195            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17196        }
17197    });
17198    executor.run_until_parked();
17199    cx.assert_state_with_diff(
17200        r#"
17201        - use some::mod;
17202        + ˇuse some::modified;
17203
17204
17205          fn main() {
17206        -     println!("hello");
17207        +     println!("hello there");
17208
17209        +     println!("around the");
17210              println!("world");
17211          }
17212        "#
17213        .unindent(),
17214    );
17215
17216    cx.update_editor(|editor, window, cx| {
17217        editor.go_to_next_hunk(&GoToHunk, window, cx);
17218        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17219    });
17220    executor.run_until_parked();
17221    cx.assert_state_with_diff(
17222        r#"
17223        - use some::mod;
17224        + use some::modified;
17225
17226        - const A: u32 = 42;
17227          ˇ
17228          fn main() {
17229        -     println!("hello");
17230        +     println!("hello there");
17231
17232        +     println!("around the");
17233              println!("world");
17234          }
17235        "#
17236        .unindent(),
17237    );
17238
17239    cx.update_editor(|editor, window, cx| {
17240        editor.cancel(&Cancel, window, cx);
17241    });
17242
17243    cx.assert_state_with_diff(
17244        r#"
17245          use some::modified;
17246
17247          ˇ
17248          fn main() {
17249              println!("hello there");
17250
17251              println!("around the");
17252              println!("world");
17253          }
17254        "#
17255        .unindent(),
17256    );
17257}
17258
17259#[gpui::test]
17260async fn test_diff_base_change_with_expanded_diff_hunks(
17261    executor: BackgroundExecutor,
17262    cx: &mut TestAppContext,
17263) {
17264    init_test(cx, |_| {});
17265
17266    let mut cx = EditorTestContext::new(cx).await;
17267
17268    let diff_base = r#"
17269        use some::mod1;
17270        use some::mod2;
17271
17272        const A: u32 = 42;
17273        const B: u32 = 42;
17274        const C: u32 = 42;
17275
17276        fn main() {
17277            println!("hello");
17278
17279            println!("world");
17280        }
17281        "#
17282    .unindent();
17283
17284    cx.set_state(
17285        &r#"
17286        use some::mod2;
17287
17288        const A: u32 = 42;
17289        const C: u32 = 42;
17290
17291        fn main(ˇ) {
17292            //println!("hello");
17293
17294            println!("world");
17295            //
17296            //
17297        }
17298        "#
17299        .unindent(),
17300    );
17301
17302    cx.set_head_text(&diff_base);
17303    executor.run_until_parked();
17304
17305    cx.update_editor(|editor, window, cx| {
17306        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17307    });
17308    executor.run_until_parked();
17309    cx.assert_state_with_diff(
17310        r#"
17311        - use some::mod1;
17312          use some::mod2;
17313
17314          const A: u32 = 42;
17315        - const B: u32 = 42;
17316          const C: u32 = 42;
17317
17318          fn main(ˇ) {
17319        -     println!("hello");
17320        +     //println!("hello");
17321
17322              println!("world");
17323        +     //
17324        +     //
17325          }
17326        "#
17327        .unindent(),
17328    );
17329
17330    cx.set_head_text("new diff base!");
17331    executor.run_until_parked();
17332    cx.assert_state_with_diff(
17333        r#"
17334        - new diff base!
17335        + use some::mod2;
17336        +
17337        + const A: u32 = 42;
17338        + const C: u32 = 42;
17339        +
17340        + fn main(ˇ) {
17341        +     //println!("hello");
17342        +
17343        +     println!("world");
17344        +     //
17345        +     //
17346        + }
17347        "#
17348        .unindent(),
17349    );
17350}
17351
17352#[gpui::test]
17353async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
17354    init_test(cx, |_| {});
17355
17356    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
17357    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
17358    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
17359    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
17360    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
17361    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
17362
17363    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
17364    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
17365    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
17366
17367    let multi_buffer = cx.new(|cx| {
17368        let mut multibuffer = MultiBuffer::new(ReadWrite);
17369        multibuffer.push_excerpts(
17370            buffer_1.clone(),
17371            [
17372                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17373                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17374                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17375            ],
17376            cx,
17377        );
17378        multibuffer.push_excerpts(
17379            buffer_2.clone(),
17380            [
17381                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17382                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17383                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17384            ],
17385            cx,
17386        );
17387        multibuffer.push_excerpts(
17388            buffer_3.clone(),
17389            [
17390                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17391                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17392                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17393            ],
17394            cx,
17395        );
17396        multibuffer
17397    });
17398
17399    let editor =
17400        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
17401    editor
17402        .update(cx, |editor, _window, cx| {
17403            for (buffer, diff_base) in [
17404                (buffer_1.clone(), file_1_old),
17405                (buffer_2.clone(), file_2_old),
17406                (buffer_3.clone(), file_3_old),
17407            ] {
17408                let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
17409                editor
17410                    .buffer
17411                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
17412            }
17413        })
17414        .unwrap();
17415
17416    let mut cx = EditorTestContext::for_editor(editor, cx).await;
17417    cx.run_until_parked();
17418
17419    cx.assert_editor_state(
17420        &"
17421            ˇaaa
17422            ccc
17423            ddd
17424
17425            ggg
17426            hhh
17427
17428
17429            lll
17430            mmm
17431            NNN
17432
17433            qqq
17434            rrr
17435
17436            uuu
17437            111
17438            222
17439            333
17440
17441            666
17442            777
17443
17444            000
17445            !!!"
17446        .unindent(),
17447    );
17448
17449    cx.update_editor(|editor, window, cx| {
17450        editor.select_all(&SelectAll, window, cx);
17451        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17452    });
17453    cx.executor().run_until_parked();
17454
17455    cx.assert_state_with_diff(
17456        "
17457            «aaa
17458          - bbb
17459            ccc
17460            ddd
17461
17462            ggg
17463            hhh
17464
17465
17466            lll
17467            mmm
17468          - nnn
17469          + NNN
17470
17471            qqq
17472            rrr
17473
17474            uuu
17475            111
17476            222
17477            333
17478
17479          + 666
17480            777
17481
17482            000
17483            !!!ˇ»"
17484            .unindent(),
17485    );
17486}
17487
17488#[gpui::test]
17489async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
17490    init_test(cx, |_| {});
17491
17492    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
17493    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
17494
17495    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
17496    let multi_buffer = cx.new(|cx| {
17497        let mut multibuffer = MultiBuffer::new(ReadWrite);
17498        multibuffer.push_excerpts(
17499            buffer.clone(),
17500            [
17501                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
17502                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
17503                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
17504            ],
17505            cx,
17506        );
17507        multibuffer
17508    });
17509
17510    let editor =
17511        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
17512    editor
17513        .update(cx, |editor, _window, cx| {
17514            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
17515            editor
17516                .buffer
17517                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
17518        })
17519        .unwrap();
17520
17521    let mut cx = EditorTestContext::for_editor(editor, cx).await;
17522    cx.run_until_parked();
17523
17524    cx.update_editor(|editor, window, cx| {
17525        editor.expand_all_diff_hunks(&Default::default(), window, cx)
17526    });
17527    cx.executor().run_until_parked();
17528
17529    // When the start of a hunk coincides with the start of its excerpt,
17530    // the hunk is expanded. When the start of a a hunk is earlier than
17531    // the start of its excerpt, the hunk is not expanded.
17532    cx.assert_state_with_diff(
17533        "
17534            ˇaaa
17535          - bbb
17536          + BBB
17537
17538          - ddd
17539          - eee
17540          + DDD
17541          + EEE
17542            fff
17543
17544            iii
17545        "
17546        .unindent(),
17547    );
17548}
17549
17550#[gpui::test]
17551async fn test_edits_around_expanded_insertion_hunks(
17552    executor: BackgroundExecutor,
17553    cx: &mut TestAppContext,
17554) {
17555    init_test(cx, |_| {});
17556
17557    let mut cx = EditorTestContext::new(cx).await;
17558
17559    let diff_base = r#"
17560        use some::mod1;
17561        use some::mod2;
17562
17563        const A: u32 = 42;
17564
17565        fn main() {
17566            println!("hello");
17567
17568            println!("world");
17569        }
17570        "#
17571    .unindent();
17572    executor.run_until_parked();
17573    cx.set_state(
17574        &r#"
17575        use some::mod1;
17576        use some::mod2;
17577
17578        const A: u32 = 42;
17579        const B: u32 = 42;
17580        const C: u32 = 42;
17581        ˇ
17582
17583        fn main() {
17584            println!("hello");
17585
17586            println!("world");
17587        }
17588        "#
17589        .unindent(),
17590    );
17591
17592    cx.set_head_text(&diff_base);
17593    executor.run_until_parked();
17594
17595    cx.update_editor(|editor, window, cx| {
17596        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17597    });
17598    executor.run_until_parked();
17599
17600    cx.assert_state_with_diff(
17601        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      + ˇ
17609
17610        fn main() {
17611            println!("hello");
17612
17613            println!("world");
17614        }
17615      "#
17616        .unindent(),
17617    );
17618
17619    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
17620    executor.run_until_parked();
17621
17622    cx.assert_state_with_diff(
17623        r#"
17624        use some::mod1;
17625        use some::mod2;
17626
17627        const A: u32 = 42;
17628      + const B: u32 = 42;
17629      + const C: u32 = 42;
17630      + const D: u32 = 42;
17631      + ˇ
17632
17633        fn main() {
17634            println!("hello");
17635
17636            println!("world");
17637        }
17638      "#
17639        .unindent(),
17640    );
17641
17642    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
17643    executor.run_until_parked();
17644
17645    cx.assert_state_with_diff(
17646        r#"
17647        use some::mod1;
17648        use some::mod2;
17649
17650        const A: u32 = 42;
17651      + const B: u32 = 42;
17652      + const C: u32 = 42;
17653      + const D: u32 = 42;
17654      + const E: u32 = 42;
17655      + ˇ
17656
17657        fn main() {
17658            println!("hello");
17659
17660            println!("world");
17661        }
17662      "#
17663        .unindent(),
17664    );
17665
17666    cx.update_editor(|editor, window, cx| {
17667        editor.delete_line(&DeleteLine, window, cx);
17668    });
17669    executor.run_until_parked();
17670
17671    cx.assert_state_with_diff(
17672        r#"
17673        use some::mod1;
17674        use some::mod2;
17675
17676        const A: u32 = 42;
17677      + const B: u32 = 42;
17678      + const C: u32 = 42;
17679      + const D: u32 = 42;
17680      + const E: u32 = 42;
17681        ˇ
17682        fn main() {
17683            println!("hello");
17684
17685            println!("world");
17686        }
17687      "#
17688        .unindent(),
17689    );
17690
17691    cx.update_editor(|editor, window, cx| {
17692        editor.move_up(&MoveUp, window, cx);
17693        editor.delete_line(&DeleteLine, window, cx);
17694        editor.move_up(&MoveUp, window, cx);
17695        editor.delete_line(&DeleteLine, window, cx);
17696        editor.move_up(&MoveUp, window, cx);
17697        editor.delete_line(&DeleteLine, window, cx);
17698    });
17699    executor.run_until_parked();
17700    cx.assert_state_with_diff(
17701        r#"
17702        use some::mod1;
17703        use some::mod2;
17704
17705        const A: u32 = 42;
17706      + const B: u32 = 42;
17707        ˇ
17708        fn main() {
17709            println!("hello");
17710
17711            println!("world");
17712        }
17713      "#
17714        .unindent(),
17715    );
17716
17717    cx.update_editor(|editor, window, cx| {
17718        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
17719        editor.delete_line(&DeleteLine, window, cx);
17720    });
17721    executor.run_until_parked();
17722    cx.assert_state_with_diff(
17723        r#"
17724        ˇ
17725        fn main() {
17726            println!("hello");
17727
17728            println!("world");
17729        }
17730      "#
17731        .unindent(),
17732    );
17733}
17734
17735#[gpui::test]
17736async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
17737    init_test(cx, |_| {});
17738
17739    let mut cx = EditorTestContext::new(cx).await;
17740    cx.set_head_text(indoc! { "
17741        one
17742        two
17743        three
17744        four
17745        five
17746        "
17747    });
17748    cx.set_state(indoc! { "
17749        one
17750        ˇthree
17751        five
17752    "});
17753    cx.run_until_parked();
17754    cx.update_editor(|editor, window, cx| {
17755        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17756    });
17757    cx.assert_state_with_diff(
17758        indoc! { "
17759        one
17760      - two
17761        ˇthree
17762      - four
17763        five
17764    "}
17765        .to_string(),
17766    );
17767    cx.update_editor(|editor, window, cx| {
17768        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17769    });
17770
17771    cx.assert_state_with_diff(
17772        indoc! { "
17773        one
17774        ˇthree
17775        five
17776    "}
17777        .to_string(),
17778    );
17779
17780    cx.set_state(indoc! { "
17781        one
17782        ˇTWO
17783        three
17784        four
17785        five
17786    "});
17787    cx.run_until_parked();
17788    cx.update_editor(|editor, window, cx| {
17789        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17790    });
17791
17792    cx.assert_state_with_diff(
17793        indoc! { "
17794            one
17795          - two
17796          + ˇTWO
17797            three
17798            four
17799            five
17800        "}
17801        .to_string(),
17802    );
17803    cx.update_editor(|editor, window, cx| {
17804        editor.move_up(&Default::default(), window, cx);
17805        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17806    });
17807    cx.assert_state_with_diff(
17808        indoc! { "
17809            one
17810            ˇTWO
17811            three
17812            four
17813            five
17814        "}
17815        .to_string(),
17816    );
17817}
17818
17819#[gpui::test]
17820async fn test_edits_around_expanded_deletion_hunks(
17821    executor: BackgroundExecutor,
17822    cx: &mut TestAppContext,
17823) {
17824    init_test(cx, |_| {});
17825
17826    let mut cx = EditorTestContext::new(cx).await;
17827
17828    let diff_base = r#"
17829        use some::mod1;
17830        use some::mod2;
17831
17832        const A: u32 = 42;
17833        const B: u32 = 42;
17834        const C: u32 = 42;
17835
17836
17837        fn main() {
17838            println!("hello");
17839
17840            println!("world");
17841        }
17842    "#
17843    .unindent();
17844    executor.run_until_parked();
17845    cx.set_state(
17846        &r#"
17847        use some::mod1;
17848        use some::mod2;
17849
17850        ˇconst B: u32 = 42;
17851        const C: u32 = 42;
17852
17853
17854        fn main() {
17855            println!("hello");
17856
17857            println!("world");
17858        }
17859        "#
17860        .unindent(),
17861    );
17862
17863    cx.set_head_text(&diff_base);
17864    executor.run_until_parked();
17865
17866    cx.update_editor(|editor, window, cx| {
17867        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17868    });
17869    executor.run_until_parked();
17870
17871    cx.assert_state_with_diff(
17872        r#"
17873        use some::mod1;
17874        use some::mod2;
17875
17876      - const A: u32 = 42;
17877        ˇconst B: u32 = 42;
17878        const C: u32 = 42;
17879
17880
17881        fn main() {
17882            println!("hello");
17883
17884            println!("world");
17885        }
17886      "#
17887        .unindent(),
17888    );
17889
17890    cx.update_editor(|editor, window, cx| {
17891        editor.delete_line(&DeleteLine, window, cx);
17892    });
17893    executor.run_until_parked();
17894    cx.assert_state_with_diff(
17895        r#"
17896        use some::mod1;
17897        use some::mod2;
17898
17899      - const A: u32 = 42;
17900      - const B: u32 = 42;
17901        ˇconst C: u32 = 42;
17902
17903
17904        fn main() {
17905            println!("hello");
17906
17907            println!("world");
17908        }
17909      "#
17910        .unindent(),
17911    );
17912
17913    cx.update_editor(|editor, window, cx| {
17914        editor.delete_line(&DeleteLine, window, cx);
17915    });
17916    executor.run_until_parked();
17917    cx.assert_state_with_diff(
17918        r#"
17919        use some::mod1;
17920        use some::mod2;
17921
17922      - const A: u32 = 42;
17923      - const B: u32 = 42;
17924      - const C: u32 = 42;
17925        ˇ
17926
17927        fn main() {
17928            println!("hello");
17929
17930            println!("world");
17931        }
17932      "#
17933        .unindent(),
17934    );
17935
17936    cx.update_editor(|editor, window, cx| {
17937        editor.handle_input("replacement", window, cx);
17938    });
17939    executor.run_until_parked();
17940    cx.assert_state_with_diff(
17941        r#"
17942        use some::mod1;
17943        use some::mod2;
17944
17945      - const A: u32 = 42;
17946      - const B: u32 = 42;
17947      - const C: u32 = 42;
17948      -
17949      + replacementˇ
17950
17951        fn main() {
17952            println!("hello");
17953
17954            println!("world");
17955        }
17956      "#
17957        .unindent(),
17958    );
17959}
17960
17961#[gpui::test]
17962async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17963    init_test(cx, |_| {});
17964
17965    let mut cx = EditorTestContext::new(cx).await;
17966
17967    let base_text = r#"
17968        one
17969        two
17970        three
17971        four
17972        five
17973    "#
17974    .unindent();
17975    executor.run_until_parked();
17976    cx.set_state(
17977        &r#"
17978        one
17979        two
17980        fˇour
17981        five
17982        "#
17983        .unindent(),
17984    );
17985
17986    cx.set_head_text(&base_text);
17987    executor.run_until_parked();
17988
17989    cx.update_editor(|editor, window, cx| {
17990        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17991    });
17992    executor.run_until_parked();
17993
17994    cx.assert_state_with_diff(
17995        r#"
17996          one
17997          two
17998        - three
17999          fˇour
18000          five
18001        "#
18002        .unindent(),
18003    );
18004
18005    cx.update_editor(|editor, window, cx| {
18006        editor.backspace(&Backspace, window, cx);
18007        editor.backspace(&Backspace, window, cx);
18008    });
18009    executor.run_until_parked();
18010    cx.assert_state_with_diff(
18011        r#"
18012          one
18013          two
18014        - threeˇ
18015        - four
18016        + our
18017          five
18018        "#
18019        .unindent(),
18020    );
18021}
18022
18023#[gpui::test]
18024async fn test_edit_after_expanded_modification_hunk(
18025    executor: BackgroundExecutor,
18026    cx: &mut TestAppContext,
18027) {
18028    init_test(cx, |_| {});
18029
18030    let mut cx = EditorTestContext::new(cx).await;
18031
18032    let diff_base = r#"
18033        use some::mod1;
18034        use some::mod2;
18035
18036        const A: u32 = 42;
18037        const B: u32 = 42;
18038        const C: u32 = 42;
18039        const D: u32 = 42;
18040
18041
18042        fn main() {
18043            println!("hello");
18044
18045            println!("world");
18046        }"#
18047    .unindent();
18048
18049    cx.set_state(
18050        &r#"
18051        use some::mod1;
18052        use some::mod2;
18053
18054        const A: u32 = 42;
18055        const B: u32 = 42;
18056        const C: u32 = 43ˇ
18057        const D: u32 = 42;
18058
18059
18060        fn main() {
18061            println!("hello");
18062
18063            println!("world");
18064        }"#
18065        .unindent(),
18066    );
18067
18068    cx.set_head_text(&diff_base);
18069    executor.run_until_parked();
18070    cx.update_editor(|editor, window, cx| {
18071        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18072    });
18073    executor.run_until_parked();
18074
18075    cx.assert_state_with_diff(
18076        r#"
18077        use some::mod1;
18078        use some::mod2;
18079
18080        const A: u32 = 42;
18081        const B: u32 = 42;
18082      - const C: u32 = 42;
18083      + const C: u32 = 43ˇ
18084        const D: u32 = 42;
18085
18086
18087        fn main() {
18088            println!("hello");
18089
18090            println!("world");
18091        }"#
18092        .unindent(),
18093    );
18094
18095    cx.update_editor(|editor, window, cx| {
18096        editor.handle_input("\nnew_line\n", window, cx);
18097    });
18098    executor.run_until_parked();
18099
18100    cx.assert_state_with_diff(
18101        r#"
18102        use some::mod1;
18103        use some::mod2;
18104
18105        const A: u32 = 42;
18106        const B: u32 = 42;
18107      - const C: u32 = 42;
18108      + const C: u32 = 43
18109      + new_line
18110      + ˇ
18111        const D: u32 = 42;
18112
18113
18114        fn main() {
18115            println!("hello");
18116
18117            println!("world");
18118        }"#
18119        .unindent(),
18120    );
18121}
18122
18123#[gpui::test]
18124async fn test_stage_and_unstage_added_file_hunk(
18125    executor: BackgroundExecutor,
18126    cx: &mut TestAppContext,
18127) {
18128    init_test(cx, |_| {});
18129
18130    let mut cx = EditorTestContext::new(cx).await;
18131    cx.update_editor(|editor, _, cx| {
18132        editor.set_expand_all_diff_hunks(cx);
18133    });
18134
18135    let working_copy = r#"
18136            ˇfn main() {
18137                println!("hello, world!");
18138            }
18139        "#
18140    .unindent();
18141
18142    cx.set_state(&working_copy);
18143    executor.run_until_parked();
18144
18145    cx.assert_state_with_diff(
18146        r#"
18147            + ˇfn main() {
18148            +     println!("hello, world!");
18149            + }
18150        "#
18151        .unindent(),
18152    );
18153    cx.assert_index_text(None);
18154
18155    cx.update_editor(|editor, window, cx| {
18156        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18157    });
18158    executor.run_until_parked();
18159    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
18160    cx.assert_state_with_diff(
18161        r#"
18162            + ˇfn main() {
18163            +     println!("hello, world!");
18164            + }
18165        "#
18166        .unindent(),
18167    );
18168
18169    cx.update_editor(|editor, window, cx| {
18170        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18171    });
18172    executor.run_until_parked();
18173    cx.assert_index_text(None);
18174}
18175
18176async fn setup_indent_guides_editor(
18177    text: &str,
18178    cx: &mut TestAppContext,
18179) -> (BufferId, EditorTestContext) {
18180    init_test(cx, |_| {});
18181
18182    let mut cx = EditorTestContext::new(cx).await;
18183
18184    let buffer_id = cx.update_editor(|editor, window, cx| {
18185        editor.set_text(text, window, cx);
18186        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
18187
18188        buffer_ids[0]
18189    });
18190
18191    (buffer_id, cx)
18192}
18193
18194fn assert_indent_guides(
18195    range: Range<u32>,
18196    expected: Vec<IndentGuide>,
18197    active_indices: Option<Vec<usize>>,
18198    cx: &mut EditorTestContext,
18199) {
18200    let indent_guides = cx.update_editor(|editor, window, cx| {
18201        let snapshot = editor.snapshot(window, cx).display_snapshot;
18202        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
18203            editor,
18204            MultiBufferRow(range.start)..MultiBufferRow(range.end),
18205            true,
18206            &snapshot,
18207            cx,
18208        );
18209
18210        indent_guides.sort_by(|a, b| {
18211            a.depth.cmp(&b.depth).then(
18212                a.start_row
18213                    .cmp(&b.start_row)
18214                    .then(a.end_row.cmp(&b.end_row)),
18215            )
18216        });
18217        indent_guides
18218    });
18219
18220    if let Some(expected) = active_indices {
18221        let active_indices = cx.update_editor(|editor, window, cx| {
18222            let snapshot = editor.snapshot(window, cx).display_snapshot;
18223            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
18224        });
18225
18226        assert_eq!(
18227            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
18228            expected,
18229            "Active indent guide indices do not match"
18230        );
18231    }
18232
18233    assert_eq!(indent_guides, expected, "Indent guides do not match");
18234}
18235
18236fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
18237    IndentGuide {
18238        buffer_id,
18239        start_row: MultiBufferRow(start_row),
18240        end_row: MultiBufferRow(end_row),
18241        depth,
18242        tab_size: 4,
18243        settings: IndentGuideSettings {
18244            enabled: true,
18245            line_width: 1,
18246            active_line_width: 1,
18247            ..Default::default()
18248        },
18249    }
18250}
18251
18252#[gpui::test]
18253async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
18254    let (buffer_id, mut cx) = setup_indent_guides_editor(
18255        &"
18256        fn main() {
18257            let a = 1;
18258        }"
18259        .unindent(),
18260        cx,
18261    )
18262    .await;
18263
18264    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
18265}
18266
18267#[gpui::test]
18268async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
18269    let (buffer_id, mut cx) = setup_indent_guides_editor(
18270        &"
18271        fn main() {
18272            let a = 1;
18273            let b = 2;
18274        }"
18275        .unindent(),
18276        cx,
18277    )
18278    .await;
18279
18280    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
18281}
18282
18283#[gpui::test]
18284async fn test_indent_guide_nested(cx: &mut TestAppContext) {
18285    let (buffer_id, mut cx) = setup_indent_guides_editor(
18286        &"
18287        fn main() {
18288            let a = 1;
18289            if a == 3 {
18290                let b = 2;
18291            } else {
18292                let c = 3;
18293            }
18294        }"
18295        .unindent(),
18296        cx,
18297    )
18298    .await;
18299
18300    assert_indent_guides(
18301        0..8,
18302        vec![
18303            indent_guide(buffer_id, 1, 6, 0),
18304            indent_guide(buffer_id, 3, 3, 1),
18305            indent_guide(buffer_id, 5, 5, 1),
18306        ],
18307        None,
18308        &mut cx,
18309    );
18310}
18311
18312#[gpui::test]
18313async fn test_indent_guide_tab(cx: &mut TestAppContext) {
18314    let (buffer_id, mut cx) = setup_indent_guides_editor(
18315        &"
18316        fn main() {
18317            let a = 1;
18318                let b = 2;
18319            let c = 3;
18320        }"
18321        .unindent(),
18322        cx,
18323    )
18324    .await;
18325
18326    assert_indent_guides(
18327        0..5,
18328        vec![
18329            indent_guide(buffer_id, 1, 3, 0),
18330            indent_guide(buffer_id, 2, 2, 1),
18331        ],
18332        None,
18333        &mut cx,
18334    );
18335}
18336
18337#[gpui::test]
18338async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
18339    let (buffer_id, mut cx) = setup_indent_guides_editor(
18340        &"
18341        fn main() {
18342            let a = 1;
18343
18344            let c = 3;
18345        }"
18346        .unindent(),
18347        cx,
18348    )
18349    .await;
18350
18351    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
18352}
18353
18354#[gpui::test]
18355async fn test_indent_guide_complex(cx: &mut TestAppContext) {
18356    let (buffer_id, mut cx) = setup_indent_guides_editor(
18357        &"
18358        fn main() {
18359            let a = 1;
18360
18361            let c = 3;
18362
18363            if a == 3 {
18364                let b = 2;
18365            } else {
18366                let c = 3;
18367            }
18368        }"
18369        .unindent(),
18370        cx,
18371    )
18372    .await;
18373
18374    assert_indent_guides(
18375        0..11,
18376        vec![
18377            indent_guide(buffer_id, 1, 9, 0),
18378            indent_guide(buffer_id, 6, 6, 1),
18379            indent_guide(buffer_id, 8, 8, 1),
18380        ],
18381        None,
18382        &mut cx,
18383    );
18384}
18385
18386#[gpui::test]
18387async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
18388    let (buffer_id, mut cx) = setup_indent_guides_editor(
18389        &"
18390        fn main() {
18391            let a = 1;
18392
18393            let c = 3;
18394
18395            if a == 3 {
18396                let b = 2;
18397            } else {
18398                let c = 3;
18399            }
18400        }"
18401        .unindent(),
18402        cx,
18403    )
18404    .await;
18405
18406    assert_indent_guides(
18407        1..11,
18408        vec![
18409            indent_guide(buffer_id, 1, 9, 0),
18410            indent_guide(buffer_id, 6, 6, 1),
18411            indent_guide(buffer_id, 8, 8, 1),
18412        ],
18413        None,
18414        &mut cx,
18415    );
18416}
18417
18418#[gpui::test]
18419async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
18420    let (buffer_id, mut cx) = setup_indent_guides_editor(
18421        &"
18422        fn main() {
18423            let a = 1;
18424
18425            let c = 3;
18426
18427            if a == 3 {
18428                let b = 2;
18429            } else {
18430                let c = 3;
18431            }
18432        }"
18433        .unindent(),
18434        cx,
18435    )
18436    .await;
18437
18438    assert_indent_guides(
18439        1..10,
18440        vec![
18441            indent_guide(buffer_id, 1, 9, 0),
18442            indent_guide(buffer_id, 6, 6, 1),
18443            indent_guide(buffer_id, 8, 8, 1),
18444        ],
18445        None,
18446        &mut cx,
18447    );
18448}
18449
18450#[gpui::test]
18451async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
18452    let (buffer_id, mut cx) = setup_indent_guides_editor(
18453        &"
18454        fn main() {
18455            if a {
18456                b(
18457                    c,
18458                    d,
18459                )
18460            } else {
18461                e(
18462                    f
18463                )
18464            }
18465        }"
18466        .unindent(),
18467        cx,
18468    )
18469    .await;
18470
18471    assert_indent_guides(
18472        0..11,
18473        vec![
18474            indent_guide(buffer_id, 1, 10, 0),
18475            indent_guide(buffer_id, 2, 5, 1),
18476            indent_guide(buffer_id, 7, 9, 1),
18477            indent_guide(buffer_id, 3, 4, 2),
18478            indent_guide(buffer_id, 8, 8, 2),
18479        ],
18480        None,
18481        &mut cx,
18482    );
18483
18484    cx.update_editor(|editor, window, cx| {
18485        editor.fold_at(MultiBufferRow(2), window, cx);
18486        assert_eq!(
18487            editor.display_text(cx),
18488            "
18489            fn main() {
18490                if a {
18491                    b(⋯
18492                    )
18493                } else {
18494                    e(
18495                        f
18496                    )
18497                }
18498            }"
18499            .unindent()
18500        );
18501    });
18502
18503    assert_indent_guides(
18504        0..11,
18505        vec![
18506            indent_guide(buffer_id, 1, 10, 0),
18507            indent_guide(buffer_id, 2, 5, 1),
18508            indent_guide(buffer_id, 7, 9, 1),
18509            indent_guide(buffer_id, 8, 8, 2),
18510        ],
18511        None,
18512        &mut cx,
18513    );
18514}
18515
18516#[gpui::test]
18517async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
18518    let (buffer_id, mut cx) = setup_indent_guides_editor(
18519        &"
18520        block1
18521            block2
18522                block3
18523                    block4
18524            block2
18525        block1
18526        block1"
18527            .unindent(),
18528        cx,
18529    )
18530    .await;
18531
18532    assert_indent_guides(
18533        1..10,
18534        vec![
18535            indent_guide(buffer_id, 1, 4, 0),
18536            indent_guide(buffer_id, 2, 3, 1),
18537            indent_guide(buffer_id, 3, 3, 2),
18538        ],
18539        None,
18540        &mut cx,
18541    );
18542}
18543
18544#[gpui::test]
18545async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
18546    let (buffer_id, mut cx) = setup_indent_guides_editor(
18547        &"
18548        block1
18549            block2
18550                block3
18551
18552        block1
18553        block1"
18554            .unindent(),
18555        cx,
18556    )
18557    .await;
18558
18559    assert_indent_guides(
18560        0..6,
18561        vec![
18562            indent_guide(buffer_id, 1, 2, 0),
18563            indent_guide(buffer_id, 2, 2, 1),
18564        ],
18565        None,
18566        &mut cx,
18567    );
18568}
18569
18570#[gpui::test]
18571async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
18572    let (buffer_id, mut cx) = setup_indent_guides_editor(
18573        &"
18574        function component() {
18575        \treturn (
18576        \t\t\t
18577        \t\t<div>
18578        \t\t\t<abc></abc>
18579        \t\t</div>
18580        \t)
18581        }"
18582        .unindent(),
18583        cx,
18584    )
18585    .await;
18586
18587    assert_indent_guides(
18588        0..8,
18589        vec![
18590            indent_guide(buffer_id, 1, 6, 0),
18591            indent_guide(buffer_id, 2, 5, 1),
18592            indent_guide(buffer_id, 4, 4, 2),
18593        ],
18594        None,
18595        &mut cx,
18596    );
18597}
18598
18599#[gpui::test]
18600async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
18601    let (buffer_id, mut cx) = setup_indent_guides_editor(
18602        &"
18603        function component() {
18604        \treturn (
18605        \t
18606        \t\t<div>
18607        \t\t\t<abc></abc>
18608        \t\t</div>
18609        \t)
18610        }"
18611        .unindent(),
18612        cx,
18613    )
18614    .await;
18615
18616    assert_indent_guides(
18617        0..8,
18618        vec![
18619            indent_guide(buffer_id, 1, 6, 0),
18620            indent_guide(buffer_id, 2, 5, 1),
18621            indent_guide(buffer_id, 4, 4, 2),
18622        ],
18623        None,
18624        &mut cx,
18625    );
18626}
18627
18628#[gpui::test]
18629async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
18630    let (buffer_id, mut cx) = setup_indent_guides_editor(
18631        &"
18632        block1
18633
18634
18635
18636            block2
18637        "
18638        .unindent(),
18639        cx,
18640    )
18641    .await;
18642
18643    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
18644}
18645
18646#[gpui::test]
18647async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
18648    let (buffer_id, mut cx) = setup_indent_guides_editor(
18649        &"
18650        def a:
18651        \tb = 3
18652        \tif True:
18653        \t\tc = 4
18654        \t\td = 5
18655        \tprint(b)
18656        "
18657        .unindent(),
18658        cx,
18659    )
18660    .await;
18661
18662    assert_indent_guides(
18663        0..6,
18664        vec![
18665            indent_guide(buffer_id, 1, 5, 0),
18666            indent_guide(buffer_id, 3, 4, 1),
18667        ],
18668        None,
18669        &mut cx,
18670    );
18671}
18672
18673#[gpui::test]
18674async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
18675    let (buffer_id, mut cx) = setup_indent_guides_editor(
18676        &"
18677    fn main() {
18678        let a = 1;
18679    }"
18680        .unindent(),
18681        cx,
18682    )
18683    .await;
18684
18685    cx.update_editor(|editor, window, cx| {
18686        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18687            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18688        });
18689    });
18690
18691    assert_indent_guides(
18692        0..3,
18693        vec![indent_guide(buffer_id, 1, 1, 0)],
18694        Some(vec![0]),
18695        &mut cx,
18696    );
18697}
18698
18699#[gpui::test]
18700async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
18701    let (buffer_id, mut cx) = setup_indent_guides_editor(
18702        &"
18703    fn main() {
18704        if 1 == 2 {
18705            let a = 1;
18706        }
18707    }"
18708        .unindent(),
18709        cx,
18710    )
18711    .await;
18712
18713    cx.update_editor(|editor, window, cx| {
18714        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18715            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18716        });
18717    });
18718
18719    assert_indent_guides(
18720        0..4,
18721        vec![
18722            indent_guide(buffer_id, 1, 3, 0),
18723            indent_guide(buffer_id, 2, 2, 1),
18724        ],
18725        Some(vec![1]),
18726        &mut cx,
18727    );
18728
18729    cx.update_editor(|editor, window, cx| {
18730        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18731            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
18732        });
18733    });
18734
18735    assert_indent_guides(
18736        0..4,
18737        vec![
18738            indent_guide(buffer_id, 1, 3, 0),
18739            indent_guide(buffer_id, 2, 2, 1),
18740        ],
18741        Some(vec![1]),
18742        &mut cx,
18743    );
18744
18745    cx.update_editor(|editor, window, cx| {
18746        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18747            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
18748        });
18749    });
18750
18751    assert_indent_guides(
18752        0..4,
18753        vec![
18754            indent_guide(buffer_id, 1, 3, 0),
18755            indent_guide(buffer_id, 2, 2, 1),
18756        ],
18757        Some(vec![0]),
18758        &mut cx,
18759    );
18760}
18761
18762#[gpui::test]
18763async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
18764    let (buffer_id, mut cx) = setup_indent_guides_editor(
18765        &"
18766    fn main() {
18767        let a = 1;
18768
18769        let b = 2;
18770    }"
18771        .unindent(),
18772        cx,
18773    )
18774    .await;
18775
18776    cx.update_editor(|editor, window, cx| {
18777        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18778            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
18779        });
18780    });
18781
18782    assert_indent_guides(
18783        0..5,
18784        vec![indent_guide(buffer_id, 1, 3, 0)],
18785        Some(vec![0]),
18786        &mut cx,
18787    );
18788}
18789
18790#[gpui::test]
18791async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
18792    let (buffer_id, mut cx) = setup_indent_guides_editor(
18793        &"
18794    def m:
18795        a = 1
18796        pass"
18797            .unindent(),
18798        cx,
18799    )
18800    .await;
18801
18802    cx.update_editor(|editor, window, cx| {
18803        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18804            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18805        });
18806    });
18807
18808    assert_indent_guides(
18809        0..3,
18810        vec![indent_guide(buffer_id, 1, 2, 0)],
18811        Some(vec![0]),
18812        &mut cx,
18813    );
18814}
18815
18816#[gpui::test]
18817async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
18818    init_test(cx, |_| {});
18819    let mut cx = EditorTestContext::new(cx).await;
18820    let text = indoc! {
18821        "
18822        impl A {
18823            fn b() {
18824                0;
18825                3;
18826                5;
18827                6;
18828                7;
18829            }
18830        }
18831        "
18832    };
18833    let base_text = indoc! {
18834        "
18835        impl A {
18836            fn b() {
18837                0;
18838                1;
18839                2;
18840                3;
18841                4;
18842            }
18843            fn c() {
18844                5;
18845                6;
18846                7;
18847            }
18848        }
18849        "
18850    };
18851
18852    cx.update_editor(|editor, window, cx| {
18853        editor.set_text(text, window, cx);
18854
18855        editor.buffer().update(cx, |multibuffer, cx| {
18856            let buffer = multibuffer.as_singleton().unwrap();
18857            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
18858
18859            multibuffer.set_all_diff_hunks_expanded(cx);
18860            multibuffer.add_diff(diff, cx);
18861
18862            buffer.read(cx).remote_id()
18863        })
18864    });
18865    cx.run_until_parked();
18866
18867    cx.assert_state_with_diff(
18868        indoc! { "
18869          impl A {
18870              fn b() {
18871                  0;
18872        -         1;
18873        -         2;
18874                  3;
18875        -         4;
18876        -     }
18877        -     fn c() {
18878                  5;
18879                  6;
18880                  7;
18881              }
18882          }
18883          ˇ"
18884        }
18885        .to_string(),
18886    );
18887
18888    let mut actual_guides = cx.update_editor(|editor, window, cx| {
18889        editor
18890            .snapshot(window, cx)
18891            .buffer_snapshot
18892            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
18893            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
18894            .collect::<Vec<_>>()
18895    });
18896    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
18897    assert_eq!(
18898        actual_guides,
18899        vec![
18900            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
18901            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
18902            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
18903        ]
18904    );
18905}
18906
18907#[gpui::test]
18908async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18909    init_test(cx, |_| {});
18910    let mut cx = EditorTestContext::new(cx).await;
18911
18912    let diff_base = r#"
18913        a
18914        b
18915        c
18916        "#
18917    .unindent();
18918
18919    cx.set_state(
18920        &r#"
18921        ˇA
18922        b
18923        C
18924        "#
18925        .unindent(),
18926    );
18927    cx.set_head_text(&diff_base);
18928    cx.update_editor(|editor, window, cx| {
18929        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18930    });
18931    executor.run_until_parked();
18932
18933    let both_hunks_expanded = r#"
18934        - a
18935        + ˇA
18936          b
18937        - c
18938        + C
18939        "#
18940    .unindent();
18941
18942    cx.assert_state_with_diff(both_hunks_expanded.clone());
18943
18944    let hunk_ranges = cx.update_editor(|editor, window, cx| {
18945        let snapshot = editor.snapshot(window, cx);
18946        let hunks = editor
18947            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18948            .collect::<Vec<_>>();
18949        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
18950        let buffer_id = hunks[0].buffer_id;
18951        hunks
18952            .into_iter()
18953            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
18954            .collect::<Vec<_>>()
18955    });
18956    assert_eq!(hunk_ranges.len(), 2);
18957
18958    cx.update_editor(|editor, _, cx| {
18959        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18960    });
18961    executor.run_until_parked();
18962
18963    let second_hunk_expanded = r#"
18964          ˇA
18965          b
18966        - c
18967        + C
18968        "#
18969    .unindent();
18970
18971    cx.assert_state_with_diff(second_hunk_expanded);
18972
18973    cx.update_editor(|editor, _, cx| {
18974        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18975    });
18976    executor.run_until_parked();
18977
18978    cx.assert_state_with_diff(both_hunks_expanded.clone());
18979
18980    cx.update_editor(|editor, _, cx| {
18981        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
18982    });
18983    executor.run_until_parked();
18984
18985    let first_hunk_expanded = r#"
18986        - a
18987        + ˇA
18988          b
18989          C
18990        "#
18991    .unindent();
18992
18993    cx.assert_state_with_diff(first_hunk_expanded);
18994
18995    cx.update_editor(|editor, _, cx| {
18996        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
18997    });
18998    executor.run_until_parked();
18999
19000    cx.assert_state_with_diff(both_hunks_expanded);
19001
19002    cx.set_state(
19003        &r#"
19004        ˇA
19005        b
19006        "#
19007        .unindent(),
19008    );
19009    cx.run_until_parked();
19010
19011    // TODO this cursor position seems bad
19012    cx.assert_state_with_diff(
19013        r#"
19014        - ˇa
19015        + A
19016          b
19017        "#
19018        .unindent(),
19019    );
19020
19021    cx.update_editor(|editor, window, cx| {
19022        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19023    });
19024
19025    cx.assert_state_with_diff(
19026        r#"
19027            - ˇa
19028            + A
19029              b
19030            - c
19031            "#
19032        .unindent(),
19033    );
19034
19035    let hunk_ranges = cx.update_editor(|editor, window, cx| {
19036        let snapshot = editor.snapshot(window, cx);
19037        let hunks = editor
19038            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19039            .collect::<Vec<_>>();
19040        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
19041        let buffer_id = hunks[0].buffer_id;
19042        hunks
19043            .into_iter()
19044            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
19045            .collect::<Vec<_>>()
19046    });
19047    assert_eq!(hunk_ranges.len(), 2);
19048
19049    cx.update_editor(|editor, _, cx| {
19050        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
19051    });
19052    executor.run_until_parked();
19053
19054    cx.assert_state_with_diff(
19055        r#"
19056        - ˇa
19057        + A
19058          b
19059        "#
19060        .unindent(),
19061    );
19062}
19063
19064#[gpui::test]
19065async fn test_toggle_deletion_hunk_at_start_of_file(
19066    executor: BackgroundExecutor,
19067    cx: &mut TestAppContext,
19068) {
19069    init_test(cx, |_| {});
19070    let mut cx = EditorTestContext::new(cx).await;
19071
19072    let diff_base = r#"
19073        a
19074        b
19075        c
19076        "#
19077    .unindent();
19078
19079    cx.set_state(
19080        &r#"
19081        ˇb
19082        c
19083        "#
19084        .unindent(),
19085    );
19086    cx.set_head_text(&diff_base);
19087    cx.update_editor(|editor, window, cx| {
19088        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19089    });
19090    executor.run_until_parked();
19091
19092    let hunk_expanded = r#"
19093        - a
19094          ˇb
19095          c
19096        "#
19097    .unindent();
19098
19099    cx.assert_state_with_diff(hunk_expanded.clone());
19100
19101    let hunk_ranges = cx.update_editor(|editor, window, cx| {
19102        let snapshot = editor.snapshot(window, cx);
19103        let hunks = editor
19104            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19105            .collect::<Vec<_>>();
19106        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
19107        let buffer_id = hunks[0].buffer_id;
19108        hunks
19109            .into_iter()
19110            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
19111            .collect::<Vec<_>>()
19112    });
19113    assert_eq!(hunk_ranges.len(), 1);
19114
19115    cx.update_editor(|editor, _, cx| {
19116        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19117    });
19118    executor.run_until_parked();
19119
19120    let hunk_collapsed = r#"
19121          ˇb
19122          c
19123        "#
19124    .unindent();
19125
19126    cx.assert_state_with_diff(hunk_collapsed);
19127
19128    cx.update_editor(|editor, _, cx| {
19129        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19130    });
19131    executor.run_until_parked();
19132
19133    cx.assert_state_with_diff(hunk_expanded.clone());
19134}
19135
19136#[gpui::test]
19137async fn test_display_diff_hunks(cx: &mut TestAppContext) {
19138    init_test(cx, |_| {});
19139
19140    let fs = FakeFs::new(cx.executor());
19141    fs.insert_tree(
19142        path!("/test"),
19143        json!({
19144            ".git": {},
19145            "file-1": "ONE\n",
19146            "file-2": "TWO\n",
19147            "file-3": "THREE\n",
19148        }),
19149    )
19150    .await;
19151
19152    fs.set_head_for_repo(
19153        path!("/test/.git").as_ref(),
19154        &[
19155            ("file-1".into(), "one\n".into()),
19156            ("file-2".into(), "two\n".into()),
19157            ("file-3".into(), "three\n".into()),
19158        ],
19159        "deadbeef",
19160    );
19161
19162    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
19163    let mut buffers = vec![];
19164    for i in 1..=3 {
19165        let buffer = project
19166            .update(cx, |project, cx| {
19167                let path = format!(path!("/test/file-{}"), i);
19168                project.open_local_buffer(path, cx)
19169            })
19170            .await
19171            .unwrap();
19172        buffers.push(buffer);
19173    }
19174
19175    let multibuffer = cx.new(|cx| {
19176        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
19177        multibuffer.set_all_diff_hunks_expanded(cx);
19178        for buffer in &buffers {
19179            let snapshot = buffer.read(cx).snapshot();
19180            multibuffer.set_excerpts_for_path(
19181                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
19182                buffer.clone(),
19183                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
19184                DEFAULT_MULTIBUFFER_CONTEXT,
19185                cx,
19186            );
19187        }
19188        multibuffer
19189    });
19190
19191    let editor = cx.add_window(|window, cx| {
19192        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
19193    });
19194    cx.run_until_parked();
19195
19196    let snapshot = editor
19197        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19198        .unwrap();
19199    let hunks = snapshot
19200        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
19201        .map(|hunk| match hunk {
19202            DisplayDiffHunk::Unfolded {
19203                display_row_range, ..
19204            } => display_row_range,
19205            DisplayDiffHunk::Folded { .. } => unreachable!(),
19206        })
19207        .collect::<Vec<_>>();
19208    assert_eq!(
19209        hunks,
19210        [
19211            DisplayRow(2)..DisplayRow(4),
19212            DisplayRow(7)..DisplayRow(9),
19213            DisplayRow(12)..DisplayRow(14),
19214        ]
19215    );
19216}
19217
19218#[gpui::test]
19219async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
19220    init_test(cx, |_| {});
19221
19222    let mut cx = EditorTestContext::new(cx).await;
19223    cx.set_head_text(indoc! { "
19224        one
19225        two
19226        three
19227        four
19228        five
19229        "
19230    });
19231    cx.set_index_text(indoc! { "
19232        one
19233        two
19234        three
19235        four
19236        five
19237        "
19238    });
19239    cx.set_state(indoc! {"
19240        one
19241        TWO
19242        ˇTHREE
19243        FOUR
19244        five
19245    "});
19246    cx.run_until_parked();
19247    cx.update_editor(|editor, window, cx| {
19248        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19249    });
19250    cx.run_until_parked();
19251    cx.assert_index_text(Some(indoc! {"
19252        one
19253        TWO
19254        THREE
19255        FOUR
19256        five
19257    "}));
19258    cx.set_state(indoc! { "
19259        one
19260        TWO
19261        ˇTHREE-HUNDRED
19262        FOUR
19263        five
19264    "});
19265    cx.run_until_parked();
19266    cx.update_editor(|editor, window, cx| {
19267        let snapshot = editor.snapshot(window, cx);
19268        let hunks = editor
19269            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19270            .collect::<Vec<_>>();
19271        assert_eq!(hunks.len(), 1);
19272        assert_eq!(
19273            hunks[0].status(),
19274            DiffHunkStatus {
19275                kind: DiffHunkStatusKind::Modified,
19276                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
19277            }
19278        );
19279
19280        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19281    });
19282    cx.run_until_parked();
19283    cx.assert_index_text(Some(indoc! {"
19284        one
19285        TWO
19286        THREE-HUNDRED
19287        FOUR
19288        five
19289    "}));
19290}
19291
19292#[gpui::test]
19293fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
19294    init_test(cx, |_| {});
19295
19296    let editor = cx.add_window(|window, cx| {
19297        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
19298        build_editor(buffer, window, cx)
19299    });
19300
19301    let render_args = Arc::new(Mutex::new(None));
19302    let snapshot = editor
19303        .update(cx, |editor, window, cx| {
19304            let snapshot = editor.buffer().read(cx).snapshot(cx);
19305            let range =
19306                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
19307
19308            struct RenderArgs {
19309                row: MultiBufferRow,
19310                folded: bool,
19311                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
19312            }
19313
19314            let crease = Crease::inline(
19315                range,
19316                FoldPlaceholder::test(),
19317                {
19318                    let toggle_callback = render_args.clone();
19319                    move |row, folded, callback, _window, _cx| {
19320                        *toggle_callback.lock() = Some(RenderArgs {
19321                            row,
19322                            folded,
19323                            callback,
19324                        });
19325                        div()
19326                    }
19327                },
19328                |_row, _folded, _window, _cx| div(),
19329            );
19330
19331            editor.insert_creases(Some(crease), cx);
19332            let snapshot = editor.snapshot(window, cx);
19333            let _div = snapshot.render_crease_toggle(
19334                MultiBufferRow(1),
19335                false,
19336                cx.entity().clone(),
19337                window,
19338                cx,
19339            );
19340            snapshot
19341        })
19342        .unwrap();
19343
19344    let render_args = render_args.lock().take().unwrap();
19345    assert_eq!(render_args.row, MultiBufferRow(1));
19346    assert!(!render_args.folded);
19347    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
19348
19349    cx.update_window(*editor, |_, window, cx| {
19350        (render_args.callback)(true, window, cx)
19351    })
19352    .unwrap();
19353    let snapshot = editor
19354        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19355        .unwrap();
19356    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
19357
19358    cx.update_window(*editor, |_, window, cx| {
19359        (render_args.callback)(false, window, cx)
19360    })
19361    .unwrap();
19362    let snapshot = editor
19363        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19364        .unwrap();
19365    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
19366}
19367
19368#[gpui::test]
19369async fn test_input_text(cx: &mut TestAppContext) {
19370    init_test(cx, |_| {});
19371    let mut cx = EditorTestContext::new(cx).await;
19372
19373    cx.set_state(
19374        &r#"ˇone
19375        two
19376
19377        three
19378        fourˇ
19379        five
19380
19381        siˇx"#
19382            .unindent(),
19383    );
19384
19385    cx.dispatch_action(HandleInput(String::new()));
19386    cx.assert_editor_state(
19387        &r#"ˇone
19388        two
19389
19390        three
19391        fourˇ
19392        five
19393
19394        siˇx"#
19395            .unindent(),
19396    );
19397
19398    cx.dispatch_action(HandleInput("AAAA".to_string()));
19399    cx.assert_editor_state(
19400        &r#"AAAAˇone
19401        two
19402
19403        three
19404        fourAAAAˇ
19405        five
19406
19407        siAAAAˇx"#
19408            .unindent(),
19409    );
19410}
19411
19412#[gpui::test]
19413async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
19414    init_test(cx, |_| {});
19415
19416    let mut cx = EditorTestContext::new(cx).await;
19417    cx.set_state(
19418        r#"let foo = 1;
19419let foo = 2;
19420let foo = 3;
19421let fooˇ = 4;
19422let foo = 5;
19423let foo = 6;
19424let foo = 7;
19425let foo = 8;
19426let foo = 9;
19427let foo = 10;
19428let foo = 11;
19429let foo = 12;
19430let foo = 13;
19431let foo = 14;
19432let foo = 15;"#,
19433    );
19434
19435    cx.update_editor(|e, window, cx| {
19436        assert_eq!(
19437            e.next_scroll_position,
19438            NextScrollCursorCenterTopBottom::Center,
19439            "Default next scroll direction is center",
19440        );
19441
19442        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19443        assert_eq!(
19444            e.next_scroll_position,
19445            NextScrollCursorCenterTopBottom::Top,
19446            "After center, next scroll direction should be top",
19447        );
19448
19449        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19450        assert_eq!(
19451            e.next_scroll_position,
19452            NextScrollCursorCenterTopBottom::Bottom,
19453            "After top, next scroll direction should be bottom",
19454        );
19455
19456        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19457        assert_eq!(
19458            e.next_scroll_position,
19459            NextScrollCursorCenterTopBottom::Center,
19460            "After bottom, scrolling should start over",
19461        );
19462
19463        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19464        assert_eq!(
19465            e.next_scroll_position,
19466            NextScrollCursorCenterTopBottom::Top,
19467            "Scrolling continues if retriggered fast enough"
19468        );
19469    });
19470
19471    cx.executor()
19472        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
19473    cx.executor().run_until_parked();
19474    cx.update_editor(|e, _, _| {
19475        assert_eq!(
19476            e.next_scroll_position,
19477            NextScrollCursorCenterTopBottom::Center,
19478            "If scrolling is not triggered fast enough, it should reset"
19479        );
19480    });
19481}
19482
19483#[gpui::test]
19484async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
19485    init_test(cx, |_| {});
19486    let mut cx = EditorLspTestContext::new_rust(
19487        lsp::ServerCapabilities {
19488            definition_provider: Some(lsp::OneOf::Left(true)),
19489            references_provider: Some(lsp::OneOf::Left(true)),
19490            ..lsp::ServerCapabilities::default()
19491        },
19492        cx,
19493    )
19494    .await;
19495
19496    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
19497        let go_to_definition = cx
19498            .lsp
19499            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
19500                move |params, _| async move {
19501                    if empty_go_to_definition {
19502                        Ok(None)
19503                    } else {
19504                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
19505                            uri: params.text_document_position_params.text_document.uri,
19506                            range: lsp::Range::new(
19507                                lsp::Position::new(4, 3),
19508                                lsp::Position::new(4, 6),
19509                            ),
19510                        })))
19511                    }
19512                },
19513            );
19514        let references = cx
19515            .lsp
19516            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
19517                Ok(Some(vec![lsp::Location {
19518                    uri: params.text_document_position.text_document.uri,
19519                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
19520                }]))
19521            });
19522        (go_to_definition, references)
19523    };
19524
19525    cx.set_state(
19526        &r#"fn one() {
19527            let mut a = ˇtwo();
19528        }
19529
19530        fn two() {}"#
19531            .unindent(),
19532    );
19533    set_up_lsp_handlers(false, &mut cx);
19534    let navigated = cx
19535        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19536        .await
19537        .expect("Failed to navigate to definition");
19538    assert_eq!(
19539        navigated,
19540        Navigated::Yes,
19541        "Should have navigated to definition from the GetDefinition response"
19542    );
19543    cx.assert_editor_state(
19544        &r#"fn one() {
19545            let mut a = two();
19546        }
19547
19548        fn «twoˇ»() {}"#
19549            .unindent(),
19550    );
19551
19552    let editors = cx.update_workspace(|workspace, _, cx| {
19553        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19554    });
19555    cx.update_editor(|_, _, test_editor_cx| {
19556        assert_eq!(
19557            editors.len(),
19558            1,
19559            "Initially, only one, test, editor should be open in the workspace"
19560        );
19561        assert_eq!(
19562            test_editor_cx.entity(),
19563            editors.last().expect("Asserted len is 1").clone()
19564        );
19565    });
19566
19567    set_up_lsp_handlers(true, &mut cx);
19568    let navigated = cx
19569        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19570        .await
19571        .expect("Failed to navigate to lookup references");
19572    assert_eq!(
19573        navigated,
19574        Navigated::Yes,
19575        "Should have navigated to references as a fallback after empty GoToDefinition response"
19576    );
19577    // We should not change the selections in the existing file,
19578    // if opening another milti buffer with the references
19579    cx.assert_editor_state(
19580        &r#"fn one() {
19581            let mut a = two();
19582        }
19583
19584        fn «twoˇ»() {}"#
19585            .unindent(),
19586    );
19587    let editors = cx.update_workspace(|workspace, _, cx| {
19588        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19589    });
19590    cx.update_editor(|_, _, test_editor_cx| {
19591        assert_eq!(
19592            editors.len(),
19593            2,
19594            "After falling back to references search, we open a new editor with the results"
19595        );
19596        let references_fallback_text = editors
19597            .into_iter()
19598            .find(|new_editor| *new_editor != test_editor_cx.entity())
19599            .expect("Should have one non-test editor now")
19600            .read(test_editor_cx)
19601            .text(test_editor_cx);
19602        assert_eq!(
19603            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
19604            "Should use the range from the references response and not the GoToDefinition one"
19605        );
19606    });
19607}
19608
19609#[gpui::test]
19610async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
19611    init_test(cx, |_| {});
19612    cx.update(|cx| {
19613        let mut editor_settings = EditorSettings::get_global(cx).clone();
19614        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
19615        EditorSettings::override_global(editor_settings, cx);
19616    });
19617    let mut cx = EditorLspTestContext::new_rust(
19618        lsp::ServerCapabilities {
19619            definition_provider: Some(lsp::OneOf::Left(true)),
19620            references_provider: Some(lsp::OneOf::Left(true)),
19621            ..lsp::ServerCapabilities::default()
19622        },
19623        cx,
19624    )
19625    .await;
19626    let original_state = r#"fn one() {
19627        let mut a = ˇtwo();
19628    }
19629
19630    fn two() {}"#
19631        .unindent();
19632    cx.set_state(&original_state);
19633
19634    let mut go_to_definition = cx
19635        .lsp
19636        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
19637            move |_, _| async move { Ok(None) },
19638        );
19639    let _references = cx
19640        .lsp
19641        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
19642            panic!("Should not call for references with no go to definition fallback")
19643        });
19644
19645    let navigated = cx
19646        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19647        .await
19648        .expect("Failed to navigate to lookup references");
19649    go_to_definition
19650        .next()
19651        .await
19652        .expect("Should have called the go_to_definition handler");
19653
19654    assert_eq!(
19655        navigated,
19656        Navigated::No,
19657        "Should have navigated to references as a fallback after empty GoToDefinition response"
19658    );
19659    cx.assert_editor_state(&original_state);
19660    let editors = cx.update_workspace(|workspace, _, cx| {
19661        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19662    });
19663    cx.update_editor(|_, _, _| {
19664        assert_eq!(
19665            editors.len(),
19666            1,
19667            "After unsuccessful fallback, no other editor should have been opened"
19668        );
19669    });
19670}
19671
19672#[gpui::test]
19673async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
19674    init_test(cx, |_| {});
19675
19676    let language = Arc::new(Language::new(
19677        LanguageConfig::default(),
19678        Some(tree_sitter_rust::LANGUAGE.into()),
19679    ));
19680
19681    let text = r#"
19682        #[cfg(test)]
19683        mod tests() {
19684            #[test]
19685            fn runnable_1() {
19686                let a = 1;
19687            }
19688
19689            #[test]
19690            fn runnable_2() {
19691                let a = 1;
19692                let b = 2;
19693            }
19694        }
19695    "#
19696    .unindent();
19697
19698    let fs = FakeFs::new(cx.executor());
19699    fs.insert_file("/file.rs", Default::default()).await;
19700
19701    let project = Project::test(fs, ["/a".as_ref()], cx).await;
19702    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19703    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19704    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
19705    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
19706
19707    let editor = cx.new_window_entity(|window, cx| {
19708        Editor::new(
19709            EditorMode::full(),
19710            multi_buffer,
19711            Some(project.clone()),
19712            window,
19713            cx,
19714        )
19715    });
19716
19717    editor.update_in(cx, |editor, window, cx| {
19718        let snapshot = editor.buffer().read(cx).snapshot(cx);
19719        editor.tasks.insert(
19720            (buffer.read(cx).remote_id(), 3),
19721            RunnableTasks {
19722                templates: vec![],
19723                offset: snapshot.anchor_before(43),
19724                column: 0,
19725                extra_variables: HashMap::default(),
19726                context_range: BufferOffset(43)..BufferOffset(85),
19727            },
19728        );
19729        editor.tasks.insert(
19730            (buffer.read(cx).remote_id(), 8),
19731            RunnableTasks {
19732                templates: vec![],
19733                offset: snapshot.anchor_before(86),
19734                column: 0,
19735                extra_variables: HashMap::default(),
19736                context_range: BufferOffset(86)..BufferOffset(191),
19737            },
19738        );
19739
19740        // Test finding task when cursor is inside function body
19741        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19742            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
19743        });
19744        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
19745        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
19746
19747        // Test finding task when cursor is on function name
19748        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19749            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
19750        });
19751        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
19752        assert_eq!(row, 8, "Should find task when cursor is on function name");
19753    });
19754}
19755
19756#[gpui::test]
19757async fn test_folding_buffers(cx: &mut TestAppContext) {
19758    init_test(cx, |_| {});
19759
19760    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
19761    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
19762    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
19763
19764    let fs = FakeFs::new(cx.executor());
19765    fs.insert_tree(
19766        path!("/a"),
19767        json!({
19768            "first.rs": sample_text_1,
19769            "second.rs": sample_text_2,
19770            "third.rs": sample_text_3,
19771        }),
19772    )
19773    .await;
19774    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19775    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19776    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19777    let worktree = project.update(cx, |project, cx| {
19778        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19779        assert_eq!(worktrees.len(), 1);
19780        worktrees.pop().unwrap()
19781    });
19782    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19783
19784    let buffer_1 = project
19785        .update(cx, |project, cx| {
19786            project.open_buffer((worktree_id, "first.rs"), cx)
19787        })
19788        .await
19789        .unwrap();
19790    let buffer_2 = project
19791        .update(cx, |project, cx| {
19792            project.open_buffer((worktree_id, "second.rs"), cx)
19793        })
19794        .await
19795        .unwrap();
19796    let buffer_3 = project
19797        .update(cx, |project, cx| {
19798            project.open_buffer((worktree_id, "third.rs"), cx)
19799        })
19800        .await
19801        .unwrap();
19802
19803    let multi_buffer = cx.new(|cx| {
19804        let mut multi_buffer = MultiBuffer::new(ReadWrite);
19805        multi_buffer.push_excerpts(
19806            buffer_1.clone(),
19807            [
19808                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19809                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19810                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19811            ],
19812            cx,
19813        );
19814        multi_buffer.push_excerpts(
19815            buffer_2.clone(),
19816            [
19817                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19818                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19819                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19820            ],
19821            cx,
19822        );
19823        multi_buffer.push_excerpts(
19824            buffer_3.clone(),
19825            [
19826                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19827                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19828                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19829            ],
19830            cx,
19831        );
19832        multi_buffer
19833    });
19834    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19835        Editor::new(
19836            EditorMode::full(),
19837            multi_buffer.clone(),
19838            Some(project.clone()),
19839            window,
19840            cx,
19841        )
19842    });
19843
19844    assert_eq!(
19845        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19846        "\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",
19847    );
19848
19849    multi_buffer_editor.update(cx, |editor, cx| {
19850        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
19851    });
19852    assert_eq!(
19853        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19854        "\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",
19855        "After folding the first buffer, its text should not be displayed"
19856    );
19857
19858    multi_buffer_editor.update(cx, |editor, cx| {
19859        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
19860    });
19861    assert_eq!(
19862        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19863        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
19864        "After folding the second buffer, its text should not be displayed"
19865    );
19866
19867    multi_buffer_editor.update(cx, |editor, cx| {
19868        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
19869    });
19870    assert_eq!(
19871        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19872        "\n\n\n\n\n",
19873        "After folding the third buffer, its text should not be displayed"
19874    );
19875
19876    // Emulate selection inside the fold logic, that should work
19877    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19878        editor
19879            .snapshot(window, cx)
19880            .next_line_boundary(Point::new(0, 4));
19881    });
19882
19883    multi_buffer_editor.update(cx, |editor, cx| {
19884        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
19885    });
19886    assert_eq!(
19887        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19888        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
19889        "After unfolding the second buffer, its text should be displayed"
19890    );
19891
19892    // Typing inside of buffer 1 causes that buffer to be unfolded.
19893    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19894        assert_eq!(
19895            multi_buffer
19896                .read(cx)
19897                .snapshot(cx)
19898                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
19899                .collect::<String>(),
19900            "bbbb"
19901        );
19902        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
19903            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
19904        });
19905        editor.handle_input("B", window, cx);
19906    });
19907
19908    assert_eq!(
19909        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19910        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
19911        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
19912    );
19913
19914    multi_buffer_editor.update(cx, |editor, cx| {
19915        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
19916    });
19917    assert_eq!(
19918        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19919        "\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",
19920        "After unfolding the all buffers, all original text should be displayed"
19921    );
19922}
19923
19924#[gpui::test]
19925async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
19926    init_test(cx, |_| {});
19927
19928    let sample_text_1 = "1111\n2222\n3333".to_string();
19929    let sample_text_2 = "4444\n5555\n6666".to_string();
19930    let sample_text_3 = "7777\n8888\n9999".to_string();
19931
19932    let fs = FakeFs::new(cx.executor());
19933    fs.insert_tree(
19934        path!("/a"),
19935        json!({
19936            "first.rs": sample_text_1,
19937            "second.rs": sample_text_2,
19938            "third.rs": sample_text_3,
19939        }),
19940    )
19941    .await;
19942    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19943    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19944    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19945    let worktree = project.update(cx, |project, cx| {
19946        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19947        assert_eq!(worktrees.len(), 1);
19948        worktrees.pop().unwrap()
19949    });
19950    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19951
19952    let buffer_1 = project
19953        .update(cx, |project, cx| {
19954            project.open_buffer((worktree_id, "first.rs"), cx)
19955        })
19956        .await
19957        .unwrap();
19958    let buffer_2 = project
19959        .update(cx, |project, cx| {
19960            project.open_buffer((worktree_id, "second.rs"), cx)
19961        })
19962        .await
19963        .unwrap();
19964    let buffer_3 = project
19965        .update(cx, |project, cx| {
19966            project.open_buffer((worktree_id, "third.rs"), cx)
19967        })
19968        .await
19969        .unwrap();
19970
19971    let multi_buffer = cx.new(|cx| {
19972        let mut multi_buffer = MultiBuffer::new(ReadWrite);
19973        multi_buffer.push_excerpts(
19974            buffer_1.clone(),
19975            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19976            cx,
19977        );
19978        multi_buffer.push_excerpts(
19979            buffer_2.clone(),
19980            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19981            cx,
19982        );
19983        multi_buffer.push_excerpts(
19984            buffer_3.clone(),
19985            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19986            cx,
19987        );
19988        multi_buffer
19989    });
19990
19991    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19992        Editor::new(
19993            EditorMode::full(),
19994            multi_buffer,
19995            Some(project.clone()),
19996            window,
19997            cx,
19998        )
19999    });
20000
20001    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
20002    assert_eq!(
20003        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20004        full_text,
20005    );
20006
20007    multi_buffer_editor.update(cx, |editor, cx| {
20008        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
20009    });
20010    assert_eq!(
20011        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20012        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
20013        "After folding the first buffer, its text should not be displayed"
20014    );
20015
20016    multi_buffer_editor.update(cx, |editor, cx| {
20017        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
20018    });
20019
20020    assert_eq!(
20021        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20022        "\n\n\n\n\n\n7777\n8888\n9999",
20023        "After folding the second buffer, its text should not be displayed"
20024    );
20025
20026    multi_buffer_editor.update(cx, |editor, cx| {
20027        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
20028    });
20029    assert_eq!(
20030        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20031        "\n\n\n\n\n",
20032        "After folding the third buffer, its text should not be displayed"
20033    );
20034
20035    multi_buffer_editor.update(cx, |editor, cx| {
20036        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
20037    });
20038    assert_eq!(
20039        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20040        "\n\n\n\n4444\n5555\n6666\n\n",
20041        "After unfolding the second buffer, its text should be displayed"
20042    );
20043
20044    multi_buffer_editor.update(cx, |editor, cx| {
20045        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
20046    });
20047    assert_eq!(
20048        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20049        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
20050        "After unfolding the first buffer, its text should be displayed"
20051    );
20052
20053    multi_buffer_editor.update(cx, |editor, cx| {
20054        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
20055    });
20056    assert_eq!(
20057        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20058        full_text,
20059        "After unfolding all buffers, all original text should be displayed"
20060    );
20061}
20062
20063#[gpui::test]
20064async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
20065    init_test(cx, |_| {});
20066
20067    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
20068
20069    let fs = FakeFs::new(cx.executor());
20070    fs.insert_tree(
20071        path!("/a"),
20072        json!({
20073            "main.rs": sample_text,
20074        }),
20075    )
20076    .await;
20077    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20078    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20079    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20080    let worktree = project.update(cx, |project, cx| {
20081        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
20082        assert_eq!(worktrees.len(), 1);
20083        worktrees.pop().unwrap()
20084    });
20085    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
20086
20087    let buffer_1 = project
20088        .update(cx, |project, cx| {
20089            project.open_buffer((worktree_id, "main.rs"), cx)
20090        })
20091        .await
20092        .unwrap();
20093
20094    let multi_buffer = cx.new(|cx| {
20095        let mut multi_buffer = MultiBuffer::new(ReadWrite);
20096        multi_buffer.push_excerpts(
20097            buffer_1.clone(),
20098            [ExcerptRange::new(
20099                Point::new(0, 0)
20100                    ..Point::new(
20101                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
20102                        0,
20103                    ),
20104            )],
20105            cx,
20106        );
20107        multi_buffer
20108    });
20109    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20110        Editor::new(
20111            EditorMode::full(),
20112            multi_buffer,
20113            Some(project.clone()),
20114            window,
20115            cx,
20116        )
20117    });
20118
20119    let selection_range = Point::new(1, 0)..Point::new(2, 0);
20120    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20121        enum TestHighlight {}
20122        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
20123        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
20124        editor.highlight_text::<TestHighlight>(
20125            vec![highlight_range.clone()],
20126            HighlightStyle::color(Hsla::green()),
20127            cx,
20128        );
20129        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20130            s.select_ranges(Some(highlight_range))
20131        });
20132    });
20133
20134    let full_text = format!("\n\n{sample_text}");
20135    assert_eq!(
20136        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20137        full_text,
20138    );
20139}
20140
20141#[gpui::test]
20142async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
20143    init_test(cx, |_| {});
20144    cx.update(|cx| {
20145        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
20146            "keymaps/default-linux.json",
20147            cx,
20148        )
20149        .unwrap();
20150        cx.bind_keys(default_key_bindings);
20151    });
20152
20153    let (editor, cx) = cx.add_window_view(|window, cx| {
20154        let multi_buffer = MultiBuffer::build_multi(
20155            [
20156                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
20157                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
20158                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
20159                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
20160            ],
20161            cx,
20162        );
20163        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
20164
20165        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
20166        // fold all but the second buffer, so that we test navigating between two
20167        // adjacent folded buffers, as well as folded buffers at the start and
20168        // end the multibuffer
20169        editor.fold_buffer(buffer_ids[0], cx);
20170        editor.fold_buffer(buffer_ids[2], cx);
20171        editor.fold_buffer(buffer_ids[3], cx);
20172
20173        editor
20174    });
20175    cx.simulate_resize(size(px(1000.), px(1000.)));
20176
20177    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
20178    cx.assert_excerpts_with_selections(indoc! {"
20179        [EXCERPT]
20180        ˇ[FOLDED]
20181        [EXCERPT]
20182        a1
20183        b1
20184        [EXCERPT]
20185        [FOLDED]
20186        [EXCERPT]
20187        [FOLDED]
20188        "
20189    });
20190    cx.simulate_keystroke("down");
20191    cx.assert_excerpts_with_selections(indoc! {"
20192        [EXCERPT]
20193        [FOLDED]
20194        [EXCERPT]
20195        ˇa1
20196        b1
20197        [EXCERPT]
20198        [FOLDED]
20199        [EXCERPT]
20200        [FOLDED]
20201        "
20202    });
20203    cx.simulate_keystroke("down");
20204    cx.assert_excerpts_with_selections(indoc! {"
20205        [EXCERPT]
20206        [FOLDED]
20207        [EXCERPT]
20208        a1
20209        ˇb1
20210        [EXCERPT]
20211        [FOLDED]
20212        [EXCERPT]
20213        [FOLDED]
20214        "
20215    });
20216    cx.simulate_keystroke("down");
20217    cx.assert_excerpts_with_selections(indoc! {"
20218        [EXCERPT]
20219        [FOLDED]
20220        [EXCERPT]
20221        a1
20222        b1
20223        ˇ[EXCERPT]
20224        [FOLDED]
20225        [EXCERPT]
20226        [FOLDED]
20227        "
20228    });
20229    cx.simulate_keystroke("down");
20230    cx.assert_excerpts_with_selections(indoc! {"
20231        [EXCERPT]
20232        [FOLDED]
20233        [EXCERPT]
20234        a1
20235        b1
20236        [EXCERPT]
20237        ˇ[FOLDED]
20238        [EXCERPT]
20239        [FOLDED]
20240        "
20241    });
20242    for _ in 0..5 {
20243        cx.simulate_keystroke("down");
20244        cx.assert_excerpts_with_selections(indoc! {"
20245            [EXCERPT]
20246            [FOLDED]
20247            [EXCERPT]
20248            a1
20249            b1
20250            [EXCERPT]
20251            [FOLDED]
20252            [EXCERPT]
20253            ˇ[FOLDED]
20254            "
20255        });
20256    }
20257
20258    cx.simulate_keystroke("up");
20259    cx.assert_excerpts_with_selections(indoc! {"
20260        [EXCERPT]
20261        [FOLDED]
20262        [EXCERPT]
20263        a1
20264        b1
20265        [EXCERPT]
20266        ˇ[FOLDED]
20267        [EXCERPT]
20268        [FOLDED]
20269        "
20270    });
20271    cx.simulate_keystroke("up");
20272    cx.assert_excerpts_with_selections(indoc! {"
20273        [EXCERPT]
20274        [FOLDED]
20275        [EXCERPT]
20276        a1
20277        b1
20278        ˇ[EXCERPT]
20279        [FOLDED]
20280        [EXCERPT]
20281        [FOLDED]
20282        "
20283    });
20284    cx.simulate_keystroke("up");
20285    cx.assert_excerpts_with_selections(indoc! {"
20286        [EXCERPT]
20287        [FOLDED]
20288        [EXCERPT]
20289        a1
20290        ˇb1
20291        [EXCERPT]
20292        [FOLDED]
20293        [EXCERPT]
20294        [FOLDED]
20295        "
20296    });
20297    cx.simulate_keystroke("up");
20298    cx.assert_excerpts_with_selections(indoc! {"
20299        [EXCERPT]
20300        [FOLDED]
20301        [EXCERPT]
20302        ˇa1
20303        b1
20304        [EXCERPT]
20305        [FOLDED]
20306        [EXCERPT]
20307        [FOLDED]
20308        "
20309    });
20310    for _ in 0..5 {
20311        cx.simulate_keystroke("up");
20312        cx.assert_excerpts_with_selections(indoc! {"
20313            [EXCERPT]
20314            ˇ[FOLDED]
20315            [EXCERPT]
20316            a1
20317            b1
20318            [EXCERPT]
20319            [FOLDED]
20320            [EXCERPT]
20321            [FOLDED]
20322            "
20323        });
20324    }
20325}
20326
20327#[gpui::test]
20328async fn test_inline_completion_text(cx: &mut TestAppContext) {
20329    init_test(cx, |_| {});
20330
20331    // Simple insertion
20332    assert_highlighted_edits(
20333        "Hello, world!",
20334        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
20335        true,
20336        cx,
20337        |highlighted_edits, cx| {
20338            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
20339            assert_eq!(highlighted_edits.highlights.len(), 1);
20340            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
20341            assert_eq!(
20342                highlighted_edits.highlights[0].1.background_color,
20343                Some(cx.theme().status().created_background)
20344            );
20345        },
20346    )
20347    .await;
20348
20349    // Replacement
20350    assert_highlighted_edits(
20351        "This is a test.",
20352        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
20353        false,
20354        cx,
20355        |highlighted_edits, cx| {
20356            assert_eq!(highlighted_edits.text, "That is a test.");
20357            assert_eq!(highlighted_edits.highlights.len(), 1);
20358            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
20359            assert_eq!(
20360                highlighted_edits.highlights[0].1.background_color,
20361                Some(cx.theme().status().created_background)
20362            );
20363        },
20364    )
20365    .await;
20366
20367    // Multiple edits
20368    assert_highlighted_edits(
20369        "Hello, world!",
20370        vec![
20371            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
20372            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
20373        ],
20374        false,
20375        cx,
20376        |highlighted_edits, cx| {
20377            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
20378            assert_eq!(highlighted_edits.highlights.len(), 2);
20379            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
20380            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
20381            assert_eq!(
20382                highlighted_edits.highlights[0].1.background_color,
20383                Some(cx.theme().status().created_background)
20384            );
20385            assert_eq!(
20386                highlighted_edits.highlights[1].1.background_color,
20387                Some(cx.theme().status().created_background)
20388            );
20389        },
20390    )
20391    .await;
20392
20393    // Multiple lines with edits
20394    assert_highlighted_edits(
20395        "First line\nSecond line\nThird line\nFourth line",
20396        vec![
20397            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
20398            (
20399                Point::new(2, 0)..Point::new(2, 10),
20400                "New third line".to_string(),
20401            ),
20402            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
20403        ],
20404        false,
20405        cx,
20406        |highlighted_edits, cx| {
20407            assert_eq!(
20408                highlighted_edits.text,
20409                "Second modified\nNew third line\nFourth updated line"
20410            );
20411            assert_eq!(highlighted_edits.highlights.len(), 3);
20412            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
20413            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
20414            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
20415            for highlight in &highlighted_edits.highlights {
20416                assert_eq!(
20417                    highlight.1.background_color,
20418                    Some(cx.theme().status().created_background)
20419                );
20420            }
20421        },
20422    )
20423    .await;
20424}
20425
20426#[gpui::test]
20427async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
20428    init_test(cx, |_| {});
20429
20430    // Deletion
20431    assert_highlighted_edits(
20432        "Hello, world!",
20433        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
20434        true,
20435        cx,
20436        |highlighted_edits, cx| {
20437            assert_eq!(highlighted_edits.text, "Hello, world!");
20438            assert_eq!(highlighted_edits.highlights.len(), 1);
20439            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
20440            assert_eq!(
20441                highlighted_edits.highlights[0].1.background_color,
20442                Some(cx.theme().status().deleted_background)
20443            );
20444        },
20445    )
20446    .await;
20447
20448    // Insertion
20449    assert_highlighted_edits(
20450        "Hello, world!",
20451        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
20452        true,
20453        cx,
20454        |highlighted_edits, cx| {
20455            assert_eq!(highlighted_edits.highlights.len(), 1);
20456            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
20457            assert_eq!(
20458                highlighted_edits.highlights[0].1.background_color,
20459                Some(cx.theme().status().created_background)
20460            );
20461        },
20462    )
20463    .await;
20464}
20465
20466async fn assert_highlighted_edits(
20467    text: &str,
20468    edits: Vec<(Range<Point>, String)>,
20469    include_deletions: bool,
20470    cx: &mut TestAppContext,
20471    assertion_fn: impl Fn(HighlightedText, &App),
20472) {
20473    let window = cx.add_window(|window, cx| {
20474        let buffer = MultiBuffer::build_simple(text, cx);
20475        Editor::new(EditorMode::full(), buffer, None, window, cx)
20476    });
20477    let cx = &mut VisualTestContext::from_window(*window, cx);
20478
20479    let (buffer, snapshot) = window
20480        .update(cx, |editor, _window, cx| {
20481            (
20482                editor.buffer().clone(),
20483                editor.buffer().read(cx).snapshot(cx),
20484            )
20485        })
20486        .unwrap();
20487
20488    let edits = edits
20489        .into_iter()
20490        .map(|(range, edit)| {
20491            (
20492                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
20493                edit,
20494            )
20495        })
20496        .collect::<Vec<_>>();
20497
20498    let text_anchor_edits = edits
20499        .clone()
20500        .into_iter()
20501        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
20502        .collect::<Vec<_>>();
20503
20504    let edit_preview = window
20505        .update(cx, |_, _window, cx| {
20506            buffer
20507                .read(cx)
20508                .as_singleton()
20509                .unwrap()
20510                .read(cx)
20511                .preview_edits(text_anchor_edits.into(), cx)
20512        })
20513        .unwrap()
20514        .await;
20515
20516    cx.update(|_window, cx| {
20517        let highlighted_edits = inline_completion_edit_text(
20518            &snapshot.as_singleton().unwrap().2,
20519            &edits,
20520            &edit_preview,
20521            include_deletions,
20522            cx,
20523        );
20524        assertion_fn(highlighted_edits, cx)
20525    });
20526}
20527
20528#[track_caller]
20529fn assert_breakpoint(
20530    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
20531    path: &Arc<Path>,
20532    expected: Vec<(u32, Breakpoint)>,
20533) {
20534    if expected.len() == 0usize {
20535        assert!(!breakpoints.contains_key(path), "{}", path.display());
20536    } else {
20537        let mut breakpoint = breakpoints
20538            .get(path)
20539            .unwrap()
20540            .into_iter()
20541            .map(|breakpoint| {
20542                (
20543                    breakpoint.row,
20544                    Breakpoint {
20545                        message: breakpoint.message.clone(),
20546                        state: breakpoint.state,
20547                        condition: breakpoint.condition.clone(),
20548                        hit_condition: breakpoint.hit_condition.clone(),
20549                    },
20550                )
20551            })
20552            .collect::<Vec<_>>();
20553
20554        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
20555
20556        assert_eq!(expected, breakpoint);
20557    }
20558}
20559
20560fn add_log_breakpoint_at_cursor(
20561    editor: &mut Editor,
20562    log_message: &str,
20563    window: &mut Window,
20564    cx: &mut Context<Editor>,
20565) {
20566    let (anchor, bp) = editor
20567        .breakpoints_at_cursors(window, cx)
20568        .first()
20569        .and_then(|(anchor, bp)| {
20570            if let Some(bp) = bp {
20571                Some((*anchor, bp.clone()))
20572            } else {
20573                None
20574            }
20575        })
20576        .unwrap_or_else(|| {
20577            let cursor_position: Point = editor.selections.newest(cx).head();
20578
20579            let breakpoint_position = editor
20580                .snapshot(window, cx)
20581                .display_snapshot
20582                .buffer_snapshot
20583                .anchor_before(Point::new(cursor_position.row, 0));
20584
20585            (breakpoint_position, Breakpoint::new_log(&log_message))
20586        });
20587
20588    editor.edit_breakpoint_at_anchor(
20589        anchor,
20590        bp,
20591        BreakpointEditAction::EditLogMessage(log_message.into()),
20592        cx,
20593    );
20594}
20595
20596#[gpui::test]
20597async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
20598    init_test(cx, |_| {});
20599
20600    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20601    let fs = FakeFs::new(cx.executor());
20602    fs.insert_tree(
20603        path!("/a"),
20604        json!({
20605            "main.rs": sample_text,
20606        }),
20607    )
20608    .await;
20609    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20610    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20611    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20612
20613    let fs = FakeFs::new(cx.executor());
20614    fs.insert_tree(
20615        path!("/a"),
20616        json!({
20617            "main.rs": sample_text,
20618        }),
20619    )
20620    .await;
20621    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20622    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20623    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20624    let worktree_id = workspace
20625        .update(cx, |workspace, _window, cx| {
20626            workspace.project().update(cx, |project, cx| {
20627                project.worktrees(cx).next().unwrap().read(cx).id()
20628            })
20629        })
20630        .unwrap();
20631
20632    let buffer = project
20633        .update(cx, |project, cx| {
20634            project.open_buffer((worktree_id, "main.rs"), cx)
20635        })
20636        .await
20637        .unwrap();
20638
20639    let (editor, cx) = cx.add_window_view(|window, cx| {
20640        Editor::new(
20641            EditorMode::full(),
20642            MultiBuffer::build_from_buffer(buffer, cx),
20643            Some(project.clone()),
20644            window,
20645            cx,
20646        )
20647    });
20648
20649    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20650    let abs_path = project.read_with(cx, |project, cx| {
20651        project
20652            .absolute_path(&project_path, cx)
20653            .map(|path_buf| Arc::from(path_buf.to_owned()))
20654            .unwrap()
20655    });
20656
20657    // assert we can add breakpoint on the first line
20658    editor.update_in(cx, |editor, window, cx| {
20659        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20660        editor.move_to_end(&MoveToEnd, window, cx);
20661        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20662    });
20663
20664    let breakpoints = editor.update(cx, |editor, cx| {
20665        editor
20666            .breakpoint_store()
20667            .as_ref()
20668            .unwrap()
20669            .read(cx)
20670            .all_source_breakpoints(cx)
20671            .clone()
20672    });
20673
20674    assert_eq!(1, breakpoints.len());
20675    assert_breakpoint(
20676        &breakpoints,
20677        &abs_path,
20678        vec![
20679            (0, Breakpoint::new_standard()),
20680            (3, Breakpoint::new_standard()),
20681        ],
20682    );
20683
20684    editor.update_in(cx, |editor, window, cx| {
20685        editor.move_to_beginning(&MoveToBeginning, window, cx);
20686        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20687    });
20688
20689    let breakpoints = editor.update(cx, |editor, cx| {
20690        editor
20691            .breakpoint_store()
20692            .as_ref()
20693            .unwrap()
20694            .read(cx)
20695            .all_source_breakpoints(cx)
20696            .clone()
20697    });
20698
20699    assert_eq!(1, breakpoints.len());
20700    assert_breakpoint(
20701        &breakpoints,
20702        &abs_path,
20703        vec![(3, Breakpoint::new_standard())],
20704    );
20705
20706    editor.update_in(cx, |editor, window, cx| {
20707        editor.move_to_end(&MoveToEnd, window, cx);
20708        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20709    });
20710
20711    let breakpoints = editor.update(cx, |editor, cx| {
20712        editor
20713            .breakpoint_store()
20714            .as_ref()
20715            .unwrap()
20716            .read(cx)
20717            .all_source_breakpoints(cx)
20718            .clone()
20719    });
20720
20721    assert_eq!(0, breakpoints.len());
20722    assert_breakpoint(&breakpoints, &abs_path, vec![]);
20723}
20724
20725#[gpui::test]
20726async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
20727    init_test(cx, |_| {});
20728
20729    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20730
20731    let fs = FakeFs::new(cx.executor());
20732    fs.insert_tree(
20733        path!("/a"),
20734        json!({
20735            "main.rs": sample_text,
20736        }),
20737    )
20738    .await;
20739    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20740    let (workspace, cx) =
20741        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
20742
20743    let worktree_id = workspace.update(cx, |workspace, cx| {
20744        workspace.project().update(cx, |project, cx| {
20745            project.worktrees(cx).next().unwrap().read(cx).id()
20746        })
20747    });
20748
20749    let buffer = project
20750        .update(cx, |project, cx| {
20751            project.open_buffer((worktree_id, "main.rs"), cx)
20752        })
20753        .await
20754        .unwrap();
20755
20756    let (editor, cx) = cx.add_window_view(|window, cx| {
20757        Editor::new(
20758            EditorMode::full(),
20759            MultiBuffer::build_from_buffer(buffer, cx),
20760            Some(project.clone()),
20761            window,
20762            cx,
20763        )
20764    });
20765
20766    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20767    let abs_path = project.read_with(cx, |project, cx| {
20768        project
20769            .absolute_path(&project_path, cx)
20770            .map(|path_buf| Arc::from(path_buf.to_owned()))
20771            .unwrap()
20772    });
20773
20774    editor.update_in(cx, |editor, window, cx| {
20775        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
20776    });
20777
20778    let breakpoints = editor.update(cx, |editor, cx| {
20779        editor
20780            .breakpoint_store()
20781            .as_ref()
20782            .unwrap()
20783            .read(cx)
20784            .all_source_breakpoints(cx)
20785            .clone()
20786    });
20787
20788    assert_breakpoint(
20789        &breakpoints,
20790        &abs_path,
20791        vec![(0, Breakpoint::new_log("hello world"))],
20792    );
20793
20794    // Removing a log message from a log breakpoint should remove it
20795    editor.update_in(cx, |editor, window, cx| {
20796        add_log_breakpoint_at_cursor(editor, "", window, cx);
20797    });
20798
20799    let breakpoints = editor.update(cx, |editor, cx| {
20800        editor
20801            .breakpoint_store()
20802            .as_ref()
20803            .unwrap()
20804            .read(cx)
20805            .all_source_breakpoints(cx)
20806            .clone()
20807    });
20808
20809    assert_breakpoint(&breakpoints, &abs_path, vec![]);
20810
20811    editor.update_in(cx, |editor, window, cx| {
20812        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20813        editor.move_to_end(&MoveToEnd, window, cx);
20814        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20815        // Not adding a log message to a standard breakpoint shouldn't remove it
20816        add_log_breakpoint_at_cursor(editor, "", window, cx);
20817    });
20818
20819    let breakpoints = editor.update(cx, |editor, cx| {
20820        editor
20821            .breakpoint_store()
20822            .as_ref()
20823            .unwrap()
20824            .read(cx)
20825            .all_source_breakpoints(cx)
20826            .clone()
20827    });
20828
20829    assert_breakpoint(
20830        &breakpoints,
20831        &abs_path,
20832        vec![
20833            (0, Breakpoint::new_standard()),
20834            (3, Breakpoint::new_standard()),
20835        ],
20836    );
20837
20838    editor.update_in(cx, |editor, window, cx| {
20839        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
20840    });
20841
20842    let breakpoints = editor.update(cx, |editor, cx| {
20843        editor
20844            .breakpoint_store()
20845            .as_ref()
20846            .unwrap()
20847            .read(cx)
20848            .all_source_breakpoints(cx)
20849            .clone()
20850    });
20851
20852    assert_breakpoint(
20853        &breakpoints,
20854        &abs_path,
20855        vec![
20856            (0, Breakpoint::new_standard()),
20857            (3, Breakpoint::new_log("hello world")),
20858        ],
20859    );
20860
20861    editor.update_in(cx, |editor, window, cx| {
20862        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
20863    });
20864
20865    let breakpoints = editor.update(cx, |editor, cx| {
20866        editor
20867            .breakpoint_store()
20868            .as_ref()
20869            .unwrap()
20870            .read(cx)
20871            .all_source_breakpoints(cx)
20872            .clone()
20873    });
20874
20875    assert_breakpoint(
20876        &breakpoints,
20877        &abs_path,
20878        vec![
20879            (0, Breakpoint::new_standard()),
20880            (3, Breakpoint::new_log("hello Earth!!")),
20881        ],
20882    );
20883}
20884
20885/// This also tests that Editor::breakpoint_at_cursor_head is working properly
20886/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
20887/// or when breakpoints were placed out of order. This tests for a regression too
20888#[gpui::test]
20889async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
20890    init_test(cx, |_| {});
20891
20892    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20893    let fs = FakeFs::new(cx.executor());
20894    fs.insert_tree(
20895        path!("/a"),
20896        json!({
20897            "main.rs": sample_text,
20898        }),
20899    )
20900    .await;
20901    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20902    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20903    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20904
20905    let fs = FakeFs::new(cx.executor());
20906    fs.insert_tree(
20907        path!("/a"),
20908        json!({
20909            "main.rs": sample_text,
20910        }),
20911    )
20912    .await;
20913    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20914    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20915    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20916    let worktree_id = workspace
20917        .update(cx, |workspace, _window, cx| {
20918            workspace.project().update(cx, |project, cx| {
20919                project.worktrees(cx).next().unwrap().read(cx).id()
20920            })
20921        })
20922        .unwrap();
20923
20924    let buffer = project
20925        .update(cx, |project, cx| {
20926            project.open_buffer((worktree_id, "main.rs"), cx)
20927        })
20928        .await
20929        .unwrap();
20930
20931    let (editor, cx) = cx.add_window_view(|window, cx| {
20932        Editor::new(
20933            EditorMode::full(),
20934            MultiBuffer::build_from_buffer(buffer, cx),
20935            Some(project.clone()),
20936            window,
20937            cx,
20938        )
20939    });
20940
20941    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20942    let abs_path = project.read_with(cx, |project, cx| {
20943        project
20944            .absolute_path(&project_path, cx)
20945            .map(|path_buf| Arc::from(path_buf.to_owned()))
20946            .unwrap()
20947    });
20948
20949    // assert we can add breakpoint on the first line
20950    editor.update_in(cx, |editor, window, cx| {
20951        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20952        editor.move_to_end(&MoveToEnd, window, cx);
20953        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20954        editor.move_up(&MoveUp, window, cx);
20955        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20956    });
20957
20958    let breakpoints = editor.update(cx, |editor, cx| {
20959        editor
20960            .breakpoint_store()
20961            .as_ref()
20962            .unwrap()
20963            .read(cx)
20964            .all_source_breakpoints(cx)
20965            .clone()
20966    });
20967
20968    assert_eq!(1, breakpoints.len());
20969    assert_breakpoint(
20970        &breakpoints,
20971        &abs_path,
20972        vec![
20973            (0, Breakpoint::new_standard()),
20974            (2, Breakpoint::new_standard()),
20975            (3, Breakpoint::new_standard()),
20976        ],
20977    );
20978
20979    editor.update_in(cx, |editor, window, cx| {
20980        editor.move_to_beginning(&MoveToBeginning, window, cx);
20981        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20982        editor.move_to_end(&MoveToEnd, window, cx);
20983        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20984        // Disabling a breakpoint that doesn't exist should do nothing
20985        editor.move_up(&MoveUp, window, cx);
20986        editor.move_up(&MoveUp, window, cx);
20987        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20988    });
20989
20990    let breakpoints = editor.update(cx, |editor, cx| {
20991        editor
20992            .breakpoint_store()
20993            .as_ref()
20994            .unwrap()
20995            .read(cx)
20996            .all_source_breakpoints(cx)
20997            .clone()
20998    });
20999
21000    let disable_breakpoint = {
21001        let mut bp = Breakpoint::new_standard();
21002        bp.state = BreakpointState::Disabled;
21003        bp
21004    };
21005
21006    assert_eq!(1, breakpoints.len());
21007    assert_breakpoint(
21008        &breakpoints,
21009        &abs_path,
21010        vec![
21011            (0, disable_breakpoint.clone()),
21012            (2, Breakpoint::new_standard()),
21013            (3, disable_breakpoint.clone()),
21014        ],
21015    );
21016
21017    editor.update_in(cx, |editor, window, cx| {
21018        editor.move_to_beginning(&MoveToBeginning, window, cx);
21019        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
21020        editor.move_to_end(&MoveToEnd, window, cx);
21021        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
21022        editor.move_up(&MoveUp, window, cx);
21023        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21024    });
21025
21026    let breakpoints = editor.update(cx, |editor, cx| {
21027        editor
21028            .breakpoint_store()
21029            .as_ref()
21030            .unwrap()
21031            .read(cx)
21032            .all_source_breakpoints(cx)
21033            .clone()
21034    });
21035
21036    assert_eq!(1, breakpoints.len());
21037    assert_breakpoint(
21038        &breakpoints,
21039        &abs_path,
21040        vec![
21041            (0, Breakpoint::new_standard()),
21042            (2, disable_breakpoint),
21043            (3, Breakpoint::new_standard()),
21044        ],
21045    );
21046}
21047
21048#[gpui::test]
21049async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
21050    init_test(cx, |_| {});
21051    let capabilities = lsp::ServerCapabilities {
21052        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
21053            prepare_provider: Some(true),
21054            work_done_progress_options: Default::default(),
21055        })),
21056        ..Default::default()
21057    };
21058    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
21059
21060    cx.set_state(indoc! {"
21061        struct Fˇoo {}
21062    "});
21063
21064    cx.update_editor(|editor, _, cx| {
21065        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
21066        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
21067        editor.highlight_background::<DocumentHighlightRead>(
21068            &[highlight_range],
21069            |theme| theme.colors().editor_document_highlight_read_background,
21070            cx,
21071        );
21072    });
21073
21074    let mut prepare_rename_handler = cx
21075        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
21076            move |_, _, _| async move {
21077                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
21078                    start: lsp::Position {
21079                        line: 0,
21080                        character: 7,
21081                    },
21082                    end: lsp::Position {
21083                        line: 0,
21084                        character: 10,
21085                    },
21086                })))
21087            },
21088        );
21089    let prepare_rename_task = cx
21090        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
21091        .expect("Prepare rename was not started");
21092    prepare_rename_handler.next().await.unwrap();
21093    prepare_rename_task.await.expect("Prepare rename failed");
21094
21095    let mut rename_handler =
21096        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
21097            let edit = lsp::TextEdit {
21098                range: lsp::Range {
21099                    start: lsp::Position {
21100                        line: 0,
21101                        character: 7,
21102                    },
21103                    end: lsp::Position {
21104                        line: 0,
21105                        character: 10,
21106                    },
21107                },
21108                new_text: "FooRenamed".to_string(),
21109            };
21110            Ok(Some(lsp::WorkspaceEdit::new(
21111                // Specify the same edit twice
21112                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
21113            )))
21114        });
21115    let rename_task = cx
21116        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
21117        .expect("Confirm rename was not started");
21118    rename_handler.next().await.unwrap();
21119    rename_task.await.expect("Confirm rename failed");
21120    cx.run_until_parked();
21121
21122    // Despite two edits, only one is actually applied as those are identical
21123    cx.assert_editor_state(indoc! {"
21124        struct FooRenamedˇ {}
21125    "});
21126}
21127
21128#[gpui::test]
21129async fn test_rename_without_prepare(cx: &mut TestAppContext) {
21130    init_test(cx, |_| {});
21131    // These capabilities indicate that the server does not support prepare rename.
21132    let capabilities = lsp::ServerCapabilities {
21133        rename_provider: Some(lsp::OneOf::Left(true)),
21134        ..Default::default()
21135    };
21136    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
21137
21138    cx.set_state(indoc! {"
21139        struct Fˇoo {}
21140    "});
21141
21142    cx.update_editor(|editor, _window, cx| {
21143        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
21144        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
21145        editor.highlight_background::<DocumentHighlightRead>(
21146            &[highlight_range],
21147            |theme| theme.colors().editor_document_highlight_read_background,
21148            cx,
21149        );
21150    });
21151
21152    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
21153        .expect("Prepare rename was not started")
21154        .await
21155        .expect("Prepare rename failed");
21156
21157    let mut rename_handler =
21158        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
21159            let edit = lsp::TextEdit {
21160                range: lsp::Range {
21161                    start: lsp::Position {
21162                        line: 0,
21163                        character: 7,
21164                    },
21165                    end: lsp::Position {
21166                        line: 0,
21167                        character: 10,
21168                    },
21169                },
21170                new_text: "FooRenamed".to_string(),
21171            };
21172            Ok(Some(lsp::WorkspaceEdit::new(
21173                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
21174            )))
21175        });
21176    let rename_task = cx
21177        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
21178        .expect("Confirm rename was not started");
21179    rename_handler.next().await.unwrap();
21180    rename_task.await.expect("Confirm rename failed");
21181    cx.run_until_parked();
21182
21183    // Correct range is renamed, as `surrounding_word` is used to find it.
21184    cx.assert_editor_state(indoc! {"
21185        struct FooRenamedˇ {}
21186    "});
21187}
21188
21189#[gpui::test]
21190async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
21191    init_test(cx, |_| {});
21192    let mut cx = EditorTestContext::new(cx).await;
21193
21194    let language = Arc::new(
21195        Language::new(
21196            LanguageConfig::default(),
21197            Some(tree_sitter_html::LANGUAGE.into()),
21198        )
21199        .with_brackets_query(
21200            r#"
21201            ("<" @open "/>" @close)
21202            ("</" @open ">" @close)
21203            ("<" @open ">" @close)
21204            ("\"" @open "\"" @close)
21205            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
21206        "#,
21207        )
21208        .unwrap(),
21209    );
21210    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
21211
21212    cx.set_state(indoc! {"
21213        <span>ˇ</span>
21214    "});
21215    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21216    cx.assert_editor_state(indoc! {"
21217        <span>
21218        ˇ
21219        </span>
21220    "});
21221
21222    cx.set_state(indoc! {"
21223        <span><span></span>ˇ</span>
21224    "});
21225    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21226    cx.assert_editor_state(indoc! {"
21227        <span><span></span>
21228        ˇ</span>
21229    "});
21230
21231    cx.set_state(indoc! {"
21232        <span>ˇ
21233        </span>
21234    "});
21235    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21236    cx.assert_editor_state(indoc! {"
21237        <span>
21238        ˇ
21239        </span>
21240    "});
21241}
21242
21243#[gpui::test(iterations = 10)]
21244async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
21245    init_test(cx, |_| {});
21246
21247    let fs = FakeFs::new(cx.executor());
21248    fs.insert_tree(
21249        path!("/dir"),
21250        json!({
21251            "a.ts": "a",
21252        }),
21253    )
21254    .await;
21255
21256    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
21257    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21258    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21259
21260    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21261    language_registry.add(Arc::new(Language::new(
21262        LanguageConfig {
21263            name: "TypeScript".into(),
21264            matcher: LanguageMatcher {
21265                path_suffixes: vec!["ts".to_string()],
21266                ..Default::default()
21267            },
21268            ..Default::default()
21269        },
21270        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
21271    )));
21272    let mut fake_language_servers = language_registry.register_fake_lsp(
21273        "TypeScript",
21274        FakeLspAdapter {
21275            capabilities: lsp::ServerCapabilities {
21276                code_lens_provider: Some(lsp::CodeLensOptions {
21277                    resolve_provider: Some(true),
21278                }),
21279                execute_command_provider: Some(lsp::ExecuteCommandOptions {
21280                    commands: vec!["_the/command".to_string()],
21281                    ..lsp::ExecuteCommandOptions::default()
21282                }),
21283                ..lsp::ServerCapabilities::default()
21284            },
21285            ..FakeLspAdapter::default()
21286        },
21287    );
21288
21289    let editor = workspace
21290        .update(cx, |workspace, window, cx| {
21291            workspace.open_abs_path(
21292                PathBuf::from(path!("/dir/a.ts")),
21293                OpenOptions::default(),
21294                window,
21295                cx,
21296            )
21297        })
21298        .unwrap()
21299        .await
21300        .unwrap()
21301        .downcast::<Editor>()
21302        .unwrap();
21303    cx.executor().run_until_parked();
21304
21305    let fake_server = fake_language_servers.next().await.unwrap();
21306
21307    let buffer = editor.update(cx, |editor, cx| {
21308        editor
21309            .buffer()
21310            .read(cx)
21311            .as_singleton()
21312            .expect("have opened a single file by path")
21313    });
21314
21315    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
21316    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
21317    drop(buffer_snapshot);
21318    let actions = cx
21319        .update_window(*workspace, |_, window, cx| {
21320            project.code_actions(&buffer, anchor..anchor, window, cx)
21321        })
21322        .unwrap();
21323
21324    fake_server
21325        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
21326            Ok(Some(vec![
21327                lsp::CodeLens {
21328                    range: lsp::Range::default(),
21329                    command: Some(lsp::Command {
21330                        title: "Code lens command".to_owned(),
21331                        command: "_the/command".to_owned(),
21332                        arguments: None,
21333                    }),
21334                    data: None,
21335                },
21336                lsp::CodeLens {
21337                    range: lsp::Range::default(),
21338                    command: Some(lsp::Command {
21339                        title: "Command not in capabilities".to_owned(),
21340                        command: "not in capabilities".to_owned(),
21341                        arguments: None,
21342                    }),
21343                    data: None,
21344                },
21345                lsp::CodeLens {
21346                    range: lsp::Range {
21347                        start: lsp::Position {
21348                            line: 1,
21349                            character: 1,
21350                        },
21351                        end: lsp::Position {
21352                            line: 1,
21353                            character: 1,
21354                        },
21355                    },
21356                    command: Some(lsp::Command {
21357                        title: "Command not in range".to_owned(),
21358                        command: "_the/command".to_owned(),
21359                        arguments: None,
21360                    }),
21361                    data: None,
21362                },
21363            ]))
21364        })
21365        .next()
21366        .await;
21367
21368    let actions = actions.await.unwrap();
21369    assert_eq!(
21370        actions.len(),
21371        1,
21372        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
21373    );
21374    let action = actions[0].clone();
21375    let apply = project.update(cx, |project, cx| {
21376        project.apply_code_action(buffer.clone(), action, true, cx)
21377    });
21378
21379    // Resolving the code action does not populate its edits. In absence of
21380    // edits, we must execute the given command.
21381    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
21382        |mut lens, _| async move {
21383            let lens_command = lens.command.as_mut().expect("should have a command");
21384            assert_eq!(lens_command.title, "Code lens command");
21385            lens_command.arguments = Some(vec![json!("the-argument")]);
21386            Ok(lens)
21387        },
21388    );
21389
21390    // While executing the command, the language server sends the editor
21391    // a `workspaceEdit` request.
21392    fake_server
21393        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
21394            let fake = fake_server.clone();
21395            move |params, _| {
21396                assert_eq!(params.command, "_the/command");
21397                let fake = fake.clone();
21398                async move {
21399                    fake.server
21400                        .request::<lsp::request::ApplyWorkspaceEdit>(
21401                            lsp::ApplyWorkspaceEditParams {
21402                                label: None,
21403                                edit: lsp::WorkspaceEdit {
21404                                    changes: Some(
21405                                        [(
21406                                            lsp::Url::from_file_path(path!("/dir/a.ts")).unwrap(),
21407                                            vec![lsp::TextEdit {
21408                                                range: lsp::Range::new(
21409                                                    lsp::Position::new(0, 0),
21410                                                    lsp::Position::new(0, 0),
21411                                                ),
21412                                                new_text: "X".into(),
21413                                            }],
21414                                        )]
21415                                        .into_iter()
21416                                        .collect(),
21417                                    ),
21418                                    ..lsp::WorkspaceEdit::default()
21419                                },
21420                            },
21421                        )
21422                        .await
21423                        .into_response()
21424                        .unwrap();
21425                    Ok(Some(json!(null)))
21426                }
21427            }
21428        })
21429        .next()
21430        .await;
21431
21432    // Applying the code lens command returns a project transaction containing the edits
21433    // sent by the language server in its `workspaceEdit` request.
21434    let transaction = apply.await.unwrap();
21435    assert!(transaction.0.contains_key(&buffer));
21436    buffer.update(cx, |buffer, cx| {
21437        assert_eq!(buffer.text(), "Xa");
21438        buffer.undo(cx);
21439        assert_eq!(buffer.text(), "a");
21440    });
21441
21442    let actions_after_edits = cx
21443        .update_window(*workspace, |_, window, cx| {
21444            project.code_actions(&buffer, anchor..anchor, window, cx)
21445        })
21446        .unwrap()
21447        .await
21448        .unwrap();
21449    assert_eq!(
21450        actions, actions_after_edits,
21451        "For the same selection, same code lens actions should be returned"
21452    );
21453
21454    let _responses =
21455        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
21456            panic!("No more code lens requests are expected");
21457        });
21458    editor.update_in(cx, |editor, window, cx| {
21459        editor.select_all(&SelectAll, window, cx);
21460    });
21461    cx.executor().run_until_parked();
21462    let new_actions = cx
21463        .update_window(*workspace, |_, window, cx| {
21464            project.code_actions(&buffer, anchor..anchor, window, cx)
21465        })
21466        .unwrap()
21467        .await
21468        .unwrap();
21469    assert_eq!(
21470        actions, new_actions,
21471        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
21472    );
21473}
21474
21475#[gpui::test]
21476async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
21477    init_test(cx, |_| {});
21478
21479    let fs = FakeFs::new(cx.executor());
21480    let main_text = r#"fn main() {
21481println!("1");
21482println!("2");
21483println!("3");
21484println!("4");
21485println!("5");
21486}"#;
21487    let lib_text = "mod foo {}";
21488    fs.insert_tree(
21489        path!("/a"),
21490        json!({
21491            "lib.rs": lib_text,
21492            "main.rs": main_text,
21493        }),
21494    )
21495    .await;
21496
21497    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21498    let (workspace, cx) =
21499        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21500    let worktree_id = workspace.update(cx, |workspace, cx| {
21501        workspace.project().update(cx, |project, cx| {
21502            project.worktrees(cx).next().unwrap().read(cx).id()
21503        })
21504    });
21505
21506    let expected_ranges = vec![
21507        Point::new(0, 0)..Point::new(0, 0),
21508        Point::new(1, 0)..Point::new(1, 1),
21509        Point::new(2, 0)..Point::new(2, 2),
21510        Point::new(3, 0)..Point::new(3, 3),
21511    ];
21512
21513    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21514    let editor_1 = workspace
21515        .update_in(cx, |workspace, window, cx| {
21516            workspace.open_path(
21517                (worktree_id, "main.rs"),
21518                Some(pane_1.downgrade()),
21519                true,
21520                window,
21521                cx,
21522            )
21523        })
21524        .unwrap()
21525        .await
21526        .downcast::<Editor>()
21527        .unwrap();
21528    pane_1.update(cx, |pane, cx| {
21529        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21530        open_editor.update(cx, |editor, cx| {
21531            assert_eq!(
21532                editor.display_text(cx),
21533                main_text,
21534                "Original main.rs text on initial open",
21535            );
21536            assert_eq!(
21537                editor
21538                    .selections
21539                    .all::<Point>(cx)
21540                    .into_iter()
21541                    .map(|s| s.range())
21542                    .collect::<Vec<_>>(),
21543                vec![Point::zero()..Point::zero()],
21544                "Default selections on initial open",
21545            );
21546        })
21547    });
21548    editor_1.update_in(cx, |editor, window, cx| {
21549        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21550            s.select_ranges(expected_ranges.clone());
21551        });
21552    });
21553
21554    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
21555        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
21556    });
21557    let editor_2 = workspace
21558        .update_in(cx, |workspace, window, cx| {
21559            workspace.open_path(
21560                (worktree_id, "main.rs"),
21561                Some(pane_2.downgrade()),
21562                true,
21563                window,
21564                cx,
21565            )
21566        })
21567        .unwrap()
21568        .await
21569        .downcast::<Editor>()
21570        .unwrap();
21571    pane_2.update(cx, |pane, cx| {
21572        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21573        open_editor.update(cx, |editor, cx| {
21574            assert_eq!(
21575                editor.display_text(cx),
21576                main_text,
21577                "Original main.rs text on initial open in another panel",
21578            );
21579            assert_eq!(
21580                editor
21581                    .selections
21582                    .all::<Point>(cx)
21583                    .into_iter()
21584                    .map(|s| s.range())
21585                    .collect::<Vec<_>>(),
21586                vec![Point::zero()..Point::zero()],
21587                "Default selections on initial open in another panel",
21588            );
21589        })
21590    });
21591
21592    editor_2.update_in(cx, |editor, window, cx| {
21593        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
21594    });
21595
21596    let _other_editor_1 = workspace
21597        .update_in(cx, |workspace, window, cx| {
21598            workspace.open_path(
21599                (worktree_id, "lib.rs"),
21600                Some(pane_1.downgrade()),
21601                true,
21602                window,
21603                cx,
21604            )
21605        })
21606        .unwrap()
21607        .await
21608        .downcast::<Editor>()
21609        .unwrap();
21610    pane_1
21611        .update_in(cx, |pane, window, cx| {
21612            pane.close_inactive_items(&CloseInactiveItems::default(), None, window, cx)
21613        })
21614        .await
21615        .unwrap();
21616    drop(editor_1);
21617    pane_1.update(cx, |pane, cx| {
21618        pane.active_item()
21619            .unwrap()
21620            .downcast::<Editor>()
21621            .unwrap()
21622            .update(cx, |editor, cx| {
21623                assert_eq!(
21624                    editor.display_text(cx),
21625                    lib_text,
21626                    "Other file should be open and active",
21627                );
21628            });
21629        assert_eq!(pane.items().count(), 1, "No other editors should be open");
21630    });
21631
21632    let _other_editor_2 = workspace
21633        .update_in(cx, |workspace, window, cx| {
21634            workspace.open_path(
21635                (worktree_id, "lib.rs"),
21636                Some(pane_2.downgrade()),
21637                true,
21638                window,
21639                cx,
21640            )
21641        })
21642        .unwrap()
21643        .await
21644        .downcast::<Editor>()
21645        .unwrap();
21646    pane_2
21647        .update_in(cx, |pane, window, cx| {
21648            pane.close_inactive_items(&CloseInactiveItems::default(), None, window, cx)
21649        })
21650        .await
21651        .unwrap();
21652    drop(editor_2);
21653    pane_2.update(cx, |pane, cx| {
21654        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21655        open_editor.update(cx, |editor, cx| {
21656            assert_eq!(
21657                editor.display_text(cx),
21658                lib_text,
21659                "Other file should be open and active in another panel too",
21660            );
21661        });
21662        assert_eq!(
21663            pane.items().count(),
21664            1,
21665            "No other editors should be open in another pane",
21666        );
21667    });
21668
21669    let _editor_1_reopened = workspace
21670        .update_in(cx, |workspace, window, cx| {
21671            workspace.open_path(
21672                (worktree_id, "main.rs"),
21673                Some(pane_1.downgrade()),
21674                true,
21675                window,
21676                cx,
21677            )
21678        })
21679        .unwrap()
21680        .await
21681        .downcast::<Editor>()
21682        .unwrap();
21683    let _editor_2_reopened = workspace
21684        .update_in(cx, |workspace, window, cx| {
21685            workspace.open_path(
21686                (worktree_id, "main.rs"),
21687                Some(pane_2.downgrade()),
21688                true,
21689                window,
21690                cx,
21691            )
21692        })
21693        .unwrap()
21694        .await
21695        .downcast::<Editor>()
21696        .unwrap();
21697    pane_1.update(cx, |pane, cx| {
21698        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21699        open_editor.update(cx, |editor, cx| {
21700            assert_eq!(
21701                editor.display_text(cx),
21702                main_text,
21703                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
21704            );
21705            assert_eq!(
21706                editor
21707                    .selections
21708                    .all::<Point>(cx)
21709                    .into_iter()
21710                    .map(|s| s.range())
21711                    .collect::<Vec<_>>(),
21712                expected_ranges,
21713                "Previous editor in the 1st panel had selections and should get them restored on reopen",
21714            );
21715        })
21716    });
21717    pane_2.update(cx, |pane, cx| {
21718        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21719        open_editor.update(cx, |editor, cx| {
21720            assert_eq!(
21721                editor.display_text(cx),
21722                r#"fn main() {
21723⋯rintln!("1");
21724⋯intln!("2");
21725⋯ntln!("3");
21726println!("4");
21727println!("5");
21728}"#,
21729                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
21730            );
21731            assert_eq!(
21732                editor
21733                    .selections
21734                    .all::<Point>(cx)
21735                    .into_iter()
21736                    .map(|s| s.range())
21737                    .collect::<Vec<_>>(),
21738                vec![Point::zero()..Point::zero()],
21739                "Previous editor in the 2nd pane had no selections changed hence should restore none",
21740            );
21741        })
21742    });
21743}
21744
21745#[gpui::test]
21746async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
21747    init_test(cx, |_| {});
21748
21749    let fs = FakeFs::new(cx.executor());
21750    let main_text = r#"fn main() {
21751println!("1");
21752println!("2");
21753println!("3");
21754println!("4");
21755println!("5");
21756}"#;
21757    let lib_text = "mod foo {}";
21758    fs.insert_tree(
21759        path!("/a"),
21760        json!({
21761            "lib.rs": lib_text,
21762            "main.rs": main_text,
21763        }),
21764    )
21765    .await;
21766
21767    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21768    let (workspace, cx) =
21769        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21770    let worktree_id = workspace.update(cx, |workspace, cx| {
21771        workspace.project().update(cx, |project, cx| {
21772            project.worktrees(cx).next().unwrap().read(cx).id()
21773        })
21774    });
21775
21776    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21777    let editor = workspace
21778        .update_in(cx, |workspace, window, cx| {
21779            workspace.open_path(
21780                (worktree_id, "main.rs"),
21781                Some(pane.downgrade()),
21782                true,
21783                window,
21784                cx,
21785            )
21786        })
21787        .unwrap()
21788        .await
21789        .downcast::<Editor>()
21790        .unwrap();
21791    pane.update(cx, |pane, cx| {
21792        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21793        open_editor.update(cx, |editor, cx| {
21794            assert_eq!(
21795                editor.display_text(cx),
21796                main_text,
21797                "Original main.rs text on initial open",
21798            );
21799        })
21800    });
21801    editor.update_in(cx, |editor, window, cx| {
21802        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
21803    });
21804
21805    cx.update_global(|store: &mut SettingsStore, cx| {
21806        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
21807            s.restore_on_file_reopen = Some(false);
21808        });
21809    });
21810    editor.update_in(cx, |editor, window, cx| {
21811        editor.fold_ranges(
21812            vec![
21813                Point::new(1, 0)..Point::new(1, 1),
21814                Point::new(2, 0)..Point::new(2, 2),
21815                Point::new(3, 0)..Point::new(3, 3),
21816            ],
21817            false,
21818            window,
21819            cx,
21820        );
21821    });
21822    pane.update_in(cx, |pane, window, cx| {
21823        pane.close_all_items(&CloseAllItems::default(), window, cx)
21824    })
21825    .await
21826    .unwrap();
21827    pane.update(cx, |pane, _| {
21828        assert!(pane.active_item().is_none());
21829    });
21830    cx.update_global(|store: &mut SettingsStore, cx| {
21831        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
21832            s.restore_on_file_reopen = Some(true);
21833        });
21834    });
21835
21836    let _editor_reopened = workspace
21837        .update_in(cx, |workspace, window, cx| {
21838            workspace.open_path(
21839                (worktree_id, "main.rs"),
21840                Some(pane.downgrade()),
21841                true,
21842                window,
21843                cx,
21844            )
21845        })
21846        .unwrap()
21847        .await
21848        .downcast::<Editor>()
21849        .unwrap();
21850    pane.update(cx, |pane, cx| {
21851        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21852        open_editor.update(cx, |editor, cx| {
21853            assert_eq!(
21854                editor.display_text(cx),
21855                main_text,
21856                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
21857            );
21858        })
21859    });
21860}
21861
21862#[gpui::test]
21863async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
21864    struct EmptyModalView {
21865        focus_handle: gpui::FocusHandle,
21866    }
21867    impl EventEmitter<DismissEvent> for EmptyModalView {}
21868    impl Render for EmptyModalView {
21869        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
21870            div()
21871        }
21872    }
21873    impl Focusable for EmptyModalView {
21874        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
21875            self.focus_handle.clone()
21876        }
21877    }
21878    impl workspace::ModalView for EmptyModalView {}
21879    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
21880        EmptyModalView {
21881            focus_handle: cx.focus_handle(),
21882        }
21883    }
21884
21885    init_test(cx, |_| {});
21886
21887    let fs = FakeFs::new(cx.executor());
21888    let project = Project::test(fs, [], cx).await;
21889    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21890    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
21891    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21892    let editor = cx.new_window_entity(|window, cx| {
21893        Editor::new(
21894            EditorMode::full(),
21895            buffer,
21896            Some(project.clone()),
21897            window,
21898            cx,
21899        )
21900    });
21901    workspace
21902        .update(cx, |workspace, window, cx| {
21903            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
21904        })
21905        .unwrap();
21906    editor.update_in(cx, |editor, window, cx| {
21907        editor.open_context_menu(&OpenContextMenu, window, cx);
21908        assert!(editor.mouse_context_menu.is_some());
21909    });
21910    workspace
21911        .update(cx, |workspace, window, cx| {
21912            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
21913        })
21914        .unwrap();
21915    cx.read(|cx| {
21916        assert!(editor.read(cx).mouse_context_menu.is_none());
21917    });
21918}
21919
21920#[gpui::test]
21921async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
21922    init_test(cx, |_| {});
21923
21924    let fs = FakeFs::new(cx.executor());
21925    fs.insert_file(path!("/file.html"), Default::default())
21926        .await;
21927
21928    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
21929
21930    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21931    let html_language = Arc::new(Language::new(
21932        LanguageConfig {
21933            name: "HTML".into(),
21934            matcher: LanguageMatcher {
21935                path_suffixes: vec!["html".to_string()],
21936                ..LanguageMatcher::default()
21937            },
21938            brackets: BracketPairConfig {
21939                pairs: vec![BracketPair {
21940                    start: "<".into(),
21941                    end: ">".into(),
21942                    close: true,
21943                    ..Default::default()
21944                }],
21945                ..Default::default()
21946            },
21947            ..Default::default()
21948        },
21949        Some(tree_sitter_html::LANGUAGE.into()),
21950    ));
21951    language_registry.add(html_language);
21952    let mut fake_servers = language_registry.register_fake_lsp(
21953        "HTML",
21954        FakeLspAdapter {
21955            capabilities: lsp::ServerCapabilities {
21956                completion_provider: Some(lsp::CompletionOptions {
21957                    resolve_provider: Some(true),
21958                    ..Default::default()
21959                }),
21960                ..Default::default()
21961            },
21962            ..Default::default()
21963        },
21964    );
21965
21966    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21967    let cx = &mut VisualTestContext::from_window(*workspace, cx);
21968
21969    let worktree_id = workspace
21970        .update(cx, |workspace, _window, cx| {
21971            workspace.project().update(cx, |project, cx| {
21972                project.worktrees(cx).next().unwrap().read(cx).id()
21973            })
21974        })
21975        .unwrap();
21976    project
21977        .update(cx, |project, cx| {
21978            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
21979        })
21980        .await
21981        .unwrap();
21982    let editor = workspace
21983        .update(cx, |workspace, window, cx| {
21984            workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
21985        })
21986        .unwrap()
21987        .await
21988        .unwrap()
21989        .downcast::<Editor>()
21990        .unwrap();
21991
21992    let fake_server = fake_servers.next().await.unwrap();
21993    editor.update_in(cx, |editor, window, cx| {
21994        editor.set_text("<ad></ad>", window, cx);
21995        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21996            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
21997        });
21998        let Some((buffer, _)) = editor
21999            .buffer
22000            .read(cx)
22001            .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
22002        else {
22003            panic!("Failed to get buffer for selection position");
22004        };
22005        let buffer = buffer.read(cx);
22006        let buffer_id = buffer.remote_id();
22007        let opening_range =
22008            buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3));
22009        let closing_range =
22010            buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8));
22011        let mut linked_ranges = HashMap::default();
22012        linked_ranges.insert(
22013            buffer_id,
22014            vec![(opening_range.clone(), vec![closing_range.clone()])],
22015        );
22016        editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
22017    });
22018    let mut completion_handle =
22019        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
22020            Ok(Some(lsp::CompletionResponse::Array(vec![
22021                lsp::CompletionItem {
22022                    label: "head".to_string(),
22023                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22024                        lsp::InsertReplaceEdit {
22025                            new_text: "head".to_string(),
22026                            insert: lsp::Range::new(
22027                                lsp::Position::new(0, 1),
22028                                lsp::Position::new(0, 3),
22029                            ),
22030                            replace: lsp::Range::new(
22031                                lsp::Position::new(0, 1),
22032                                lsp::Position::new(0, 3),
22033                            ),
22034                        },
22035                    )),
22036                    ..Default::default()
22037                },
22038            ])))
22039        });
22040    editor.update_in(cx, |editor, window, cx| {
22041        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
22042    });
22043    cx.run_until_parked();
22044    completion_handle.next().await.unwrap();
22045    editor.update(cx, |editor, _| {
22046        assert!(
22047            editor.context_menu_visible(),
22048            "Completion menu should be visible"
22049        );
22050    });
22051    editor.update_in(cx, |editor, window, cx| {
22052        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
22053    });
22054    cx.executor().run_until_parked();
22055    editor.update(cx, |editor, cx| {
22056        assert_eq!(editor.text(cx), "<head></head>");
22057    });
22058}
22059
22060#[gpui::test]
22061async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
22062    init_test(cx, |_| {});
22063
22064    let fs = FakeFs::new(cx.executor());
22065    fs.insert_tree(
22066        path!("/root"),
22067        json!({
22068            "a": {
22069                "main.rs": "fn main() {}",
22070            },
22071            "foo": {
22072                "bar": {
22073                    "external_file.rs": "pub mod external {}",
22074                }
22075            }
22076        }),
22077    )
22078    .await;
22079
22080    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
22081    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22082    language_registry.add(rust_lang());
22083    let _fake_servers = language_registry.register_fake_lsp(
22084        "Rust",
22085        FakeLspAdapter {
22086            ..FakeLspAdapter::default()
22087        },
22088    );
22089    let (workspace, cx) =
22090        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22091    let worktree_id = workspace.update(cx, |workspace, cx| {
22092        workspace.project().update(cx, |project, cx| {
22093            project.worktrees(cx).next().unwrap().read(cx).id()
22094        })
22095    });
22096
22097    let assert_language_servers_count =
22098        |expected: usize, context: &str, cx: &mut VisualTestContext| {
22099            project.update(cx, |project, cx| {
22100                let current = project
22101                    .lsp_store()
22102                    .read(cx)
22103                    .as_local()
22104                    .unwrap()
22105                    .language_servers
22106                    .len();
22107                assert_eq!(expected, current, "{context}");
22108            });
22109        };
22110
22111    assert_language_servers_count(
22112        0,
22113        "No servers should be running before any file is open",
22114        cx,
22115    );
22116    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
22117    let main_editor = workspace
22118        .update_in(cx, |workspace, window, cx| {
22119            workspace.open_path(
22120                (worktree_id, "main.rs"),
22121                Some(pane.downgrade()),
22122                true,
22123                window,
22124                cx,
22125            )
22126        })
22127        .unwrap()
22128        .await
22129        .downcast::<Editor>()
22130        .unwrap();
22131    pane.update(cx, |pane, cx| {
22132        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22133        open_editor.update(cx, |editor, cx| {
22134            assert_eq!(
22135                editor.display_text(cx),
22136                "fn main() {}",
22137                "Original main.rs text on initial open",
22138            );
22139        });
22140        assert_eq!(open_editor, main_editor);
22141    });
22142    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
22143
22144    let external_editor = workspace
22145        .update_in(cx, |workspace, window, cx| {
22146            workspace.open_abs_path(
22147                PathBuf::from("/root/foo/bar/external_file.rs"),
22148                OpenOptions::default(),
22149                window,
22150                cx,
22151            )
22152        })
22153        .await
22154        .expect("opening external file")
22155        .downcast::<Editor>()
22156        .expect("downcasted external file's open element to editor");
22157    pane.update(cx, |pane, cx| {
22158        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22159        open_editor.update(cx, |editor, cx| {
22160            assert_eq!(
22161                editor.display_text(cx),
22162                "pub mod external {}",
22163                "External file is open now",
22164            );
22165        });
22166        assert_eq!(open_editor, external_editor);
22167    });
22168    assert_language_servers_count(
22169        1,
22170        "Second, external, *.rs file should join the existing server",
22171        cx,
22172    );
22173
22174    pane.update_in(cx, |pane, window, cx| {
22175        pane.close_active_item(&CloseActiveItem::default(), window, cx)
22176    })
22177    .await
22178    .unwrap();
22179    pane.update_in(cx, |pane, window, cx| {
22180        pane.navigate_backward(window, cx);
22181    });
22182    cx.run_until_parked();
22183    pane.update(cx, |pane, cx| {
22184        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22185        open_editor.update(cx, |editor, cx| {
22186            assert_eq!(
22187                editor.display_text(cx),
22188                "pub mod external {}",
22189                "External file is open now",
22190            );
22191        });
22192    });
22193    assert_language_servers_count(
22194        1,
22195        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
22196        cx,
22197    );
22198
22199    cx.update(|_, cx| {
22200        workspace::reload(&workspace::Reload::default(), cx);
22201    });
22202    assert_language_servers_count(
22203        1,
22204        "After reloading the worktree with local and external files opened, only one project should be started",
22205        cx,
22206    );
22207}
22208
22209#[gpui::test]
22210async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
22211    init_test(cx, |_| {});
22212
22213    let mut cx = EditorTestContext::new(cx).await;
22214    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22215    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22216
22217    // test cursor move to start of each line on tab
22218    // for `if`, `elif`, `else`, `while`, `with` and `for`
22219    cx.set_state(indoc! {"
22220        def main():
22221        ˇ    for item in items:
22222        ˇ        while item.active:
22223        ˇ            if item.value > 10:
22224        ˇ                continue
22225        ˇ            elif item.value < 0:
22226        ˇ                break
22227        ˇ            else:
22228        ˇ                with item.context() as ctx:
22229        ˇ                    yield count
22230        ˇ        else:
22231        ˇ            log('while else')
22232        ˇ    else:
22233        ˇ        log('for else')
22234    "});
22235    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22236    cx.assert_editor_state(indoc! {"
22237        def main():
22238            ˇfor item in items:
22239                ˇwhile item.active:
22240                    ˇif item.value > 10:
22241                        ˇcontinue
22242                    ˇelif item.value < 0:
22243                        ˇbreak
22244                    ˇelse:
22245                        ˇwith item.context() as ctx:
22246                            ˇyield count
22247                ˇelse:
22248                    ˇlog('while else')
22249            ˇelse:
22250                ˇlog('for else')
22251    "});
22252    // test relative indent is preserved when tab
22253    // for `if`, `elif`, `else`, `while`, `with` and `for`
22254    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22255    cx.assert_editor_state(indoc! {"
22256        def main():
22257                ˇfor item in items:
22258                    ˇwhile item.active:
22259                        ˇif item.value > 10:
22260                            ˇcontinue
22261                        ˇelif item.value < 0:
22262                            ˇbreak
22263                        ˇelse:
22264                            ˇwith item.context() as ctx:
22265                                ˇyield count
22266                    ˇelse:
22267                        ˇlog('while else')
22268                ˇelse:
22269                    ˇlog('for else')
22270    "});
22271
22272    // test cursor move to start of each line on tab
22273    // for `try`, `except`, `else`, `finally`, `match` and `def`
22274    cx.set_state(indoc! {"
22275        def main():
22276        ˇ    try:
22277        ˇ        fetch()
22278        ˇ    except ValueError:
22279        ˇ        handle_error()
22280        ˇ    else:
22281        ˇ        match value:
22282        ˇ            case _:
22283        ˇ    finally:
22284        ˇ        def status():
22285        ˇ            return 0
22286    "});
22287    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22288    cx.assert_editor_state(indoc! {"
22289        def main():
22290            ˇtry:
22291                ˇfetch()
22292            ˇexcept ValueError:
22293                ˇhandle_error()
22294            ˇelse:
22295                ˇmatch value:
22296                    ˇcase _:
22297            ˇfinally:
22298                ˇdef status():
22299                    ˇreturn 0
22300    "});
22301    // test relative indent is preserved when tab
22302    // for `try`, `except`, `else`, `finally`, `match` and `def`
22303    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22304    cx.assert_editor_state(indoc! {"
22305        def main():
22306                ˇtry:
22307                    ˇfetch()
22308                ˇexcept ValueError:
22309                    ˇhandle_error()
22310                ˇelse:
22311                    ˇmatch value:
22312                        ˇcase _:
22313                ˇfinally:
22314                    ˇdef status():
22315                        ˇreturn 0
22316    "});
22317}
22318
22319#[gpui::test]
22320async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
22321    init_test(cx, |_| {});
22322
22323    let mut cx = EditorTestContext::new(cx).await;
22324    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22325    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22326
22327    // test `else` auto outdents when typed inside `if` block
22328    cx.set_state(indoc! {"
22329        def main():
22330            if i == 2:
22331                return
22332                ˇ
22333    "});
22334    cx.update_editor(|editor, window, cx| {
22335        editor.handle_input("else:", window, cx);
22336    });
22337    cx.assert_editor_state(indoc! {"
22338        def main():
22339            if i == 2:
22340                return
22341            else:ˇ
22342    "});
22343
22344    // test `except` auto outdents when typed inside `try` block
22345    cx.set_state(indoc! {"
22346        def main():
22347            try:
22348                i = 2
22349                ˇ
22350    "});
22351    cx.update_editor(|editor, window, cx| {
22352        editor.handle_input("except:", window, cx);
22353    });
22354    cx.assert_editor_state(indoc! {"
22355        def main():
22356            try:
22357                i = 2
22358            except:ˇ
22359    "});
22360
22361    // test `else` auto outdents when typed inside `except` block
22362    cx.set_state(indoc! {"
22363        def main():
22364            try:
22365                i = 2
22366            except:
22367                j = 2
22368                ˇ
22369    "});
22370    cx.update_editor(|editor, window, cx| {
22371        editor.handle_input("else:", window, cx);
22372    });
22373    cx.assert_editor_state(indoc! {"
22374        def main():
22375            try:
22376                i = 2
22377            except:
22378                j = 2
22379            else:ˇ
22380    "});
22381
22382    // test `finally` auto outdents when typed inside `else` block
22383    cx.set_state(indoc! {"
22384        def main():
22385            try:
22386                i = 2
22387            except:
22388                j = 2
22389            else:
22390                k = 2
22391                ˇ
22392    "});
22393    cx.update_editor(|editor, window, cx| {
22394        editor.handle_input("finally:", window, cx);
22395    });
22396    cx.assert_editor_state(indoc! {"
22397        def main():
22398            try:
22399                i = 2
22400            except:
22401                j = 2
22402            else:
22403                k = 2
22404            finally:ˇ
22405    "});
22406
22407    // test `else` does not outdents when typed inside `except` block right after for block
22408    cx.set_state(indoc! {"
22409        def main():
22410            try:
22411                i = 2
22412            except:
22413                for i in range(n):
22414                    pass
22415                ˇ
22416    "});
22417    cx.update_editor(|editor, window, cx| {
22418        editor.handle_input("else:", window, cx);
22419    });
22420    cx.assert_editor_state(indoc! {"
22421        def main():
22422            try:
22423                i = 2
22424            except:
22425                for i in range(n):
22426                    pass
22427                else:ˇ
22428    "});
22429
22430    // test `finally` auto outdents when typed inside `else` block right after for block
22431    cx.set_state(indoc! {"
22432        def main():
22433            try:
22434                i = 2
22435            except:
22436                j = 2
22437            else:
22438                for i in range(n):
22439                    pass
22440                ˇ
22441    "});
22442    cx.update_editor(|editor, window, cx| {
22443        editor.handle_input("finally:", window, cx);
22444    });
22445    cx.assert_editor_state(indoc! {"
22446        def main():
22447            try:
22448                i = 2
22449            except:
22450                j = 2
22451            else:
22452                for i in range(n):
22453                    pass
22454            finally:ˇ
22455    "});
22456
22457    // test `except` outdents to inner "try" block
22458    cx.set_state(indoc! {"
22459        def main():
22460            try:
22461                i = 2
22462                if i == 2:
22463                    try:
22464                        i = 3
22465                        ˇ
22466    "});
22467    cx.update_editor(|editor, window, cx| {
22468        editor.handle_input("except:", window, cx);
22469    });
22470    cx.assert_editor_state(indoc! {"
22471        def main():
22472            try:
22473                i = 2
22474                if i == 2:
22475                    try:
22476                        i = 3
22477                    except:ˇ
22478    "});
22479
22480    // test `except` outdents to outer "try" block
22481    cx.set_state(indoc! {"
22482        def main():
22483            try:
22484                i = 2
22485                if i == 2:
22486                    try:
22487                        i = 3
22488                ˇ
22489    "});
22490    cx.update_editor(|editor, window, cx| {
22491        editor.handle_input("except:", window, cx);
22492    });
22493    cx.assert_editor_state(indoc! {"
22494        def main():
22495            try:
22496                i = 2
22497                if i == 2:
22498                    try:
22499                        i = 3
22500            except:ˇ
22501    "});
22502
22503    // test `else` stays at correct indent when typed after `for` block
22504    cx.set_state(indoc! {"
22505        def main():
22506            for i in range(10):
22507                if i == 3:
22508                    break
22509            ˇ
22510    "});
22511    cx.update_editor(|editor, window, cx| {
22512        editor.handle_input("else:", window, cx);
22513    });
22514    cx.assert_editor_state(indoc! {"
22515        def main():
22516            for i in range(10):
22517                if i == 3:
22518                    break
22519            else:ˇ
22520    "});
22521
22522    // test does not outdent on typing after line with square brackets
22523    cx.set_state(indoc! {"
22524        def f() -> list[str]:
22525            ˇ
22526    "});
22527    cx.update_editor(|editor, window, cx| {
22528        editor.handle_input("a", window, cx);
22529    });
22530    cx.assert_editor_state(indoc! {"
22531        def f() -> list[str]:
2253222533    "});
22534
22535    // test does not outdent on typing : after case keyword
22536    cx.set_state(indoc! {"
22537        match 1:
22538            caseˇ
22539    "});
22540    cx.update_editor(|editor, window, cx| {
22541        editor.handle_input(":", window, cx);
22542    });
22543    cx.assert_editor_state(indoc! {"
22544        match 1:
22545            case:ˇ
22546    "});
22547}
22548
22549#[gpui::test]
22550async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
22551    init_test(cx, |_| {});
22552    update_test_language_settings(cx, |settings| {
22553        settings.defaults.extend_comment_on_newline = Some(false);
22554    });
22555    let mut cx = EditorTestContext::new(cx).await;
22556    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22557    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22558
22559    // test correct indent after newline on comment
22560    cx.set_state(indoc! {"
22561        # COMMENT:ˇ
22562    "});
22563    cx.update_editor(|editor, window, cx| {
22564        editor.newline(&Newline, window, cx);
22565    });
22566    cx.assert_editor_state(indoc! {"
22567        # COMMENT:
22568        ˇ
22569    "});
22570
22571    // test correct indent after newline in brackets
22572    cx.set_state(indoc! {"
22573        {ˇ}
22574    "});
22575    cx.update_editor(|editor, window, cx| {
22576        editor.newline(&Newline, window, cx);
22577    });
22578    cx.run_until_parked();
22579    cx.assert_editor_state(indoc! {"
22580        {
22581            ˇ
22582        }
22583    "});
22584
22585    cx.set_state(indoc! {"
22586        (ˇ)
22587    "});
22588    cx.update_editor(|editor, window, cx| {
22589        editor.newline(&Newline, window, cx);
22590    });
22591    cx.run_until_parked();
22592    cx.assert_editor_state(indoc! {"
22593        (
22594            ˇ
22595        )
22596    "});
22597
22598    // do not indent after empty lists or dictionaries
22599    cx.set_state(indoc! {"
22600        a = []ˇ
22601    "});
22602    cx.update_editor(|editor, window, cx| {
22603        editor.newline(&Newline, window, cx);
22604    });
22605    cx.run_until_parked();
22606    cx.assert_editor_state(indoc! {"
22607        a = []
22608        ˇ
22609    "});
22610}
22611
22612fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
22613    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
22614    point..point
22615}
22616
22617#[track_caller]
22618fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
22619    let (text, ranges) = marked_text_ranges(marked_text, true);
22620    assert_eq!(editor.text(cx), text);
22621    assert_eq!(
22622        editor.selections.ranges(cx),
22623        ranges,
22624        "Assert selections are {}",
22625        marked_text
22626    );
22627}
22628
22629pub fn handle_signature_help_request(
22630    cx: &mut EditorLspTestContext,
22631    mocked_response: lsp::SignatureHelp,
22632) -> impl Future<Output = ()> + use<> {
22633    let mut request =
22634        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
22635            let mocked_response = mocked_response.clone();
22636            async move { Ok(Some(mocked_response)) }
22637        });
22638
22639    async move {
22640        request.next().await;
22641    }
22642}
22643
22644#[track_caller]
22645pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
22646    cx.update_editor(|editor, _, _| {
22647        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
22648            let entries = menu.entries.borrow();
22649            let entries = entries
22650                .iter()
22651                .map(|entry| entry.string.as_str())
22652                .collect::<Vec<_>>();
22653            assert_eq!(entries, expected);
22654        } else {
22655            panic!("Expected completions menu");
22656        }
22657    });
22658}
22659
22660/// Handle completion request passing a marked string specifying where the completion
22661/// should be triggered from using '|' character, what range should be replaced, and what completions
22662/// should be returned using '<' and '>' to delimit the range.
22663///
22664/// Also see `handle_completion_request_with_insert_and_replace`.
22665#[track_caller]
22666pub fn handle_completion_request(
22667    marked_string: &str,
22668    completions: Vec<&'static str>,
22669    is_incomplete: bool,
22670    counter: Arc<AtomicUsize>,
22671    cx: &mut EditorLspTestContext,
22672) -> impl Future<Output = ()> {
22673    let complete_from_marker: TextRangeMarker = '|'.into();
22674    let replace_range_marker: TextRangeMarker = ('<', '>').into();
22675    let (_, mut marked_ranges) = marked_text_ranges_by(
22676        marked_string,
22677        vec![complete_from_marker.clone(), replace_range_marker.clone()],
22678    );
22679
22680    let complete_from_position =
22681        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
22682    let replace_range =
22683        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
22684
22685    let mut request =
22686        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
22687            let completions = completions.clone();
22688            counter.fetch_add(1, atomic::Ordering::Release);
22689            async move {
22690                assert_eq!(params.text_document_position.text_document.uri, url.clone());
22691                assert_eq!(
22692                    params.text_document_position.position,
22693                    complete_from_position
22694                );
22695                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
22696                    is_incomplete: is_incomplete,
22697                    item_defaults: None,
22698                    items: completions
22699                        .iter()
22700                        .map(|completion_text| lsp::CompletionItem {
22701                            label: completion_text.to_string(),
22702                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
22703                                range: replace_range,
22704                                new_text: completion_text.to_string(),
22705                            })),
22706                            ..Default::default()
22707                        })
22708                        .collect(),
22709                })))
22710            }
22711        });
22712
22713    async move {
22714        request.next().await;
22715    }
22716}
22717
22718/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
22719/// given instead, which also contains an `insert` range.
22720///
22721/// This function uses markers to define ranges:
22722/// - `|` marks the cursor position
22723/// - `<>` marks the replace range
22724/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
22725pub fn handle_completion_request_with_insert_and_replace(
22726    cx: &mut EditorLspTestContext,
22727    marked_string: &str,
22728    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
22729    counter: Arc<AtomicUsize>,
22730) -> impl Future<Output = ()> {
22731    let complete_from_marker: TextRangeMarker = '|'.into();
22732    let replace_range_marker: TextRangeMarker = ('<', '>').into();
22733    let insert_range_marker: TextRangeMarker = ('{', '}').into();
22734
22735    let (_, mut marked_ranges) = marked_text_ranges_by(
22736        marked_string,
22737        vec![
22738            complete_from_marker.clone(),
22739            replace_range_marker.clone(),
22740            insert_range_marker.clone(),
22741        ],
22742    );
22743
22744    let complete_from_position =
22745        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
22746    let replace_range =
22747        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
22748
22749    let insert_range = match marked_ranges.remove(&insert_range_marker) {
22750        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
22751        _ => lsp::Range {
22752            start: replace_range.start,
22753            end: complete_from_position,
22754        },
22755    };
22756
22757    let mut request =
22758        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
22759            let completions = completions.clone();
22760            counter.fetch_add(1, atomic::Ordering::Release);
22761            async move {
22762                assert_eq!(params.text_document_position.text_document.uri, url.clone());
22763                assert_eq!(
22764                    params.text_document_position.position, complete_from_position,
22765                    "marker `|` position doesn't match",
22766                );
22767                Ok(Some(lsp::CompletionResponse::Array(
22768                    completions
22769                        .iter()
22770                        .map(|(label, new_text)| lsp::CompletionItem {
22771                            label: label.to_string(),
22772                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22773                                lsp::InsertReplaceEdit {
22774                                    insert: insert_range,
22775                                    replace: replace_range,
22776                                    new_text: new_text.to_string(),
22777                                },
22778                            )),
22779                            ..Default::default()
22780                        })
22781                        .collect(),
22782                )))
22783            }
22784        });
22785
22786    async move {
22787        request.next().await;
22788    }
22789}
22790
22791fn handle_resolve_completion_request(
22792    cx: &mut EditorLspTestContext,
22793    edits: Option<Vec<(&'static str, &'static str)>>,
22794) -> impl Future<Output = ()> {
22795    let edits = edits.map(|edits| {
22796        edits
22797            .iter()
22798            .map(|(marked_string, new_text)| {
22799                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
22800                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
22801                lsp::TextEdit::new(replace_range, new_text.to_string())
22802            })
22803            .collect::<Vec<_>>()
22804    });
22805
22806    let mut request =
22807        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
22808            let edits = edits.clone();
22809            async move {
22810                Ok(lsp::CompletionItem {
22811                    additional_text_edits: edits,
22812                    ..Default::default()
22813                })
22814            }
22815        });
22816
22817    async move {
22818        request.next().await;
22819    }
22820}
22821
22822pub(crate) fn update_test_language_settings(
22823    cx: &mut TestAppContext,
22824    f: impl Fn(&mut AllLanguageSettingsContent),
22825) {
22826    cx.update(|cx| {
22827        SettingsStore::update_global(cx, |store, cx| {
22828            store.update_user_settings::<AllLanguageSettings>(cx, f);
22829        });
22830    });
22831}
22832
22833pub(crate) fn update_test_project_settings(
22834    cx: &mut TestAppContext,
22835    f: impl Fn(&mut ProjectSettings),
22836) {
22837    cx.update(|cx| {
22838        SettingsStore::update_global(cx, |store, cx| {
22839            store.update_user_settings::<ProjectSettings>(cx, f);
22840        });
22841    });
22842}
22843
22844pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
22845    cx.update(|cx| {
22846        assets::Assets.load_test_fonts(cx);
22847        let store = SettingsStore::test(cx);
22848        cx.set_global(store);
22849        theme::init(theme::LoadThemes::JustBase, cx);
22850        release_channel::init(SemanticVersion::default(), cx);
22851        client::init_settings(cx);
22852        language::init(cx);
22853        Project::init_settings(cx);
22854        workspace::init_settings(cx);
22855        crate::init(cx);
22856    });
22857    zlog::init_test();
22858    update_test_language_settings(cx, f);
22859}
22860
22861#[track_caller]
22862fn assert_hunk_revert(
22863    not_reverted_text_with_selections: &str,
22864    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
22865    expected_reverted_text_with_selections: &str,
22866    base_text: &str,
22867    cx: &mut EditorLspTestContext,
22868) {
22869    cx.set_state(not_reverted_text_with_selections);
22870    cx.set_head_text(base_text);
22871    cx.executor().run_until_parked();
22872
22873    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
22874        let snapshot = editor.snapshot(window, cx);
22875        let reverted_hunk_statuses = snapshot
22876            .buffer_snapshot
22877            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
22878            .map(|hunk| hunk.status().kind)
22879            .collect::<Vec<_>>();
22880
22881        editor.git_restore(&Default::default(), window, cx);
22882        reverted_hunk_statuses
22883    });
22884    cx.executor().run_until_parked();
22885    cx.assert_editor_state(expected_reverted_text_with_selections);
22886    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
22887}
22888
22889#[gpui::test(iterations = 10)]
22890async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
22891    init_test(cx, |_| {});
22892
22893    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
22894    let counter = diagnostic_requests.clone();
22895
22896    let fs = FakeFs::new(cx.executor());
22897    fs.insert_tree(
22898        path!("/a"),
22899        json!({
22900            "first.rs": "fn main() { let a = 5; }",
22901            "second.rs": "// Test file",
22902        }),
22903    )
22904    .await;
22905
22906    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22907    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22908    let cx = &mut VisualTestContext::from_window(*workspace, cx);
22909
22910    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22911    language_registry.add(rust_lang());
22912    let mut fake_servers = language_registry.register_fake_lsp(
22913        "Rust",
22914        FakeLspAdapter {
22915            capabilities: lsp::ServerCapabilities {
22916                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
22917                    lsp::DiagnosticOptions {
22918                        identifier: None,
22919                        inter_file_dependencies: true,
22920                        workspace_diagnostics: true,
22921                        work_done_progress_options: Default::default(),
22922                    },
22923                )),
22924                ..Default::default()
22925            },
22926            ..Default::default()
22927        },
22928    );
22929
22930    let editor = workspace
22931        .update(cx, |workspace, window, cx| {
22932            workspace.open_abs_path(
22933                PathBuf::from(path!("/a/first.rs")),
22934                OpenOptions::default(),
22935                window,
22936                cx,
22937            )
22938        })
22939        .unwrap()
22940        .await
22941        .unwrap()
22942        .downcast::<Editor>()
22943        .unwrap();
22944    let fake_server = fake_servers.next().await.unwrap();
22945    let server_id = fake_server.server.server_id();
22946    let mut first_request = fake_server
22947        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
22948            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
22949            let result_id = Some(new_result_id.to_string());
22950            assert_eq!(
22951                params.text_document.uri,
22952                lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
22953            );
22954            async move {
22955                Ok(lsp::DocumentDiagnosticReportResult::Report(
22956                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
22957                        related_documents: None,
22958                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
22959                            items: Vec::new(),
22960                            result_id,
22961                        },
22962                    }),
22963                ))
22964            }
22965        });
22966
22967    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
22968        project.update(cx, |project, cx| {
22969            let buffer_id = editor
22970                .read(cx)
22971                .buffer()
22972                .read(cx)
22973                .as_singleton()
22974                .expect("created a singleton buffer")
22975                .read(cx)
22976                .remote_id();
22977            let buffer_result_id = project
22978                .lsp_store()
22979                .read(cx)
22980                .result_id(server_id, buffer_id, cx);
22981            assert_eq!(expected, buffer_result_id);
22982        });
22983    };
22984
22985    ensure_result_id(None, cx);
22986    cx.executor().advance_clock(Duration::from_millis(60));
22987    cx.executor().run_until_parked();
22988    assert_eq!(
22989        diagnostic_requests.load(atomic::Ordering::Acquire),
22990        1,
22991        "Opening file should trigger diagnostic request"
22992    );
22993    first_request
22994        .next()
22995        .await
22996        .expect("should have sent the first diagnostics pull request");
22997    ensure_result_id(Some("1".to_string()), cx);
22998
22999    // Editing should trigger diagnostics
23000    editor.update_in(cx, |editor, window, cx| {
23001        editor.handle_input("2", window, cx)
23002    });
23003    cx.executor().advance_clock(Duration::from_millis(60));
23004    cx.executor().run_until_parked();
23005    assert_eq!(
23006        diagnostic_requests.load(atomic::Ordering::Acquire),
23007        2,
23008        "Editing should trigger diagnostic request"
23009    );
23010    ensure_result_id(Some("2".to_string()), cx);
23011
23012    // Moving cursor should not trigger diagnostic request
23013    editor.update_in(cx, |editor, window, cx| {
23014        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23015            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
23016        });
23017    });
23018    cx.executor().advance_clock(Duration::from_millis(60));
23019    cx.executor().run_until_parked();
23020    assert_eq!(
23021        diagnostic_requests.load(atomic::Ordering::Acquire),
23022        2,
23023        "Cursor movement should not trigger diagnostic request"
23024    );
23025    ensure_result_id(Some("2".to_string()), cx);
23026    // Multiple rapid edits should be debounced
23027    for _ in 0..5 {
23028        editor.update_in(cx, |editor, window, cx| {
23029            editor.handle_input("x", window, cx)
23030        });
23031    }
23032    cx.executor().advance_clock(Duration::from_millis(60));
23033    cx.executor().run_until_parked();
23034
23035    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
23036    assert!(
23037        final_requests <= 4,
23038        "Multiple rapid edits should be debounced (got {final_requests} requests)",
23039    );
23040    ensure_result_id(Some(final_requests.to_string()), cx);
23041}
23042
23043#[gpui::test]
23044async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
23045    // Regression test for issue #11671
23046    // Previously, adding a cursor after moving multiple cursors would reset
23047    // the cursor count instead of adding to the existing cursors.
23048    init_test(cx, |_| {});
23049    let mut cx = EditorTestContext::new(cx).await;
23050
23051    // Create a simple buffer with cursor at start
23052    cx.set_state(indoc! {"
23053        ˇaaaa
23054        bbbb
23055        cccc
23056        dddd
23057        eeee
23058        ffff
23059        gggg
23060        hhhh"});
23061
23062    // Add 2 cursors below (so we have 3 total)
23063    cx.update_editor(|editor, window, cx| {
23064        editor.add_selection_below(&Default::default(), window, cx);
23065        editor.add_selection_below(&Default::default(), window, cx);
23066    });
23067
23068    // Verify we have 3 cursors
23069    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
23070    assert_eq!(
23071        initial_count, 3,
23072        "Should have 3 cursors after adding 2 below"
23073    );
23074
23075    // Move down one line
23076    cx.update_editor(|editor, window, cx| {
23077        editor.move_down(&MoveDown, window, cx);
23078    });
23079
23080    // Add another cursor below
23081    cx.update_editor(|editor, window, cx| {
23082        editor.add_selection_below(&Default::default(), window, cx);
23083    });
23084
23085    // Should now have 4 cursors (3 original + 1 new)
23086    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
23087    assert_eq!(
23088        final_count, 4,
23089        "Should have 4 cursors after moving and adding another"
23090    );
23091}
23092
23093#[gpui::test(iterations = 10)]
23094async fn test_document_colors(cx: &mut TestAppContext) {
23095    let expected_color = Rgba {
23096        r: 0.33,
23097        g: 0.33,
23098        b: 0.33,
23099        a: 0.33,
23100    };
23101
23102    init_test(cx, |_| {});
23103
23104    let fs = FakeFs::new(cx.executor());
23105    fs.insert_tree(
23106        path!("/a"),
23107        json!({
23108            "first.rs": "fn main() { let a = 5; }",
23109        }),
23110    )
23111    .await;
23112
23113    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23114    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23115    let cx = &mut VisualTestContext::from_window(*workspace, cx);
23116
23117    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23118    language_registry.add(rust_lang());
23119    let mut fake_servers = language_registry.register_fake_lsp(
23120        "Rust",
23121        FakeLspAdapter {
23122            capabilities: lsp::ServerCapabilities {
23123                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
23124                ..lsp::ServerCapabilities::default()
23125            },
23126            name: "rust-analyzer",
23127            ..FakeLspAdapter::default()
23128        },
23129    );
23130    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
23131        "Rust",
23132        FakeLspAdapter {
23133            capabilities: lsp::ServerCapabilities {
23134                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
23135                ..lsp::ServerCapabilities::default()
23136            },
23137            name: "not-rust-analyzer",
23138            ..FakeLspAdapter::default()
23139        },
23140    );
23141
23142    let editor = workspace
23143        .update(cx, |workspace, window, cx| {
23144            workspace.open_abs_path(
23145                PathBuf::from(path!("/a/first.rs")),
23146                OpenOptions::default(),
23147                window,
23148                cx,
23149            )
23150        })
23151        .unwrap()
23152        .await
23153        .unwrap()
23154        .downcast::<Editor>()
23155        .unwrap();
23156    let fake_language_server = fake_servers.next().await.unwrap();
23157    let fake_language_server_without_capabilities =
23158        fake_servers_without_capabilities.next().await.unwrap();
23159    let requests_made = Arc::new(AtomicUsize::new(0));
23160    let closure_requests_made = Arc::clone(&requests_made);
23161    let mut color_request_handle = fake_language_server
23162        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
23163            let requests_made = Arc::clone(&closure_requests_made);
23164            async move {
23165                assert_eq!(
23166                    params.text_document.uri,
23167                    lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
23168                );
23169                requests_made.fetch_add(1, atomic::Ordering::Release);
23170                Ok(vec![
23171                    lsp::ColorInformation {
23172                        range: lsp::Range {
23173                            start: lsp::Position {
23174                                line: 0,
23175                                character: 0,
23176                            },
23177                            end: lsp::Position {
23178                                line: 0,
23179                                character: 1,
23180                            },
23181                        },
23182                        color: lsp::Color {
23183                            red: 0.33,
23184                            green: 0.33,
23185                            blue: 0.33,
23186                            alpha: 0.33,
23187                        },
23188                    },
23189                    lsp::ColorInformation {
23190                        range: lsp::Range {
23191                            start: lsp::Position {
23192                                line: 0,
23193                                character: 0,
23194                            },
23195                            end: lsp::Position {
23196                                line: 0,
23197                                character: 1,
23198                            },
23199                        },
23200                        color: lsp::Color {
23201                            red: 0.33,
23202                            green: 0.33,
23203                            blue: 0.33,
23204                            alpha: 0.33,
23205                        },
23206                    },
23207                ])
23208            }
23209        });
23210
23211    let _handle = fake_language_server_without_capabilities
23212        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
23213            panic!("Should not be called");
23214        });
23215    cx.executor().advance_clock(Duration::from_millis(100));
23216    color_request_handle.next().await.unwrap();
23217    cx.run_until_parked();
23218    assert_eq!(
23219        1,
23220        requests_made.load(atomic::Ordering::Acquire),
23221        "Should query for colors once per editor open"
23222    );
23223    editor.update_in(cx, |editor, _, cx| {
23224        assert_eq!(
23225            vec![expected_color],
23226            extract_color_inlays(editor, cx),
23227            "Should have an initial inlay"
23228        );
23229    });
23230
23231    // opening another file in a split should not influence the LSP query counter
23232    workspace
23233        .update(cx, |workspace, window, cx| {
23234            assert_eq!(
23235                workspace.panes().len(),
23236                1,
23237                "Should have one pane with one editor"
23238            );
23239            workspace.move_item_to_pane_in_direction(
23240                &MoveItemToPaneInDirection {
23241                    direction: SplitDirection::Right,
23242                    focus: false,
23243                    clone: true,
23244                },
23245                window,
23246                cx,
23247            );
23248        })
23249        .unwrap();
23250    cx.run_until_parked();
23251    workspace
23252        .update(cx, |workspace, _, cx| {
23253            let panes = workspace.panes();
23254            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
23255            for pane in panes {
23256                let editor = pane
23257                    .read(cx)
23258                    .active_item()
23259                    .and_then(|item| item.downcast::<Editor>())
23260                    .expect("Should have opened an editor in each split");
23261                let editor_file = editor
23262                    .read(cx)
23263                    .buffer()
23264                    .read(cx)
23265                    .as_singleton()
23266                    .expect("test deals with singleton buffers")
23267                    .read(cx)
23268                    .file()
23269                    .expect("test buffese should have a file")
23270                    .path();
23271                assert_eq!(
23272                    editor_file.as_ref(),
23273                    Path::new("first.rs"),
23274                    "Both editors should be opened for the same file"
23275                )
23276            }
23277        })
23278        .unwrap();
23279
23280    cx.executor().advance_clock(Duration::from_millis(500));
23281    let save = editor.update_in(cx, |editor, window, cx| {
23282        editor.move_to_end(&MoveToEnd, window, cx);
23283        editor.handle_input("dirty", window, cx);
23284        editor.save(
23285            SaveOptions {
23286                format: true,
23287                autosave: true,
23288            },
23289            project.clone(),
23290            window,
23291            cx,
23292        )
23293    });
23294    save.await.unwrap();
23295
23296    color_request_handle.next().await.unwrap();
23297    cx.run_until_parked();
23298    assert_eq!(
23299        3,
23300        requests_made.load(atomic::Ordering::Acquire),
23301        "Should query for colors once per save and once per formatting after save"
23302    );
23303
23304    drop(editor);
23305    let close = workspace
23306        .update(cx, |workspace, window, cx| {
23307            workspace.active_pane().update(cx, |pane, cx| {
23308                pane.close_active_item(&CloseActiveItem::default(), window, cx)
23309            })
23310        })
23311        .unwrap();
23312    close.await.unwrap();
23313    let close = workspace
23314        .update(cx, |workspace, window, cx| {
23315            workspace.active_pane().update(cx, |pane, cx| {
23316                pane.close_active_item(&CloseActiveItem::default(), window, cx)
23317            })
23318        })
23319        .unwrap();
23320    close.await.unwrap();
23321    assert_eq!(
23322        3,
23323        requests_made.load(atomic::Ordering::Acquire),
23324        "After saving and closing all editors, no extra requests should be made"
23325    );
23326    workspace
23327        .update(cx, |workspace, _, cx| {
23328            assert!(
23329                workspace.active_item(cx).is_none(),
23330                "Should close all editors"
23331            )
23332        })
23333        .unwrap();
23334
23335    workspace
23336        .update(cx, |workspace, window, cx| {
23337            workspace.active_pane().update(cx, |pane, cx| {
23338                pane.navigate_backward(window, cx);
23339            })
23340        })
23341        .unwrap();
23342    cx.executor().advance_clock(Duration::from_millis(100));
23343    cx.run_until_parked();
23344    let editor = workspace
23345        .update(cx, |workspace, _, cx| {
23346            workspace
23347                .active_item(cx)
23348                .expect("Should have reopened the editor again after navigating back")
23349                .downcast::<Editor>()
23350                .expect("Should be an editor")
23351        })
23352        .unwrap();
23353    color_request_handle.next().await.unwrap();
23354    assert_eq!(
23355        3,
23356        requests_made.load(atomic::Ordering::Acquire),
23357        "Cache should be reused on buffer close and reopen"
23358    );
23359    editor.update(cx, |editor, cx| {
23360        assert_eq!(
23361            vec![expected_color],
23362            extract_color_inlays(editor, cx),
23363            "Should have an initial inlay"
23364        );
23365    });
23366}
23367
23368#[gpui::test]
23369async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
23370    init_test(cx, |_| {});
23371    let (editor, cx) = cx.add_window_view(Editor::single_line);
23372    editor.update_in(cx, |editor, window, cx| {
23373        editor.set_text("oops\n\nwow\n", window, cx)
23374    });
23375    cx.run_until_parked();
23376    editor.update(cx, |editor, cx| {
23377        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
23378    });
23379    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
23380    cx.run_until_parked();
23381    editor.update(cx, |editor, cx| {
23382        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
23383    });
23384}
23385
23386#[track_caller]
23387fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
23388    editor
23389        .all_inlays(cx)
23390        .into_iter()
23391        .filter_map(|inlay| inlay.get_color())
23392        .map(Rgba::from)
23393        .collect()
23394}