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_multibuffer_format_during_save(cx: &mut TestAppContext) {
 9575    init_test(cx, |_| {});
 9576
 9577    let cols = 4;
 9578    let rows = 10;
 9579    let sample_text_1 = sample_text(rows, cols, 'a');
 9580    assert_eq!(
 9581        sample_text_1,
 9582        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
 9583    );
 9584    let sample_text_2 = sample_text(rows, cols, 'l');
 9585    assert_eq!(
 9586        sample_text_2,
 9587        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
 9588    );
 9589    let sample_text_3 = sample_text(rows, cols, 'v');
 9590    assert_eq!(
 9591        sample_text_3,
 9592        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
 9593    );
 9594
 9595    let fs = FakeFs::new(cx.executor());
 9596    fs.insert_tree(
 9597        path!("/a"),
 9598        json!({
 9599            "main.rs": sample_text_1,
 9600            "other.rs": sample_text_2,
 9601            "lib.rs": sample_text_3,
 9602        }),
 9603    )
 9604    .await;
 9605
 9606    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
 9607    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9608    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 9609
 9610    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9611    language_registry.add(rust_lang());
 9612    let mut fake_servers = language_registry.register_fake_lsp(
 9613        "Rust",
 9614        FakeLspAdapter {
 9615            capabilities: lsp::ServerCapabilities {
 9616                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9617                ..Default::default()
 9618            },
 9619            ..Default::default()
 9620        },
 9621    );
 9622
 9623    let worktree = project.update(cx, |project, cx| {
 9624        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
 9625        assert_eq!(worktrees.len(), 1);
 9626        worktrees.pop().unwrap()
 9627    });
 9628    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 9629
 9630    let buffer_1 = project
 9631        .update(cx, |project, cx| {
 9632            project.open_buffer((worktree_id, "main.rs"), cx)
 9633        })
 9634        .await
 9635        .unwrap();
 9636    let buffer_2 = project
 9637        .update(cx, |project, cx| {
 9638            project.open_buffer((worktree_id, "other.rs"), cx)
 9639        })
 9640        .await
 9641        .unwrap();
 9642    let buffer_3 = project
 9643        .update(cx, |project, cx| {
 9644            project.open_buffer((worktree_id, "lib.rs"), cx)
 9645        })
 9646        .await
 9647        .unwrap();
 9648
 9649    let multi_buffer = cx.new(|cx| {
 9650        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 9651        multi_buffer.push_excerpts(
 9652            buffer_1.clone(),
 9653            [
 9654                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9655                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9656                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9657            ],
 9658            cx,
 9659        );
 9660        multi_buffer.push_excerpts(
 9661            buffer_2.clone(),
 9662            [
 9663                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9664                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9665                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9666            ],
 9667            cx,
 9668        );
 9669        multi_buffer.push_excerpts(
 9670            buffer_3.clone(),
 9671            [
 9672                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9673                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9674                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9675            ],
 9676            cx,
 9677        );
 9678        multi_buffer
 9679    });
 9680    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
 9681        Editor::new(
 9682            EditorMode::full(),
 9683            multi_buffer,
 9684            Some(project.clone()),
 9685            window,
 9686            cx,
 9687        )
 9688    });
 9689
 9690    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 9691        editor.change_selections(
 9692            SelectionEffects::scroll(Autoscroll::Next),
 9693            window,
 9694            cx,
 9695            |s| s.select_ranges(Some(1..2)),
 9696        );
 9697        editor.insert("|one|two|three|", window, cx);
 9698    });
 9699    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 9700    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 9701        editor.change_selections(
 9702            SelectionEffects::scroll(Autoscroll::Next),
 9703            window,
 9704            cx,
 9705            |s| s.select_ranges(Some(60..70)),
 9706        );
 9707        editor.insert("|four|five|six|", window, cx);
 9708    });
 9709    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 9710
 9711    // First two buffers should be edited, but not the third one.
 9712    assert_eq!(
 9713        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 9714        "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}",
 9715    );
 9716    buffer_1.update(cx, |buffer, _| {
 9717        assert!(buffer.is_dirty());
 9718        assert_eq!(
 9719            buffer.text(),
 9720            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
 9721        )
 9722    });
 9723    buffer_2.update(cx, |buffer, _| {
 9724        assert!(buffer.is_dirty());
 9725        assert_eq!(
 9726            buffer.text(),
 9727            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
 9728        )
 9729    });
 9730    buffer_3.update(cx, |buffer, _| {
 9731        assert!(!buffer.is_dirty());
 9732        assert_eq!(buffer.text(), sample_text_3,)
 9733    });
 9734    cx.executor().run_until_parked();
 9735
 9736    cx.executor().start_waiting();
 9737    let save = multi_buffer_editor
 9738        .update_in(cx, |editor, window, cx| {
 9739            editor.save(
 9740                SaveOptions {
 9741                    format: true,
 9742                    autosave: false,
 9743                },
 9744                project.clone(),
 9745                window,
 9746                cx,
 9747            )
 9748        })
 9749        .unwrap();
 9750
 9751    let fake_server = fake_servers.next().await.unwrap();
 9752    fake_server
 9753        .server
 9754        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9755            Ok(Some(vec![lsp::TextEdit::new(
 9756                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9757                format!("[{} formatted]", params.text_document.uri),
 9758            )]))
 9759        })
 9760        .detach();
 9761    save.await;
 9762
 9763    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
 9764    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
 9765    assert_eq!(
 9766        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 9767        uri!(
 9768            "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}"
 9769        ),
 9770    );
 9771    buffer_1.update(cx, |buffer, _| {
 9772        assert!(!buffer.is_dirty());
 9773        assert_eq!(
 9774            buffer.text(),
 9775            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
 9776        )
 9777    });
 9778    buffer_2.update(cx, |buffer, _| {
 9779        assert!(!buffer.is_dirty());
 9780        assert_eq!(
 9781            buffer.text(),
 9782            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
 9783        )
 9784    });
 9785    buffer_3.update(cx, |buffer, _| {
 9786        assert!(!buffer.is_dirty());
 9787        assert_eq!(buffer.text(), sample_text_3,)
 9788    });
 9789}
 9790
 9791#[gpui::test]
 9792async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
 9793    init_test(cx, |_| {});
 9794
 9795    let fs = FakeFs::new(cx.executor());
 9796    fs.insert_tree(
 9797        path!("/dir"),
 9798        json!({
 9799            "file1.rs": "fn main() { println!(\"hello\"); }",
 9800            "file2.rs": "fn test() { println!(\"test\"); }",
 9801            "file3.rs": "fn other() { println!(\"other\"); }\n",
 9802        }),
 9803    )
 9804    .await;
 9805
 9806    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
 9807    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9808    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 9809
 9810    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9811    language_registry.add(rust_lang());
 9812
 9813    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
 9814    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 9815
 9816    // Open three buffers
 9817    let buffer_1 = project
 9818        .update(cx, |project, cx| {
 9819            project.open_buffer((worktree_id, "file1.rs"), cx)
 9820        })
 9821        .await
 9822        .unwrap();
 9823    let buffer_2 = project
 9824        .update(cx, |project, cx| {
 9825            project.open_buffer((worktree_id, "file2.rs"), cx)
 9826        })
 9827        .await
 9828        .unwrap();
 9829    let buffer_3 = project
 9830        .update(cx, |project, cx| {
 9831            project.open_buffer((worktree_id, "file3.rs"), cx)
 9832        })
 9833        .await
 9834        .unwrap();
 9835
 9836    // Create a multi-buffer with all three buffers
 9837    let multi_buffer = cx.new(|cx| {
 9838        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 9839        multi_buffer.push_excerpts(
 9840            buffer_1.clone(),
 9841            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9842            cx,
 9843        );
 9844        multi_buffer.push_excerpts(
 9845            buffer_2.clone(),
 9846            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9847            cx,
 9848        );
 9849        multi_buffer.push_excerpts(
 9850            buffer_3.clone(),
 9851            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9852            cx,
 9853        );
 9854        multi_buffer
 9855    });
 9856
 9857    let editor = cx.new_window_entity(|window, cx| {
 9858        Editor::new(
 9859            EditorMode::full(),
 9860            multi_buffer,
 9861            Some(project.clone()),
 9862            window,
 9863            cx,
 9864        )
 9865    });
 9866
 9867    // Edit only the first buffer
 9868    editor.update_in(cx, |editor, window, cx| {
 9869        editor.change_selections(
 9870            SelectionEffects::scroll(Autoscroll::Next),
 9871            window,
 9872            cx,
 9873            |s| s.select_ranges(Some(10..10)),
 9874        );
 9875        editor.insert("// edited", window, cx);
 9876    });
 9877
 9878    // Verify that only buffer 1 is dirty
 9879    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
 9880    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9881    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9882
 9883    // Get write counts after file creation (files were created with initial content)
 9884    // We expect each file to have been written once during creation
 9885    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
 9886    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
 9887    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
 9888
 9889    // Perform autosave
 9890    let save_task = editor.update_in(cx, |editor, window, cx| {
 9891        editor.save(
 9892            SaveOptions {
 9893                format: true,
 9894                autosave: true,
 9895            },
 9896            project.clone(),
 9897            window,
 9898            cx,
 9899        )
 9900    });
 9901    save_task.await.unwrap();
 9902
 9903    // Only the dirty buffer should have been saved
 9904    assert_eq!(
 9905        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
 9906        1,
 9907        "Buffer 1 was dirty, so it should have been written once during autosave"
 9908    );
 9909    assert_eq!(
 9910        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
 9911        0,
 9912        "Buffer 2 was clean, so it should not have been written during autosave"
 9913    );
 9914    assert_eq!(
 9915        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
 9916        0,
 9917        "Buffer 3 was clean, so it should not have been written during autosave"
 9918    );
 9919
 9920    // Verify buffer states after autosave
 9921    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9922    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9923    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9924
 9925    // Now perform a manual save (format = true)
 9926    let save_task = editor.update_in(cx, |editor, window, cx| {
 9927        editor.save(
 9928            SaveOptions {
 9929                format: true,
 9930                autosave: false,
 9931            },
 9932            project.clone(),
 9933            window,
 9934            cx,
 9935        )
 9936    });
 9937    save_task.await.unwrap();
 9938
 9939    // During manual save, clean buffers don't get written to disk
 9940    // They just get did_save called for language server notifications
 9941    assert_eq!(
 9942        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
 9943        1,
 9944        "Buffer 1 should only have been written once total (during autosave, not manual save)"
 9945    );
 9946    assert_eq!(
 9947        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
 9948        0,
 9949        "Buffer 2 should not have been written at all"
 9950    );
 9951    assert_eq!(
 9952        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
 9953        0,
 9954        "Buffer 3 should not have been written at all"
 9955    );
 9956}
 9957
 9958#[gpui::test]
 9959async fn test_range_format_during_save(cx: &mut TestAppContext) {
 9960    init_test(cx, |_| {});
 9961
 9962    let fs = FakeFs::new(cx.executor());
 9963    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9964
 9965    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 9966
 9967    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9968    language_registry.add(rust_lang());
 9969    let mut fake_servers = language_registry.register_fake_lsp(
 9970        "Rust",
 9971        FakeLspAdapter {
 9972            capabilities: lsp::ServerCapabilities {
 9973                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
 9974                ..Default::default()
 9975            },
 9976            ..Default::default()
 9977        },
 9978    );
 9979
 9980    let buffer = project
 9981        .update(cx, |project, cx| {
 9982            project.open_local_buffer(path!("/file.rs"), cx)
 9983        })
 9984        .await
 9985        .unwrap();
 9986
 9987    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9988    let (editor, cx) = cx.add_window_view(|window, cx| {
 9989        build_editor_with_project(project.clone(), buffer, window, cx)
 9990    });
 9991    editor.update_in(cx, |editor, window, cx| {
 9992        editor.set_text("one\ntwo\nthree\n", window, cx)
 9993    });
 9994    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9995
 9996    cx.executor().start_waiting();
 9997    let fake_server = fake_servers.next().await.unwrap();
 9998
 9999    let save = editor
10000        .update_in(cx, |editor, window, cx| {
10001            editor.save(
10002                SaveOptions {
10003                    format: true,
10004                    autosave: false,
10005                },
10006                project.clone(),
10007                window,
10008                cx,
10009            )
10010        })
10011        .unwrap();
10012    fake_server
10013        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
10014            assert_eq!(
10015                params.text_document.uri,
10016                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10017            );
10018            assert_eq!(params.options.tab_size, 4);
10019            Ok(Some(vec![lsp::TextEdit::new(
10020                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10021                ", ".to_string(),
10022            )]))
10023        })
10024        .next()
10025        .await;
10026    cx.executor().start_waiting();
10027    save.await;
10028    assert_eq!(
10029        editor.update(cx, |editor, cx| editor.text(cx)),
10030        "one, two\nthree\n"
10031    );
10032    assert!(!cx.read(|cx| editor.is_dirty(cx)));
10033
10034    editor.update_in(cx, |editor, window, cx| {
10035        editor.set_text("one\ntwo\nthree\n", window, cx)
10036    });
10037    assert!(cx.read(|cx| editor.is_dirty(cx)));
10038
10039    // Ensure we can still save even if formatting hangs.
10040    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
10041        move |params, _| async move {
10042            assert_eq!(
10043                params.text_document.uri,
10044                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10045            );
10046            futures::future::pending::<()>().await;
10047            unreachable!()
10048        },
10049    );
10050    let save = editor
10051        .update_in(cx, |editor, window, cx| {
10052            editor.save(
10053                SaveOptions {
10054                    format: true,
10055                    autosave: false,
10056                },
10057                project.clone(),
10058                window,
10059                cx,
10060            )
10061        })
10062        .unwrap();
10063    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10064    cx.executor().start_waiting();
10065    save.await;
10066    assert_eq!(
10067        editor.update(cx, |editor, cx| editor.text(cx)),
10068        "one\ntwo\nthree\n"
10069    );
10070    assert!(!cx.read(|cx| editor.is_dirty(cx)));
10071
10072    // For non-dirty buffer, no formatting request should be sent
10073    let save = editor
10074        .update_in(cx, |editor, window, cx| {
10075            editor.save(
10076                SaveOptions {
10077                    format: false,
10078                    autosave: false,
10079                },
10080                project.clone(),
10081                window,
10082                cx,
10083            )
10084        })
10085        .unwrap();
10086    let _pending_format_request = fake_server
10087        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
10088            panic!("Should not be invoked");
10089        })
10090        .next();
10091    cx.executor().start_waiting();
10092    save.await;
10093
10094    // Set Rust language override and assert overridden tabsize is sent to language server
10095    update_test_language_settings(cx, |settings| {
10096        settings.languages.0.insert(
10097            "Rust".into(),
10098            LanguageSettingsContent {
10099                tab_size: NonZeroU32::new(8),
10100                ..Default::default()
10101            },
10102        );
10103    });
10104
10105    editor.update_in(cx, |editor, window, cx| {
10106        editor.set_text("somehting_new\n", window, cx)
10107    });
10108    assert!(cx.read(|cx| editor.is_dirty(cx)));
10109    let save = editor
10110        .update_in(cx, |editor, window, cx| {
10111            editor.save(
10112                SaveOptions {
10113                    format: true,
10114                    autosave: false,
10115                },
10116                project.clone(),
10117                window,
10118                cx,
10119            )
10120        })
10121        .unwrap();
10122    fake_server
10123        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
10124            assert_eq!(
10125                params.text_document.uri,
10126                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10127            );
10128            assert_eq!(params.options.tab_size, 8);
10129            Ok(Some(Vec::new()))
10130        })
10131        .next()
10132        .await;
10133    save.await;
10134}
10135
10136#[gpui::test]
10137async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
10138    init_test(cx, |settings| {
10139        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
10140            Formatter::LanguageServer { name: None },
10141        )))
10142    });
10143
10144    let fs = FakeFs::new(cx.executor());
10145    fs.insert_file(path!("/file.rs"), Default::default()).await;
10146
10147    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10148
10149    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10150    language_registry.add(Arc::new(Language::new(
10151        LanguageConfig {
10152            name: "Rust".into(),
10153            matcher: LanguageMatcher {
10154                path_suffixes: vec!["rs".to_string()],
10155                ..Default::default()
10156            },
10157            ..LanguageConfig::default()
10158        },
10159        Some(tree_sitter_rust::LANGUAGE.into()),
10160    )));
10161    update_test_language_settings(cx, |settings| {
10162        // Enable Prettier formatting for the same buffer, and ensure
10163        // LSP is called instead of Prettier.
10164        settings.defaults.prettier = Some(PrettierSettings {
10165            allowed: true,
10166            ..PrettierSettings::default()
10167        });
10168    });
10169    let mut fake_servers = language_registry.register_fake_lsp(
10170        "Rust",
10171        FakeLspAdapter {
10172            capabilities: lsp::ServerCapabilities {
10173                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10174                ..Default::default()
10175            },
10176            ..Default::default()
10177        },
10178    );
10179
10180    let buffer = project
10181        .update(cx, |project, cx| {
10182            project.open_local_buffer(path!("/file.rs"), cx)
10183        })
10184        .await
10185        .unwrap();
10186
10187    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10188    let (editor, cx) = cx.add_window_view(|window, cx| {
10189        build_editor_with_project(project.clone(), buffer, window, cx)
10190    });
10191    editor.update_in(cx, |editor, window, cx| {
10192        editor.set_text("one\ntwo\nthree\n", window, cx)
10193    });
10194
10195    cx.executor().start_waiting();
10196    let fake_server = fake_servers.next().await.unwrap();
10197
10198    let format = editor
10199        .update_in(cx, |editor, window, cx| {
10200            editor.perform_format(
10201                project.clone(),
10202                FormatTrigger::Manual,
10203                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10204                window,
10205                cx,
10206            )
10207        })
10208        .unwrap();
10209    fake_server
10210        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
10211            assert_eq!(
10212                params.text_document.uri,
10213                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10214            );
10215            assert_eq!(params.options.tab_size, 4);
10216            Ok(Some(vec![lsp::TextEdit::new(
10217                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10218                ", ".to_string(),
10219            )]))
10220        })
10221        .next()
10222        .await;
10223    cx.executor().start_waiting();
10224    format.await;
10225    assert_eq!(
10226        editor.update(cx, |editor, cx| editor.text(cx)),
10227        "one, two\nthree\n"
10228    );
10229
10230    editor.update_in(cx, |editor, window, cx| {
10231        editor.set_text("one\ntwo\nthree\n", window, cx)
10232    });
10233    // Ensure we don't lock if formatting hangs.
10234    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10235        move |params, _| async move {
10236            assert_eq!(
10237                params.text_document.uri,
10238                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10239            );
10240            futures::future::pending::<()>().await;
10241            unreachable!()
10242        },
10243    );
10244    let format = editor
10245        .update_in(cx, |editor, window, cx| {
10246            editor.perform_format(
10247                project,
10248                FormatTrigger::Manual,
10249                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10250                window,
10251                cx,
10252            )
10253        })
10254        .unwrap();
10255    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10256    cx.executor().start_waiting();
10257    format.await;
10258    assert_eq!(
10259        editor.update(cx, |editor, cx| editor.text(cx)),
10260        "one\ntwo\nthree\n"
10261    );
10262}
10263
10264#[gpui::test]
10265async fn test_multiple_formatters(cx: &mut TestAppContext) {
10266    init_test(cx, |settings| {
10267        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
10268        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
10269            Formatter::LanguageServer { name: None },
10270            Formatter::CodeActions(
10271                [
10272                    ("code-action-1".into(), true),
10273                    ("code-action-2".into(), true),
10274                ]
10275                .into_iter()
10276                .collect(),
10277            ),
10278        ])))
10279    });
10280
10281    let fs = FakeFs::new(cx.executor());
10282    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
10283        .await;
10284
10285    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10286    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10287    language_registry.add(rust_lang());
10288
10289    let mut fake_servers = language_registry.register_fake_lsp(
10290        "Rust",
10291        FakeLspAdapter {
10292            capabilities: lsp::ServerCapabilities {
10293                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10294                execute_command_provider: Some(lsp::ExecuteCommandOptions {
10295                    commands: vec!["the-command-for-code-action-1".into()],
10296                    ..Default::default()
10297                }),
10298                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10299                ..Default::default()
10300            },
10301            ..Default::default()
10302        },
10303    );
10304
10305    let buffer = project
10306        .update(cx, |project, cx| {
10307            project.open_local_buffer(path!("/file.rs"), cx)
10308        })
10309        .await
10310        .unwrap();
10311
10312    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10313    let (editor, cx) = cx.add_window_view(|window, cx| {
10314        build_editor_with_project(project.clone(), buffer, window, cx)
10315    });
10316
10317    cx.executor().start_waiting();
10318
10319    let fake_server = fake_servers.next().await.unwrap();
10320    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10321        move |_params, _| async move {
10322            Ok(Some(vec![lsp::TextEdit::new(
10323                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10324                "applied-formatting\n".to_string(),
10325            )]))
10326        },
10327    );
10328    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
10329        move |params, _| async move {
10330            assert_eq!(
10331                params.context.only,
10332                Some(vec!["code-action-1".into(), "code-action-2".into()])
10333            );
10334            let uri = lsp::Url::from_file_path(path!("/file.rs")).unwrap();
10335            Ok(Some(vec![
10336                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10337                    kind: Some("code-action-1".into()),
10338                    edit: Some(lsp::WorkspaceEdit::new(
10339                        [(
10340                            uri.clone(),
10341                            vec![lsp::TextEdit::new(
10342                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10343                                "applied-code-action-1-edit\n".to_string(),
10344                            )],
10345                        )]
10346                        .into_iter()
10347                        .collect(),
10348                    )),
10349                    command: Some(lsp::Command {
10350                        command: "the-command-for-code-action-1".into(),
10351                        ..Default::default()
10352                    }),
10353                    ..Default::default()
10354                }),
10355                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10356                    kind: Some("code-action-2".into()),
10357                    edit: Some(lsp::WorkspaceEdit::new(
10358                        [(
10359                            uri.clone(),
10360                            vec![lsp::TextEdit::new(
10361                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10362                                "applied-code-action-2-edit\n".to_string(),
10363                            )],
10364                        )]
10365                        .into_iter()
10366                        .collect(),
10367                    )),
10368                    ..Default::default()
10369                }),
10370            ]))
10371        },
10372    );
10373
10374    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
10375        move |params, _| async move { Ok(params) }
10376    });
10377
10378    let command_lock = Arc::new(futures::lock::Mutex::new(()));
10379    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
10380        let fake = fake_server.clone();
10381        let lock = command_lock.clone();
10382        move |params, _| {
10383            assert_eq!(params.command, "the-command-for-code-action-1");
10384            let fake = fake.clone();
10385            let lock = lock.clone();
10386            async move {
10387                lock.lock().await;
10388                fake.server
10389                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
10390                        label: None,
10391                        edit: lsp::WorkspaceEdit {
10392                            changes: Some(
10393                                [(
10394                                    lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
10395                                    vec![lsp::TextEdit {
10396                                        range: lsp::Range::new(
10397                                            lsp::Position::new(0, 0),
10398                                            lsp::Position::new(0, 0),
10399                                        ),
10400                                        new_text: "applied-code-action-1-command\n".into(),
10401                                    }],
10402                                )]
10403                                .into_iter()
10404                                .collect(),
10405                            ),
10406                            ..Default::default()
10407                        },
10408                    })
10409                    .await
10410                    .into_response()
10411                    .unwrap();
10412                Ok(Some(json!(null)))
10413            }
10414        }
10415    });
10416
10417    cx.executor().start_waiting();
10418    editor
10419        .update_in(cx, |editor, window, cx| {
10420            editor.perform_format(
10421                project.clone(),
10422                FormatTrigger::Manual,
10423                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10424                window,
10425                cx,
10426            )
10427        })
10428        .unwrap()
10429        .await;
10430    editor.update(cx, |editor, cx| {
10431        assert_eq!(
10432            editor.text(cx),
10433            r#"
10434                applied-code-action-2-edit
10435                applied-code-action-1-command
10436                applied-code-action-1-edit
10437                applied-formatting
10438                one
10439                two
10440                three
10441            "#
10442            .unindent()
10443        );
10444    });
10445
10446    editor.update_in(cx, |editor, window, cx| {
10447        editor.undo(&Default::default(), window, cx);
10448        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10449    });
10450
10451    // Perform a manual edit while waiting for an LSP command
10452    // that's being run as part of a formatting code action.
10453    let lock_guard = command_lock.lock().await;
10454    let format = editor
10455        .update_in(cx, |editor, window, cx| {
10456            editor.perform_format(
10457                project.clone(),
10458                FormatTrigger::Manual,
10459                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10460                window,
10461                cx,
10462            )
10463        })
10464        .unwrap();
10465    cx.run_until_parked();
10466    editor.update(cx, |editor, cx| {
10467        assert_eq!(
10468            editor.text(cx),
10469            r#"
10470                applied-code-action-1-edit
10471                applied-formatting
10472                one
10473                two
10474                three
10475            "#
10476            .unindent()
10477        );
10478
10479        editor.buffer.update(cx, |buffer, cx| {
10480            let ix = buffer.len(cx);
10481            buffer.edit([(ix..ix, "edited\n")], None, cx);
10482        });
10483    });
10484
10485    // Allow the LSP command to proceed. Because the buffer was edited,
10486    // the second code action will not be run.
10487    drop(lock_guard);
10488    format.await;
10489    editor.update_in(cx, |editor, window, cx| {
10490        assert_eq!(
10491            editor.text(cx),
10492            r#"
10493                applied-code-action-1-command
10494                applied-code-action-1-edit
10495                applied-formatting
10496                one
10497                two
10498                three
10499                edited
10500            "#
10501            .unindent()
10502        );
10503
10504        // The manual edit is undone first, because it is the last thing the user did
10505        // (even though the command completed afterwards).
10506        editor.undo(&Default::default(), window, cx);
10507        assert_eq!(
10508            editor.text(cx),
10509            r#"
10510                applied-code-action-1-command
10511                applied-code-action-1-edit
10512                applied-formatting
10513                one
10514                two
10515                three
10516            "#
10517            .unindent()
10518        );
10519
10520        // All the formatting (including the command, which completed after the manual edit)
10521        // is undone together.
10522        editor.undo(&Default::default(), window, cx);
10523        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10524    });
10525}
10526
10527#[gpui::test]
10528async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
10529    init_test(cx, |settings| {
10530        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
10531            Formatter::LanguageServer { name: None },
10532        ])))
10533    });
10534
10535    let fs = FakeFs::new(cx.executor());
10536    fs.insert_file(path!("/file.ts"), Default::default()).await;
10537
10538    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10539
10540    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10541    language_registry.add(Arc::new(Language::new(
10542        LanguageConfig {
10543            name: "TypeScript".into(),
10544            matcher: LanguageMatcher {
10545                path_suffixes: vec!["ts".to_string()],
10546                ..Default::default()
10547            },
10548            ..LanguageConfig::default()
10549        },
10550        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
10551    )));
10552    update_test_language_settings(cx, |settings| {
10553        settings.defaults.prettier = Some(PrettierSettings {
10554            allowed: true,
10555            ..PrettierSettings::default()
10556        });
10557    });
10558    let mut fake_servers = language_registry.register_fake_lsp(
10559        "TypeScript",
10560        FakeLspAdapter {
10561            capabilities: lsp::ServerCapabilities {
10562                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10563                ..Default::default()
10564            },
10565            ..Default::default()
10566        },
10567    );
10568
10569    let buffer = project
10570        .update(cx, |project, cx| {
10571            project.open_local_buffer(path!("/file.ts"), cx)
10572        })
10573        .await
10574        .unwrap();
10575
10576    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10577    let (editor, cx) = cx.add_window_view(|window, cx| {
10578        build_editor_with_project(project.clone(), buffer, window, cx)
10579    });
10580    editor.update_in(cx, |editor, window, cx| {
10581        editor.set_text(
10582            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
10583            window,
10584            cx,
10585        )
10586    });
10587
10588    cx.executor().start_waiting();
10589    let fake_server = fake_servers.next().await.unwrap();
10590
10591    let format = editor
10592        .update_in(cx, |editor, window, cx| {
10593            editor.perform_code_action_kind(
10594                project.clone(),
10595                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
10596                window,
10597                cx,
10598            )
10599        })
10600        .unwrap();
10601    fake_server
10602        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
10603            assert_eq!(
10604                params.text_document.uri,
10605                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
10606            );
10607            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
10608                lsp::CodeAction {
10609                    title: "Organize Imports".to_string(),
10610                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
10611                    edit: Some(lsp::WorkspaceEdit {
10612                        changes: Some(
10613                            [(
10614                                params.text_document.uri.clone(),
10615                                vec![lsp::TextEdit::new(
10616                                    lsp::Range::new(
10617                                        lsp::Position::new(1, 0),
10618                                        lsp::Position::new(2, 0),
10619                                    ),
10620                                    "".to_string(),
10621                                )],
10622                            )]
10623                            .into_iter()
10624                            .collect(),
10625                        ),
10626                        ..Default::default()
10627                    }),
10628                    ..Default::default()
10629                },
10630            )]))
10631        })
10632        .next()
10633        .await;
10634    cx.executor().start_waiting();
10635    format.await;
10636    assert_eq!(
10637        editor.update(cx, |editor, cx| editor.text(cx)),
10638        "import { a } from 'module';\n\nconst x = a;\n"
10639    );
10640
10641    editor.update_in(cx, |editor, window, cx| {
10642        editor.set_text(
10643            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
10644            window,
10645            cx,
10646        )
10647    });
10648    // Ensure we don't lock if code action hangs.
10649    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
10650        move |params, _| async move {
10651            assert_eq!(
10652                params.text_document.uri,
10653                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
10654            );
10655            futures::future::pending::<()>().await;
10656            unreachable!()
10657        },
10658    );
10659    let format = editor
10660        .update_in(cx, |editor, window, cx| {
10661            editor.perform_code_action_kind(
10662                project,
10663                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
10664                window,
10665                cx,
10666            )
10667        })
10668        .unwrap();
10669    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
10670    cx.executor().start_waiting();
10671    format.await;
10672    assert_eq!(
10673        editor.update(cx, |editor, cx| editor.text(cx)),
10674        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
10675    );
10676}
10677
10678#[gpui::test]
10679async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
10680    init_test(cx, |_| {});
10681
10682    let mut cx = EditorLspTestContext::new_rust(
10683        lsp::ServerCapabilities {
10684            document_formatting_provider: Some(lsp::OneOf::Left(true)),
10685            ..Default::default()
10686        },
10687        cx,
10688    )
10689    .await;
10690
10691    cx.set_state(indoc! {"
10692        one.twoˇ
10693    "});
10694
10695    // The format request takes a long time. When it completes, it inserts
10696    // a newline and an indent before the `.`
10697    cx.lsp
10698        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
10699            let executor = cx.background_executor().clone();
10700            async move {
10701                executor.timer(Duration::from_millis(100)).await;
10702                Ok(Some(vec![lsp::TextEdit {
10703                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
10704                    new_text: "\n    ".into(),
10705                }]))
10706            }
10707        });
10708
10709    // Submit a format request.
10710    let format_1 = cx
10711        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10712        .unwrap();
10713    cx.executor().run_until_parked();
10714
10715    // Submit a second format request.
10716    let format_2 = cx
10717        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10718        .unwrap();
10719    cx.executor().run_until_parked();
10720
10721    // Wait for both format requests to complete
10722    cx.executor().advance_clock(Duration::from_millis(200));
10723    cx.executor().start_waiting();
10724    format_1.await.unwrap();
10725    cx.executor().start_waiting();
10726    format_2.await.unwrap();
10727
10728    // The formatting edits only happens once.
10729    cx.assert_editor_state(indoc! {"
10730        one
10731            .twoˇ
10732    "});
10733}
10734
10735#[gpui::test]
10736async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
10737    init_test(cx, |settings| {
10738        settings.defaults.formatter = Some(SelectedFormatter::Auto)
10739    });
10740
10741    let mut cx = EditorLspTestContext::new_rust(
10742        lsp::ServerCapabilities {
10743            document_formatting_provider: Some(lsp::OneOf::Left(true)),
10744            ..Default::default()
10745        },
10746        cx,
10747    )
10748    .await;
10749
10750    // Set up a buffer white some trailing whitespace and no trailing newline.
10751    cx.set_state(
10752        &[
10753            "one ",   //
10754            "twoˇ",   //
10755            "three ", //
10756            "four",   //
10757        ]
10758        .join("\n"),
10759    );
10760
10761    // Submit a format request.
10762    let format = cx
10763        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10764        .unwrap();
10765
10766    // Record which buffer changes have been sent to the language server
10767    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
10768    cx.lsp
10769        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
10770            let buffer_changes = buffer_changes.clone();
10771            move |params, _| {
10772                buffer_changes.lock().extend(
10773                    params
10774                        .content_changes
10775                        .into_iter()
10776                        .map(|e| (e.range.unwrap(), e.text)),
10777                );
10778            }
10779        });
10780
10781    // Handle formatting requests to the language server.
10782    cx.lsp
10783        .set_request_handler::<lsp::request::Formatting, _, _>({
10784            let buffer_changes = buffer_changes.clone();
10785            move |_, _| {
10786                // When formatting is requested, trailing whitespace has already been stripped,
10787                // and the trailing newline has already been added.
10788                assert_eq!(
10789                    &buffer_changes.lock()[1..],
10790                    &[
10791                        (
10792                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
10793                            "".into()
10794                        ),
10795                        (
10796                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
10797                            "".into()
10798                        ),
10799                        (
10800                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
10801                            "\n".into()
10802                        ),
10803                    ]
10804                );
10805
10806                // Insert blank lines between each line of the buffer.
10807                async move {
10808                    Ok(Some(vec![
10809                        lsp::TextEdit {
10810                            range: lsp::Range::new(
10811                                lsp::Position::new(1, 0),
10812                                lsp::Position::new(1, 0),
10813                            ),
10814                            new_text: "\n".into(),
10815                        },
10816                        lsp::TextEdit {
10817                            range: lsp::Range::new(
10818                                lsp::Position::new(2, 0),
10819                                lsp::Position::new(2, 0),
10820                            ),
10821                            new_text: "\n".into(),
10822                        },
10823                    ]))
10824                }
10825            }
10826        });
10827
10828    // After formatting the buffer, the trailing whitespace is stripped,
10829    // a newline is appended, and the edits provided by the language server
10830    // have been applied.
10831    format.await.unwrap();
10832    cx.assert_editor_state(
10833        &[
10834            "one",   //
10835            "",      //
10836            "twoˇ",  //
10837            "",      //
10838            "three", //
10839            "four",  //
10840            "",      //
10841        ]
10842        .join("\n"),
10843    );
10844
10845    // Undoing the formatting undoes the trailing whitespace removal, the
10846    // trailing newline, and the LSP edits.
10847    cx.update_buffer(|buffer, cx| buffer.undo(cx));
10848    cx.assert_editor_state(
10849        &[
10850            "one ",   //
10851            "twoˇ",   //
10852            "three ", //
10853            "four",   //
10854        ]
10855        .join("\n"),
10856    );
10857}
10858
10859#[gpui::test]
10860async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
10861    cx: &mut TestAppContext,
10862) {
10863    init_test(cx, |_| {});
10864
10865    cx.update(|cx| {
10866        cx.update_global::<SettingsStore, _>(|settings, cx| {
10867            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10868                settings.auto_signature_help = Some(true);
10869            });
10870        });
10871    });
10872
10873    let mut cx = EditorLspTestContext::new_rust(
10874        lsp::ServerCapabilities {
10875            signature_help_provider: Some(lsp::SignatureHelpOptions {
10876                ..Default::default()
10877            }),
10878            ..Default::default()
10879        },
10880        cx,
10881    )
10882    .await;
10883
10884    let language = Language::new(
10885        LanguageConfig {
10886            name: "Rust".into(),
10887            brackets: BracketPairConfig {
10888                pairs: vec![
10889                    BracketPair {
10890                        start: "{".to_string(),
10891                        end: "}".to_string(),
10892                        close: true,
10893                        surround: true,
10894                        newline: true,
10895                    },
10896                    BracketPair {
10897                        start: "(".to_string(),
10898                        end: ")".to_string(),
10899                        close: true,
10900                        surround: true,
10901                        newline: true,
10902                    },
10903                    BracketPair {
10904                        start: "/*".to_string(),
10905                        end: " */".to_string(),
10906                        close: true,
10907                        surround: true,
10908                        newline: true,
10909                    },
10910                    BracketPair {
10911                        start: "[".to_string(),
10912                        end: "]".to_string(),
10913                        close: false,
10914                        surround: false,
10915                        newline: true,
10916                    },
10917                    BracketPair {
10918                        start: "\"".to_string(),
10919                        end: "\"".to_string(),
10920                        close: true,
10921                        surround: true,
10922                        newline: false,
10923                    },
10924                    BracketPair {
10925                        start: "<".to_string(),
10926                        end: ">".to_string(),
10927                        close: false,
10928                        surround: true,
10929                        newline: true,
10930                    },
10931                ],
10932                ..Default::default()
10933            },
10934            autoclose_before: "})]".to_string(),
10935            ..Default::default()
10936        },
10937        Some(tree_sitter_rust::LANGUAGE.into()),
10938    );
10939    let language = Arc::new(language);
10940
10941    cx.language_registry().add(language.clone());
10942    cx.update_buffer(|buffer, cx| {
10943        buffer.set_language(Some(language), cx);
10944    });
10945
10946    cx.set_state(
10947        &r#"
10948            fn main() {
10949                sampleˇ
10950            }
10951        "#
10952        .unindent(),
10953    );
10954
10955    cx.update_editor(|editor, window, cx| {
10956        editor.handle_input("(", window, cx);
10957    });
10958    cx.assert_editor_state(
10959        &"
10960            fn main() {
10961                sample(ˇ)
10962            }
10963        "
10964        .unindent(),
10965    );
10966
10967    let mocked_response = lsp::SignatureHelp {
10968        signatures: vec![lsp::SignatureInformation {
10969            label: "fn sample(param1: u8, param2: u8)".to_string(),
10970            documentation: None,
10971            parameters: Some(vec![
10972                lsp::ParameterInformation {
10973                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10974                    documentation: None,
10975                },
10976                lsp::ParameterInformation {
10977                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10978                    documentation: None,
10979                },
10980            ]),
10981            active_parameter: None,
10982        }],
10983        active_signature: Some(0),
10984        active_parameter: Some(0),
10985    };
10986    handle_signature_help_request(&mut cx, mocked_response).await;
10987
10988    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10989        .await;
10990
10991    cx.editor(|editor, _, _| {
10992        let signature_help_state = editor.signature_help_state.popover().cloned();
10993        let signature = signature_help_state.unwrap();
10994        assert_eq!(
10995            signature.signatures[signature.current_signature].label,
10996            "fn sample(param1: u8, param2: u8)"
10997        );
10998    });
10999}
11000
11001#[gpui::test]
11002async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
11003    init_test(cx, |_| {});
11004
11005    cx.update(|cx| {
11006        cx.update_global::<SettingsStore, _>(|settings, cx| {
11007            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11008                settings.auto_signature_help = Some(false);
11009                settings.show_signature_help_after_edits = Some(false);
11010            });
11011        });
11012    });
11013
11014    let mut cx = EditorLspTestContext::new_rust(
11015        lsp::ServerCapabilities {
11016            signature_help_provider: Some(lsp::SignatureHelpOptions {
11017                ..Default::default()
11018            }),
11019            ..Default::default()
11020        },
11021        cx,
11022    )
11023    .await;
11024
11025    let language = Language::new(
11026        LanguageConfig {
11027            name: "Rust".into(),
11028            brackets: BracketPairConfig {
11029                pairs: vec![
11030                    BracketPair {
11031                        start: "{".to_string(),
11032                        end: "}".to_string(),
11033                        close: true,
11034                        surround: true,
11035                        newline: true,
11036                    },
11037                    BracketPair {
11038                        start: "(".to_string(),
11039                        end: ")".to_string(),
11040                        close: true,
11041                        surround: true,
11042                        newline: true,
11043                    },
11044                    BracketPair {
11045                        start: "/*".to_string(),
11046                        end: " */".to_string(),
11047                        close: true,
11048                        surround: true,
11049                        newline: true,
11050                    },
11051                    BracketPair {
11052                        start: "[".to_string(),
11053                        end: "]".to_string(),
11054                        close: false,
11055                        surround: false,
11056                        newline: true,
11057                    },
11058                    BracketPair {
11059                        start: "\"".to_string(),
11060                        end: "\"".to_string(),
11061                        close: true,
11062                        surround: true,
11063                        newline: false,
11064                    },
11065                    BracketPair {
11066                        start: "<".to_string(),
11067                        end: ">".to_string(),
11068                        close: false,
11069                        surround: true,
11070                        newline: true,
11071                    },
11072                ],
11073                ..Default::default()
11074            },
11075            autoclose_before: "})]".to_string(),
11076            ..Default::default()
11077        },
11078        Some(tree_sitter_rust::LANGUAGE.into()),
11079    );
11080    let language = Arc::new(language);
11081
11082    cx.language_registry().add(language.clone());
11083    cx.update_buffer(|buffer, cx| {
11084        buffer.set_language(Some(language), cx);
11085    });
11086
11087    // Ensure that signature_help is not called when no signature help is enabled.
11088    cx.set_state(
11089        &r#"
11090            fn main() {
11091                sampleˇ
11092            }
11093        "#
11094        .unindent(),
11095    );
11096    cx.update_editor(|editor, window, cx| {
11097        editor.handle_input("(", window, cx);
11098    });
11099    cx.assert_editor_state(
11100        &"
11101            fn main() {
11102                sample(ˇ)
11103            }
11104        "
11105        .unindent(),
11106    );
11107    cx.editor(|editor, _, _| {
11108        assert!(editor.signature_help_state.task().is_none());
11109    });
11110
11111    let mocked_response = lsp::SignatureHelp {
11112        signatures: vec![lsp::SignatureInformation {
11113            label: "fn sample(param1: u8, param2: u8)".to_string(),
11114            documentation: None,
11115            parameters: Some(vec![
11116                lsp::ParameterInformation {
11117                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11118                    documentation: None,
11119                },
11120                lsp::ParameterInformation {
11121                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11122                    documentation: None,
11123                },
11124            ]),
11125            active_parameter: None,
11126        }],
11127        active_signature: Some(0),
11128        active_parameter: Some(0),
11129    };
11130
11131    // Ensure that signature_help is called when enabled afte edits
11132    cx.update(|_, cx| {
11133        cx.update_global::<SettingsStore, _>(|settings, cx| {
11134            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11135                settings.auto_signature_help = Some(false);
11136                settings.show_signature_help_after_edits = Some(true);
11137            });
11138        });
11139    });
11140    cx.set_state(
11141        &r#"
11142            fn main() {
11143                sampleˇ
11144            }
11145        "#
11146        .unindent(),
11147    );
11148    cx.update_editor(|editor, window, cx| {
11149        editor.handle_input("(", window, cx);
11150    });
11151    cx.assert_editor_state(
11152        &"
11153            fn main() {
11154                sample(ˇ)
11155            }
11156        "
11157        .unindent(),
11158    );
11159    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11160    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11161        .await;
11162    cx.update_editor(|editor, _, _| {
11163        let signature_help_state = editor.signature_help_state.popover().cloned();
11164        assert!(signature_help_state.is_some());
11165        let signature = signature_help_state.unwrap();
11166        assert_eq!(
11167            signature.signatures[signature.current_signature].label,
11168            "fn sample(param1: u8, param2: u8)"
11169        );
11170        editor.signature_help_state = SignatureHelpState::default();
11171    });
11172
11173    // Ensure that signature_help is called when auto signature help override is enabled
11174    cx.update(|_, cx| {
11175        cx.update_global::<SettingsStore, _>(|settings, cx| {
11176            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11177                settings.auto_signature_help = Some(true);
11178                settings.show_signature_help_after_edits = Some(false);
11179            });
11180        });
11181    });
11182    cx.set_state(
11183        &r#"
11184            fn main() {
11185                sampleˇ
11186            }
11187        "#
11188        .unindent(),
11189    );
11190    cx.update_editor(|editor, window, cx| {
11191        editor.handle_input("(", window, cx);
11192    });
11193    cx.assert_editor_state(
11194        &"
11195            fn main() {
11196                sample(ˇ)
11197            }
11198        "
11199        .unindent(),
11200    );
11201    handle_signature_help_request(&mut cx, mocked_response).await;
11202    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11203        .await;
11204    cx.editor(|editor, _, _| {
11205        let signature_help_state = editor.signature_help_state.popover().cloned();
11206        assert!(signature_help_state.is_some());
11207        let signature = signature_help_state.unwrap();
11208        assert_eq!(
11209            signature.signatures[signature.current_signature].label,
11210            "fn sample(param1: u8, param2: u8)"
11211        );
11212    });
11213}
11214
11215#[gpui::test]
11216async fn test_signature_help(cx: &mut TestAppContext) {
11217    init_test(cx, |_| {});
11218    cx.update(|cx| {
11219        cx.update_global::<SettingsStore, _>(|settings, cx| {
11220            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11221                settings.auto_signature_help = Some(true);
11222            });
11223        });
11224    });
11225
11226    let mut cx = EditorLspTestContext::new_rust(
11227        lsp::ServerCapabilities {
11228            signature_help_provider: Some(lsp::SignatureHelpOptions {
11229                ..Default::default()
11230            }),
11231            ..Default::default()
11232        },
11233        cx,
11234    )
11235    .await;
11236
11237    // A test that directly calls `show_signature_help`
11238    cx.update_editor(|editor, window, cx| {
11239        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11240    });
11241
11242    let mocked_response = lsp::SignatureHelp {
11243        signatures: vec![lsp::SignatureInformation {
11244            label: "fn sample(param1: u8, param2: u8)".to_string(),
11245            documentation: None,
11246            parameters: Some(vec![
11247                lsp::ParameterInformation {
11248                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11249                    documentation: None,
11250                },
11251                lsp::ParameterInformation {
11252                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11253                    documentation: None,
11254                },
11255            ]),
11256            active_parameter: None,
11257        }],
11258        active_signature: Some(0),
11259        active_parameter: Some(0),
11260    };
11261    handle_signature_help_request(&mut cx, mocked_response).await;
11262
11263    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11264        .await;
11265
11266    cx.editor(|editor, _, _| {
11267        let signature_help_state = editor.signature_help_state.popover().cloned();
11268        assert!(signature_help_state.is_some());
11269        let signature = signature_help_state.unwrap();
11270        assert_eq!(
11271            signature.signatures[signature.current_signature].label,
11272            "fn sample(param1: u8, param2: u8)"
11273        );
11274    });
11275
11276    // When exiting outside from inside the brackets, `signature_help` is closed.
11277    cx.set_state(indoc! {"
11278        fn main() {
11279            sample(ˇ);
11280        }
11281
11282        fn sample(param1: u8, param2: u8) {}
11283    "});
11284
11285    cx.update_editor(|editor, window, cx| {
11286        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11287            s.select_ranges([0..0])
11288        });
11289    });
11290
11291    let mocked_response = lsp::SignatureHelp {
11292        signatures: Vec::new(),
11293        active_signature: None,
11294        active_parameter: None,
11295    };
11296    handle_signature_help_request(&mut cx, mocked_response).await;
11297
11298    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11299        .await;
11300
11301    cx.editor(|editor, _, _| {
11302        assert!(!editor.signature_help_state.is_shown());
11303    });
11304
11305    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
11306    cx.set_state(indoc! {"
11307        fn main() {
11308            sample(ˇ);
11309        }
11310
11311        fn sample(param1: u8, param2: u8) {}
11312    "});
11313
11314    let mocked_response = lsp::SignatureHelp {
11315        signatures: vec![lsp::SignatureInformation {
11316            label: "fn sample(param1: u8, param2: u8)".to_string(),
11317            documentation: None,
11318            parameters: Some(vec![
11319                lsp::ParameterInformation {
11320                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11321                    documentation: None,
11322                },
11323                lsp::ParameterInformation {
11324                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11325                    documentation: None,
11326                },
11327            ]),
11328            active_parameter: None,
11329        }],
11330        active_signature: Some(0),
11331        active_parameter: Some(0),
11332    };
11333    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11334    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11335        .await;
11336    cx.editor(|editor, _, _| {
11337        assert!(editor.signature_help_state.is_shown());
11338    });
11339
11340    // Restore the popover with more parameter input
11341    cx.set_state(indoc! {"
11342        fn main() {
11343            sample(param1, param2ˇ);
11344        }
11345
11346        fn sample(param1: u8, param2: u8) {}
11347    "});
11348
11349    let mocked_response = lsp::SignatureHelp {
11350        signatures: vec![lsp::SignatureInformation {
11351            label: "fn sample(param1: u8, param2: u8)".to_string(),
11352            documentation: None,
11353            parameters: Some(vec![
11354                lsp::ParameterInformation {
11355                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11356                    documentation: None,
11357                },
11358                lsp::ParameterInformation {
11359                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11360                    documentation: None,
11361                },
11362            ]),
11363            active_parameter: None,
11364        }],
11365        active_signature: Some(0),
11366        active_parameter: Some(1),
11367    };
11368    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11369    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11370        .await;
11371
11372    // When selecting a range, the popover is gone.
11373    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
11374    cx.update_editor(|editor, window, cx| {
11375        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11376            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11377        })
11378    });
11379    cx.assert_editor_state(indoc! {"
11380        fn main() {
11381            sample(param1, «ˇparam2»);
11382        }
11383
11384        fn sample(param1: u8, param2: u8) {}
11385    "});
11386    cx.editor(|editor, _, _| {
11387        assert!(!editor.signature_help_state.is_shown());
11388    });
11389
11390    // When unselecting again, the popover is back if within the brackets.
11391    cx.update_editor(|editor, window, cx| {
11392        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11393            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11394        })
11395    });
11396    cx.assert_editor_state(indoc! {"
11397        fn main() {
11398            sample(param1, ˇparam2);
11399        }
11400
11401        fn sample(param1: u8, param2: u8) {}
11402    "});
11403    handle_signature_help_request(&mut cx, mocked_response).await;
11404    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11405        .await;
11406    cx.editor(|editor, _, _| {
11407        assert!(editor.signature_help_state.is_shown());
11408    });
11409
11410    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
11411    cx.update_editor(|editor, window, cx| {
11412        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11413            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
11414            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11415        })
11416    });
11417    cx.assert_editor_state(indoc! {"
11418        fn main() {
11419            sample(param1, ˇparam2);
11420        }
11421
11422        fn sample(param1: u8, param2: u8) {}
11423    "});
11424
11425    let mocked_response = lsp::SignatureHelp {
11426        signatures: vec![lsp::SignatureInformation {
11427            label: "fn sample(param1: u8, param2: u8)".to_string(),
11428            documentation: None,
11429            parameters: Some(vec![
11430                lsp::ParameterInformation {
11431                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11432                    documentation: None,
11433                },
11434                lsp::ParameterInformation {
11435                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11436                    documentation: None,
11437                },
11438            ]),
11439            active_parameter: None,
11440        }],
11441        active_signature: Some(0),
11442        active_parameter: Some(1),
11443    };
11444    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11445    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11446        .await;
11447    cx.update_editor(|editor, _, cx| {
11448        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
11449    });
11450    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11451        .await;
11452    cx.update_editor(|editor, window, cx| {
11453        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11454            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11455        })
11456    });
11457    cx.assert_editor_state(indoc! {"
11458        fn main() {
11459            sample(param1, «ˇparam2»);
11460        }
11461
11462        fn sample(param1: u8, param2: u8) {}
11463    "});
11464    cx.update_editor(|editor, window, cx| {
11465        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11466            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11467        })
11468    });
11469    cx.assert_editor_state(indoc! {"
11470        fn main() {
11471            sample(param1, ˇparam2);
11472        }
11473
11474        fn sample(param1: u8, param2: u8) {}
11475    "});
11476    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
11477        .await;
11478}
11479
11480#[gpui::test]
11481async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
11482    init_test(cx, |_| {});
11483
11484    let mut cx = EditorLspTestContext::new_rust(
11485        lsp::ServerCapabilities {
11486            signature_help_provider: Some(lsp::SignatureHelpOptions {
11487                ..Default::default()
11488            }),
11489            ..Default::default()
11490        },
11491        cx,
11492    )
11493    .await;
11494
11495    cx.set_state(indoc! {"
11496        fn main() {
11497            overloadedˇ
11498        }
11499    "});
11500
11501    cx.update_editor(|editor, window, cx| {
11502        editor.handle_input("(", window, cx);
11503        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11504    });
11505
11506    // Mock response with 3 signatures
11507    let mocked_response = lsp::SignatureHelp {
11508        signatures: vec![
11509            lsp::SignatureInformation {
11510                label: "fn overloaded(x: i32)".to_string(),
11511                documentation: None,
11512                parameters: Some(vec![lsp::ParameterInformation {
11513                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11514                    documentation: None,
11515                }]),
11516                active_parameter: None,
11517            },
11518            lsp::SignatureInformation {
11519                label: "fn overloaded(x: i32, y: i32)".to_string(),
11520                documentation: None,
11521                parameters: Some(vec![
11522                    lsp::ParameterInformation {
11523                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11524                        documentation: None,
11525                    },
11526                    lsp::ParameterInformation {
11527                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
11528                        documentation: None,
11529                    },
11530                ]),
11531                active_parameter: None,
11532            },
11533            lsp::SignatureInformation {
11534                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
11535                documentation: None,
11536                parameters: Some(vec![
11537                    lsp::ParameterInformation {
11538                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11539                        documentation: None,
11540                    },
11541                    lsp::ParameterInformation {
11542                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
11543                        documentation: None,
11544                    },
11545                    lsp::ParameterInformation {
11546                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
11547                        documentation: None,
11548                    },
11549                ]),
11550                active_parameter: None,
11551            },
11552        ],
11553        active_signature: Some(1),
11554        active_parameter: Some(0),
11555    };
11556    handle_signature_help_request(&mut cx, mocked_response).await;
11557
11558    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11559        .await;
11560
11561    // Verify we have multiple signatures and the right one is selected
11562    cx.editor(|editor, _, _| {
11563        let popover = editor.signature_help_state.popover().cloned().unwrap();
11564        assert_eq!(popover.signatures.len(), 3);
11565        // active_signature was 1, so that should be the current
11566        assert_eq!(popover.current_signature, 1);
11567        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
11568        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
11569        assert_eq!(
11570            popover.signatures[2].label,
11571            "fn overloaded(x: i32, y: i32, z: i32)"
11572        );
11573    });
11574
11575    // Test navigation functionality
11576    cx.update_editor(|editor, window, cx| {
11577        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
11578    });
11579
11580    cx.editor(|editor, _, _| {
11581        let popover = editor.signature_help_state.popover().cloned().unwrap();
11582        assert_eq!(popover.current_signature, 2);
11583    });
11584
11585    // Test wrap around
11586    cx.update_editor(|editor, window, cx| {
11587        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
11588    });
11589
11590    cx.editor(|editor, _, _| {
11591        let popover = editor.signature_help_state.popover().cloned().unwrap();
11592        assert_eq!(popover.current_signature, 0);
11593    });
11594
11595    // Test previous navigation
11596    cx.update_editor(|editor, window, cx| {
11597        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
11598    });
11599
11600    cx.editor(|editor, _, _| {
11601        let popover = editor.signature_help_state.popover().cloned().unwrap();
11602        assert_eq!(popover.current_signature, 2);
11603    });
11604}
11605
11606#[gpui::test]
11607async fn test_completion_mode(cx: &mut TestAppContext) {
11608    init_test(cx, |_| {});
11609    let mut cx = EditorLspTestContext::new_rust(
11610        lsp::ServerCapabilities {
11611            completion_provider: Some(lsp::CompletionOptions {
11612                resolve_provider: Some(true),
11613                ..Default::default()
11614            }),
11615            ..Default::default()
11616        },
11617        cx,
11618    )
11619    .await;
11620
11621    struct Run {
11622        run_description: &'static str,
11623        initial_state: String,
11624        buffer_marked_text: String,
11625        completion_label: &'static str,
11626        completion_text: &'static str,
11627        expected_with_insert_mode: String,
11628        expected_with_replace_mode: String,
11629        expected_with_replace_subsequence_mode: String,
11630        expected_with_replace_suffix_mode: String,
11631    }
11632
11633    let runs = [
11634        Run {
11635            run_description: "Start of word matches completion text",
11636            initial_state: "before ediˇ after".into(),
11637            buffer_marked_text: "before <edi|> after".into(),
11638            completion_label: "editor",
11639            completion_text: "editor",
11640            expected_with_insert_mode: "before editorˇ after".into(),
11641            expected_with_replace_mode: "before editorˇ after".into(),
11642            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11643            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11644        },
11645        Run {
11646            run_description: "Accept same text at the middle of the word",
11647            initial_state: "before ediˇtor after".into(),
11648            buffer_marked_text: "before <edi|tor> after".into(),
11649            completion_label: "editor",
11650            completion_text: "editor",
11651            expected_with_insert_mode: "before editorˇtor after".into(),
11652            expected_with_replace_mode: "before editorˇ after".into(),
11653            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11654            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11655        },
11656        Run {
11657            run_description: "End of word matches completion text -- cursor at end",
11658            initial_state: "before torˇ after".into(),
11659            buffer_marked_text: "before <tor|> after".into(),
11660            completion_label: "editor",
11661            completion_text: "editor",
11662            expected_with_insert_mode: "before editorˇ after".into(),
11663            expected_with_replace_mode: "before editorˇ after".into(),
11664            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11665            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11666        },
11667        Run {
11668            run_description: "End of word matches completion text -- cursor at start",
11669            initial_state: "before ˇtor after".into(),
11670            buffer_marked_text: "before <|tor> after".into(),
11671            completion_label: "editor",
11672            completion_text: "editor",
11673            expected_with_insert_mode: "before editorˇtor after".into(),
11674            expected_with_replace_mode: "before editorˇ after".into(),
11675            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11676            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11677        },
11678        Run {
11679            run_description: "Prepend text containing whitespace",
11680            initial_state: "pˇfield: bool".into(),
11681            buffer_marked_text: "<p|field>: bool".into(),
11682            completion_label: "pub ",
11683            completion_text: "pub ",
11684            expected_with_insert_mode: "pub ˇfield: bool".into(),
11685            expected_with_replace_mode: "pub ˇ: bool".into(),
11686            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
11687            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
11688        },
11689        Run {
11690            run_description: "Add element to start of list",
11691            initial_state: "[element_ˇelement_2]".into(),
11692            buffer_marked_text: "[<element_|element_2>]".into(),
11693            completion_label: "element_1",
11694            completion_text: "element_1",
11695            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
11696            expected_with_replace_mode: "[element_1ˇ]".into(),
11697            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
11698            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
11699        },
11700        Run {
11701            run_description: "Add element to start of list -- first and second elements are equal",
11702            initial_state: "[elˇelement]".into(),
11703            buffer_marked_text: "[<el|element>]".into(),
11704            completion_label: "element",
11705            completion_text: "element",
11706            expected_with_insert_mode: "[elementˇelement]".into(),
11707            expected_with_replace_mode: "[elementˇ]".into(),
11708            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
11709            expected_with_replace_suffix_mode: "[elementˇ]".into(),
11710        },
11711        Run {
11712            run_description: "Ends with matching suffix",
11713            initial_state: "SubˇError".into(),
11714            buffer_marked_text: "<Sub|Error>".into(),
11715            completion_label: "SubscriptionError",
11716            completion_text: "SubscriptionError",
11717            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
11718            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11719            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11720            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
11721        },
11722        Run {
11723            run_description: "Suffix is a subsequence -- contiguous",
11724            initial_state: "SubˇErr".into(),
11725            buffer_marked_text: "<Sub|Err>".into(),
11726            completion_label: "SubscriptionError",
11727            completion_text: "SubscriptionError",
11728            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
11729            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11730            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11731            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
11732        },
11733        Run {
11734            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
11735            initial_state: "Suˇscrirr".into(),
11736            buffer_marked_text: "<Su|scrirr>".into(),
11737            completion_label: "SubscriptionError",
11738            completion_text: "SubscriptionError",
11739            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
11740            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11741            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11742            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
11743        },
11744        Run {
11745            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
11746            initial_state: "foo(indˇix)".into(),
11747            buffer_marked_text: "foo(<ind|ix>)".into(),
11748            completion_label: "node_index",
11749            completion_text: "node_index",
11750            expected_with_insert_mode: "foo(node_indexˇix)".into(),
11751            expected_with_replace_mode: "foo(node_indexˇ)".into(),
11752            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
11753            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
11754        },
11755        Run {
11756            run_description: "Replace range ends before cursor - should extend to cursor",
11757            initial_state: "before editˇo after".into(),
11758            buffer_marked_text: "before <{ed}>it|o after".into(),
11759            completion_label: "editor",
11760            completion_text: "editor",
11761            expected_with_insert_mode: "before editorˇo after".into(),
11762            expected_with_replace_mode: "before editorˇo after".into(),
11763            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
11764            expected_with_replace_suffix_mode: "before editorˇo after".into(),
11765        },
11766        Run {
11767            run_description: "Uses label for suffix matching",
11768            initial_state: "before ediˇtor after".into(),
11769            buffer_marked_text: "before <edi|tor> after".into(),
11770            completion_label: "editor",
11771            completion_text: "editor()",
11772            expected_with_insert_mode: "before editor()ˇtor after".into(),
11773            expected_with_replace_mode: "before editor()ˇ after".into(),
11774            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
11775            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
11776        },
11777        Run {
11778            run_description: "Case insensitive subsequence and suffix matching",
11779            initial_state: "before EDiˇtoR after".into(),
11780            buffer_marked_text: "before <EDi|toR> after".into(),
11781            completion_label: "editor",
11782            completion_text: "editor",
11783            expected_with_insert_mode: "before editorˇtoR after".into(),
11784            expected_with_replace_mode: "before editorˇ after".into(),
11785            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11786            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11787        },
11788    ];
11789
11790    for run in runs {
11791        let run_variations = [
11792            (LspInsertMode::Insert, run.expected_with_insert_mode),
11793            (LspInsertMode::Replace, run.expected_with_replace_mode),
11794            (
11795                LspInsertMode::ReplaceSubsequence,
11796                run.expected_with_replace_subsequence_mode,
11797            ),
11798            (
11799                LspInsertMode::ReplaceSuffix,
11800                run.expected_with_replace_suffix_mode,
11801            ),
11802        ];
11803
11804        for (lsp_insert_mode, expected_text) in run_variations {
11805            eprintln!(
11806                "run = {:?}, mode = {lsp_insert_mode:.?}",
11807                run.run_description,
11808            );
11809
11810            update_test_language_settings(&mut cx, |settings| {
11811                settings.defaults.completions = Some(CompletionSettings {
11812                    lsp_insert_mode,
11813                    words: WordsCompletionMode::Disabled,
11814                    lsp: true,
11815                    lsp_fetch_timeout_ms: 0,
11816                });
11817            });
11818
11819            cx.set_state(&run.initial_state);
11820            cx.update_editor(|editor, window, cx| {
11821                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11822            });
11823
11824            let counter = Arc::new(AtomicUsize::new(0));
11825            handle_completion_request_with_insert_and_replace(
11826                &mut cx,
11827                &run.buffer_marked_text,
11828                vec![(run.completion_label, run.completion_text)],
11829                counter.clone(),
11830            )
11831            .await;
11832            cx.condition(|editor, _| editor.context_menu_visible())
11833                .await;
11834            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11835
11836            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11837                editor
11838                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
11839                    .unwrap()
11840            });
11841            cx.assert_editor_state(&expected_text);
11842            handle_resolve_completion_request(&mut cx, None).await;
11843            apply_additional_edits.await.unwrap();
11844        }
11845    }
11846}
11847
11848#[gpui::test]
11849async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
11850    init_test(cx, |_| {});
11851    let mut cx = EditorLspTestContext::new_rust(
11852        lsp::ServerCapabilities {
11853            completion_provider: Some(lsp::CompletionOptions {
11854                resolve_provider: Some(true),
11855                ..Default::default()
11856            }),
11857            ..Default::default()
11858        },
11859        cx,
11860    )
11861    .await;
11862
11863    let initial_state = "SubˇError";
11864    let buffer_marked_text = "<Sub|Error>";
11865    let completion_text = "SubscriptionError";
11866    let expected_with_insert_mode = "SubscriptionErrorˇError";
11867    let expected_with_replace_mode = "SubscriptionErrorˇ";
11868
11869    update_test_language_settings(&mut cx, |settings| {
11870        settings.defaults.completions = Some(CompletionSettings {
11871            words: WordsCompletionMode::Disabled,
11872            // set the opposite here to ensure that the action is overriding the default behavior
11873            lsp_insert_mode: LspInsertMode::Insert,
11874            lsp: true,
11875            lsp_fetch_timeout_ms: 0,
11876        });
11877    });
11878
11879    cx.set_state(initial_state);
11880    cx.update_editor(|editor, window, cx| {
11881        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11882    });
11883
11884    let counter = Arc::new(AtomicUsize::new(0));
11885    handle_completion_request_with_insert_and_replace(
11886        &mut cx,
11887        &buffer_marked_text,
11888        vec![(completion_text, completion_text)],
11889        counter.clone(),
11890    )
11891    .await;
11892    cx.condition(|editor, _| editor.context_menu_visible())
11893        .await;
11894    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11895
11896    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11897        editor
11898            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11899            .unwrap()
11900    });
11901    cx.assert_editor_state(&expected_with_replace_mode);
11902    handle_resolve_completion_request(&mut cx, None).await;
11903    apply_additional_edits.await.unwrap();
11904
11905    update_test_language_settings(&mut cx, |settings| {
11906        settings.defaults.completions = Some(CompletionSettings {
11907            words: WordsCompletionMode::Disabled,
11908            // set the opposite here to ensure that the action is overriding the default behavior
11909            lsp_insert_mode: LspInsertMode::Replace,
11910            lsp: true,
11911            lsp_fetch_timeout_ms: 0,
11912        });
11913    });
11914
11915    cx.set_state(initial_state);
11916    cx.update_editor(|editor, window, cx| {
11917        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11918    });
11919    handle_completion_request_with_insert_and_replace(
11920        &mut cx,
11921        &buffer_marked_text,
11922        vec![(completion_text, completion_text)],
11923        counter.clone(),
11924    )
11925    .await;
11926    cx.condition(|editor, _| editor.context_menu_visible())
11927        .await;
11928    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
11929
11930    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11931        editor
11932            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
11933            .unwrap()
11934    });
11935    cx.assert_editor_state(&expected_with_insert_mode);
11936    handle_resolve_completion_request(&mut cx, None).await;
11937    apply_additional_edits.await.unwrap();
11938}
11939
11940#[gpui::test]
11941async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
11942    init_test(cx, |_| {});
11943    let mut cx = EditorLspTestContext::new_rust(
11944        lsp::ServerCapabilities {
11945            completion_provider: Some(lsp::CompletionOptions {
11946                resolve_provider: Some(true),
11947                ..Default::default()
11948            }),
11949            ..Default::default()
11950        },
11951        cx,
11952    )
11953    .await;
11954
11955    // scenario: surrounding text matches completion text
11956    let completion_text = "to_offset";
11957    let initial_state = indoc! {"
11958        1. buf.to_offˇsuffix
11959        2. buf.to_offˇsuf
11960        3. buf.to_offˇfix
11961        4. buf.to_offˇ
11962        5. into_offˇensive
11963        6. ˇsuffix
11964        7. let ˇ //
11965        8. aaˇzz
11966        9. buf.to_off«zzzzzˇ»suffix
11967        10. buf.«ˇzzzzz»suffix
11968        11. to_off«ˇzzzzz»
11969
11970        buf.to_offˇsuffix  // newest cursor
11971    "};
11972    let completion_marked_buffer = indoc! {"
11973        1. buf.to_offsuffix
11974        2. buf.to_offsuf
11975        3. buf.to_offfix
11976        4. buf.to_off
11977        5. into_offensive
11978        6. suffix
11979        7. let  //
11980        8. aazz
11981        9. buf.to_offzzzzzsuffix
11982        10. buf.zzzzzsuffix
11983        11. to_offzzzzz
11984
11985        buf.<to_off|suffix>  // newest cursor
11986    "};
11987    let expected = indoc! {"
11988        1. buf.to_offsetˇ
11989        2. buf.to_offsetˇsuf
11990        3. buf.to_offsetˇfix
11991        4. buf.to_offsetˇ
11992        5. into_offsetˇensive
11993        6. to_offsetˇsuffix
11994        7. let to_offsetˇ //
11995        8. aato_offsetˇzz
11996        9. buf.to_offsetˇ
11997        10. buf.to_offsetˇsuffix
11998        11. to_offsetˇ
11999
12000        buf.to_offsetˇ  // newest cursor
12001    "};
12002    cx.set_state(initial_state);
12003    cx.update_editor(|editor, window, cx| {
12004        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12005    });
12006    handle_completion_request_with_insert_and_replace(
12007        &mut cx,
12008        completion_marked_buffer,
12009        vec![(completion_text, completion_text)],
12010        Arc::new(AtomicUsize::new(0)),
12011    )
12012    .await;
12013    cx.condition(|editor, _| editor.context_menu_visible())
12014        .await;
12015    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12016        editor
12017            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12018            .unwrap()
12019    });
12020    cx.assert_editor_state(expected);
12021    handle_resolve_completion_request(&mut cx, None).await;
12022    apply_additional_edits.await.unwrap();
12023
12024    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
12025    let completion_text = "foo_and_bar";
12026    let initial_state = indoc! {"
12027        1. ooanbˇ
12028        2. zooanbˇ
12029        3. ooanbˇz
12030        4. zooanbˇz
12031        5. ooanˇ
12032        6. oanbˇ
12033
12034        ooanbˇ
12035    "};
12036    let completion_marked_buffer = indoc! {"
12037        1. ooanb
12038        2. zooanb
12039        3. ooanbz
12040        4. zooanbz
12041        5. ooan
12042        6. oanb
12043
12044        <ooanb|>
12045    "};
12046    let expected = indoc! {"
12047        1. foo_and_barˇ
12048        2. zfoo_and_barˇ
12049        3. foo_and_barˇz
12050        4. zfoo_and_barˇz
12051        5. ooanfoo_and_barˇ
12052        6. oanbfoo_and_barˇ
12053
12054        foo_and_barˇ
12055    "};
12056    cx.set_state(initial_state);
12057    cx.update_editor(|editor, window, cx| {
12058        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12059    });
12060    handle_completion_request_with_insert_and_replace(
12061        &mut cx,
12062        completion_marked_buffer,
12063        vec![(completion_text, completion_text)],
12064        Arc::new(AtomicUsize::new(0)),
12065    )
12066    .await;
12067    cx.condition(|editor, _| editor.context_menu_visible())
12068        .await;
12069    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12070        editor
12071            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12072            .unwrap()
12073    });
12074    cx.assert_editor_state(expected);
12075    handle_resolve_completion_request(&mut cx, None).await;
12076    apply_additional_edits.await.unwrap();
12077
12078    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
12079    // (expects the same as if it was inserted at the end)
12080    let completion_text = "foo_and_bar";
12081    let initial_state = indoc! {"
12082        1. ooˇanb
12083        2. zooˇanb
12084        3. ooˇanbz
12085        4. zooˇanbz
12086
12087        ooˇanb
12088    "};
12089    let completion_marked_buffer = indoc! {"
12090        1. ooanb
12091        2. zooanb
12092        3. ooanbz
12093        4. zooanbz
12094
12095        <oo|anb>
12096    "};
12097    let expected = indoc! {"
12098        1. foo_and_barˇ
12099        2. zfoo_and_barˇ
12100        3. foo_and_barˇz
12101        4. zfoo_and_barˇz
12102
12103        foo_and_barˇ
12104    "};
12105    cx.set_state(initial_state);
12106    cx.update_editor(|editor, window, cx| {
12107        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12108    });
12109    handle_completion_request_with_insert_and_replace(
12110        &mut cx,
12111        completion_marked_buffer,
12112        vec![(completion_text, completion_text)],
12113        Arc::new(AtomicUsize::new(0)),
12114    )
12115    .await;
12116    cx.condition(|editor, _| editor.context_menu_visible())
12117        .await;
12118    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12119        editor
12120            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12121            .unwrap()
12122    });
12123    cx.assert_editor_state(expected);
12124    handle_resolve_completion_request(&mut cx, None).await;
12125    apply_additional_edits.await.unwrap();
12126}
12127
12128// This used to crash
12129#[gpui::test]
12130async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
12131    init_test(cx, |_| {});
12132
12133    let buffer_text = indoc! {"
12134        fn main() {
12135            10.satu;
12136
12137            //
12138            // separate cursors so they open in different excerpts (manually reproducible)
12139            //
12140
12141            10.satu20;
12142        }
12143    "};
12144    let multibuffer_text_with_selections = indoc! {"
12145        fn main() {
12146            10.satuˇ;
12147
12148            //
12149
12150            //
12151
12152            10.satuˇ20;
12153        }
12154    "};
12155    let expected_multibuffer = indoc! {"
12156        fn main() {
12157            10.saturating_sub()ˇ;
12158
12159            //
12160
12161            //
12162
12163            10.saturating_sub()ˇ;
12164        }
12165    "};
12166
12167    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
12168    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
12169
12170    let fs = FakeFs::new(cx.executor());
12171    fs.insert_tree(
12172        path!("/a"),
12173        json!({
12174            "main.rs": buffer_text,
12175        }),
12176    )
12177    .await;
12178
12179    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12180    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12181    language_registry.add(rust_lang());
12182    let mut fake_servers = language_registry.register_fake_lsp(
12183        "Rust",
12184        FakeLspAdapter {
12185            capabilities: lsp::ServerCapabilities {
12186                completion_provider: Some(lsp::CompletionOptions {
12187                    resolve_provider: None,
12188                    ..lsp::CompletionOptions::default()
12189                }),
12190                ..lsp::ServerCapabilities::default()
12191            },
12192            ..FakeLspAdapter::default()
12193        },
12194    );
12195    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12196    let cx = &mut VisualTestContext::from_window(*workspace, cx);
12197    let buffer = project
12198        .update(cx, |project, cx| {
12199            project.open_local_buffer(path!("/a/main.rs"), cx)
12200        })
12201        .await
12202        .unwrap();
12203
12204    let multi_buffer = cx.new(|cx| {
12205        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
12206        multi_buffer.push_excerpts(
12207            buffer.clone(),
12208            [ExcerptRange::new(0..first_excerpt_end)],
12209            cx,
12210        );
12211        multi_buffer.push_excerpts(
12212            buffer.clone(),
12213            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
12214            cx,
12215        );
12216        multi_buffer
12217    });
12218
12219    let editor = workspace
12220        .update(cx, |_, window, cx| {
12221            cx.new(|cx| {
12222                Editor::new(
12223                    EditorMode::Full {
12224                        scale_ui_elements_with_buffer_font_size: false,
12225                        show_active_line_background: false,
12226                        sized_by_content: false,
12227                    },
12228                    multi_buffer.clone(),
12229                    Some(project.clone()),
12230                    window,
12231                    cx,
12232                )
12233            })
12234        })
12235        .unwrap();
12236
12237    let pane = workspace
12238        .update(cx, |workspace, _, _| workspace.active_pane().clone())
12239        .unwrap();
12240    pane.update_in(cx, |pane, window, cx| {
12241        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
12242    });
12243
12244    let fake_server = fake_servers.next().await.unwrap();
12245
12246    editor.update_in(cx, |editor, window, cx| {
12247        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12248            s.select_ranges([
12249                Point::new(1, 11)..Point::new(1, 11),
12250                Point::new(7, 11)..Point::new(7, 11),
12251            ])
12252        });
12253
12254        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
12255    });
12256
12257    editor.update_in(cx, |editor, window, cx| {
12258        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12259    });
12260
12261    fake_server
12262        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
12263            let completion_item = lsp::CompletionItem {
12264                label: "saturating_sub()".into(),
12265                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
12266                    lsp::InsertReplaceEdit {
12267                        new_text: "saturating_sub()".to_owned(),
12268                        insert: lsp::Range::new(
12269                            lsp::Position::new(7, 7),
12270                            lsp::Position::new(7, 11),
12271                        ),
12272                        replace: lsp::Range::new(
12273                            lsp::Position::new(7, 7),
12274                            lsp::Position::new(7, 13),
12275                        ),
12276                    },
12277                )),
12278                ..lsp::CompletionItem::default()
12279            };
12280
12281            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
12282        })
12283        .next()
12284        .await
12285        .unwrap();
12286
12287    cx.condition(&editor, |editor, _| editor.context_menu_visible())
12288        .await;
12289
12290    editor
12291        .update_in(cx, |editor, window, cx| {
12292            editor
12293                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12294                .unwrap()
12295        })
12296        .await
12297        .unwrap();
12298
12299    editor.update(cx, |editor, cx| {
12300        assert_text_with_selections(editor, expected_multibuffer, cx);
12301    })
12302}
12303
12304#[gpui::test]
12305async fn test_completion(cx: &mut TestAppContext) {
12306    init_test(cx, |_| {});
12307
12308    let mut cx = EditorLspTestContext::new_rust(
12309        lsp::ServerCapabilities {
12310            completion_provider: Some(lsp::CompletionOptions {
12311                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12312                resolve_provider: Some(true),
12313                ..Default::default()
12314            }),
12315            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12316            ..Default::default()
12317        },
12318        cx,
12319    )
12320    .await;
12321    let counter = Arc::new(AtomicUsize::new(0));
12322
12323    cx.set_state(indoc! {"
12324        oneˇ
12325        two
12326        three
12327    "});
12328    cx.simulate_keystroke(".");
12329    handle_completion_request(
12330        indoc! {"
12331            one.|<>
12332            two
12333            three
12334        "},
12335        vec!["first_completion", "second_completion"],
12336        true,
12337        counter.clone(),
12338        &mut cx,
12339    )
12340    .await;
12341    cx.condition(|editor, _| editor.context_menu_visible())
12342        .await;
12343    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12344
12345    let _handler = handle_signature_help_request(
12346        &mut cx,
12347        lsp::SignatureHelp {
12348            signatures: vec![lsp::SignatureInformation {
12349                label: "test signature".to_string(),
12350                documentation: None,
12351                parameters: Some(vec![lsp::ParameterInformation {
12352                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
12353                    documentation: None,
12354                }]),
12355                active_parameter: None,
12356            }],
12357            active_signature: None,
12358            active_parameter: None,
12359        },
12360    );
12361    cx.update_editor(|editor, window, cx| {
12362        assert!(
12363            !editor.signature_help_state.is_shown(),
12364            "No signature help was called for"
12365        );
12366        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12367    });
12368    cx.run_until_parked();
12369    cx.update_editor(|editor, _, _| {
12370        assert!(
12371            !editor.signature_help_state.is_shown(),
12372            "No signature help should be shown when completions menu is open"
12373        );
12374    });
12375
12376    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12377        editor.context_menu_next(&Default::default(), window, cx);
12378        editor
12379            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12380            .unwrap()
12381    });
12382    cx.assert_editor_state(indoc! {"
12383        one.second_completionˇ
12384        two
12385        three
12386    "});
12387
12388    handle_resolve_completion_request(
12389        &mut cx,
12390        Some(vec![
12391            (
12392                //This overlaps with the primary completion edit which is
12393                //misbehavior from the LSP spec, test that we filter it out
12394                indoc! {"
12395                    one.second_ˇcompletion
12396                    two
12397                    threeˇ
12398                "},
12399                "overlapping additional edit",
12400            ),
12401            (
12402                indoc! {"
12403                    one.second_completion
12404                    two
12405                    threeˇ
12406                "},
12407                "\nadditional edit",
12408            ),
12409        ]),
12410    )
12411    .await;
12412    apply_additional_edits.await.unwrap();
12413    cx.assert_editor_state(indoc! {"
12414        one.second_completionˇ
12415        two
12416        three
12417        additional edit
12418    "});
12419
12420    cx.set_state(indoc! {"
12421        one.second_completion
12422        twoˇ
12423        threeˇ
12424        additional edit
12425    "});
12426    cx.simulate_keystroke(" ");
12427    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12428    cx.simulate_keystroke("s");
12429    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12430
12431    cx.assert_editor_state(indoc! {"
12432        one.second_completion
12433        two sˇ
12434        three sˇ
12435        additional edit
12436    "});
12437    handle_completion_request(
12438        indoc! {"
12439            one.second_completion
12440            two s
12441            three <s|>
12442            additional edit
12443        "},
12444        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
12445        true,
12446        counter.clone(),
12447        &mut cx,
12448    )
12449    .await;
12450    cx.condition(|editor, _| editor.context_menu_visible())
12451        .await;
12452    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12453
12454    cx.simulate_keystroke("i");
12455
12456    handle_completion_request(
12457        indoc! {"
12458            one.second_completion
12459            two si
12460            three <si|>
12461            additional edit
12462        "},
12463        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
12464        true,
12465        counter.clone(),
12466        &mut cx,
12467    )
12468    .await;
12469    cx.condition(|editor, _| editor.context_menu_visible())
12470        .await;
12471    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12472
12473    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12474        editor
12475            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12476            .unwrap()
12477    });
12478    cx.assert_editor_state(indoc! {"
12479        one.second_completion
12480        two sixth_completionˇ
12481        three sixth_completionˇ
12482        additional edit
12483    "});
12484
12485    apply_additional_edits.await.unwrap();
12486
12487    update_test_language_settings(&mut cx, |settings| {
12488        settings.defaults.show_completions_on_input = Some(false);
12489    });
12490    cx.set_state("editorˇ");
12491    cx.simulate_keystroke(".");
12492    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12493    cx.simulate_keystrokes("c l o");
12494    cx.assert_editor_state("editor.cloˇ");
12495    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12496    cx.update_editor(|editor, window, cx| {
12497        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12498    });
12499    handle_completion_request(
12500        "editor.<clo|>",
12501        vec!["close", "clobber"],
12502        true,
12503        counter.clone(),
12504        &mut cx,
12505    )
12506    .await;
12507    cx.condition(|editor, _| editor.context_menu_visible())
12508        .await;
12509    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
12510
12511    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12512        editor
12513            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12514            .unwrap()
12515    });
12516    cx.assert_editor_state("editor.clobberˇ");
12517    handle_resolve_completion_request(&mut cx, None).await;
12518    apply_additional_edits.await.unwrap();
12519}
12520
12521#[gpui::test]
12522async fn test_completion_reuse(cx: &mut TestAppContext) {
12523    init_test(cx, |_| {});
12524
12525    let mut cx = EditorLspTestContext::new_rust(
12526        lsp::ServerCapabilities {
12527            completion_provider: Some(lsp::CompletionOptions {
12528                trigger_characters: Some(vec![".".to_string()]),
12529                ..Default::default()
12530            }),
12531            ..Default::default()
12532        },
12533        cx,
12534    )
12535    .await;
12536
12537    let counter = Arc::new(AtomicUsize::new(0));
12538    cx.set_state("objˇ");
12539    cx.simulate_keystroke(".");
12540
12541    // Initial completion request returns complete results
12542    let is_incomplete = false;
12543    handle_completion_request(
12544        "obj.|<>",
12545        vec!["a", "ab", "abc"],
12546        is_incomplete,
12547        counter.clone(),
12548        &mut cx,
12549    )
12550    .await;
12551    cx.run_until_parked();
12552    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12553    cx.assert_editor_state("obj.ˇ");
12554    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12555
12556    // Type "a" - filters existing completions
12557    cx.simulate_keystroke("a");
12558    cx.run_until_parked();
12559    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12560    cx.assert_editor_state("obj.aˇ");
12561    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12562
12563    // Type "b" - filters existing completions
12564    cx.simulate_keystroke("b");
12565    cx.run_until_parked();
12566    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12567    cx.assert_editor_state("obj.abˇ");
12568    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12569
12570    // Type "c" - filters existing completions
12571    cx.simulate_keystroke("c");
12572    cx.run_until_parked();
12573    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12574    cx.assert_editor_state("obj.abcˇ");
12575    check_displayed_completions(vec!["abc"], &mut cx);
12576
12577    // Backspace to delete "c" - filters existing completions
12578    cx.update_editor(|editor, window, cx| {
12579        editor.backspace(&Backspace, window, cx);
12580    });
12581    cx.run_until_parked();
12582    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12583    cx.assert_editor_state("obj.abˇ");
12584    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12585
12586    // Moving cursor to the left dismisses menu.
12587    cx.update_editor(|editor, window, cx| {
12588        editor.move_left(&MoveLeft, window, cx);
12589    });
12590    cx.run_until_parked();
12591    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12592    cx.assert_editor_state("obj.aˇb");
12593    cx.update_editor(|editor, _, _| {
12594        assert_eq!(editor.context_menu_visible(), false);
12595    });
12596
12597    // Type "b" - new request
12598    cx.simulate_keystroke("b");
12599    let is_incomplete = false;
12600    handle_completion_request(
12601        "obj.<ab|>a",
12602        vec!["ab", "abc"],
12603        is_incomplete,
12604        counter.clone(),
12605        &mut cx,
12606    )
12607    .await;
12608    cx.run_until_parked();
12609    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12610    cx.assert_editor_state("obj.abˇb");
12611    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12612
12613    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
12614    cx.update_editor(|editor, window, cx| {
12615        editor.backspace(&Backspace, window, cx);
12616    });
12617    let is_incomplete = false;
12618    handle_completion_request(
12619        "obj.<a|>b",
12620        vec!["a", "ab", "abc"],
12621        is_incomplete,
12622        counter.clone(),
12623        &mut cx,
12624    )
12625    .await;
12626    cx.run_until_parked();
12627    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12628    cx.assert_editor_state("obj.aˇb");
12629    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12630
12631    // Backspace to delete "a" - dismisses menu.
12632    cx.update_editor(|editor, window, cx| {
12633        editor.backspace(&Backspace, window, cx);
12634    });
12635    cx.run_until_parked();
12636    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12637    cx.assert_editor_state("obj.ˇb");
12638    cx.update_editor(|editor, _, _| {
12639        assert_eq!(editor.context_menu_visible(), false);
12640    });
12641}
12642
12643#[gpui::test]
12644async fn test_word_completion(cx: &mut TestAppContext) {
12645    let lsp_fetch_timeout_ms = 10;
12646    init_test(cx, |language_settings| {
12647        language_settings.defaults.completions = Some(CompletionSettings {
12648            words: WordsCompletionMode::Fallback,
12649            lsp: true,
12650            lsp_fetch_timeout_ms: 10,
12651            lsp_insert_mode: LspInsertMode::Insert,
12652        });
12653    });
12654
12655    let mut cx = EditorLspTestContext::new_rust(
12656        lsp::ServerCapabilities {
12657            completion_provider: Some(lsp::CompletionOptions {
12658                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12659                ..lsp::CompletionOptions::default()
12660            }),
12661            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12662            ..lsp::ServerCapabilities::default()
12663        },
12664        cx,
12665    )
12666    .await;
12667
12668    let throttle_completions = Arc::new(AtomicBool::new(false));
12669
12670    let lsp_throttle_completions = throttle_completions.clone();
12671    let _completion_requests_handler =
12672        cx.lsp
12673            .server
12674            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
12675                let lsp_throttle_completions = lsp_throttle_completions.clone();
12676                let cx = cx.clone();
12677                async move {
12678                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
12679                        cx.background_executor()
12680                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
12681                            .await;
12682                    }
12683                    Ok(Some(lsp::CompletionResponse::Array(vec![
12684                        lsp::CompletionItem {
12685                            label: "first".into(),
12686                            ..lsp::CompletionItem::default()
12687                        },
12688                        lsp::CompletionItem {
12689                            label: "last".into(),
12690                            ..lsp::CompletionItem::default()
12691                        },
12692                    ])))
12693                }
12694            });
12695
12696    cx.set_state(indoc! {"
12697        oneˇ
12698        two
12699        three
12700    "});
12701    cx.simulate_keystroke(".");
12702    cx.executor().run_until_parked();
12703    cx.condition(|editor, _| editor.context_menu_visible())
12704        .await;
12705    cx.update_editor(|editor, window, cx| {
12706        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12707        {
12708            assert_eq!(
12709                completion_menu_entries(&menu),
12710                &["first", "last"],
12711                "When LSP server is fast to reply, no fallback word completions are used"
12712            );
12713        } else {
12714            panic!("expected completion menu to be open");
12715        }
12716        editor.cancel(&Cancel, window, cx);
12717    });
12718    cx.executor().run_until_parked();
12719    cx.condition(|editor, _| !editor.context_menu_visible())
12720        .await;
12721
12722    throttle_completions.store(true, atomic::Ordering::Release);
12723    cx.simulate_keystroke(".");
12724    cx.executor()
12725        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
12726    cx.executor().run_until_parked();
12727    cx.condition(|editor, _| editor.context_menu_visible())
12728        .await;
12729    cx.update_editor(|editor, _, _| {
12730        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12731        {
12732            assert_eq!(completion_menu_entries(&menu), &["one", "three", "two"],
12733                "When LSP server is slow, document words can be shown instead, if configured accordingly");
12734        } else {
12735            panic!("expected completion menu to be open");
12736        }
12737    });
12738}
12739
12740#[gpui::test]
12741async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
12742    init_test(cx, |language_settings| {
12743        language_settings.defaults.completions = Some(CompletionSettings {
12744            words: WordsCompletionMode::Enabled,
12745            lsp: true,
12746            lsp_fetch_timeout_ms: 0,
12747            lsp_insert_mode: LspInsertMode::Insert,
12748        });
12749    });
12750
12751    let mut cx = EditorLspTestContext::new_rust(
12752        lsp::ServerCapabilities {
12753            completion_provider: Some(lsp::CompletionOptions {
12754                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12755                ..lsp::CompletionOptions::default()
12756            }),
12757            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12758            ..lsp::ServerCapabilities::default()
12759        },
12760        cx,
12761    )
12762    .await;
12763
12764    let _completion_requests_handler =
12765        cx.lsp
12766            .server
12767            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12768                Ok(Some(lsp::CompletionResponse::Array(vec![
12769                    lsp::CompletionItem {
12770                        label: "first".into(),
12771                        ..lsp::CompletionItem::default()
12772                    },
12773                    lsp::CompletionItem {
12774                        label: "last".into(),
12775                        ..lsp::CompletionItem::default()
12776                    },
12777                ])))
12778            });
12779
12780    cx.set_state(indoc! {"ˇ
12781        first
12782        last
12783        second
12784    "});
12785    cx.simulate_keystroke(".");
12786    cx.executor().run_until_parked();
12787    cx.condition(|editor, _| editor.context_menu_visible())
12788        .await;
12789    cx.update_editor(|editor, _, _| {
12790        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12791        {
12792            assert_eq!(
12793                completion_menu_entries(&menu),
12794                &["first", "last", "second"],
12795                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
12796            );
12797        } else {
12798            panic!("expected completion menu to be open");
12799        }
12800    });
12801}
12802
12803#[gpui::test]
12804async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
12805    init_test(cx, |language_settings| {
12806        language_settings.defaults.completions = Some(CompletionSettings {
12807            words: WordsCompletionMode::Disabled,
12808            lsp: true,
12809            lsp_fetch_timeout_ms: 0,
12810            lsp_insert_mode: LspInsertMode::Insert,
12811        });
12812    });
12813
12814    let mut cx = EditorLspTestContext::new_rust(
12815        lsp::ServerCapabilities {
12816            completion_provider: Some(lsp::CompletionOptions {
12817                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12818                ..lsp::CompletionOptions::default()
12819            }),
12820            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12821            ..lsp::ServerCapabilities::default()
12822        },
12823        cx,
12824    )
12825    .await;
12826
12827    let _completion_requests_handler =
12828        cx.lsp
12829            .server
12830            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12831                panic!("LSP completions should not be queried when dealing with word completions")
12832            });
12833
12834    cx.set_state(indoc! {"ˇ
12835        first
12836        last
12837        second
12838    "});
12839    cx.update_editor(|editor, window, cx| {
12840        editor.show_word_completions(&ShowWordCompletions, window, cx);
12841    });
12842    cx.executor().run_until_parked();
12843    cx.condition(|editor, _| editor.context_menu_visible())
12844        .await;
12845    cx.update_editor(|editor, _, _| {
12846        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12847        {
12848            assert_eq!(
12849                completion_menu_entries(&menu),
12850                &["first", "last", "second"],
12851                "`ShowWordCompletions` action should show word completions"
12852            );
12853        } else {
12854            panic!("expected completion menu to be open");
12855        }
12856    });
12857
12858    cx.simulate_keystroke("l");
12859    cx.executor().run_until_parked();
12860    cx.condition(|editor, _| editor.context_menu_visible())
12861        .await;
12862    cx.update_editor(|editor, _, _| {
12863        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12864        {
12865            assert_eq!(
12866                completion_menu_entries(&menu),
12867                &["last"],
12868                "After showing word completions, further editing should filter them and not query the LSP"
12869            );
12870        } else {
12871            panic!("expected completion menu to be open");
12872        }
12873    });
12874}
12875
12876#[gpui::test]
12877async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
12878    init_test(cx, |language_settings| {
12879        language_settings.defaults.completions = Some(CompletionSettings {
12880            words: WordsCompletionMode::Fallback,
12881            lsp: false,
12882            lsp_fetch_timeout_ms: 0,
12883            lsp_insert_mode: LspInsertMode::Insert,
12884        });
12885    });
12886
12887    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12888
12889    cx.set_state(indoc! {"ˇ
12890        0_usize
12891        let
12892        33
12893        4.5f32
12894    "});
12895    cx.update_editor(|editor, window, cx| {
12896        editor.show_completions(&ShowCompletions::default(), window, cx);
12897    });
12898    cx.executor().run_until_parked();
12899    cx.condition(|editor, _| editor.context_menu_visible())
12900        .await;
12901    cx.update_editor(|editor, window, cx| {
12902        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12903        {
12904            assert_eq!(
12905                completion_menu_entries(&menu),
12906                &["let"],
12907                "With no digits in the completion query, no digits should be in the word completions"
12908            );
12909        } else {
12910            panic!("expected completion menu to be open");
12911        }
12912        editor.cancel(&Cancel, window, cx);
12913    });
12914
12915    cx.set_state(indoc! {"12916        0_usize
12917        let
12918        3
12919        33.35f32
12920    "});
12921    cx.update_editor(|editor, window, cx| {
12922        editor.show_completions(&ShowCompletions::default(), window, cx);
12923    });
12924    cx.executor().run_until_parked();
12925    cx.condition(|editor, _| editor.context_menu_visible())
12926        .await;
12927    cx.update_editor(|editor, _, _| {
12928        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12929        {
12930            assert_eq!(completion_menu_entries(&menu), &["33", "35f32"], "The digit is in the completion query, \
12931                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
12932        } else {
12933            panic!("expected completion menu to be open");
12934        }
12935    });
12936}
12937
12938fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
12939    let position = || lsp::Position {
12940        line: params.text_document_position.position.line,
12941        character: params.text_document_position.position.character,
12942    };
12943    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12944        range: lsp::Range {
12945            start: position(),
12946            end: position(),
12947        },
12948        new_text: text.to_string(),
12949    }))
12950}
12951
12952#[gpui::test]
12953async fn test_multiline_completion(cx: &mut TestAppContext) {
12954    init_test(cx, |_| {});
12955
12956    let fs = FakeFs::new(cx.executor());
12957    fs.insert_tree(
12958        path!("/a"),
12959        json!({
12960            "main.ts": "a",
12961        }),
12962    )
12963    .await;
12964
12965    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12966    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12967    let typescript_language = Arc::new(Language::new(
12968        LanguageConfig {
12969            name: "TypeScript".into(),
12970            matcher: LanguageMatcher {
12971                path_suffixes: vec!["ts".to_string()],
12972                ..LanguageMatcher::default()
12973            },
12974            line_comments: vec!["// ".into()],
12975            ..LanguageConfig::default()
12976        },
12977        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12978    ));
12979    language_registry.add(typescript_language.clone());
12980    let mut fake_servers = language_registry.register_fake_lsp(
12981        "TypeScript",
12982        FakeLspAdapter {
12983            capabilities: lsp::ServerCapabilities {
12984                completion_provider: Some(lsp::CompletionOptions {
12985                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12986                    ..lsp::CompletionOptions::default()
12987                }),
12988                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12989                ..lsp::ServerCapabilities::default()
12990            },
12991            // Emulate vtsls label generation
12992            label_for_completion: Some(Box::new(|item, _| {
12993                let text = if let Some(description) = item
12994                    .label_details
12995                    .as_ref()
12996                    .and_then(|label_details| label_details.description.as_ref())
12997                {
12998                    format!("{} {}", item.label, description)
12999                } else if let Some(detail) = &item.detail {
13000                    format!("{} {}", item.label, detail)
13001                } else {
13002                    item.label.clone()
13003                };
13004                let len = text.len();
13005                Some(language::CodeLabel {
13006                    text,
13007                    runs: Vec::new(),
13008                    filter_range: 0..len,
13009                })
13010            })),
13011            ..FakeLspAdapter::default()
13012        },
13013    );
13014    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13015    let cx = &mut VisualTestContext::from_window(*workspace, cx);
13016    let worktree_id = workspace
13017        .update(cx, |workspace, _window, cx| {
13018            workspace.project().update(cx, |project, cx| {
13019                project.worktrees(cx).next().unwrap().read(cx).id()
13020            })
13021        })
13022        .unwrap();
13023    let _buffer = project
13024        .update(cx, |project, cx| {
13025            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
13026        })
13027        .await
13028        .unwrap();
13029    let editor = workspace
13030        .update(cx, |workspace, window, cx| {
13031            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
13032        })
13033        .unwrap()
13034        .await
13035        .unwrap()
13036        .downcast::<Editor>()
13037        .unwrap();
13038    let fake_server = fake_servers.next().await.unwrap();
13039
13040    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
13041    let multiline_label_2 = "a\nb\nc\n";
13042    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
13043    let multiline_description = "d\ne\nf\n";
13044    let multiline_detail_2 = "g\nh\ni\n";
13045
13046    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
13047        move |params, _| async move {
13048            Ok(Some(lsp::CompletionResponse::Array(vec![
13049                lsp::CompletionItem {
13050                    label: multiline_label.to_string(),
13051                    text_edit: gen_text_edit(&params, "new_text_1"),
13052                    ..lsp::CompletionItem::default()
13053                },
13054                lsp::CompletionItem {
13055                    label: "single line label 1".to_string(),
13056                    detail: Some(multiline_detail.to_string()),
13057                    text_edit: gen_text_edit(&params, "new_text_2"),
13058                    ..lsp::CompletionItem::default()
13059                },
13060                lsp::CompletionItem {
13061                    label: "single line label 2".to_string(),
13062                    label_details: Some(lsp::CompletionItemLabelDetails {
13063                        description: Some(multiline_description.to_string()),
13064                        detail: None,
13065                    }),
13066                    text_edit: gen_text_edit(&params, "new_text_2"),
13067                    ..lsp::CompletionItem::default()
13068                },
13069                lsp::CompletionItem {
13070                    label: multiline_label_2.to_string(),
13071                    detail: Some(multiline_detail_2.to_string()),
13072                    text_edit: gen_text_edit(&params, "new_text_3"),
13073                    ..lsp::CompletionItem::default()
13074                },
13075                lsp::CompletionItem {
13076                    label: "Label with many     spaces and \t but without newlines".to_string(),
13077                    detail: Some(
13078                        "Details with many     spaces and \t but without newlines".to_string(),
13079                    ),
13080                    text_edit: gen_text_edit(&params, "new_text_4"),
13081                    ..lsp::CompletionItem::default()
13082                },
13083            ])))
13084        },
13085    );
13086
13087    editor.update_in(cx, |editor, window, cx| {
13088        cx.focus_self(window);
13089        editor.move_to_end(&MoveToEnd, window, cx);
13090        editor.handle_input(".", window, cx);
13091    });
13092    cx.run_until_parked();
13093    completion_handle.next().await.unwrap();
13094
13095    editor.update(cx, |editor, _| {
13096        assert!(editor.context_menu_visible());
13097        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13098        {
13099            let completion_labels = menu
13100                .completions
13101                .borrow()
13102                .iter()
13103                .map(|c| c.label.text.clone())
13104                .collect::<Vec<_>>();
13105            assert_eq!(
13106                completion_labels,
13107                &[
13108                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
13109                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
13110                    "single line label 2 d e f ",
13111                    "a b c g h i ",
13112                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
13113                ],
13114                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
13115            );
13116
13117            for completion in menu
13118                .completions
13119                .borrow()
13120                .iter() {
13121                    assert_eq!(
13122                        completion.label.filter_range,
13123                        0..completion.label.text.len(),
13124                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
13125                    );
13126                }
13127        } else {
13128            panic!("expected completion menu to be open");
13129        }
13130    });
13131}
13132
13133#[gpui::test]
13134async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
13135    init_test(cx, |_| {});
13136    let mut cx = EditorLspTestContext::new_rust(
13137        lsp::ServerCapabilities {
13138            completion_provider: Some(lsp::CompletionOptions {
13139                trigger_characters: Some(vec![".".to_string()]),
13140                ..Default::default()
13141            }),
13142            ..Default::default()
13143        },
13144        cx,
13145    )
13146    .await;
13147    cx.lsp
13148        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13149            Ok(Some(lsp::CompletionResponse::Array(vec![
13150                lsp::CompletionItem {
13151                    label: "first".into(),
13152                    ..Default::default()
13153                },
13154                lsp::CompletionItem {
13155                    label: "last".into(),
13156                    ..Default::default()
13157                },
13158            ])))
13159        });
13160    cx.set_state("variableˇ");
13161    cx.simulate_keystroke(".");
13162    cx.executor().run_until_parked();
13163
13164    cx.update_editor(|editor, _, _| {
13165        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13166        {
13167            assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
13168        } else {
13169            panic!("expected completion menu to be open");
13170        }
13171    });
13172
13173    cx.update_editor(|editor, window, cx| {
13174        editor.move_page_down(&MovePageDown::default(), window, cx);
13175        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13176        {
13177            assert!(
13178                menu.selected_item == 1,
13179                "expected PageDown to select the last item from the context menu"
13180            );
13181        } else {
13182            panic!("expected completion menu to stay open after PageDown");
13183        }
13184    });
13185
13186    cx.update_editor(|editor, window, cx| {
13187        editor.move_page_up(&MovePageUp::default(), window, cx);
13188        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13189        {
13190            assert!(
13191                menu.selected_item == 0,
13192                "expected PageUp to select the first item from the context menu"
13193            );
13194        } else {
13195            panic!("expected completion menu to stay open after PageUp");
13196        }
13197    });
13198}
13199
13200#[gpui::test]
13201async fn test_as_is_completions(cx: &mut TestAppContext) {
13202    init_test(cx, |_| {});
13203    let mut cx = EditorLspTestContext::new_rust(
13204        lsp::ServerCapabilities {
13205            completion_provider: Some(lsp::CompletionOptions {
13206                ..Default::default()
13207            }),
13208            ..Default::default()
13209        },
13210        cx,
13211    )
13212    .await;
13213    cx.lsp
13214        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13215            Ok(Some(lsp::CompletionResponse::Array(vec![
13216                lsp::CompletionItem {
13217                    label: "unsafe".into(),
13218                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13219                        range: lsp::Range {
13220                            start: lsp::Position {
13221                                line: 1,
13222                                character: 2,
13223                            },
13224                            end: lsp::Position {
13225                                line: 1,
13226                                character: 3,
13227                            },
13228                        },
13229                        new_text: "unsafe".to_string(),
13230                    })),
13231                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
13232                    ..Default::default()
13233                },
13234            ])))
13235        });
13236    cx.set_state("fn a() {}\n");
13237    cx.executor().run_until_parked();
13238    cx.update_editor(|editor, window, cx| {
13239        editor.show_completions(
13240            &ShowCompletions {
13241                trigger: Some("\n".into()),
13242            },
13243            window,
13244            cx,
13245        );
13246    });
13247    cx.executor().run_until_parked();
13248
13249    cx.update_editor(|editor, window, cx| {
13250        editor.confirm_completion(&Default::default(), window, cx)
13251    });
13252    cx.executor().run_until_parked();
13253    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
13254}
13255
13256#[gpui::test]
13257async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
13258    init_test(cx, |_| {});
13259
13260    let mut cx = EditorLspTestContext::new_rust(
13261        lsp::ServerCapabilities {
13262            completion_provider: Some(lsp::CompletionOptions {
13263                trigger_characters: Some(vec![".".to_string()]),
13264                resolve_provider: Some(true),
13265                ..Default::default()
13266            }),
13267            ..Default::default()
13268        },
13269        cx,
13270    )
13271    .await;
13272
13273    cx.set_state("fn main() { let a = 2ˇ; }");
13274    cx.simulate_keystroke(".");
13275    let completion_item = lsp::CompletionItem {
13276        label: "Some".into(),
13277        kind: Some(lsp::CompletionItemKind::SNIPPET),
13278        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
13279        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
13280            kind: lsp::MarkupKind::Markdown,
13281            value: "```rust\nSome(2)\n```".to_string(),
13282        })),
13283        deprecated: Some(false),
13284        sort_text: Some("Some".to_string()),
13285        filter_text: Some("Some".to_string()),
13286        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
13287        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13288            range: lsp::Range {
13289                start: lsp::Position {
13290                    line: 0,
13291                    character: 22,
13292                },
13293                end: lsp::Position {
13294                    line: 0,
13295                    character: 22,
13296                },
13297            },
13298            new_text: "Some(2)".to_string(),
13299        })),
13300        additional_text_edits: Some(vec![lsp::TextEdit {
13301            range: lsp::Range {
13302                start: lsp::Position {
13303                    line: 0,
13304                    character: 20,
13305                },
13306                end: lsp::Position {
13307                    line: 0,
13308                    character: 22,
13309                },
13310            },
13311            new_text: "".to_string(),
13312        }]),
13313        ..Default::default()
13314    };
13315
13316    let closure_completion_item = completion_item.clone();
13317    let counter = Arc::new(AtomicUsize::new(0));
13318    let counter_clone = counter.clone();
13319    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
13320        let task_completion_item = closure_completion_item.clone();
13321        counter_clone.fetch_add(1, atomic::Ordering::Release);
13322        async move {
13323            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
13324                is_incomplete: true,
13325                item_defaults: None,
13326                items: vec![task_completion_item],
13327            })))
13328        }
13329    });
13330
13331    cx.condition(|editor, _| editor.context_menu_visible())
13332        .await;
13333    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
13334    assert!(request.next().await.is_some());
13335    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13336
13337    cx.simulate_keystrokes("S o m");
13338    cx.condition(|editor, _| editor.context_menu_visible())
13339        .await;
13340    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
13341    assert!(request.next().await.is_some());
13342    assert!(request.next().await.is_some());
13343    assert!(request.next().await.is_some());
13344    request.close();
13345    assert!(request.next().await.is_none());
13346    assert_eq!(
13347        counter.load(atomic::Ordering::Acquire),
13348        4,
13349        "With the completions menu open, only one LSP request should happen per input"
13350    );
13351}
13352
13353#[gpui::test]
13354async fn test_toggle_comment(cx: &mut TestAppContext) {
13355    init_test(cx, |_| {});
13356    let mut cx = EditorTestContext::new(cx).await;
13357    let language = Arc::new(Language::new(
13358        LanguageConfig {
13359            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
13360            ..Default::default()
13361        },
13362        Some(tree_sitter_rust::LANGUAGE.into()),
13363    ));
13364    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
13365
13366    // If multiple selections intersect a line, the line is only toggled once.
13367    cx.set_state(indoc! {"
13368        fn a() {
13369            «//b();
13370            ˇ»// «c();
13371            //ˇ»  d();
13372        }
13373    "});
13374
13375    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13376
13377    cx.assert_editor_state(indoc! {"
13378        fn a() {
13379            «b();
13380            c();
13381            ˇ» d();
13382        }
13383    "});
13384
13385    // The comment prefix is inserted at the same column for every line in a
13386    // selection.
13387    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13388
13389    cx.assert_editor_state(indoc! {"
13390        fn a() {
13391            // «b();
13392            // c();
13393            ˇ»//  d();
13394        }
13395    "});
13396
13397    // If a selection ends at the beginning of a line, that line is not toggled.
13398    cx.set_selections_state(indoc! {"
13399        fn a() {
13400            // b();
13401            «// c();
13402        ˇ»    //  d();
13403        }
13404    "});
13405
13406    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13407
13408    cx.assert_editor_state(indoc! {"
13409        fn a() {
13410            // b();
13411            «c();
13412        ˇ»    //  d();
13413        }
13414    "});
13415
13416    // If a selection span a single line and is empty, the line is toggled.
13417    cx.set_state(indoc! {"
13418        fn a() {
13419            a();
13420            b();
13421        ˇ
13422        }
13423    "});
13424
13425    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13426
13427    cx.assert_editor_state(indoc! {"
13428        fn a() {
13429            a();
13430            b();
13431        //•ˇ
13432        }
13433    "});
13434
13435    // If a selection span multiple lines, empty lines are not toggled.
13436    cx.set_state(indoc! {"
13437        fn a() {
13438            «a();
13439
13440            c();ˇ»
13441        }
13442    "});
13443
13444    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13445
13446    cx.assert_editor_state(indoc! {"
13447        fn a() {
13448            // «a();
13449
13450            // c();ˇ»
13451        }
13452    "});
13453
13454    // If a selection includes multiple comment prefixes, all lines are uncommented.
13455    cx.set_state(indoc! {"
13456        fn a() {
13457            «// a();
13458            /// b();
13459            //! c();ˇ»
13460        }
13461    "});
13462
13463    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13464
13465    cx.assert_editor_state(indoc! {"
13466        fn a() {
13467            «a();
13468            b();
13469            c();ˇ»
13470        }
13471    "});
13472}
13473
13474#[gpui::test]
13475async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
13476    init_test(cx, |_| {});
13477    let mut cx = EditorTestContext::new(cx).await;
13478    let language = Arc::new(Language::new(
13479        LanguageConfig {
13480            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
13481            ..Default::default()
13482        },
13483        Some(tree_sitter_rust::LANGUAGE.into()),
13484    ));
13485    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
13486
13487    let toggle_comments = &ToggleComments {
13488        advance_downwards: false,
13489        ignore_indent: true,
13490    };
13491
13492    // If multiple selections intersect a line, the line is only toggled once.
13493    cx.set_state(indoc! {"
13494        fn a() {
13495        //    «b();
13496        //    c();
13497        //    ˇ» d();
13498        }
13499    "});
13500
13501    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13502
13503    cx.assert_editor_state(indoc! {"
13504        fn a() {
13505            «b();
13506            c();
13507            ˇ» d();
13508        }
13509    "});
13510
13511    // The comment prefix is inserted at the beginning of each line
13512    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13513
13514    cx.assert_editor_state(indoc! {"
13515        fn a() {
13516        //    «b();
13517        //    c();
13518        //    ˇ» d();
13519        }
13520    "});
13521
13522    // If a selection ends at the beginning of a line, that line is not toggled.
13523    cx.set_selections_state(indoc! {"
13524        fn a() {
13525        //    b();
13526        //    «c();
13527        ˇ»//     d();
13528        }
13529    "});
13530
13531    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13532
13533    cx.assert_editor_state(indoc! {"
13534        fn a() {
13535        //    b();
13536            «c();
13537        ˇ»//     d();
13538        }
13539    "});
13540
13541    // If a selection span a single line and is empty, the line is toggled.
13542    cx.set_state(indoc! {"
13543        fn a() {
13544            a();
13545            b();
13546        ˇ
13547        }
13548    "});
13549
13550    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13551
13552    cx.assert_editor_state(indoc! {"
13553        fn a() {
13554            a();
13555            b();
13556        //ˇ
13557        }
13558    "});
13559
13560    // If a selection span multiple lines, empty lines are not toggled.
13561    cx.set_state(indoc! {"
13562        fn a() {
13563            «a();
13564
13565            c();ˇ»
13566        }
13567    "});
13568
13569    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13570
13571    cx.assert_editor_state(indoc! {"
13572        fn a() {
13573        //    «a();
13574
13575        //    c();ˇ»
13576        }
13577    "});
13578
13579    // If a selection includes multiple comment prefixes, all lines are uncommented.
13580    cx.set_state(indoc! {"
13581        fn a() {
13582        //    «a();
13583        ///    b();
13584        //!    c();ˇ»
13585        }
13586    "});
13587
13588    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13589
13590    cx.assert_editor_state(indoc! {"
13591        fn a() {
13592            «a();
13593            b();
13594            c();ˇ»
13595        }
13596    "});
13597}
13598
13599#[gpui::test]
13600async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
13601    init_test(cx, |_| {});
13602
13603    let language = Arc::new(Language::new(
13604        LanguageConfig {
13605            line_comments: vec!["// ".into()],
13606            ..Default::default()
13607        },
13608        Some(tree_sitter_rust::LANGUAGE.into()),
13609    ));
13610
13611    let mut cx = EditorTestContext::new(cx).await;
13612
13613    cx.language_registry().add(language.clone());
13614    cx.update_buffer(|buffer, cx| {
13615        buffer.set_language(Some(language), cx);
13616    });
13617
13618    let toggle_comments = &ToggleComments {
13619        advance_downwards: true,
13620        ignore_indent: false,
13621    };
13622
13623    // Single cursor on one line -> advance
13624    // Cursor moves horizontally 3 characters as well on non-blank line
13625    cx.set_state(indoc!(
13626        "fn a() {
13627             ˇdog();
13628             cat();
13629        }"
13630    ));
13631    cx.update_editor(|editor, window, cx| {
13632        editor.toggle_comments(toggle_comments, window, cx);
13633    });
13634    cx.assert_editor_state(indoc!(
13635        "fn a() {
13636             // dog();
13637             catˇ();
13638        }"
13639    ));
13640
13641    // Single selection on one line -> don't advance
13642    cx.set_state(indoc!(
13643        "fn a() {
13644             «dog()ˇ»;
13645             cat();
13646        }"
13647    ));
13648    cx.update_editor(|editor, window, cx| {
13649        editor.toggle_comments(toggle_comments, window, cx);
13650    });
13651    cx.assert_editor_state(indoc!(
13652        "fn a() {
13653             // «dog()ˇ»;
13654             cat();
13655        }"
13656    ));
13657
13658    // Multiple cursors on one line -> advance
13659    cx.set_state(indoc!(
13660        "fn a() {
13661             ˇdˇog();
13662             cat();
13663        }"
13664    ));
13665    cx.update_editor(|editor, window, cx| {
13666        editor.toggle_comments(toggle_comments, window, cx);
13667    });
13668    cx.assert_editor_state(indoc!(
13669        "fn a() {
13670             // dog();
13671             catˇ(ˇ);
13672        }"
13673    ));
13674
13675    // Multiple cursors on one line, with selection -> don't advance
13676    cx.set_state(indoc!(
13677        "fn a() {
13678             ˇdˇog«()ˇ»;
13679             cat();
13680        }"
13681    ));
13682    cx.update_editor(|editor, window, cx| {
13683        editor.toggle_comments(toggle_comments, window, cx);
13684    });
13685    cx.assert_editor_state(indoc!(
13686        "fn a() {
13687             // ˇdˇog«()ˇ»;
13688             cat();
13689        }"
13690    ));
13691
13692    // Single cursor on one line -> advance
13693    // Cursor moves to column 0 on blank line
13694    cx.set_state(indoc!(
13695        "fn a() {
13696             ˇdog();
13697
13698             cat();
13699        }"
13700    ));
13701    cx.update_editor(|editor, window, cx| {
13702        editor.toggle_comments(toggle_comments, window, cx);
13703    });
13704    cx.assert_editor_state(indoc!(
13705        "fn a() {
13706             // dog();
13707        ˇ
13708             cat();
13709        }"
13710    ));
13711
13712    // Single cursor on one line -> advance
13713    // Cursor starts and ends at column 0
13714    cx.set_state(indoc!(
13715        "fn a() {
13716         ˇ    dog();
13717             cat();
13718        }"
13719    ));
13720    cx.update_editor(|editor, window, cx| {
13721        editor.toggle_comments(toggle_comments, window, cx);
13722    });
13723    cx.assert_editor_state(indoc!(
13724        "fn a() {
13725             // dog();
13726         ˇ    cat();
13727        }"
13728    ));
13729}
13730
13731#[gpui::test]
13732async fn test_toggle_block_comment(cx: &mut TestAppContext) {
13733    init_test(cx, |_| {});
13734
13735    let mut cx = EditorTestContext::new(cx).await;
13736
13737    let html_language = Arc::new(
13738        Language::new(
13739            LanguageConfig {
13740                name: "HTML".into(),
13741                block_comment: Some(("<!-- ".into(), " -->".into())),
13742                ..Default::default()
13743            },
13744            Some(tree_sitter_html::LANGUAGE.into()),
13745        )
13746        .with_injection_query(
13747            r#"
13748            (script_element
13749                (raw_text) @injection.content
13750                (#set! injection.language "javascript"))
13751            "#,
13752        )
13753        .unwrap(),
13754    );
13755
13756    let javascript_language = Arc::new(Language::new(
13757        LanguageConfig {
13758            name: "JavaScript".into(),
13759            line_comments: vec!["// ".into()],
13760            ..Default::default()
13761        },
13762        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
13763    ));
13764
13765    cx.language_registry().add(html_language.clone());
13766    cx.language_registry().add(javascript_language.clone());
13767    cx.update_buffer(|buffer, cx| {
13768        buffer.set_language(Some(html_language), cx);
13769    });
13770
13771    // Toggle comments for empty selections
13772    cx.set_state(
13773        &r#"
13774            <p>A</p>ˇ
13775            <p>B</p>ˇ
13776            <p>C</p>ˇ
13777        "#
13778        .unindent(),
13779    );
13780    cx.update_editor(|editor, window, cx| {
13781        editor.toggle_comments(&ToggleComments::default(), window, cx)
13782    });
13783    cx.assert_editor_state(
13784        &r#"
13785            <!-- <p>A</p>ˇ -->
13786            <!-- <p>B</p>ˇ -->
13787            <!-- <p>C</p>ˇ -->
13788        "#
13789        .unindent(),
13790    );
13791    cx.update_editor(|editor, window, cx| {
13792        editor.toggle_comments(&ToggleComments::default(), window, cx)
13793    });
13794    cx.assert_editor_state(
13795        &r#"
13796            <p>A</p>ˇ
13797            <p>B</p>ˇ
13798            <p>C</p>ˇ
13799        "#
13800        .unindent(),
13801    );
13802
13803    // Toggle comments for mixture of empty and non-empty selections, where
13804    // multiple selections occupy a given line.
13805    cx.set_state(
13806        &r#"
13807            <p>A«</p>
13808            <p>ˇ»B</p>ˇ
13809            <p>C«</p>
13810            <p>ˇ»D</p>ˇ
13811        "#
13812        .unindent(),
13813    );
13814
13815    cx.update_editor(|editor, window, cx| {
13816        editor.toggle_comments(&ToggleComments::default(), window, cx)
13817    });
13818    cx.assert_editor_state(
13819        &r#"
13820            <!-- <p>A«</p>
13821            <p>ˇ»B</p>ˇ -->
13822            <!-- <p>C«</p>
13823            <p>ˇ»D</p>ˇ -->
13824        "#
13825        .unindent(),
13826    );
13827    cx.update_editor(|editor, window, cx| {
13828        editor.toggle_comments(&ToggleComments::default(), window, cx)
13829    });
13830    cx.assert_editor_state(
13831        &r#"
13832            <p>A«</p>
13833            <p>ˇ»B</p>ˇ
13834            <p>C«</p>
13835            <p>ˇ»D</p>ˇ
13836        "#
13837        .unindent(),
13838    );
13839
13840    // Toggle comments when different languages are active for different
13841    // selections.
13842    cx.set_state(
13843        &r#"
13844            ˇ<script>
13845                ˇvar x = new Y();
13846            ˇ</script>
13847        "#
13848        .unindent(),
13849    );
13850    cx.executor().run_until_parked();
13851    cx.update_editor(|editor, window, cx| {
13852        editor.toggle_comments(&ToggleComments::default(), window, cx)
13853    });
13854    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
13855    // Uncommenting and commenting from this position brings in even more wrong artifacts.
13856    cx.assert_editor_state(
13857        &r#"
13858            <!-- ˇ<script> -->
13859                // ˇvar x = new Y();
13860            <!-- ˇ</script> -->
13861        "#
13862        .unindent(),
13863    );
13864}
13865
13866#[gpui::test]
13867fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
13868    init_test(cx, |_| {});
13869
13870    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
13871    let multibuffer = cx.new(|cx| {
13872        let mut multibuffer = MultiBuffer::new(ReadWrite);
13873        multibuffer.push_excerpts(
13874            buffer.clone(),
13875            [
13876                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
13877                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
13878            ],
13879            cx,
13880        );
13881        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
13882        multibuffer
13883    });
13884
13885    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
13886    editor.update_in(cx, |editor, window, cx| {
13887        assert_eq!(editor.text(cx), "aaaa\nbbbb");
13888        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13889            s.select_ranges([
13890                Point::new(0, 0)..Point::new(0, 0),
13891                Point::new(1, 0)..Point::new(1, 0),
13892            ])
13893        });
13894
13895        editor.handle_input("X", window, cx);
13896        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
13897        assert_eq!(
13898            editor.selections.ranges(cx),
13899            [
13900                Point::new(0, 1)..Point::new(0, 1),
13901                Point::new(1, 1)..Point::new(1, 1),
13902            ]
13903        );
13904
13905        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
13906        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13907            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
13908        });
13909        editor.backspace(&Default::default(), window, cx);
13910        assert_eq!(editor.text(cx), "Xa\nbbb");
13911        assert_eq!(
13912            editor.selections.ranges(cx),
13913            [Point::new(1, 0)..Point::new(1, 0)]
13914        );
13915
13916        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13917            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
13918        });
13919        editor.backspace(&Default::default(), window, cx);
13920        assert_eq!(editor.text(cx), "X\nbb");
13921        assert_eq!(
13922            editor.selections.ranges(cx),
13923            [Point::new(0, 1)..Point::new(0, 1)]
13924        );
13925    });
13926}
13927
13928#[gpui::test]
13929fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
13930    init_test(cx, |_| {});
13931
13932    let markers = vec![('[', ']').into(), ('(', ')').into()];
13933    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
13934        indoc! {"
13935            [aaaa
13936            (bbbb]
13937            cccc)",
13938        },
13939        markers.clone(),
13940    );
13941    let excerpt_ranges = markers.into_iter().map(|marker| {
13942        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
13943        ExcerptRange::new(context.clone())
13944    });
13945    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
13946    let multibuffer = cx.new(|cx| {
13947        let mut multibuffer = MultiBuffer::new(ReadWrite);
13948        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
13949        multibuffer
13950    });
13951
13952    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
13953    editor.update_in(cx, |editor, window, cx| {
13954        let (expected_text, selection_ranges) = marked_text_ranges(
13955            indoc! {"
13956                aaaa
13957                bˇbbb
13958                bˇbbˇb
13959                cccc"
13960            },
13961            true,
13962        );
13963        assert_eq!(editor.text(cx), expected_text);
13964        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13965            s.select_ranges(selection_ranges)
13966        });
13967
13968        editor.handle_input("X", window, cx);
13969
13970        let (expected_text, expected_selections) = marked_text_ranges(
13971            indoc! {"
13972                aaaa
13973                bXˇbbXb
13974                bXˇbbXˇb
13975                cccc"
13976            },
13977            false,
13978        );
13979        assert_eq!(editor.text(cx), expected_text);
13980        assert_eq!(editor.selections.ranges(cx), expected_selections);
13981
13982        editor.newline(&Newline, window, cx);
13983        let (expected_text, expected_selections) = marked_text_ranges(
13984            indoc! {"
13985                aaaa
13986                bX
13987                ˇbbX
13988                b
13989                bX
13990                ˇbbX
13991                ˇb
13992                cccc"
13993            },
13994            false,
13995        );
13996        assert_eq!(editor.text(cx), expected_text);
13997        assert_eq!(editor.selections.ranges(cx), expected_selections);
13998    });
13999}
14000
14001#[gpui::test]
14002fn test_refresh_selections(cx: &mut TestAppContext) {
14003    init_test(cx, |_| {});
14004
14005    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
14006    let mut excerpt1_id = None;
14007    let multibuffer = cx.new(|cx| {
14008        let mut multibuffer = MultiBuffer::new(ReadWrite);
14009        excerpt1_id = multibuffer
14010            .push_excerpts(
14011                buffer.clone(),
14012                [
14013                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
14014                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
14015                ],
14016                cx,
14017            )
14018            .into_iter()
14019            .next();
14020        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
14021        multibuffer
14022    });
14023
14024    let editor = cx.add_window(|window, cx| {
14025        let mut editor = build_editor(multibuffer.clone(), window, cx);
14026        let snapshot = editor.snapshot(window, cx);
14027        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14028            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
14029        });
14030        editor.begin_selection(
14031            Point::new(2, 1).to_display_point(&snapshot),
14032            true,
14033            1,
14034            window,
14035            cx,
14036        );
14037        assert_eq!(
14038            editor.selections.ranges(cx),
14039            [
14040                Point::new(1, 3)..Point::new(1, 3),
14041                Point::new(2, 1)..Point::new(2, 1),
14042            ]
14043        );
14044        editor
14045    });
14046
14047    // Refreshing selections is a no-op when excerpts haven't changed.
14048    _ = editor.update(cx, |editor, window, cx| {
14049        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14050        assert_eq!(
14051            editor.selections.ranges(cx),
14052            [
14053                Point::new(1, 3)..Point::new(1, 3),
14054                Point::new(2, 1)..Point::new(2, 1),
14055            ]
14056        );
14057    });
14058
14059    multibuffer.update(cx, |multibuffer, cx| {
14060        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
14061    });
14062    _ = editor.update(cx, |editor, window, cx| {
14063        // Removing an excerpt causes the first selection to become degenerate.
14064        assert_eq!(
14065            editor.selections.ranges(cx),
14066            [
14067                Point::new(0, 0)..Point::new(0, 0),
14068                Point::new(0, 1)..Point::new(0, 1)
14069            ]
14070        );
14071
14072        // Refreshing selections will relocate the first selection to the original buffer
14073        // location.
14074        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14075        assert_eq!(
14076            editor.selections.ranges(cx),
14077            [
14078                Point::new(0, 1)..Point::new(0, 1),
14079                Point::new(0, 3)..Point::new(0, 3)
14080            ]
14081        );
14082        assert!(editor.selections.pending_anchor().is_some());
14083    });
14084}
14085
14086#[gpui::test]
14087fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
14088    init_test(cx, |_| {});
14089
14090    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
14091    let mut excerpt1_id = None;
14092    let multibuffer = cx.new(|cx| {
14093        let mut multibuffer = MultiBuffer::new(ReadWrite);
14094        excerpt1_id = multibuffer
14095            .push_excerpts(
14096                buffer.clone(),
14097                [
14098                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
14099                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
14100                ],
14101                cx,
14102            )
14103            .into_iter()
14104            .next();
14105        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
14106        multibuffer
14107    });
14108
14109    let editor = cx.add_window(|window, cx| {
14110        let mut editor = build_editor(multibuffer.clone(), window, cx);
14111        let snapshot = editor.snapshot(window, cx);
14112        editor.begin_selection(
14113            Point::new(1, 3).to_display_point(&snapshot),
14114            false,
14115            1,
14116            window,
14117            cx,
14118        );
14119        assert_eq!(
14120            editor.selections.ranges(cx),
14121            [Point::new(1, 3)..Point::new(1, 3)]
14122        );
14123        editor
14124    });
14125
14126    multibuffer.update(cx, |multibuffer, cx| {
14127        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
14128    });
14129    _ = editor.update(cx, |editor, window, cx| {
14130        assert_eq!(
14131            editor.selections.ranges(cx),
14132            [Point::new(0, 0)..Point::new(0, 0)]
14133        );
14134
14135        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
14136        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14137        assert_eq!(
14138            editor.selections.ranges(cx),
14139            [Point::new(0, 3)..Point::new(0, 3)]
14140        );
14141        assert!(editor.selections.pending_anchor().is_some());
14142    });
14143}
14144
14145#[gpui::test]
14146async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
14147    init_test(cx, |_| {});
14148
14149    let language = Arc::new(
14150        Language::new(
14151            LanguageConfig {
14152                brackets: BracketPairConfig {
14153                    pairs: vec![
14154                        BracketPair {
14155                            start: "{".to_string(),
14156                            end: "}".to_string(),
14157                            close: true,
14158                            surround: true,
14159                            newline: true,
14160                        },
14161                        BracketPair {
14162                            start: "/* ".to_string(),
14163                            end: " */".to_string(),
14164                            close: true,
14165                            surround: true,
14166                            newline: true,
14167                        },
14168                    ],
14169                    ..Default::default()
14170                },
14171                ..Default::default()
14172            },
14173            Some(tree_sitter_rust::LANGUAGE.into()),
14174        )
14175        .with_indents_query("")
14176        .unwrap(),
14177    );
14178
14179    let text = concat!(
14180        "{   }\n",     //
14181        "  x\n",       //
14182        "  /*   */\n", //
14183        "x\n",         //
14184        "{{} }\n",     //
14185    );
14186
14187    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
14188    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
14189    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
14190    editor
14191        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
14192        .await;
14193
14194    editor.update_in(cx, |editor, window, cx| {
14195        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14196            s.select_display_ranges([
14197                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
14198                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
14199                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
14200            ])
14201        });
14202        editor.newline(&Newline, window, cx);
14203
14204        assert_eq!(
14205            editor.buffer().read(cx).read(cx).text(),
14206            concat!(
14207                "{ \n",    // Suppress rustfmt
14208                "\n",      //
14209                "}\n",     //
14210                "  x\n",   //
14211                "  /* \n", //
14212                "  \n",    //
14213                "  */\n",  //
14214                "x\n",     //
14215                "{{} \n",  //
14216                "}\n",     //
14217            )
14218        );
14219    });
14220}
14221
14222#[gpui::test]
14223fn test_highlighted_ranges(cx: &mut TestAppContext) {
14224    init_test(cx, |_| {});
14225
14226    let editor = cx.add_window(|window, cx| {
14227        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
14228        build_editor(buffer.clone(), window, cx)
14229    });
14230
14231    _ = editor.update(cx, |editor, window, cx| {
14232        struct Type1;
14233        struct Type2;
14234
14235        let buffer = editor.buffer.read(cx).snapshot(cx);
14236
14237        let anchor_range =
14238            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
14239
14240        editor.highlight_background::<Type1>(
14241            &[
14242                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
14243                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
14244                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
14245                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
14246            ],
14247            |_| Hsla::red(),
14248            cx,
14249        );
14250        editor.highlight_background::<Type2>(
14251            &[
14252                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
14253                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
14254                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
14255                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
14256            ],
14257            |_| Hsla::green(),
14258            cx,
14259        );
14260
14261        let snapshot = editor.snapshot(window, cx);
14262        let mut highlighted_ranges = editor.background_highlights_in_range(
14263            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
14264            &snapshot,
14265            cx.theme(),
14266        );
14267        // Enforce a consistent ordering based on color without relying on the ordering of the
14268        // highlight's `TypeId` which is non-executor.
14269        highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
14270        assert_eq!(
14271            highlighted_ranges,
14272            &[
14273                (
14274                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
14275                    Hsla::red(),
14276                ),
14277                (
14278                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
14279                    Hsla::red(),
14280                ),
14281                (
14282                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
14283                    Hsla::green(),
14284                ),
14285                (
14286                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
14287                    Hsla::green(),
14288                ),
14289            ]
14290        );
14291        assert_eq!(
14292            editor.background_highlights_in_range(
14293                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
14294                &snapshot,
14295                cx.theme(),
14296            ),
14297            &[(
14298                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
14299                Hsla::red(),
14300            )]
14301        );
14302    });
14303}
14304
14305#[gpui::test]
14306async fn test_following(cx: &mut TestAppContext) {
14307    init_test(cx, |_| {});
14308
14309    let fs = FakeFs::new(cx.executor());
14310    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
14311
14312    let buffer = project.update(cx, |project, cx| {
14313        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
14314        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
14315    });
14316    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
14317    let follower = cx.update(|cx| {
14318        cx.open_window(
14319            WindowOptions {
14320                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
14321                    gpui::Point::new(px(0.), px(0.)),
14322                    gpui::Point::new(px(10.), px(80.)),
14323                ))),
14324                ..Default::default()
14325            },
14326            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
14327        )
14328        .unwrap()
14329    });
14330
14331    let is_still_following = Rc::new(RefCell::new(true));
14332    let follower_edit_event_count = Rc::new(RefCell::new(0));
14333    let pending_update = Rc::new(RefCell::new(None));
14334    let leader_entity = leader.root(cx).unwrap();
14335    let follower_entity = follower.root(cx).unwrap();
14336    _ = follower.update(cx, {
14337        let update = pending_update.clone();
14338        let is_still_following = is_still_following.clone();
14339        let follower_edit_event_count = follower_edit_event_count.clone();
14340        |_, window, cx| {
14341            cx.subscribe_in(
14342                &leader_entity,
14343                window,
14344                move |_, leader, event, window, cx| {
14345                    leader.read(cx).add_event_to_update_proto(
14346                        event,
14347                        &mut update.borrow_mut(),
14348                        window,
14349                        cx,
14350                    );
14351                },
14352            )
14353            .detach();
14354
14355            cx.subscribe_in(
14356                &follower_entity,
14357                window,
14358                move |_, _, event: &EditorEvent, _window, _cx| {
14359                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
14360                        *is_still_following.borrow_mut() = false;
14361                    }
14362
14363                    if let EditorEvent::BufferEdited = event {
14364                        *follower_edit_event_count.borrow_mut() += 1;
14365                    }
14366                },
14367            )
14368            .detach();
14369        }
14370    });
14371
14372    // Update the selections only
14373    _ = leader.update(cx, |leader, window, cx| {
14374        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14375            s.select_ranges([1..1])
14376        });
14377    });
14378    follower
14379        .update(cx, |follower, window, cx| {
14380            follower.apply_update_proto(
14381                &project,
14382                pending_update.borrow_mut().take().unwrap(),
14383                window,
14384                cx,
14385            )
14386        })
14387        .unwrap()
14388        .await
14389        .unwrap();
14390    _ = follower.update(cx, |follower, _, cx| {
14391        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
14392    });
14393    assert!(*is_still_following.borrow());
14394    assert_eq!(*follower_edit_event_count.borrow(), 0);
14395
14396    // Update the scroll position only
14397    _ = leader.update(cx, |leader, window, cx| {
14398        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
14399    });
14400    follower
14401        .update(cx, |follower, window, cx| {
14402            follower.apply_update_proto(
14403                &project,
14404                pending_update.borrow_mut().take().unwrap(),
14405                window,
14406                cx,
14407            )
14408        })
14409        .unwrap()
14410        .await
14411        .unwrap();
14412    assert_eq!(
14413        follower
14414            .update(cx, |follower, _, cx| follower.scroll_position(cx))
14415            .unwrap(),
14416        gpui::Point::new(1.5, 3.5)
14417    );
14418    assert!(*is_still_following.borrow());
14419    assert_eq!(*follower_edit_event_count.borrow(), 0);
14420
14421    // Update the selections and scroll position. The follower's scroll position is updated
14422    // via autoscroll, not via the leader's exact scroll position.
14423    _ = leader.update(cx, |leader, window, cx| {
14424        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14425            s.select_ranges([0..0])
14426        });
14427        leader.request_autoscroll(Autoscroll::newest(), cx);
14428        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
14429    });
14430    follower
14431        .update(cx, |follower, window, cx| {
14432            follower.apply_update_proto(
14433                &project,
14434                pending_update.borrow_mut().take().unwrap(),
14435                window,
14436                cx,
14437            )
14438        })
14439        .unwrap()
14440        .await
14441        .unwrap();
14442    _ = follower.update(cx, |follower, _, cx| {
14443        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
14444        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
14445    });
14446    assert!(*is_still_following.borrow());
14447
14448    // Creating a pending selection that precedes another selection
14449    _ = leader.update(cx, |leader, window, cx| {
14450        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14451            s.select_ranges([1..1])
14452        });
14453        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
14454    });
14455    follower
14456        .update(cx, |follower, window, cx| {
14457            follower.apply_update_proto(
14458                &project,
14459                pending_update.borrow_mut().take().unwrap(),
14460                window,
14461                cx,
14462            )
14463        })
14464        .unwrap()
14465        .await
14466        .unwrap();
14467    _ = follower.update(cx, |follower, _, cx| {
14468        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
14469    });
14470    assert!(*is_still_following.borrow());
14471
14472    // Extend the pending selection so that it surrounds another selection
14473    _ = leader.update(cx, |leader, window, cx| {
14474        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
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![0..2]);
14490    });
14491
14492    // Scrolling locally breaks the follow
14493    _ = follower.update(cx, |follower, window, cx| {
14494        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
14495        follower.set_scroll_anchor(
14496            ScrollAnchor {
14497                anchor: top_anchor,
14498                offset: gpui::Point::new(0.0, 0.5),
14499            },
14500            window,
14501            cx,
14502        );
14503    });
14504    assert!(!(*is_still_following.borrow()));
14505}
14506
14507#[gpui::test]
14508async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
14509    init_test(cx, |_| {});
14510
14511    let fs = FakeFs::new(cx.executor());
14512    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
14513    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14514    let pane = workspace
14515        .update(cx, |workspace, _, _| workspace.active_pane().clone())
14516        .unwrap();
14517
14518    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14519
14520    let leader = pane.update_in(cx, |_, window, cx| {
14521        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
14522        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
14523    });
14524
14525    // Start following the editor when it has no excerpts.
14526    let mut state_message =
14527        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
14528    let workspace_entity = workspace.root(cx).unwrap();
14529    let follower_1 = cx
14530        .update_window(*workspace.deref(), |_, window, cx| {
14531            Editor::from_state_proto(
14532                workspace_entity,
14533                ViewId {
14534                    creator: CollaboratorId::PeerId(PeerId::default()),
14535                    id: 0,
14536                },
14537                &mut state_message,
14538                window,
14539                cx,
14540            )
14541        })
14542        .unwrap()
14543        .unwrap()
14544        .await
14545        .unwrap();
14546
14547    let update_message = Rc::new(RefCell::new(None));
14548    follower_1.update_in(cx, {
14549        let update = update_message.clone();
14550        |_, window, cx| {
14551            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
14552                leader.read(cx).add_event_to_update_proto(
14553                    event,
14554                    &mut update.borrow_mut(),
14555                    window,
14556                    cx,
14557                );
14558            })
14559            .detach();
14560        }
14561    });
14562
14563    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
14564        (
14565            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
14566            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
14567        )
14568    });
14569
14570    // Insert some excerpts.
14571    leader.update(cx, |leader, cx| {
14572        leader.buffer.update(cx, |multibuffer, cx| {
14573            multibuffer.set_excerpts_for_path(
14574                PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
14575                buffer_1.clone(),
14576                vec![
14577                    Point::row_range(0..3),
14578                    Point::row_range(1..6),
14579                    Point::row_range(12..15),
14580                ],
14581                0,
14582                cx,
14583            );
14584            multibuffer.set_excerpts_for_path(
14585                PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
14586                buffer_2.clone(),
14587                vec![Point::row_range(0..6), Point::row_range(8..12)],
14588                0,
14589                cx,
14590            );
14591        });
14592    });
14593
14594    // Apply the update of adding the excerpts.
14595    follower_1
14596        .update_in(cx, |follower, window, cx| {
14597            follower.apply_update_proto(
14598                &project,
14599                update_message.borrow().clone().unwrap(),
14600                window,
14601                cx,
14602            )
14603        })
14604        .await
14605        .unwrap();
14606    assert_eq!(
14607        follower_1.update(cx, |editor, cx| editor.text(cx)),
14608        leader.update(cx, |editor, cx| editor.text(cx))
14609    );
14610    update_message.borrow_mut().take();
14611
14612    // Start following separately after it already has excerpts.
14613    let mut state_message =
14614        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
14615    let workspace_entity = workspace.root(cx).unwrap();
14616    let follower_2 = cx
14617        .update_window(*workspace.deref(), |_, window, cx| {
14618            Editor::from_state_proto(
14619                workspace_entity,
14620                ViewId {
14621                    creator: CollaboratorId::PeerId(PeerId::default()),
14622                    id: 0,
14623                },
14624                &mut state_message,
14625                window,
14626                cx,
14627            )
14628        })
14629        .unwrap()
14630        .unwrap()
14631        .await
14632        .unwrap();
14633    assert_eq!(
14634        follower_2.update(cx, |editor, cx| editor.text(cx)),
14635        leader.update(cx, |editor, cx| editor.text(cx))
14636    );
14637
14638    // Remove some excerpts.
14639    leader.update(cx, |leader, cx| {
14640        leader.buffer.update(cx, |multibuffer, cx| {
14641            let excerpt_ids = multibuffer.excerpt_ids();
14642            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
14643            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
14644        });
14645    });
14646
14647    // Apply the update of removing the excerpts.
14648    follower_1
14649        .update_in(cx, |follower, window, cx| {
14650            follower.apply_update_proto(
14651                &project,
14652                update_message.borrow().clone().unwrap(),
14653                window,
14654                cx,
14655            )
14656        })
14657        .await
14658        .unwrap();
14659    follower_2
14660        .update_in(cx, |follower, window, cx| {
14661            follower.apply_update_proto(
14662                &project,
14663                update_message.borrow().clone().unwrap(),
14664                window,
14665                cx,
14666            )
14667        })
14668        .await
14669        .unwrap();
14670    update_message.borrow_mut().take();
14671    assert_eq!(
14672        follower_1.update(cx, |editor, cx| editor.text(cx)),
14673        leader.update(cx, |editor, cx| editor.text(cx))
14674    );
14675}
14676
14677#[gpui::test]
14678async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14679    init_test(cx, |_| {});
14680
14681    let mut cx = EditorTestContext::new(cx).await;
14682    let lsp_store =
14683        cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
14684
14685    cx.set_state(indoc! {"
14686        ˇfn func(abc def: i32) -> u32 {
14687        }
14688    "});
14689
14690    cx.update(|_, cx| {
14691        lsp_store.update(cx, |lsp_store, cx| {
14692            lsp_store
14693                .update_diagnostics(
14694                    LanguageServerId(0),
14695                    lsp::PublishDiagnosticsParams {
14696                        uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
14697                        version: None,
14698                        diagnostics: vec![
14699                            lsp::Diagnostic {
14700                                range: lsp::Range::new(
14701                                    lsp::Position::new(0, 11),
14702                                    lsp::Position::new(0, 12),
14703                                ),
14704                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14705                                ..Default::default()
14706                            },
14707                            lsp::Diagnostic {
14708                                range: lsp::Range::new(
14709                                    lsp::Position::new(0, 12),
14710                                    lsp::Position::new(0, 15),
14711                                ),
14712                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14713                                ..Default::default()
14714                            },
14715                            lsp::Diagnostic {
14716                                range: lsp::Range::new(
14717                                    lsp::Position::new(0, 25),
14718                                    lsp::Position::new(0, 28),
14719                                ),
14720                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14721                                ..Default::default()
14722                            },
14723                        ],
14724                    },
14725                    None,
14726                    DiagnosticSourceKind::Pushed,
14727                    &[],
14728                    cx,
14729                )
14730                .unwrap()
14731        });
14732    });
14733
14734    executor.run_until_parked();
14735
14736    cx.update_editor(|editor, window, cx| {
14737        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
14738    });
14739
14740    cx.assert_editor_state(indoc! {"
14741        fn func(abc def: i32) -> ˇu32 {
14742        }
14743    "});
14744
14745    cx.update_editor(|editor, window, cx| {
14746        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
14747    });
14748
14749    cx.assert_editor_state(indoc! {"
14750        fn func(abc ˇdef: i32) -> u32 {
14751        }
14752    "});
14753
14754    cx.update_editor(|editor, window, cx| {
14755        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
14756    });
14757
14758    cx.assert_editor_state(indoc! {"
14759        fn func(abcˇ def: i32) -> u32 {
14760        }
14761    "});
14762
14763    cx.update_editor(|editor, window, cx| {
14764        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
14765    });
14766
14767    cx.assert_editor_state(indoc! {"
14768        fn func(abc def: i32) -> ˇu32 {
14769        }
14770    "});
14771}
14772
14773#[gpui::test]
14774async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14775    init_test(cx, |_| {});
14776
14777    let mut cx = EditorTestContext::new(cx).await;
14778
14779    let diff_base = r#"
14780        use some::mod;
14781
14782        const A: u32 = 42;
14783
14784        fn main() {
14785            println!("hello");
14786
14787            println!("world");
14788        }
14789        "#
14790    .unindent();
14791
14792    // Edits are modified, removed, modified, added
14793    cx.set_state(
14794        &r#"
14795        use some::modified;
14796
14797        ˇ
14798        fn main() {
14799            println!("hello there");
14800
14801            println!("around the");
14802            println!("world");
14803        }
14804        "#
14805        .unindent(),
14806    );
14807
14808    cx.set_head_text(&diff_base);
14809    executor.run_until_parked();
14810
14811    cx.update_editor(|editor, window, cx| {
14812        //Wrap around the bottom of the buffer
14813        for _ in 0..3 {
14814            editor.go_to_next_hunk(&GoToHunk, window, cx);
14815        }
14816    });
14817
14818    cx.assert_editor_state(
14819        &r#"
14820        ˇuse some::modified;
14821
14822
14823        fn main() {
14824            println!("hello there");
14825
14826            println!("around the");
14827            println!("world");
14828        }
14829        "#
14830        .unindent(),
14831    );
14832
14833    cx.update_editor(|editor, window, cx| {
14834        //Wrap around the top of the buffer
14835        for _ in 0..2 {
14836            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14837        }
14838    });
14839
14840    cx.assert_editor_state(
14841        &r#"
14842        use some::modified;
14843
14844
14845        fn main() {
14846        ˇ    println!("hello there");
14847
14848            println!("around the");
14849            println!("world");
14850        }
14851        "#
14852        .unindent(),
14853    );
14854
14855    cx.update_editor(|editor, window, cx| {
14856        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14857    });
14858
14859    cx.assert_editor_state(
14860        &r#"
14861        use some::modified;
14862
14863        ˇ
14864        fn main() {
14865            println!("hello there");
14866
14867            println!("around the");
14868            println!("world");
14869        }
14870        "#
14871        .unindent(),
14872    );
14873
14874    cx.update_editor(|editor, window, cx| {
14875        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14876    });
14877
14878    cx.assert_editor_state(
14879        &r#"
14880        ˇuse some::modified;
14881
14882
14883        fn main() {
14884            println!("hello there");
14885
14886            println!("around the");
14887            println!("world");
14888        }
14889        "#
14890        .unindent(),
14891    );
14892
14893    cx.update_editor(|editor, window, cx| {
14894        for _ in 0..2 {
14895            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14896        }
14897    });
14898
14899    cx.assert_editor_state(
14900        &r#"
14901        use some::modified;
14902
14903
14904        fn main() {
14905        ˇ    println!("hello there");
14906
14907            println!("around the");
14908            println!("world");
14909        }
14910        "#
14911        .unindent(),
14912    );
14913
14914    cx.update_editor(|editor, window, cx| {
14915        editor.fold(&Fold, window, cx);
14916    });
14917
14918    cx.update_editor(|editor, window, cx| {
14919        editor.go_to_next_hunk(&GoToHunk, window, cx);
14920    });
14921
14922    cx.assert_editor_state(
14923        &r#"
14924        ˇuse some::modified;
14925
14926
14927        fn main() {
14928            println!("hello there");
14929
14930            println!("around the");
14931            println!("world");
14932        }
14933        "#
14934        .unindent(),
14935    );
14936}
14937
14938#[test]
14939fn test_split_words() {
14940    fn split(text: &str) -> Vec<&str> {
14941        split_words(text).collect()
14942    }
14943
14944    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
14945    assert_eq!(split("hello_world"), &["hello_", "world"]);
14946    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
14947    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
14948    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
14949    assert_eq!(split("helloworld"), &["helloworld"]);
14950
14951    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
14952}
14953
14954#[gpui::test]
14955async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
14956    init_test(cx, |_| {});
14957
14958    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
14959    let mut assert = |before, after| {
14960        let _state_context = cx.set_state(before);
14961        cx.run_until_parked();
14962        cx.update_editor(|editor, window, cx| {
14963            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
14964        });
14965        cx.run_until_parked();
14966        cx.assert_editor_state(after);
14967    };
14968
14969    // Outside bracket jumps to outside of matching bracket
14970    assert("console.logˇ(var);", "console.log(var)ˇ;");
14971    assert("console.log(var)ˇ;", "console.logˇ(var);");
14972
14973    // Inside bracket jumps to inside of matching bracket
14974    assert("console.log(ˇvar);", "console.log(varˇ);");
14975    assert("console.log(varˇ);", "console.log(ˇvar);");
14976
14977    // When outside a bracket and inside, favor jumping to the inside bracket
14978    assert(
14979        "console.log('foo', [1, 2, 3]ˇ);",
14980        "console.log(ˇ'foo', [1, 2, 3]);",
14981    );
14982    assert(
14983        "console.log(ˇ'foo', [1, 2, 3]);",
14984        "console.log('foo', [1, 2, 3]ˇ);",
14985    );
14986
14987    // Bias forward if two options are equally likely
14988    assert(
14989        "let result = curried_fun()ˇ();",
14990        "let result = curried_fun()()ˇ;",
14991    );
14992
14993    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
14994    assert(
14995        indoc! {"
14996            function test() {
14997                console.log('test')ˇ
14998            }"},
14999        indoc! {"
15000            function test() {
15001                console.logˇ('test')
15002            }"},
15003    );
15004}
15005
15006#[gpui::test]
15007async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
15008    init_test(cx, |_| {});
15009
15010    let fs = FakeFs::new(cx.executor());
15011    fs.insert_tree(
15012        path!("/a"),
15013        json!({
15014            "main.rs": "fn main() { let a = 5; }",
15015            "other.rs": "// Test file",
15016        }),
15017    )
15018    .await;
15019    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15020
15021    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15022    language_registry.add(Arc::new(Language::new(
15023        LanguageConfig {
15024            name: "Rust".into(),
15025            matcher: LanguageMatcher {
15026                path_suffixes: vec!["rs".to_string()],
15027                ..Default::default()
15028            },
15029            brackets: BracketPairConfig {
15030                pairs: vec![BracketPair {
15031                    start: "{".to_string(),
15032                    end: "}".to_string(),
15033                    close: true,
15034                    surround: true,
15035                    newline: true,
15036                }],
15037                disabled_scopes_by_bracket_ix: Vec::new(),
15038            },
15039            ..Default::default()
15040        },
15041        Some(tree_sitter_rust::LANGUAGE.into()),
15042    )));
15043    let mut fake_servers = language_registry.register_fake_lsp(
15044        "Rust",
15045        FakeLspAdapter {
15046            capabilities: lsp::ServerCapabilities {
15047                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
15048                    first_trigger_character: "{".to_string(),
15049                    more_trigger_character: None,
15050                }),
15051                ..Default::default()
15052            },
15053            ..Default::default()
15054        },
15055    );
15056
15057    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15058
15059    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15060
15061    let worktree_id = workspace
15062        .update(cx, |workspace, _, cx| {
15063            workspace.project().update(cx, |project, cx| {
15064                project.worktrees(cx).next().unwrap().read(cx).id()
15065            })
15066        })
15067        .unwrap();
15068
15069    let buffer = project
15070        .update(cx, |project, cx| {
15071            project.open_local_buffer(path!("/a/main.rs"), cx)
15072        })
15073        .await
15074        .unwrap();
15075    let editor_handle = workspace
15076        .update(cx, |workspace, window, cx| {
15077            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
15078        })
15079        .unwrap()
15080        .await
15081        .unwrap()
15082        .downcast::<Editor>()
15083        .unwrap();
15084
15085    cx.executor().start_waiting();
15086    let fake_server = fake_servers.next().await.unwrap();
15087
15088    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
15089        |params, _| async move {
15090            assert_eq!(
15091                params.text_document_position.text_document.uri,
15092                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
15093            );
15094            assert_eq!(
15095                params.text_document_position.position,
15096                lsp::Position::new(0, 21),
15097            );
15098
15099            Ok(Some(vec![lsp::TextEdit {
15100                new_text: "]".to_string(),
15101                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15102            }]))
15103        },
15104    );
15105
15106    editor_handle.update_in(cx, |editor, window, cx| {
15107        window.focus(&editor.focus_handle(cx));
15108        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15109            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
15110        });
15111        editor.handle_input("{", window, cx);
15112    });
15113
15114    cx.executor().run_until_parked();
15115
15116    buffer.update(cx, |buffer, _| {
15117        assert_eq!(
15118            buffer.text(),
15119            "fn main() { let a = {5}; }",
15120            "No extra braces from on type formatting should appear in the buffer"
15121        )
15122    });
15123}
15124
15125#[gpui::test(iterations = 20, seeds(31))]
15126async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
15127    init_test(cx, |_| {});
15128
15129    let mut cx = EditorLspTestContext::new_rust(
15130        lsp::ServerCapabilities {
15131            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
15132                first_trigger_character: ".".to_string(),
15133                more_trigger_character: None,
15134            }),
15135            ..Default::default()
15136        },
15137        cx,
15138    )
15139    .await;
15140
15141    cx.update_buffer(|buffer, _| {
15142        // This causes autoindent to be async.
15143        buffer.set_sync_parse_timeout(Duration::ZERO)
15144    });
15145
15146    cx.set_state("fn c() {\n    d()ˇ\n}\n");
15147    cx.simulate_keystroke("\n");
15148    cx.run_until_parked();
15149
15150    let buffer_cloned =
15151        cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap().clone());
15152    let mut request =
15153        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
15154            let buffer_cloned = buffer_cloned.clone();
15155            async move {
15156                buffer_cloned.update(&mut cx, |buffer, _| {
15157                    assert_eq!(
15158                        buffer.text(),
15159                        "fn c() {\n    d()\n        .\n}\n",
15160                        "OnTypeFormatting should triggered after autoindent applied"
15161                    )
15162                })?;
15163
15164                Ok(Some(vec![]))
15165            }
15166        });
15167
15168    cx.simulate_keystroke(".");
15169    cx.run_until_parked();
15170
15171    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
15172    assert!(request.next().await.is_some());
15173    request.close();
15174    assert!(request.next().await.is_none());
15175}
15176
15177#[gpui::test]
15178async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
15179    init_test(cx, |_| {});
15180
15181    let fs = FakeFs::new(cx.executor());
15182    fs.insert_tree(
15183        path!("/a"),
15184        json!({
15185            "main.rs": "fn main() { let a = 5; }",
15186            "other.rs": "// Test file",
15187        }),
15188    )
15189    .await;
15190
15191    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15192
15193    let server_restarts = Arc::new(AtomicUsize::new(0));
15194    let closure_restarts = Arc::clone(&server_restarts);
15195    let language_server_name = "test language server";
15196    let language_name: LanguageName = "Rust".into();
15197
15198    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15199    language_registry.add(Arc::new(Language::new(
15200        LanguageConfig {
15201            name: language_name.clone(),
15202            matcher: LanguageMatcher {
15203                path_suffixes: vec!["rs".to_string()],
15204                ..Default::default()
15205            },
15206            ..Default::default()
15207        },
15208        Some(tree_sitter_rust::LANGUAGE.into()),
15209    )));
15210    let mut fake_servers = language_registry.register_fake_lsp(
15211        "Rust",
15212        FakeLspAdapter {
15213            name: language_server_name,
15214            initialization_options: Some(json!({
15215                "testOptionValue": true
15216            })),
15217            initializer: Some(Box::new(move |fake_server| {
15218                let task_restarts = Arc::clone(&closure_restarts);
15219                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
15220                    task_restarts.fetch_add(1, atomic::Ordering::Release);
15221                    futures::future::ready(Ok(()))
15222                });
15223            })),
15224            ..Default::default()
15225        },
15226    );
15227
15228    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15229    let _buffer = project
15230        .update(cx, |project, cx| {
15231            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
15232        })
15233        .await
15234        .unwrap();
15235    let _fake_server = fake_servers.next().await.unwrap();
15236    update_test_language_settings(cx, |language_settings| {
15237        language_settings.languages.0.insert(
15238            language_name.clone(),
15239            LanguageSettingsContent {
15240                tab_size: NonZeroU32::new(8),
15241                ..Default::default()
15242            },
15243        );
15244    });
15245    cx.executor().run_until_parked();
15246    assert_eq!(
15247        server_restarts.load(atomic::Ordering::Acquire),
15248        0,
15249        "Should not restart LSP server on an unrelated change"
15250    );
15251
15252    update_test_project_settings(cx, |project_settings| {
15253        project_settings.lsp.insert(
15254            "Some other server name".into(),
15255            LspSettings {
15256                binary: None,
15257                settings: None,
15258                initialization_options: Some(json!({
15259                    "some other init value": false
15260                })),
15261                enable_lsp_tasks: false,
15262            },
15263        );
15264    });
15265    cx.executor().run_until_parked();
15266    assert_eq!(
15267        server_restarts.load(atomic::Ordering::Acquire),
15268        0,
15269        "Should not restart LSP server on an unrelated LSP settings change"
15270    );
15271
15272    update_test_project_settings(cx, |project_settings| {
15273        project_settings.lsp.insert(
15274            language_server_name.into(),
15275            LspSettings {
15276                binary: None,
15277                settings: None,
15278                initialization_options: Some(json!({
15279                    "anotherInitValue": false
15280                })),
15281                enable_lsp_tasks: false,
15282            },
15283        );
15284    });
15285    cx.executor().run_until_parked();
15286    assert_eq!(
15287        server_restarts.load(atomic::Ordering::Acquire),
15288        1,
15289        "Should restart LSP server on a related LSP settings change"
15290    );
15291
15292    update_test_project_settings(cx, |project_settings| {
15293        project_settings.lsp.insert(
15294            language_server_name.into(),
15295            LspSettings {
15296                binary: None,
15297                settings: None,
15298                initialization_options: Some(json!({
15299                    "anotherInitValue": false
15300                })),
15301                enable_lsp_tasks: false,
15302            },
15303        );
15304    });
15305    cx.executor().run_until_parked();
15306    assert_eq!(
15307        server_restarts.load(atomic::Ordering::Acquire),
15308        1,
15309        "Should not restart LSP server on a related LSP settings change that is the same"
15310    );
15311
15312    update_test_project_settings(cx, |project_settings| {
15313        project_settings.lsp.insert(
15314            language_server_name.into(),
15315            LspSettings {
15316                binary: None,
15317                settings: None,
15318                initialization_options: None,
15319                enable_lsp_tasks: false,
15320            },
15321        );
15322    });
15323    cx.executor().run_until_parked();
15324    assert_eq!(
15325        server_restarts.load(atomic::Ordering::Acquire),
15326        2,
15327        "Should restart LSP server on another related LSP settings change"
15328    );
15329}
15330
15331#[gpui::test]
15332async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
15333    init_test(cx, |_| {});
15334
15335    let mut cx = EditorLspTestContext::new_rust(
15336        lsp::ServerCapabilities {
15337            completion_provider: Some(lsp::CompletionOptions {
15338                trigger_characters: Some(vec![".".to_string()]),
15339                resolve_provider: Some(true),
15340                ..Default::default()
15341            }),
15342            ..Default::default()
15343        },
15344        cx,
15345    )
15346    .await;
15347
15348    cx.set_state("fn main() { let a = 2ˇ; }");
15349    cx.simulate_keystroke(".");
15350    let completion_item = lsp::CompletionItem {
15351        label: "some".into(),
15352        kind: Some(lsp::CompletionItemKind::SNIPPET),
15353        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15354        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15355            kind: lsp::MarkupKind::Markdown,
15356            value: "```rust\nSome(2)\n```".to_string(),
15357        })),
15358        deprecated: Some(false),
15359        sort_text: Some("fffffff2".to_string()),
15360        filter_text: Some("some".to_string()),
15361        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15362        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15363            range: lsp::Range {
15364                start: lsp::Position {
15365                    line: 0,
15366                    character: 22,
15367                },
15368                end: lsp::Position {
15369                    line: 0,
15370                    character: 22,
15371                },
15372            },
15373            new_text: "Some(2)".to_string(),
15374        })),
15375        additional_text_edits: Some(vec![lsp::TextEdit {
15376            range: lsp::Range {
15377                start: lsp::Position {
15378                    line: 0,
15379                    character: 20,
15380                },
15381                end: lsp::Position {
15382                    line: 0,
15383                    character: 22,
15384                },
15385            },
15386            new_text: "".to_string(),
15387        }]),
15388        ..Default::default()
15389    };
15390
15391    let closure_completion_item = completion_item.clone();
15392    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15393        let task_completion_item = closure_completion_item.clone();
15394        async move {
15395            Ok(Some(lsp::CompletionResponse::Array(vec![
15396                task_completion_item,
15397            ])))
15398        }
15399    });
15400
15401    request.next().await;
15402
15403    cx.condition(|editor, _| editor.context_menu_visible())
15404        .await;
15405    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15406        editor
15407            .confirm_completion(&ConfirmCompletion::default(), window, cx)
15408            .unwrap()
15409    });
15410    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
15411
15412    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
15413        let task_completion_item = completion_item.clone();
15414        async move { Ok(task_completion_item) }
15415    })
15416    .next()
15417    .await
15418    .unwrap();
15419    apply_additional_edits.await.unwrap();
15420    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
15421}
15422
15423#[gpui::test]
15424async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
15425    init_test(cx, |_| {});
15426
15427    let mut cx = EditorLspTestContext::new_rust(
15428        lsp::ServerCapabilities {
15429            completion_provider: Some(lsp::CompletionOptions {
15430                trigger_characters: Some(vec![".".to_string()]),
15431                resolve_provider: Some(true),
15432                ..Default::default()
15433            }),
15434            ..Default::default()
15435        },
15436        cx,
15437    )
15438    .await;
15439
15440    cx.set_state("fn main() { let a = 2ˇ; }");
15441    cx.simulate_keystroke(".");
15442
15443    let item1 = lsp::CompletionItem {
15444        label: "method id()".to_string(),
15445        filter_text: Some("id".to_string()),
15446        detail: None,
15447        documentation: None,
15448        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15449            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15450            new_text: ".id".to_string(),
15451        })),
15452        ..lsp::CompletionItem::default()
15453    };
15454
15455    let item2 = lsp::CompletionItem {
15456        label: "other".to_string(),
15457        filter_text: Some("other".to_string()),
15458        detail: None,
15459        documentation: None,
15460        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15461            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15462            new_text: ".other".to_string(),
15463        })),
15464        ..lsp::CompletionItem::default()
15465    };
15466
15467    let item1 = item1.clone();
15468    cx.set_request_handler::<lsp::request::Completion, _, _>({
15469        let item1 = item1.clone();
15470        move |_, _, _| {
15471            let item1 = item1.clone();
15472            let item2 = item2.clone();
15473            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
15474        }
15475    })
15476    .next()
15477    .await;
15478
15479    cx.condition(|editor, _| editor.context_menu_visible())
15480        .await;
15481    cx.update_editor(|editor, _, _| {
15482        let context_menu = editor.context_menu.borrow_mut();
15483        let context_menu = context_menu
15484            .as_ref()
15485            .expect("Should have the context menu deployed");
15486        match context_menu {
15487            CodeContextMenu::Completions(completions_menu) => {
15488                let completions = completions_menu.completions.borrow_mut();
15489                assert_eq!(
15490                    completions
15491                        .iter()
15492                        .map(|completion| &completion.label.text)
15493                        .collect::<Vec<_>>(),
15494                    vec!["method id()", "other"]
15495                )
15496            }
15497            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15498        }
15499    });
15500
15501    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
15502        let item1 = item1.clone();
15503        move |_, item_to_resolve, _| {
15504            let item1 = item1.clone();
15505            async move {
15506                if item1 == item_to_resolve {
15507                    Ok(lsp::CompletionItem {
15508                        label: "method id()".to_string(),
15509                        filter_text: Some("id".to_string()),
15510                        detail: Some("Now resolved!".to_string()),
15511                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
15512                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15513                            range: lsp::Range::new(
15514                                lsp::Position::new(0, 22),
15515                                lsp::Position::new(0, 22),
15516                            ),
15517                            new_text: ".id".to_string(),
15518                        })),
15519                        ..lsp::CompletionItem::default()
15520                    })
15521                } else {
15522                    Ok(item_to_resolve)
15523                }
15524            }
15525        }
15526    })
15527    .next()
15528    .await
15529    .unwrap();
15530    cx.run_until_parked();
15531
15532    cx.update_editor(|editor, window, cx| {
15533        editor.context_menu_next(&Default::default(), window, cx);
15534    });
15535
15536    cx.update_editor(|editor, _, _| {
15537        let context_menu = editor.context_menu.borrow_mut();
15538        let context_menu = context_menu
15539            .as_ref()
15540            .expect("Should have the context menu deployed");
15541        match context_menu {
15542            CodeContextMenu::Completions(completions_menu) => {
15543                let completions = completions_menu.completions.borrow_mut();
15544                assert_eq!(
15545                    completions
15546                        .iter()
15547                        .map(|completion| &completion.label.text)
15548                        .collect::<Vec<_>>(),
15549                    vec!["method id() Now resolved!", "other"],
15550                    "Should update first completion label, but not second as the filter text did not match."
15551                );
15552            }
15553            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15554        }
15555    });
15556}
15557
15558#[gpui::test]
15559async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
15560    init_test(cx, |_| {});
15561    let mut cx = EditorLspTestContext::new_rust(
15562        lsp::ServerCapabilities {
15563            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
15564            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
15565            completion_provider: Some(lsp::CompletionOptions {
15566                resolve_provider: Some(true),
15567                ..Default::default()
15568            }),
15569            ..Default::default()
15570        },
15571        cx,
15572    )
15573    .await;
15574    cx.set_state(indoc! {"
15575        struct TestStruct {
15576            field: i32
15577        }
15578
15579        fn mainˇ() {
15580            let unused_var = 42;
15581            let test_struct = TestStruct { field: 42 };
15582        }
15583    "});
15584    let symbol_range = cx.lsp_range(indoc! {"
15585        struct TestStruct {
15586            field: i32
15587        }
15588
15589        «fn main»() {
15590            let unused_var = 42;
15591            let test_struct = TestStruct { field: 42 };
15592        }
15593    "});
15594    let mut hover_requests =
15595        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
15596            Ok(Some(lsp::Hover {
15597                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
15598                    kind: lsp::MarkupKind::Markdown,
15599                    value: "Function documentation".to_string(),
15600                }),
15601                range: Some(symbol_range),
15602            }))
15603        });
15604
15605    // Case 1: Test that code action menu hide hover popover
15606    cx.dispatch_action(Hover);
15607    hover_requests.next().await;
15608    cx.condition(|editor, _| editor.hover_state.visible()).await;
15609    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
15610        move |_, _, _| async move {
15611            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
15612                lsp::CodeAction {
15613                    title: "Remove unused variable".to_string(),
15614                    kind: Some(CodeActionKind::QUICKFIX),
15615                    edit: Some(lsp::WorkspaceEdit {
15616                        changes: Some(
15617                            [(
15618                                lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
15619                                vec![lsp::TextEdit {
15620                                    range: lsp::Range::new(
15621                                        lsp::Position::new(5, 4),
15622                                        lsp::Position::new(5, 27),
15623                                    ),
15624                                    new_text: "".to_string(),
15625                                }],
15626                            )]
15627                            .into_iter()
15628                            .collect(),
15629                        ),
15630                        ..Default::default()
15631                    }),
15632                    ..Default::default()
15633                },
15634            )]))
15635        },
15636    );
15637    cx.update_editor(|editor, window, cx| {
15638        editor.toggle_code_actions(
15639            &ToggleCodeActions {
15640                deployed_from: None,
15641                quick_launch: false,
15642            },
15643            window,
15644            cx,
15645        );
15646    });
15647    code_action_requests.next().await;
15648    cx.run_until_parked();
15649    cx.condition(|editor, _| editor.context_menu_visible())
15650        .await;
15651    cx.update_editor(|editor, _, _| {
15652        assert!(
15653            !editor.hover_state.visible(),
15654            "Hover popover should be hidden when code action menu is shown"
15655        );
15656        // Hide code actions
15657        editor.context_menu.take();
15658    });
15659
15660    // Case 2: Test that code completions hide hover popover
15661    cx.dispatch_action(Hover);
15662    hover_requests.next().await;
15663    cx.condition(|editor, _| editor.hover_state.visible()).await;
15664    let counter = Arc::new(AtomicUsize::new(0));
15665    let mut completion_requests =
15666        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15667            let counter = counter.clone();
15668            async move {
15669                counter.fetch_add(1, atomic::Ordering::Release);
15670                Ok(Some(lsp::CompletionResponse::Array(vec![
15671                    lsp::CompletionItem {
15672                        label: "main".into(),
15673                        kind: Some(lsp::CompletionItemKind::FUNCTION),
15674                        detail: Some("() -> ()".to_string()),
15675                        ..Default::default()
15676                    },
15677                    lsp::CompletionItem {
15678                        label: "TestStruct".into(),
15679                        kind: Some(lsp::CompletionItemKind::STRUCT),
15680                        detail: Some("struct TestStruct".to_string()),
15681                        ..Default::default()
15682                    },
15683                ])))
15684            }
15685        });
15686    cx.update_editor(|editor, window, cx| {
15687        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15688    });
15689    completion_requests.next().await;
15690    cx.condition(|editor, _| editor.context_menu_visible())
15691        .await;
15692    cx.update_editor(|editor, _, _| {
15693        assert!(
15694            !editor.hover_state.visible(),
15695            "Hover popover should be hidden when completion menu is shown"
15696        );
15697    });
15698}
15699
15700#[gpui::test]
15701async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
15702    init_test(cx, |_| {});
15703
15704    let mut cx = EditorLspTestContext::new_rust(
15705        lsp::ServerCapabilities {
15706            completion_provider: Some(lsp::CompletionOptions {
15707                trigger_characters: Some(vec![".".to_string()]),
15708                resolve_provider: Some(true),
15709                ..Default::default()
15710            }),
15711            ..Default::default()
15712        },
15713        cx,
15714    )
15715    .await;
15716
15717    cx.set_state("fn main() { let a = 2ˇ; }");
15718    cx.simulate_keystroke(".");
15719
15720    let unresolved_item_1 = lsp::CompletionItem {
15721        label: "id".to_string(),
15722        filter_text: Some("id".to_string()),
15723        detail: None,
15724        documentation: None,
15725        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15726            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15727            new_text: ".id".to_string(),
15728        })),
15729        ..lsp::CompletionItem::default()
15730    };
15731    let resolved_item_1 = lsp::CompletionItem {
15732        additional_text_edits: Some(vec![lsp::TextEdit {
15733            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
15734            new_text: "!!".to_string(),
15735        }]),
15736        ..unresolved_item_1.clone()
15737    };
15738    let unresolved_item_2 = lsp::CompletionItem {
15739        label: "other".to_string(),
15740        filter_text: Some("other".to_string()),
15741        detail: None,
15742        documentation: None,
15743        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15744            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15745            new_text: ".other".to_string(),
15746        })),
15747        ..lsp::CompletionItem::default()
15748    };
15749    let resolved_item_2 = lsp::CompletionItem {
15750        additional_text_edits: Some(vec![lsp::TextEdit {
15751            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
15752            new_text: "??".to_string(),
15753        }]),
15754        ..unresolved_item_2.clone()
15755    };
15756
15757    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
15758    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
15759    cx.lsp
15760        .server
15761        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
15762            let unresolved_item_1 = unresolved_item_1.clone();
15763            let resolved_item_1 = resolved_item_1.clone();
15764            let unresolved_item_2 = unresolved_item_2.clone();
15765            let resolved_item_2 = resolved_item_2.clone();
15766            let resolve_requests_1 = resolve_requests_1.clone();
15767            let resolve_requests_2 = resolve_requests_2.clone();
15768            move |unresolved_request, _| {
15769                let unresolved_item_1 = unresolved_item_1.clone();
15770                let resolved_item_1 = resolved_item_1.clone();
15771                let unresolved_item_2 = unresolved_item_2.clone();
15772                let resolved_item_2 = resolved_item_2.clone();
15773                let resolve_requests_1 = resolve_requests_1.clone();
15774                let resolve_requests_2 = resolve_requests_2.clone();
15775                async move {
15776                    if unresolved_request == unresolved_item_1 {
15777                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
15778                        Ok(resolved_item_1.clone())
15779                    } else if unresolved_request == unresolved_item_2 {
15780                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
15781                        Ok(resolved_item_2.clone())
15782                    } else {
15783                        panic!("Unexpected completion item {unresolved_request:?}")
15784                    }
15785                }
15786            }
15787        })
15788        .detach();
15789
15790    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15791        let unresolved_item_1 = unresolved_item_1.clone();
15792        let unresolved_item_2 = unresolved_item_2.clone();
15793        async move {
15794            Ok(Some(lsp::CompletionResponse::Array(vec![
15795                unresolved_item_1,
15796                unresolved_item_2,
15797            ])))
15798        }
15799    })
15800    .next()
15801    .await;
15802
15803    cx.condition(|editor, _| editor.context_menu_visible())
15804        .await;
15805    cx.update_editor(|editor, _, _| {
15806        let context_menu = editor.context_menu.borrow_mut();
15807        let context_menu = context_menu
15808            .as_ref()
15809            .expect("Should have the context menu deployed");
15810        match context_menu {
15811            CodeContextMenu::Completions(completions_menu) => {
15812                let completions = completions_menu.completions.borrow_mut();
15813                assert_eq!(
15814                    completions
15815                        .iter()
15816                        .map(|completion| &completion.label.text)
15817                        .collect::<Vec<_>>(),
15818                    vec!["id", "other"]
15819                )
15820            }
15821            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15822        }
15823    });
15824    cx.run_until_parked();
15825
15826    cx.update_editor(|editor, window, cx| {
15827        editor.context_menu_next(&ContextMenuNext, window, cx);
15828    });
15829    cx.run_until_parked();
15830    cx.update_editor(|editor, window, cx| {
15831        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
15832    });
15833    cx.run_until_parked();
15834    cx.update_editor(|editor, window, cx| {
15835        editor.context_menu_next(&ContextMenuNext, window, cx);
15836    });
15837    cx.run_until_parked();
15838    cx.update_editor(|editor, window, cx| {
15839        editor
15840            .compose_completion(&ComposeCompletion::default(), window, cx)
15841            .expect("No task returned")
15842    })
15843    .await
15844    .expect("Completion failed");
15845    cx.run_until_parked();
15846
15847    cx.update_editor(|editor, _, cx| {
15848        assert_eq!(
15849            resolve_requests_1.load(atomic::Ordering::Acquire),
15850            1,
15851            "Should always resolve once despite multiple selections"
15852        );
15853        assert_eq!(
15854            resolve_requests_2.load(atomic::Ordering::Acquire),
15855            1,
15856            "Should always resolve once after multiple selections and applying the completion"
15857        );
15858        assert_eq!(
15859            editor.text(cx),
15860            "fn main() { let a = ??.other; }",
15861            "Should use resolved data when applying the completion"
15862        );
15863    });
15864}
15865
15866#[gpui::test]
15867async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
15868    init_test(cx, |_| {});
15869
15870    let item_0 = lsp::CompletionItem {
15871        label: "abs".into(),
15872        insert_text: Some("abs".into()),
15873        data: Some(json!({ "very": "special"})),
15874        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
15875        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
15876            lsp::InsertReplaceEdit {
15877                new_text: "abs".to_string(),
15878                insert: lsp::Range::default(),
15879                replace: lsp::Range::default(),
15880            },
15881        )),
15882        ..lsp::CompletionItem::default()
15883    };
15884    let items = iter::once(item_0.clone())
15885        .chain((11..51).map(|i| lsp::CompletionItem {
15886            label: format!("item_{}", i),
15887            insert_text: Some(format!("item_{}", i)),
15888            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15889            ..lsp::CompletionItem::default()
15890        }))
15891        .collect::<Vec<_>>();
15892
15893    let default_commit_characters = vec!["?".to_string()];
15894    let default_data = json!({ "default": "data"});
15895    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
15896    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
15897    let default_edit_range = lsp::Range {
15898        start: lsp::Position {
15899            line: 0,
15900            character: 5,
15901        },
15902        end: lsp::Position {
15903            line: 0,
15904            character: 5,
15905        },
15906    };
15907
15908    let mut cx = EditorLspTestContext::new_rust(
15909        lsp::ServerCapabilities {
15910            completion_provider: Some(lsp::CompletionOptions {
15911                trigger_characters: Some(vec![".".to_string()]),
15912                resolve_provider: Some(true),
15913                ..Default::default()
15914            }),
15915            ..Default::default()
15916        },
15917        cx,
15918    )
15919    .await;
15920
15921    cx.set_state("fn main() { let a = 2ˇ; }");
15922    cx.simulate_keystroke(".");
15923
15924    let completion_data = default_data.clone();
15925    let completion_characters = default_commit_characters.clone();
15926    let completion_items = items.clone();
15927    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15928        let default_data = completion_data.clone();
15929        let default_commit_characters = completion_characters.clone();
15930        let items = completion_items.clone();
15931        async move {
15932            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15933                items,
15934                item_defaults: Some(lsp::CompletionListItemDefaults {
15935                    data: Some(default_data.clone()),
15936                    commit_characters: Some(default_commit_characters.clone()),
15937                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
15938                        default_edit_range,
15939                    )),
15940                    insert_text_format: Some(default_insert_text_format),
15941                    insert_text_mode: Some(default_insert_text_mode),
15942                }),
15943                ..lsp::CompletionList::default()
15944            })))
15945        }
15946    })
15947    .next()
15948    .await;
15949
15950    let resolved_items = Arc::new(Mutex::new(Vec::new()));
15951    cx.lsp
15952        .server
15953        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
15954            let closure_resolved_items = resolved_items.clone();
15955            move |item_to_resolve, _| {
15956                let closure_resolved_items = closure_resolved_items.clone();
15957                async move {
15958                    closure_resolved_items.lock().push(item_to_resolve.clone());
15959                    Ok(item_to_resolve)
15960                }
15961            }
15962        })
15963        .detach();
15964
15965    cx.condition(|editor, _| editor.context_menu_visible())
15966        .await;
15967    cx.run_until_parked();
15968    cx.update_editor(|editor, _, _| {
15969        let menu = editor.context_menu.borrow_mut();
15970        match menu.as_ref().expect("should have the completions menu") {
15971            CodeContextMenu::Completions(completions_menu) => {
15972                assert_eq!(
15973                    completions_menu
15974                        .entries
15975                        .borrow()
15976                        .iter()
15977                        .map(|mat| mat.string.clone())
15978                        .collect::<Vec<String>>(),
15979                    items
15980                        .iter()
15981                        .map(|completion| completion.label.clone())
15982                        .collect::<Vec<String>>()
15983                );
15984            }
15985            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
15986        }
15987    });
15988    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
15989    // with 4 from the end.
15990    assert_eq!(
15991        *resolved_items.lock(),
15992        [&items[0..16], &items[items.len() - 4..items.len()]]
15993            .concat()
15994            .iter()
15995            .cloned()
15996            .map(|mut item| {
15997                if item.data.is_none() {
15998                    item.data = Some(default_data.clone());
15999                }
16000                item
16001            })
16002            .collect::<Vec<lsp::CompletionItem>>(),
16003        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
16004    );
16005    resolved_items.lock().clear();
16006
16007    cx.update_editor(|editor, window, cx| {
16008        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
16009    });
16010    cx.run_until_parked();
16011    // Completions that have already been resolved are skipped.
16012    assert_eq!(
16013        *resolved_items.lock(),
16014        items[items.len() - 17..items.len() - 4]
16015            .iter()
16016            .cloned()
16017            .map(|mut item| {
16018                if item.data.is_none() {
16019                    item.data = Some(default_data.clone());
16020                }
16021                item
16022            })
16023            .collect::<Vec<lsp::CompletionItem>>()
16024    );
16025    resolved_items.lock().clear();
16026}
16027
16028#[gpui::test]
16029async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
16030    init_test(cx, |_| {});
16031
16032    let mut cx = EditorLspTestContext::new(
16033        Language::new(
16034            LanguageConfig {
16035                matcher: LanguageMatcher {
16036                    path_suffixes: vec!["jsx".into()],
16037                    ..Default::default()
16038                },
16039                overrides: [(
16040                    "element".into(),
16041                    LanguageConfigOverride {
16042                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
16043                        ..Default::default()
16044                    },
16045                )]
16046                .into_iter()
16047                .collect(),
16048                ..Default::default()
16049            },
16050            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
16051        )
16052        .with_override_query("(jsx_self_closing_element) @element")
16053        .unwrap(),
16054        lsp::ServerCapabilities {
16055            completion_provider: Some(lsp::CompletionOptions {
16056                trigger_characters: Some(vec![":".to_string()]),
16057                ..Default::default()
16058            }),
16059            ..Default::default()
16060        },
16061        cx,
16062    )
16063    .await;
16064
16065    cx.lsp
16066        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16067            Ok(Some(lsp::CompletionResponse::Array(vec![
16068                lsp::CompletionItem {
16069                    label: "bg-blue".into(),
16070                    ..Default::default()
16071                },
16072                lsp::CompletionItem {
16073                    label: "bg-red".into(),
16074                    ..Default::default()
16075                },
16076                lsp::CompletionItem {
16077                    label: "bg-yellow".into(),
16078                    ..Default::default()
16079                },
16080            ])))
16081        });
16082
16083    cx.set_state(r#"<p class="bgˇ" />"#);
16084
16085    // Trigger completion when typing a dash, because the dash is an extra
16086    // word character in the 'element' scope, which contains the cursor.
16087    cx.simulate_keystroke("-");
16088    cx.executor().run_until_parked();
16089    cx.update_editor(|editor, _, _| {
16090        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16091        {
16092            assert_eq!(
16093                completion_menu_entries(&menu),
16094                &["bg-blue", "bg-red", "bg-yellow"]
16095            );
16096        } else {
16097            panic!("expected completion menu to be open");
16098        }
16099    });
16100
16101    cx.simulate_keystroke("l");
16102    cx.executor().run_until_parked();
16103    cx.update_editor(|editor, _, _| {
16104        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16105        {
16106            assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
16107        } else {
16108            panic!("expected completion menu to be open");
16109        }
16110    });
16111
16112    // When filtering completions, consider the character after the '-' to
16113    // be the start of a subword.
16114    cx.set_state(r#"<p class="yelˇ" />"#);
16115    cx.simulate_keystroke("l");
16116    cx.executor().run_until_parked();
16117    cx.update_editor(|editor, _, _| {
16118        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16119        {
16120            assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
16121        } else {
16122            panic!("expected completion menu to be open");
16123        }
16124    });
16125}
16126
16127fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
16128    let entries = menu.entries.borrow();
16129    entries.iter().map(|mat| mat.string.clone()).collect()
16130}
16131
16132#[gpui::test]
16133async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
16134    init_test(cx, |settings| {
16135        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
16136            Formatter::Prettier,
16137        )))
16138    });
16139
16140    let fs = FakeFs::new(cx.executor());
16141    fs.insert_file(path!("/file.ts"), Default::default()).await;
16142
16143    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
16144    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16145
16146    language_registry.add(Arc::new(Language::new(
16147        LanguageConfig {
16148            name: "TypeScript".into(),
16149            matcher: LanguageMatcher {
16150                path_suffixes: vec!["ts".to_string()],
16151                ..Default::default()
16152            },
16153            ..Default::default()
16154        },
16155        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
16156    )));
16157    update_test_language_settings(cx, |settings| {
16158        settings.defaults.prettier = Some(PrettierSettings {
16159            allowed: true,
16160            ..PrettierSettings::default()
16161        });
16162    });
16163
16164    let test_plugin = "test_plugin";
16165    let _ = language_registry.register_fake_lsp(
16166        "TypeScript",
16167        FakeLspAdapter {
16168            prettier_plugins: vec![test_plugin],
16169            ..Default::default()
16170        },
16171    );
16172
16173    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
16174    let buffer = project
16175        .update(cx, |project, cx| {
16176            project.open_local_buffer(path!("/file.ts"), cx)
16177        })
16178        .await
16179        .unwrap();
16180
16181    let buffer_text = "one\ntwo\nthree\n";
16182    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16183    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16184    editor.update_in(cx, |editor, window, cx| {
16185        editor.set_text(buffer_text, window, cx)
16186    });
16187
16188    editor
16189        .update_in(cx, |editor, window, cx| {
16190            editor.perform_format(
16191                project.clone(),
16192                FormatTrigger::Manual,
16193                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
16194                window,
16195                cx,
16196            )
16197        })
16198        .unwrap()
16199        .await;
16200    assert_eq!(
16201        editor.update(cx, |editor, cx| editor.text(cx)),
16202        buffer_text.to_string() + prettier_format_suffix,
16203        "Test prettier formatting was not applied to the original buffer text",
16204    );
16205
16206    update_test_language_settings(cx, |settings| {
16207        settings.defaults.formatter = Some(SelectedFormatter::Auto)
16208    });
16209    let format = editor.update_in(cx, |editor, window, cx| {
16210        editor.perform_format(
16211            project.clone(),
16212            FormatTrigger::Manual,
16213            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
16214            window,
16215            cx,
16216        )
16217    });
16218    format.await.unwrap();
16219    assert_eq!(
16220        editor.update(cx, |editor, cx| editor.text(cx)),
16221        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
16222        "Autoformatting (via test prettier) was not applied to the original buffer text",
16223    );
16224}
16225
16226#[gpui::test]
16227async fn test_addition_reverts(cx: &mut TestAppContext) {
16228    init_test(cx, |_| {});
16229    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16230    let base_text = indoc! {r#"
16231        struct Row;
16232        struct Row1;
16233        struct Row2;
16234
16235        struct Row4;
16236        struct Row5;
16237        struct Row6;
16238
16239        struct Row8;
16240        struct Row9;
16241        struct Row10;"#};
16242
16243    // When addition hunks are not adjacent to carets, no hunk revert is performed
16244    assert_hunk_revert(
16245        indoc! {r#"struct Row;
16246                   struct Row1;
16247                   struct Row1.1;
16248                   struct Row1.2;
16249                   struct Row2;ˇ
16250
16251                   struct Row4;
16252                   struct Row5;
16253                   struct Row6;
16254
16255                   struct Row8;
16256                   ˇstruct Row9;
16257                   struct Row9.1;
16258                   struct Row9.2;
16259                   struct Row9.3;
16260                   struct Row10;"#},
16261        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
16262        indoc! {r#"struct Row;
16263                   struct Row1;
16264                   struct Row1.1;
16265                   struct Row1.2;
16266                   struct Row2;ˇ
16267
16268                   struct Row4;
16269                   struct Row5;
16270                   struct Row6;
16271
16272                   struct Row8;
16273                   ˇstruct Row9;
16274                   struct Row9.1;
16275                   struct Row9.2;
16276                   struct Row9.3;
16277                   struct Row10;"#},
16278        base_text,
16279        &mut cx,
16280    );
16281    // Same for selections
16282    assert_hunk_revert(
16283        indoc! {r#"struct Row;
16284                   struct Row1;
16285                   struct Row2;
16286                   struct Row2.1;
16287                   struct Row2.2;
16288                   «ˇ
16289                   struct Row4;
16290                   struct» Row5;
16291                   «struct Row6;
16292                   ˇ»
16293                   struct Row9.1;
16294                   struct Row9.2;
16295                   struct Row9.3;
16296                   struct Row8;
16297                   struct Row9;
16298                   struct Row10;"#},
16299        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
16300        indoc! {r#"struct Row;
16301                   struct Row1;
16302                   struct Row2;
16303                   struct Row2.1;
16304                   struct Row2.2;
16305                   «ˇ
16306                   struct Row4;
16307                   struct» Row5;
16308                   «struct Row6;
16309                   ˇ»
16310                   struct Row9.1;
16311                   struct Row9.2;
16312                   struct Row9.3;
16313                   struct Row8;
16314                   struct Row9;
16315                   struct Row10;"#},
16316        base_text,
16317        &mut cx,
16318    );
16319
16320    // When carets and selections intersect the addition hunks, those are reverted.
16321    // Adjacent carets got merged.
16322    assert_hunk_revert(
16323        indoc! {r#"struct Row;
16324                   ˇ// something on the top
16325                   struct Row1;
16326                   struct Row2;
16327                   struct Roˇw3.1;
16328                   struct Row2.2;
16329                   struct Row2.3;ˇ
16330
16331                   struct Row4;
16332                   struct ˇRow5.1;
16333                   struct Row5.2;
16334                   struct «Rowˇ»5.3;
16335                   struct Row5;
16336                   struct Row6;
16337                   ˇ
16338                   struct Row9.1;
16339                   struct «Rowˇ»9.2;
16340                   struct «ˇRow»9.3;
16341                   struct Row8;
16342                   struct Row9;
16343                   «ˇ// something on bottom»
16344                   struct Row10;"#},
16345        vec![
16346            DiffHunkStatusKind::Added,
16347            DiffHunkStatusKind::Added,
16348            DiffHunkStatusKind::Added,
16349            DiffHunkStatusKind::Added,
16350            DiffHunkStatusKind::Added,
16351        ],
16352        indoc! {r#"struct Row;
16353                   ˇstruct Row1;
16354                   struct Row2;
16355                   ˇ
16356                   struct Row4;
16357                   ˇstruct Row5;
16358                   struct Row6;
16359                   ˇ
16360                   ˇstruct Row8;
16361                   struct Row9;
16362                   ˇstruct Row10;"#},
16363        base_text,
16364        &mut cx,
16365    );
16366}
16367
16368#[gpui::test]
16369async fn test_modification_reverts(cx: &mut TestAppContext) {
16370    init_test(cx, |_| {});
16371    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16372    let base_text = indoc! {r#"
16373        struct Row;
16374        struct Row1;
16375        struct Row2;
16376
16377        struct Row4;
16378        struct Row5;
16379        struct Row6;
16380
16381        struct Row8;
16382        struct Row9;
16383        struct Row10;"#};
16384
16385    // Modification hunks behave the same as the addition ones.
16386    assert_hunk_revert(
16387        indoc! {r#"struct Row;
16388                   struct Row1;
16389                   struct Row33;
16390                   ˇ
16391                   struct Row4;
16392                   struct Row5;
16393                   struct Row6;
16394                   ˇ
16395                   struct Row99;
16396                   struct Row9;
16397                   struct Row10;"#},
16398        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
16399        indoc! {r#"struct Row;
16400                   struct Row1;
16401                   struct Row33;
16402                   ˇ
16403                   struct Row4;
16404                   struct Row5;
16405                   struct Row6;
16406                   ˇ
16407                   struct Row99;
16408                   struct Row9;
16409                   struct Row10;"#},
16410        base_text,
16411        &mut cx,
16412    );
16413    assert_hunk_revert(
16414        indoc! {r#"struct Row;
16415                   struct Row1;
16416                   struct Row33;
16417                   «ˇ
16418                   struct Row4;
16419                   struct» Row5;
16420                   «struct Row6;
16421                   ˇ»
16422                   struct Row99;
16423                   struct Row9;
16424                   struct Row10;"#},
16425        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
16426        indoc! {r#"struct Row;
16427                   struct Row1;
16428                   struct Row33;
16429                   «ˇ
16430                   struct Row4;
16431                   struct» Row5;
16432                   «struct Row6;
16433                   ˇ»
16434                   struct Row99;
16435                   struct Row9;
16436                   struct Row10;"#},
16437        base_text,
16438        &mut cx,
16439    );
16440
16441    assert_hunk_revert(
16442        indoc! {r#"ˇstruct Row1.1;
16443                   struct Row1;
16444                   «ˇstr»uct Row22;
16445
16446                   struct ˇRow44;
16447                   struct Row5;
16448                   struct «Rˇ»ow66;ˇ
16449
16450                   «struˇ»ct Row88;
16451                   struct Row9;
16452                   struct Row1011;ˇ"#},
16453        vec![
16454            DiffHunkStatusKind::Modified,
16455            DiffHunkStatusKind::Modified,
16456            DiffHunkStatusKind::Modified,
16457            DiffHunkStatusKind::Modified,
16458            DiffHunkStatusKind::Modified,
16459            DiffHunkStatusKind::Modified,
16460        ],
16461        indoc! {r#"struct Row;
16462                   ˇstruct Row1;
16463                   struct Row2;
16464                   ˇ
16465                   struct Row4;
16466                   ˇstruct Row5;
16467                   struct Row6;
16468                   ˇ
16469                   struct Row8;
16470                   ˇstruct Row9;
16471                   struct Row10;ˇ"#},
16472        base_text,
16473        &mut cx,
16474    );
16475}
16476
16477#[gpui::test]
16478async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
16479    init_test(cx, |_| {});
16480    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16481    let base_text = indoc! {r#"
16482        one
16483
16484        two
16485        three
16486        "#};
16487
16488    cx.set_head_text(base_text);
16489    cx.set_state("\nˇ\n");
16490    cx.executor().run_until_parked();
16491    cx.update_editor(|editor, _window, cx| {
16492        editor.expand_selected_diff_hunks(cx);
16493    });
16494    cx.executor().run_until_parked();
16495    cx.update_editor(|editor, window, cx| {
16496        editor.backspace(&Default::default(), window, cx);
16497    });
16498    cx.run_until_parked();
16499    cx.assert_state_with_diff(
16500        indoc! {r#"
16501
16502        - two
16503        - threeˇ
16504        +
16505        "#}
16506        .to_string(),
16507    );
16508}
16509
16510#[gpui::test]
16511async fn test_deletion_reverts(cx: &mut TestAppContext) {
16512    init_test(cx, |_| {});
16513    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16514    let base_text = indoc! {r#"struct Row;
16515struct Row1;
16516struct Row2;
16517
16518struct Row4;
16519struct Row5;
16520struct Row6;
16521
16522struct Row8;
16523struct Row9;
16524struct Row10;"#};
16525
16526    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
16527    assert_hunk_revert(
16528        indoc! {r#"struct Row;
16529                   struct Row2;
16530
16531                   ˇstruct Row4;
16532                   struct Row5;
16533                   struct Row6;
16534                   ˇ
16535                   struct Row8;
16536                   struct Row10;"#},
16537        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16538        indoc! {r#"struct Row;
16539                   struct Row2;
16540
16541                   ˇstruct Row4;
16542                   struct Row5;
16543                   struct Row6;
16544                   ˇ
16545                   struct Row8;
16546                   struct Row10;"#},
16547        base_text,
16548        &mut cx,
16549    );
16550    assert_hunk_revert(
16551        indoc! {r#"struct Row;
16552                   struct Row2;
16553
16554                   «ˇstruct Row4;
16555                   struct» Row5;
16556                   «struct Row6;
16557                   ˇ»
16558                   struct Row8;
16559                   struct Row10;"#},
16560        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16561        indoc! {r#"struct Row;
16562                   struct Row2;
16563
16564                   «ˇstruct Row4;
16565                   struct» Row5;
16566                   «struct Row6;
16567                   ˇ»
16568                   struct Row8;
16569                   struct Row10;"#},
16570        base_text,
16571        &mut cx,
16572    );
16573
16574    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
16575    assert_hunk_revert(
16576        indoc! {r#"struct Row;
16577                   ˇstruct Row2;
16578
16579                   struct Row4;
16580                   struct Row5;
16581                   struct Row6;
16582
16583                   struct Row8;ˇ
16584                   struct Row10;"#},
16585        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16586        indoc! {r#"struct Row;
16587                   struct Row1;
16588                   ˇstruct Row2;
16589
16590                   struct Row4;
16591                   struct Row5;
16592                   struct Row6;
16593
16594                   struct Row8;ˇ
16595                   struct Row9;
16596                   struct Row10;"#},
16597        base_text,
16598        &mut cx,
16599    );
16600    assert_hunk_revert(
16601        indoc! {r#"struct Row;
16602                   struct Row2«ˇ;
16603                   struct Row4;
16604                   struct» Row5;
16605                   «struct Row6;
16606
16607                   struct Row8;ˇ»
16608                   struct Row10;"#},
16609        vec![
16610            DiffHunkStatusKind::Deleted,
16611            DiffHunkStatusKind::Deleted,
16612            DiffHunkStatusKind::Deleted,
16613        ],
16614        indoc! {r#"struct Row;
16615                   struct Row1;
16616                   struct Row2«ˇ;
16617
16618                   struct Row4;
16619                   struct» Row5;
16620                   «struct Row6;
16621
16622                   struct Row8;ˇ»
16623                   struct Row9;
16624                   struct Row10;"#},
16625        base_text,
16626        &mut cx,
16627    );
16628}
16629
16630#[gpui::test]
16631async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
16632    init_test(cx, |_| {});
16633
16634    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
16635    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
16636    let base_text_3 =
16637        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
16638
16639    let text_1 = edit_first_char_of_every_line(base_text_1);
16640    let text_2 = edit_first_char_of_every_line(base_text_2);
16641    let text_3 = edit_first_char_of_every_line(base_text_3);
16642
16643    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
16644    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
16645    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
16646
16647    let multibuffer = cx.new(|cx| {
16648        let mut multibuffer = MultiBuffer::new(ReadWrite);
16649        multibuffer.push_excerpts(
16650            buffer_1.clone(),
16651            [
16652                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16653                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16654                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16655            ],
16656            cx,
16657        );
16658        multibuffer.push_excerpts(
16659            buffer_2.clone(),
16660            [
16661                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16662                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16663                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16664            ],
16665            cx,
16666        );
16667        multibuffer.push_excerpts(
16668            buffer_3.clone(),
16669            [
16670                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16671                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16672                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16673            ],
16674            cx,
16675        );
16676        multibuffer
16677    });
16678
16679    let fs = FakeFs::new(cx.executor());
16680    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
16681    let (editor, cx) = cx
16682        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
16683    editor.update_in(cx, |editor, _window, cx| {
16684        for (buffer, diff_base) in [
16685            (buffer_1.clone(), base_text_1),
16686            (buffer_2.clone(), base_text_2),
16687            (buffer_3.clone(), base_text_3),
16688        ] {
16689            let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
16690            editor
16691                .buffer
16692                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
16693        }
16694    });
16695    cx.executor().run_until_parked();
16696
16697    editor.update_in(cx, |editor, window, cx| {
16698        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}");
16699        editor.select_all(&SelectAll, window, cx);
16700        editor.git_restore(&Default::default(), window, cx);
16701    });
16702    cx.executor().run_until_parked();
16703
16704    // When all ranges are selected, all buffer hunks are reverted.
16705    editor.update(cx, |editor, cx| {
16706        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");
16707    });
16708    buffer_1.update(cx, |buffer, _| {
16709        assert_eq!(buffer.text(), base_text_1);
16710    });
16711    buffer_2.update(cx, |buffer, _| {
16712        assert_eq!(buffer.text(), base_text_2);
16713    });
16714    buffer_3.update(cx, |buffer, _| {
16715        assert_eq!(buffer.text(), base_text_3);
16716    });
16717
16718    editor.update_in(cx, |editor, window, cx| {
16719        editor.undo(&Default::default(), window, cx);
16720    });
16721
16722    editor.update_in(cx, |editor, window, cx| {
16723        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16724            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
16725        });
16726        editor.git_restore(&Default::default(), window, cx);
16727    });
16728
16729    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
16730    // but not affect buffer_2 and its related excerpts.
16731    editor.update(cx, |editor, cx| {
16732        assert_eq!(
16733            editor.text(cx),
16734            "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}"
16735        );
16736    });
16737    buffer_1.update(cx, |buffer, _| {
16738        assert_eq!(buffer.text(), base_text_1);
16739    });
16740    buffer_2.update(cx, |buffer, _| {
16741        assert_eq!(
16742            buffer.text(),
16743            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
16744        );
16745    });
16746    buffer_3.update(cx, |buffer, _| {
16747        assert_eq!(
16748            buffer.text(),
16749            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
16750        );
16751    });
16752
16753    fn edit_first_char_of_every_line(text: &str) -> String {
16754        text.split('\n')
16755            .map(|line| format!("X{}", &line[1..]))
16756            .collect::<Vec<_>>()
16757            .join("\n")
16758    }
16759}
16760
16761#[gpui::test]
16762async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
16763    init_test(cx, |_| {});
16764
16765    let cols = 4;
16766    let rows = 10;
16767    let sample_text_1 = sample_text(rows, cols, 'a');
16768    assert_eq!(
16769        sample_text_1,
16770        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
16771    );
16772    let sample_text_2 = sample_text(rows, cols, 'l');
16773    assert_eq!(
16774        sample_text_2,
16775        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
16776    );
16777    let sample_text_3 = sample_text(rows, cols, 'v');
16778    assert_eq!(
16779        sample_text_3,
16780        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
16781    );
16782
16783    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
16784    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
16785    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
16786
16787    let multi_buffer = cx.new(|cx| {
16788        let mut multibuffer = MultiBuffer::new(ReadWrite);
16789        multibuffer.push_excerpts(
16790            buffer_1.clone(),
16791            [
16792                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16793                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16794                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16795            ],
16796            cx,
16797        );
16798        multibuffer.push_excerpts(
16799            buffer_2.clone(),
16800            [
16801                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16802                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16803                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16804            ],
16805            cx,
16806        );
16807        multibuffer.push_excerpts(
16808            buffer_3.clone(),
16809            [
16810                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16811                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16812                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16813            ],
16814            cx,
16815        );
16816        multibuffer
16817    });
16818
16819    let fs = FakeFs::new(cx.executor());
16820    fs.insert_tree(
16821        "/a",
16822        json!({
16823            "main.rs": sample_text_1,
16824            "other.rs": sample_text_2,
16825            "lib.rs": sample_text_3,
16826        }),
16827    )
16828    .await;
16829    let project = Project::test(fs, ["/a".as_ref()], cx).await;
16830    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16831    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16832    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16833        Editor::new(
16834            EditorMode::full(),
16835            multi_buffer,
16836            Some(project.clone()),
16837            window,
16838            cx,
16839        )
16840    });
16841    let multibuffer_item_id = workspace
16842        .update(cx, |workspace, window, cx| {
16843            assert!(
16844                workspace.active_item(cx).is_none(),
16845                "active item should be None before the first item is added"
16846            );
16847            workspace.add_item_to_active_pane(
16848                Box::new(multi_buffer_editor.clone()),
16849                None,
16850                true,
16851                window,
16852                cx,
16853            );
16854            let active_item = workspace
16855                .active_item(cx)
16856                .expect("should have an active item after adding the multi buffer");
16857            assert!(
16858                !active_item.is_singleton(cx),
16859                "A multi buffer was expected to active after adding"
16860            );
16861            active_item.item_id()
16862        })
16863        .unwrap();
16864    cx.executor().run_until_parked();
16865
16866    multi_buffer_editor.update_in(cx, |editor, window, cx| {
16867        editor.change_selections(
16868            SelectionEffects::scroll(Autoscroll::Next),
16869            window,
16870            cx,
16871            |s| s.select_ranges(Some(1..2)),
16872        );
16873        editor.open_excerpts(&OpenExcerpts, window, cx);
16874    });
16875    cx.executor().run_until_parked();
16876    let first_item_id = workspace
16877        .update(cx, |workspace, window, cx| {
16878            let active_item = workspace
16879                .active_item(cx)
16880                .expect("should have an active item after navigating into the 1st buffer");
16881            let first_item_id = active_item.item_id();
16882            assert_ne!(
16883                first_item_id, multibuffer_item_id,
16884                "Should navigate into the 1st buffer and activate it"
16885            );
16886            assert!(
16887                active_item.is_singleton(cx),
16888                "New active item should be a singleton buffer"
16889            );
16890            assert_eq!(
16891                active_item
16892                    .act_as::<Editor>(cx)
16893                    .expect("should have navigated into an editor for the 1st buffer")
16894                    .read(cx)
16895                    .text(cx),
16896                sample_text_1
16897            );
16898
16899            workspace
16900                .go_back(workspace.active_pane().downgrade(), window, cx)
16901                .detach_and_log_err(cx);
16902
16903            first_item_id
16904        })
16905        .unwrap();
16906    cx.executor().run_until_parked();
16907    workspace
16908        .update(cx, |workspace, _, cx| {
16909            let active_item = workspace
16910                .active_item(cx)
16911                .expect("should have an active item after navigating back");
16912            assert_eq!(
16913                active_item.item_id(),
16914                multibuffer_item_id,
16915                "Should navigate back to the multi buffer"
16916            );
16917            assert!(!active_item.is_singleton(cx));
16918        })
16919        .unwrap();
16920
16921    multi_buffer_editor.update_in(cx, |editor, window, cx| {
16922        editor.change_selections(
16923            SelectionEffects::scroll(Autoscroll::Next),
16924            window,
16925            cx,
16926            |s| s.select_ranges(Some(39..40)),
16927        );
16928        editor.open_excerpts(&OpenExcerpts, window, cx);
16929    });
16930    cx.executor().run_until_parked();
16931    let second_item_id = workspace
16932        .update(cx, |workspace, window, cx| {
16933            let active_item = workspace
16934                .active_item(cx)
16935                .expect("should have an active item after navigating into the 2nd buffer");
16936            let second_item_id = active_item.item_id();
16937            assert_ne!(
16938                second_item_id, multibuffer_item_id,
16939                "Should navigate away from the multibuffer"
16940            );
16941            assert_ne!(
16942                second_item_id, first_item_id,
16943                "Should navigate into the 2nd buffer and activate it"
16944            );
16945            assert!(
16946                active_item.is_singleton(cx),
16947                "New active item should be a singleton buffer"
16948            );
16949            assert_eq!(
16950                active_item
16951                    .act_as::<Editor>(cx)
16952                    .expect("should have navigated into an editor")
16953                    .read(cx)
16954                    .text(cx),
16955                sample_text_2
16956            );
16957
16958            workspace
16959                .go_back(workspace.active_pane().downgrade(), window, cx)
16960                .detach_and_log_err(cx);
16961
16962            second_item_id
16963        })
16964        .unwrap();
16965    cx.executor().run_until_parked();
16966    workspace
16967        .update(cx, |workspace, _, cx| {
16968            let active_item = workspace
16969                .active_item(cx)
16970                .expect("should have an active item after navigating back from the 2nd buffer");
16971            assert_eq!(
16972                active_item.item_id(),
16973                multibuffer_item_id,
16974                "Should navigate back from the 2nd buffer to the multi buffer"
16975            );
16976            assert!(!active_item.is_singleton(cx));
16977        })
16978        .unwrap();
16979
16980    multi_buffer_editor.update_in(cx, |editor, window, cx| {
16981        editor.change_selections(
16982            SelectionEffects::scroll(Autoscroll::Next),
16983            window,
16984            cx,
16985            |s| s.select_ranges(Some(70..70)),
16986        );
16987        editor.open_excerpts(&OpenExcerpts, window, cx);
16988    });
16989    cx.executor().run_until_parked();
16990    workspace
16991        .update(cx, |workspace, window, cx| {
16992            let active_item = workspace
16993                .active_item(cx)
16994                .expect("should have an active item after navigating into the 3rd buffer");
16995            let third_item_id = active_item.item_id();
16996            assert_ne!(
16997                third_item_id, multibuffer_item_id,
16998                "Should navigate into the 3rd buffer and activate it"
16999            );
17000            assert_ne!(third_item_id, first_item_id);
17001            assert_ne!(third_item_id, second_item_id);
17002            assert!(
17003                active_item.is_singleton(cx),
17004                "New active item should be a singleton buffer"
17005            );
17006            assert_eq!(
17007                active_item
17008                    .act_as::<Editor>(cx)
17009                    .expect("should have navigated into an editor")
17010                    .read(cx)
17011                    .text(cx),
17012                sample_text_3
17013            );
17014
17015            workspace
17016                .go_back(workspace.active_pane().downgrade(), window, cx)
17017                .detach_and_log_err(cx);
17018        })
17019        .unwrap();
17020    cx.executor().run_until_parked();
17021    workspace
17022        .update(cx, |workspace, _, cx| {
17023            let active_item = workspace
17024                .active_item(cx)
17025                .expect("should have an active item after navigating back from the 3rd buffer");
17026            assert_eq!(
17027                active_item.item_id(),
17028                multibuffer_item_id,
17029                "Should navigate back from the 3rd buffer to the multi buffer"
17030            );
17031            assert!(!active_item.is_singleton(cx));
17032        })
17033        .unwrap();
17034}
17035
17036#[gpui::test]
17037async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17038    init_test(cx, |_| {});
17039
17040    let mut cx = EditorTestContext::new(cx).await;
17041
17042    let diff_base = r#"
17043        use some::mod;
17044
17045        const A: u32 = 42;
17046
17047        fn main() {
17048            println!("hello");
17049
17050            println!("world");
17051        }
17052        "#
17053    .unindent();
17054
17055    cx.set_state(
17056        &r#"
17057        use some::modified;
17058
17059        ˇ
17060        fn main() {
17061            println!("hello there");
17062
17063            println!("around the");
17064            println!("world");
17065        }
17066        "#
17067        .unindent(),
17068    );
17069
17070    cx.set_head_text(&diff_base);
17071    executor.run_until_parked();
17072
17073    cx.update_editor(|editor, window, cx| {
17074        editor.go_to_next_hunk(&GoToHunk, window, cx);
17075        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17076    });
17077    executor.run_until_parked();
17078    cx.assert_state_with_diff(
17079        r#"
17080          use some::modified;
17081
17082
17083          fn main() {
17084        -     println!("hello");
17085        + ˇ    println!("hello there");
17086
17087              println!("around the");
17088              println!("world");
17089          }
17090        "#
17091        .unindent(),
17092    );
17093
17094    cx.update_editor(|editor, window, cx| {
17095        for _ in 0..2 {
17096            editor.go_to_next_hunk(&GoToHunk, window, cx);
17097            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17098        }
17099    });
17100    executor.run_until_parked();
17101    cx.assert_state_with_diff(
17102        r#"
17103        - use some::mod;
17104        + ˇuse some::modified;
17105
17106
17107          fn main() {
17108        -     println!("hello");
17109        +     println!("hello there");
17110
17111        +     println!("around the");
17112              println!("world");
17113          }
17114        "#
17115        .unindent(),
17116    );
17117
17118    cx.update_editor(|editor, window, cx| {
17119        editor.go_to_next_hunk(&GoToHunk, window, cx);
17120        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17121    });
17122    executor.run_until_parked();
17123    cx.assert_state_with_diff(
17124        r#"
17125        - use some::mod;
17126        + use some::modified;
17127
17128        - const A: u32 = 42;
17129          ˇ
17130          fn main() {
17131        -     println!("hello");
17132        +     println!("hello there");
17133
17134        +     println!("around the");
17135              println!("world");
17136          }
17137        "#
17138        .unindent(),
17139    );
17140
17141    cx.update_editor(|editor, window, cx| {
17142        editor.cancel(&Cancel, window, cx);
17143    });
17144
17145    cx.assert_state_with_diff(
17146        r#"
17147          use some::modified;
17148
17149          ˇ
17150          fn main() {
17151              println!("hello there");
17152
17153              println!("around the");
17154              println!("world");
17155          }
17156        "#
17157        .unindent(),
17158    );
17159}
17160
17161#[gpui::test]
17162async fn test_diff_base_change_with_expanded_diff_hunks(
17163    executor: BackgroundExecutor,
17164    cx: &mut TestAppContext,
17165) {
17166    init_test(cx, |_| {});
17167
17168    let mut cx = EditorTestContext::new(cx).await;
17169
17170    let diff_base = r#"
17171        use some::mod1;
17172        use some::mod2;
17173
17174        const A: u32 = 42;
17175        const B: u32 = 42;
17176        const C: u32 = 42;
17177
17178        fn main() {
17179            println!("hello");
17180
17181            println!("world");
17182        }
17183        "#
17184    .unindent();
17185
17186    cx.set_state(
17187        &r#"
17188        use some::mod2;
17189
17190        const A: u32 = 42;
17191        const C: u32 = 42;
17192
17193        fn main(ˇ) {
17194            //println!("hello");
17195
17196            println!("world");
17197            //
17198            //
17199        }
17200        "#
17201        .unindent(),
17202    );
17203
17204    cx.set_head_text(&diff_base);
17205    executor.run_until_parked();
17206
17207    cx.update_editor(|editor, window, cx| {
17208        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17209    });
17210    executor.run_until_parked();
17211    cx.assert_state_with_diff(
17212        r#"
17213        - use some::mod1;
17214          use some::mod2;
17215
17216          const A: u32 = 42;
17217        - const B: u32 = 42;
17218          const C: u32 = 42;
17219
17220          fn main(ˇ) {
17221        -     println!("hello");
17222        +     //println!("hello");
17223
17224              println!("world");
17225        +     //
17226        +     //
17227          }
17228        "#
17229        .unindent(),
17230    );
17231
17232    cx.set_head_text("new diff base!");
17233    executor.run_until_parked();
17234    cx.assert_state_with_diff(
17235        r#"
17236        - new diff base!
17237        + use some::mod2;
17238        +
17239        + const A: u32 = 42;
17240        + const C: u32 = 42;
17241        +
17242        + fn main(ˇ) {
17243        +     //println!("hello");
17244        +
17245        +     println!("world");
17246        +     //
17247        +     //
17248        + }
17249        "#
17250        .unindent(),
17251    );
17252}
17253
17254#[gpui::test]
17255async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
17256    init_test(cx, |_| {});
17257
17258    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
17259    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
17260    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
17261    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
17262    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
17263    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
17264
17265    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
17266    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
17267    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
17268
17269    let multi_buffer = cx.new(|cx| {
17270        let mut multibuffer = MultiBuffer::new(ReadWrite);
17271        multibuffer.push_excerpts(
17272            buffer_1.clone(),
17273            [
17274                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17275                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17276                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17277            ],
17278            cx,
17279        );
17280        multibuffer.push_excerpts(
17281            buffer_2.clone(),
17282            [
17283                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17284                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17285                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17286            ],
17287            cx,
17288        );
17289        multibuffer.push_excerpts(
17290            buffer_3.clone(),
17291            [
17292                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17293                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17294                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17295            ],
17296            cx,
17297        );
17298        multibuffer
17299    });
17300
17301    let editor =
17302        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
17303    editor
17304        .update(cx, |editor, _window, cx| {
17305            for (buffer, diff_base) in [
17306                (buffer_1.clone(), file_1_old),
17307                (buffer_2.clone(), file_2_old),
17308                (buffer_3.clone(), file_3_old),
17309            ] {
17310                let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
17311                editor
17312                    .buffer
17313                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
17314            }
17315        })
17316        .unwrap();
17317
17318    let mut cx = EditorTestContext::for_editor(editor, cx).await;
17319    cx.run_until_parked();
17320
17321    cx.assert_editor_state(
17322        &"
17323            ˇaaa
17324            ccc
17325            ddd
17326
17327            ggg
17328            hhh
17329
17330
17331            lll
17332            mmm
17333            NNN
17334
17335            qqq
17336            rrr
17337
17338            uuu
17339            111
17340            222
17341            333
17342
17343            666
17344            777
17345
17346            000
17347            !!!"
17348        .unindent(),
17349    );
17350
17351    cx.update_editor(|editor, window, cx| {
17352        editor.select_all(&SelectAll, window, cx);
17353        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17354    });
17355    cx.executor().run_until_parked();
17356
17357    cx.assert_state_with_diff(
17358        "
17359            «aaa
17360          - bbb
17361            ccc
17362            ddd
17363
17364            ggg
17365            hhh
17366
17367
17368            lll
17369            mmm
17370          - nnn
17371          + NNN
17372
17373            qqq
17374            rrr
17375
17376            uuu
17377            111
17378            222
17379            333
17380
17381          + 666
17382            777
17383
17384            000
17385            !!!ˇ»"
17386            .unindent(),
17387    );
17388}
17389
17390#[gpui::test]
17391async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
17392    init_test(cx, |_| {});
17393
17394    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
17395    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
17396
17397    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
17398    let multi_buffer = cx.new(|cx| {
17399        let mut multibuffer = MultiBuffer::new(ReadWrite);
17400        multibuffer.push_excerpts(
17401            buffer.clone(),
17402            [
17403                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
17404                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
17405                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
17406            ],
17407            cx,
17408        );
17409        multibuffer
17410    });
17411
17412    let editor =
17413        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
17414    editor
17415        .update(cx, |editor, _window, cx| {
17416            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
17417            editor
17418                .buffer
17419                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
17420        })
17421        .unwrap();
17422
17423    let mut cx = EditorTestContext::for_editor(editor, cx).await;
17424    cx.run_until_parked();
17425
17426    cx.update_editor(|editor, window, cx| {
17427        editor.expand_all_diff_hunks(&Default::default(), window, cx)
17428    });
17429    cx.executor().run_until_parked();
17430
17431    // When the start of a hunk coincides with the start of its excerpt,
17432    // the hunk is expanded. When the start of a a hunk is earlier than
17433    // the start of its excerpt, the hunk is not expanded.
17434    cx.assert_state_with_diff(
17435        "
17436            ˇaaa
17437          - bbb
17438          + BBB
17439
17440          - ddd
17441          - eee
17442          + DDD
17443          + EEE
17444            fff
17445
17446            iii
17447        "
17448        .unindent(),
17449    );
17450}
17451
17452#[gpui::test]
17453async fn test_edits_around_expanded_insertion_hunks(
17454    executor: BackgroundExecutor,
17455    cx: &mut TestAppContext,
17456) {
17457    init_test(cx, |_| {});
17458
17459    let mut cx = EditorTestContext::new(cx).await;
17460
17461    let diff_base = r#"
17462        use some::mod1;
17463        use some::mod2;
17464
17465        const A: u32 = 42;
17466
17467        fn main() {
17468            println!("hello");
17469
17470            println!("world");
17471        }
17472        "#
17473    .unindent();
17474    executor.run_until_parked();
17475    cx.set_state(
17476        &r#"
17477        use some::mod1;
17478        use some::mod2;
17479
17480        const A: u32 = 42;
17481        const B: u32 = 42;
17482        const C: u32 = 42;
17483        ˇ
17484
17485        fn main() {
17486            println!("hello");
17487
17488            println!("world");
17489        }
17490        "#
17491        .unindent(),
17492    );
17493
17494    cx.set_head_text(&diff_base);
17495    executor.run_until_parked();
17496
17497    cx.update_editor(|editor, window, cx| {
17498        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17499    });
17500    executor.run_until_parked();
17501
17502    cx.assert_state_with_diff(
17503        r#"
17504        use some::mod1;
17505        use some::mod2;
17506
17507        const A: u32 = 42;
17508      + const B: u32 = 42;
17509      + const C: u32 = 42;
17510      + ˇ
17511
17512        fn main() {
17513            println!("hello");
17514
17515            println!("world");
17516        }
17517      "#
17518        .unindent(),
17519    );
17520
17521    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
17522    executor.run_until_parked();
17523
17524    cx.assert_state_with_diff(
17525        r#"
17526        use some::mod1;
17527        use some::mod2;
17528
17529        const A: u32 = 42;
17530      + const B: u32 = 42;
17531      + const C: u32 = 42;
17532      + const D: u32 = 42;
17533      + ˇ
17534
17535        fn main() {
17536            println!("hello");
17537
17538            println!("world");
17539        }
17540      "#
17541        .unindent(),
17542    );
17543
17544    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
17545    executor.run_until_parked();
17546
17547    cx.assert_state_with_diff(
17548        r#"
17549        use some::mod1;
17550        use some::mod2;
17551
17552        const A: u32 = 42;
17553      + const B: u32 = 42;
17554      + const C: u32 = 42;
17555      + const D: u32 = 42;
17556      + const E: u32 = 42;
17557      + ˇ
17558
17559        fn main() {
17560            println!("hello");
17561
17562            println!("world");
17563        }
17564      "#
17565        .unindent(),
17566    );
17567
17568    cx.update_editor(|editor, window, cx| {
17569        editor.delete_line(&DeleteLine, window, cx);
17570    });
17571    executor.run_until_parked();
17572
17573    cx.assert_state_with_diff(
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      + const D: u32 = 42;
17582      + const E: u32 = 42;
17583        ˇ
17584        fn main() {
17585            println!("hello");
17586
17587            println!("world");
17588        }
17589      "#
17590        .unindent(),
17591    );
17592
17593    cx.update_editor(|editor, window, cx| {
17594        editor.move_up(&MoveUp, window, cx);
17595        editor.delete_line(&DeleteLine, window, cx);
17596        editor.move_up(&MoveUp, window, cx);
17597        editor.delete_line(&DeleteLine, window, cx);
17598        editor.move_up(&MoveUp, window, cx);
17599        editor.delete_line(&DeleteLine, window, cx);
17600    });
17601    executor.run_until_parked();
17602    cx.assert_state_with_diff(
17603        r#"
17604        use some::mod1;
17605        use some::mod2;
17606
17607        const A: u32 = 42;
17608      + const B: u32 = 42;
17609        ˇ
17610        fn main() {
17611            println!("hello");
17612
17613            println!("world");
17614        }
17615      "#
17616        .unindent(),
17617    );
17618
17619    cx.update_editor(|editor, window, cx| {
17620        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
17621        editor.delete_line(&DeleteLine, window, cx);
17622    });
17623    executor.run_until_parked();
17624    cx.assert_state_with_diff(
17625        r#"
17626        ˇ
17627        fn main() {
17628            println!("hello");
17629
17630            println!("world");
17631        }
17632      "#
17633        .unindent(),
17634    );
17635}
17636
17637#[gpui::test]
17638async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
17639    init_test(cx, |_| {});
17640
17641    let mut cx = EditorTestContext::new(cx).await;
17642    cx.set_head_text(indoc! { "
17643        one
17644        two
17645        three
17646        four
17647        five
17648        "
17649    });
17650    cx.set_state(indoc! { "
17651        one
17652        ˇthree
17653        five
17654    "});
17655    cx.run_until_parked();
17656    cx.update_editor(|editor, window, cx| {
17657        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17658    });
17659    cx.assert_state_with_diff(
17660        indoc! { "
17661        one
17662      - two
17663        ˇthree
17664      - four
17665        five
17666    "}
17667        .to_string(),
17668    );
17669    cx.update_editor(|editor, window, cx| {
17670        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17671    });
17672
17673    cx.assert_state_with_diff(
17674        indoc! { "
17675        one
17676        ˇthree
17677        five
17678    "}
17679        .to_string(),
17680    );
17681
17682    cx.set_state(indoc! { "
17683        one
17684        ˇTWO
17685        three
17686        four
17687        five
17688    "});
17689    cx.run_until_parked();
17690    cx.update_editor(|editor, window, cx| {
17691        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17692    });
17693
17694    cx.assert_state_with_diff(
17695        indoc! { "
17696            one
17697          - two
17698          + ˇTWO
17699            three
17700            four
17701            five
17702        "}
17703        .to_string(),
17704    );
17705    cx.update_editor(|editor, window, cx| {
17706        editor.move_up(&Default::default(), window, cx);
17707        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17708    });
17709    cx.assert_state_with_diff(
17710        indoc! { "
17711            one
17712            ˇTWO
17713            three
17714            four
17715            five
17716        "}
17717        .to_string(),
17718    );
17719}
17720
17721#[gpui::test]
17722async fn test_edits_around_expanded_deletion_hunks(
17723    executor: BackgroundExecutor,
17724    cx: &mut TestAppContext,
17725) {
17726    init_test(cx, |_| {});
17727
17728    let mut cx = EditorTestContext::new(cx).await;
17729
17730    let diff_base = r#"
17731        use some::mod1;
17732        use some::mod2;
17733
17734        const A: u32 = 42;
17735        const B: u32 = 42;
17736        const C: u32 = 42;
17737
17738
17739        fn main() {
17740            println!("hello");
17741
17742            println!("world");
17743        }
17744    "#
17745    .unindent();
17746    executor.run_until_parked();
17747    cx.set_state(
17748        &r#"
17749        use some::mod1;
17750        use some::mod2;
17751
17752        ˇconst B: u32 = 42;
17753        const C: u32 = 42;
17754
17755
17756        fn main() {
17757            println!("hello");
17758
17759            println!("world");
17760        }
17761        "#
17762        .unindent(),
17763    );
17764
17765    cx.set_head_text(&diff_base);
17766    executor.run_until_parked();
17767
17768    cx.update_editor(|editor, window, cx| {
17769        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17770    });
17771    executor.run_until_parked();
17772
17773    cx.assert_state_with_diff(
17774        r#"
17775        use some::mod1;
17776        use some::mod2;
17777
17778      - const A: u32 = 42;
17779        ˇconst B: u32 = 42;
17780        const C: u32 = 42;
17781
17782
17783        fn main() {
17784            println!("hello");
17785
17786            println!("world");
17787        }
17788      "#
17789        .unindent(),
17790    );
17791
17792    cx.update_editor(|editor, window, cx| {
17793        editor.delete_line(&DeleteLine, window, cx);
17794    });
17795    executor.run_until_parked();
17796    cx.assert_state_with_diff(
17797        r#"
17798        use some::mod1;
17799        use some::mod2;
17800
17801      - const A: u32 = 42;
17802      - const B: u32 = 42;
17803        ˇconst C: u32 = 42;
17804
17805
17806        fn main() {
17807            println!("hello");
17808
17809            println!("world");
17810        }
17811      "#
17812        .unindent(),
17813    );
17814
17815    cx.update_editor(|editor, window, cx| {
17816        editor.delete_line(&DeleteLine, window, cx);
17817    });
17818    executor.run_until_parked();
17819    cx.assert_state_with_diff(
17820        r#"
17821        use some::mod1;
17822        use some::mod2;
17823
17824      - const A: u32 = 42;
17825      - const B: u32 = 42;
17826      - const C: u32 = 42;
17827        ˇ
17828
17829        fn main() {
17830            println!("hello");
17831
17832            println!("world");
17833        }
17834      "#
17835        .unindent(),
17836    );
17837
17838    cx.update_editor(|editor, window, cx| {
17839        editor.handle_input("replacement", window, cx);
17840    });
17841    executor.run_until_parked();
17842    cx.assert_state_with_diff(
17843        r#"
17844        use some::mod1;
17845        use some::mod2;
17846
17847      - const A: u32 = 42;
17848      - const B: u32 = 42;
17849      - const C: u32 = 42;
17850      -
17851      + replacementˇ
17852
17853        fn main() {
17854            println!("hello");
17855
17856            println!("world");
17857        }
17858      "#
17859        .unindent(),
17860    );
17861}
17862
17863#[gpui::test]
17864async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17865    init_test(cx, |_| {});
17866
17867    let mut cx = EditorTestContext::new(cx).await;
17868
17869    let base_text = r#"
17870        one
17871        two
17872        three
17873        four
17874        five
17875    "#
17876    .unindent();
17877    executor.run_until_parked();
17878    cx.set_state(
17879        &r#"
17880        one
17881        two
17882        fˇour
17883        five
17884        "#
17885        .unindent(),
17886    );
17887
17888    cx.set_head_text(&base_text);
17889    executor.run_until_parked();
17890
17891    cx.update_editor(|editor, window, cx| {
17892        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17893    });
17894    executor.run_until_parked();
17895
17896    cx.assert_state_with_diff(
17897        r#"
17898          one
17899          two
17900        - three
17901          fˇour
17902          five
17903        "#
17904        .unindent(),
17905    );
17906
17907    cx.update_editor(|editor, window, cx| {
17908        editor.backspace(&Backspace, window, cx);
17909        editor.backspace(&Backspace, window, cx);
17910    });
17911    executor.run_until_parked();
17912    cx.assert_state_with_diff(
17913        r#"
17914          one
17915          two
17916        - threeˇ
17917        - four
17918        + our
17919          five
17920        "#
17921        .unindent(),
17922    );
17923}
17924
17925#[gpui::test]
17926async fn test_edit_after_expanded_modification_hunk(
17927    executor: BackgroundExecutor,
17928    cx: &mut TestAppContext,
17929) {
17930    init_test(cx, |_| {});
17931
17932    let mut cx = EditorTestContext::new(cx).await;
17933
17934    let diff_base = r#"
17935        use some::mod1;
17936        use some::mod2;
17937
17938        const A: u32 = 42;
17939        const B: u32 = 42;
17940        const C: u32 = 42;
17941        const D: u32 = 42;
17942
17943
17944        fn main() {
17945            println!("hello");
17946
17947            println!("world");
17948        }"#
17949    .unindent();
17950
17951    cx.set_state(
17952        &r#"
17953        use some::mod1;
17954        use some::mod2;
17955
17956        const A: u32 = 42;
17957        const B: u32 = 42;
17958        const C: u32 = 43ˇ
17959        const D: u32 = 42;
17960
17961
17962        fn main() {
17963            println!("hello");
17964
17965            println!("world");
17966        }"#
17967        .unindent(),
17968    );
17969
17970    cx.set_head_text(&diff_base);
17971    executor.run_until_parked();
17972    cx.update_editor(|editor, window, cx| {
17973        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17974    });
17975    executor.run_until_parked();
17976
17977    cx.assert_state_with_diff(
17978        r#"
17979        use some::mod1;
17980        use some::mod2;
17981
17982        const A: u32 = 42;
17983        const B: u32 = 42;
17984      - const C: u32 = 42;
17985      + const C: u32 = 43ˇ
17986        const D: u32 = 42;
17987
17988
17989        fn main() {
17990            println!("hello");
17991
17992            println!("world");
17993        }"#
17994        .unindent(),
17995    );
17996
17997    cx.update_editor(|editor, window, cx| {
17998        editor.handle_input("\nnew_line\n", window, cx);
17999    });
18000    executor.run_until_parked();
18001
18002    cx.assert_state_with_diff(
18003        r#"
18004        use some::mod1;
18005        use some::mod2;
18006
18007        const A: u32 = 42;
18008        const B: u32 = 42;
18009      - const C: u32 = 42;
18010      + const C: u32 = 43
18011      + new_line
18012      + ˇ
18013        const D: u32 = 42;
18014
18015
18016        fn main() {
18017            println!("hello");
18018
18019            println!("world");
18020        }"#
18021        .unindent(),
18022    );
18023}
18024
18025#[gpui::test]
18026async fn test_stage_and_unstage_added_file_hunk(
18027    executor: BackgroundExecutor,
18028    cx: &mut TestAppContext,
18029) {
18030    init_test(cx, |_| {});
18031
18032    let mut cx = EditorTestContext::new(cx).await;
18033    cx.update_editor(|editor, _, cx| {
18034        editor.set_expand_all_diff_hunks(cx);
18035    });
18036
18037    let working_copy = r#"
18038            ˇfn main() {
18039                println!("hello, world!");
18040            }
18041        "#
18042    .unindent();
18043
18044    cx.set_state(&working_copy);
18045    executor.run_until_parked();
18046
18047    cx.assert_state_with_diff(
18048        r#"
18049            + ˇfn main() {
18050            +     println!("hello, world!");
18051            + }
18052        "#
18053        .unindent(),
18054    );
18055    cx.assert_index_text(None);
18056
18057    cx.update_editor(|editor, window, cx| {
18058        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18059    });
18060    executor.run_until_parked();
18061    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
18062    cx.assert_state_with_diff(
18063        r#"
18064            + ˇfn main() {
18065            +     println!("hello, world!");
18066            + }
18067        "#
18068        .unindent(),
18069    );
18070
18071    cx.update_editor(|editor, window, cx| {
18072        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18073    });
18074    executor.run_until_parked();
18075    cx.assert_index_text(None);
18076}
18077
18078async fn setup_indent_guides_editor(
18079    text: &str,
18080    cx: &mut TestAppContext,
18081) -> (BufferId, EditorTestContext) {
18082    init_test(cx, |_| {});
18083
18084    let mut cx = EditorTestContext::new(cx).await;
18085
18086    let buffer_id = cx.update_editor(|editor, window, cx| {
18087        editor.set_text(text, window, cx);
18088        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
18089
18090        buffer_ids[0]
18091    });
18092
18093    (buffer_id, cx)
18094}
18095
18096fn assert_indent_guides(
18097    range: Range<u32>,
18098    expected: Vec<IndentGuide>,
18099    active_indices: Option<Vec<usize>>,
18100    cx: &mut EditorTestContext,
18101) {
18102    let indent_guides = cx.update_editor(|editor, window, cx| {
18103        let snapshot = editor.snapshot(window, cx).display_snapshot;
18104        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
18105            editor,
18106            MultiBufferRow(range.start)..MultiBufferRow(range.end),
18107            true,
18108            &snapshot,
18109            cx,
18110        );
18111
18112        indent_guides.sort_by(|a, b| {
18113            a.depth.cmp(&b.depth).then(
18114                a.start_row
18115                    .cmp(&b.start_row)
18116                    .then(a.end_row.cmp(&b.end_row)),
18117            )
18118        });
18119        indent_guides
18120    });
18121
18122    if let Some(expected) = active_indices {
18123        let active_indices = cx.update_editor(|editor, window, cx| {
18124            let snapshot = editor.snapshot(window, cx).display_snapshot;
18125            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
18126        });
18127
18128        assert_eq!(
18129            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
18130            expected,
18131            "Active indent guide indices do not match"
18132        );
18133    }
18134
18135    assert_eq!(indent_guides, expected, "Indent guides do not match");
18136}
18137
18138fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
18139    IndentGuide {
18140        buffer_id,
18141        start_row: MultiBufferRow(start_row),
18142        end_row: MultiBufferRow(end_row),
18143        depth,
18144        tab_size: 4,
18145        settings: IndentGuideSettings {
18146            enabled: true,
18147            line_width: 1,
18148            active_line_width: 1,
18149            ..Default::default()
18150        },
18151    }
18152}
18153
18154#[gpui::test]
18155async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
18156    let (buffer_id, mut cx) = setup_indent_guides_editor(
18157        &"
18158        fn main() {
18159            let a = 1;
18160        }"
18161        .unindent(),
18162        cx,
18163    )
18164    .await;
18165
18166    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
18167}
18168
18169#[gpui::test]
18170async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
18171    let (buffer_id, mut cx) = setup_indent_guides_editor(
18172        &"
18173        fn main() {
18174            let a = 1;
18175            let b = 2;
18176        }"
18177        .unindent(),
18178        cx,
18179    )
18180    .await;
18181
18182    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
18183}
18184
18185#[gpui::test]
18186async fn test_indent_guide_nested(cx: &mut TestAppContext) {
18187    let (buffer_id, mut cx) = setup_indent_guides_editor(
18188        &"
18189        fn main() {
18190            let a = 1;
18191            if a == 3 {
18192                let b = 2;
18193            } else {
18194                let c = 3;
18195            }
18196        }"
18197        .unindent(),
18198        cx,
18199    )
18200    .await;
18201
18202    assert_indent_guides(
18203        0..8,
18204        vec![
18205            indent_guide(buffer_id, 1, 6, 0),
18206            indent_guide(buffer_id, 3, 3, 1),
18207            indent_guide(buffer_id, 5, 5, 1),
18208        ],
18209        None,
18210        &mut cx,
18211    );
18212}
18213
18214#[gpui::test]
18215async fn test_indent_guide_tab(cx: &mut TestAppContext) {
18216    let (buffer_id, mut cx) = setup_indent_guides_editor(
18217        &"
18218        fn main() {
18219            let a = 1;
18220                let b = 2;
18221            let c = 3;
18222        }"
18223        .unindent(),
18224        cx,
18225    )
18226    .await;
18227
18228    assert_indent_guides(
18229        0..5,
18230        vec![
18231            indent_guide(buffer_id, 1, 3, 0),
18232            indent_guide(buffer_id, 2, 2, 1),
18233        ],
18234        None,
18235        &mut cx,
18236    );
18237}
18238
18239#[gpui::test]
18240async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
18241    let (buffer_id, mut cx) = setup_indent_guides_editor(
18242        &"
18243        fn main() {
18244            let a = 1;
18245
18246            let c = 3;
18247        }"
18248        .unindent(),
18249        cx,
18250    )
18251    .await;
18252
18253    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
18254}
18255
18256#[gpui::test]
18257async fn test_indent_guide_complex(cx: &mut TestAppContext) {
18258    let (buffer_id, mut cx) = setup_indent_guides_editor(
18259        &"
18260        fn main() {
18261            let a = 1;
18262
18263            let c = 3;
18264
18265            if a == 3 {
18266                let b = 2;
18267            } else {
18268                let c = 3;
18269            }
18270        }"
18271        .unindent(),
18272        cx,
18273    )
18274    .await;
18275
18276    assert_indent_guides(
18277        0..11,
18278        vec![
18279            indent_guide(buffer_id, 1, 9, 0),
18280            indent_guide(buffer_id, 6, 6, 1),
18281            indent_guide(buffer_id, 8, 8, 1),
18282        ],
18283        None,
18284        &mut cx,
18285    );
18286}
18287
18288#[gpui::test]
18289async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
18290    let (buffer_id, mut cx) = setup_indent_guides_editor(
18291        &"
18292        fn main() {
18293            let a = 1;
18294
18295            let c = 3;
18296
18297            if a == 3 {
18298                let b = 2;
18299            } else {
18300                let c = 3;
18301            }
18302        }"
18303        .unindent(),
18304        cx,
18305    )
18306    .await;
18307
18308    assert_indent_guides(
18309        1..11,
18310        vec![
18311            indent_guide(buffer_id, 1, 9, 0),
18312            indent_guide(buffer_id, 6, 6, 1),
18313            indent_guide(buffer_id, 8, 8, 1),
18314        ],
18315        None,
18316        &mut cx,
18317    );
18318}
18319
18320#[gpui::test]
18321async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
18322    let (buffer_id, mut cx) = setup_indent_guides_editor(
18323        &"
18324        fn main() {
18325            let a = 1;
18326
18327            let c = 3;
18328
18329            if a == 3 {
18330                let b = 2;
18331            } else {
18332                let c = 3;
18333            }
18334        }"
18335        .unindent(),
18336        cx,
18337    )
18338    .await;
18339
18340    assert_indent_guides(
18341        1..10,
18342        vec![
18343            indent_guide(buffer_id, 1, 9, 0),
18344            indent_guide(buffer_id, 6, 6, 1),
18345            indent_guide(buffer_id, 8, 8, 1),
18346        ],
18347        None,
18348        &mut cx,
18349    );
18350}
18351
18352#[gpui::test]
18353async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
18354    let (buffer_id, mut cx) = setup_indent_guides_editor(
18355        &"
18356        fn main() {
18357            if a {
18358                b(
18359                    c,
18360                    d,
18361                )
18362            } else {
18363                e(
18364                    f
18365                )
18366            }
18367        }"
18368        .unindent(),
18369        cx,
18370    )
18371    .await;
18372
18373    assert_indent_guides(
18374        0..11,
18375        vec![
18376            indent_guide(buffer_id, 1, 10, 0),
18377            indent_guide(buffer_id, 2, 5, 1),
18378            indent_guide(buffer_id, 7, 9, 1),
18379            indent_guide(buffer_id, 3, 4, 2),
18380            indent_guide(buffer_id, 8, 8, 2),
18381        ],
18382        None,
18383        &mut cx,
18384    );
18385
18386    cx.update_editor(|editor, window, cx| {
18387        editor.fold_at(MultiBufferRow(2), window, cx);
18388        assert_eq!(
18389            editor.display_text(cx),
18390            "
18391            fn main() {
18392                if a {
18393                    b(⋯
18394                    )
18395                } else {
18396                    e(
18397                        f
18398                    )
18399                }
18400            }"
18401            .unindent()
18402        );
18403    });
18404
18405    assert_indent_guides(
18406        0..11,
18407        vec![
18408            indent_guide(buffer_id, 1, 10, 0),
18409            indent_guide(buffer_id, 2, 5, 1),
18410            indent_guide(buffer_id, 7, 9, 1),
18411            indent_guide(buffer_id, 8, 8, 2),
18412        ],
18413        None,
18414        &mut cx,
18415    );
18416}
18417
18418#[gpui::test]
18419async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
18420    let (buffer_id, mut cx) = setup_indent_guides_editor(
18421        &"
18422        block1
18423            block2
18424                block3
18425                    block4
18426            block2
18427        block1
18428        block1"
18429            .unindent(),
18430        cx,
18431    )
18432    .await;
18433
18434    assert_indent_guides(
18435        1..10,
18436        vec![
18437            indent_guide(buffer_id, 1, 4, 0),
18438            indent_guide(buffer_id, 2, 3, 1),
18439            indent_guide(buffer_id, 3, 3, 2),
18440        ],
18441        None,
18442        &mut cx,
18443    );
18444}
18445
18446#[gpui::test]
18447async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
18448    let (buffer_id, mut cx) = setup_indent_guides_editor(
18449        &"
18450        block1
18451            block2
18452                block3
18453
18454        block1
18455        block1"
18456            .unindent(),
18457        cx,
18458    )
18459    .await;
18460
18461    assert_indent_guides(
18462        0..6,
18463        vec![
18464            indent_guide(buffer_id, 1, 2, 0),
18465            indent_guide(buffer_id, 2, 2, 1),
18466        ],
18467        None,
18468        &mut cx,
18469    );
18470}
18471
18472#[gpui::test]
18473async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
18474    let (buffer_id, mut cx) = setup_indent_guides_editor(
18475        &"
18476        function component() {
18477        \treturn (
18478        \t\t\t
18479        \t\t<div>
18480        \t\t\t<abc></abc>
18481        \t\t</div>
18482        \t)
18483        }"
18484        .unindent(),
18485        cx,
18486    )
18487    .await;
18488
18489    assert_indent_guides(
18490        0..8,
18491        vec![
18492            indent_guide(buffer_id, 1, 6, 0),
18493            indent_guide(buffer_id, 2, 5, 1),
18494            indent_guide(buffer_id, 4, 4, 2),
18495        ],
18496        None,
18497        &mut cx,
18498    );
18499}
18500
18501#[gpui::test]
18502async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
18503    let (buffer_id, mut cx) = setup_indent_guides_editor(
18504        &"
18505        function component() {
18506        \treturn (
18507        \t
18508        \t\t<div>
18509        \t\t\t<abc></abc>
18510        \t\t</div>
18511        \t)
18512        }"
18513        .unindent(),
18514        cx,
18515    )
18516    .await;
18517
18518    assert_indent_guides(
18519        0..8,
18520        vec![
18521            indent_guide(buffer_id, 1, 6, 0),
18522            indent_guide(buffer_id, 2, 5, 1),
18523            indent_guide(buffer_id, 4, 4, 2),
18524        ],
18525        None,
18526        &mut cx,
18527    );
18528}
18529
18530#[gpui::test]
18531async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
18532    let (buffer_id, mut cx) = setup_indent_guides_editor(
18533        &"
18534        block1
18535
18536
18537
18538            block2
18539        "
18540        .unindent(),
18541        cx,
18542    )
18543    .await;
18544
18545    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
18546}
18547
18548#[gpui::test]
18549async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
18550    let (buffer_id, mut cx) = setup_indent_guides_editor(
18551        &"
18552        def a:
18553        \tb = 3
18554        \tif True:
18555        \t\tc = 4
18556        \t\td = 5
18557        \tprint(b)
18558        "
18559        .unindent(),
18560        cx,
18561    )
18562    .await;
18563
18564    assert_indent_guides(
18565        0..6,
18566        vec![
18567            indent_guide(buffer_id, 1, 5, 0),
18568            indent_guide(buffer_id, 3, 4, 1),
18569        ],
18570        None,
18571        &mut cx,
18572    );
18573}
18574
18575#[gpui::test]
18576async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
18577    let (buffer_id, mut cx) = setup_indent_guides_editor(
18578        &"
18579    fn main() {
18580        let a = 1;
18581    }"
18582        .unindent(),
18583        cx,
18584    )
18585    .await;
18586
18587    cx.update_editor(|editor, window, cx| {
18588        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18589            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18590        });
18591    });
18592
18593    assert_indent_guides(
18594        0..3,
18595        vec![indent_guide(buffer_id, 1, 1, 0)],
18596        Some(vec![0]),
18597        &mut cx,
18598    );
18599}
18600
18601#[gpui::test]
18602async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
18603    let (buffer_id, mut cx) = setup_indent_guides_editor(
18604        &"
18605    fn main() {
18606        if 1 == 2 {
18607            let a = 1;
18608        }
18609    }"
18610        .unindent(),
18611        cx,
18612    )
18613    .await;
18614
18615    cx.update_editor(|editor, window, cx| {
18616        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18617            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18618        });
18619    });
18620
18621    assert_indent_guides(
18622        0..4,
18623        vec![
18624            indent_guide(buffer_id, 1, 3, 0),
18625            indent_guide(buffer_id, 2, 2, 1),
18626        ],
18627        Some(vec![1]),
18628        &mut cx,
18629    );
18630
18631    cx.update_editor(|editor, window, cx| {
18632        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18633            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
18634        });
18635    });
18636
18637    assert_indent_guides(
18638        0..4,
18639        vec![
18640            indent_guide(buffer_id, 1, 3, 0),
18641            indent_guide(buffer_id, 2, 2, 1),
18642        ],
18643        Some(vec![1]),
18644        &mut cx,
18645    );
18646
18647    cx.update_editor(|editor, window, cx| {
18648        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18649            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
18650        });
18651    });
18652
18653    assert_indent_guides(
18654        0..4,
18655        vec![
18656            indent_guide(buffer_id, 1, 3, 0),
18657            indent_guide(buffer_id, 2, 2, 1),
18658        ],
18659        Some(vec![0]),
18660        &mut cx,
18661    );
18662}
18663
18664#[gpui::test]
18665async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
18666    let (buffer_id, mut cx) = setup_indent_guides_editor(
18667        &"
18668    fn main() {
18669        let a = 1;
18670
18671        let b = 2;
18672    }"
18673        .unindent(),
18674        cx,
18675    )
18676    .await;
18677
18678    cx.update_editor(|editor, window, cx| {
18679        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18680            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
18681        });
18682    });
18683
18684    assert_indent_guides(
18685        0..5,
18686        vec![indent_guide(buffer_id, 1, 3, 0)],
18687        Some(vec![0]),
18688        &mut cx,
18689    );
18690}
18691
18692#[gpui::test]
18693async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
18694    let (buffer_id, mut cx) = setup_indent_guides_editor(
18695        &"
18696    def m:
18697        a = 1
18698        pass"
18699            .unindent(),
18700        cx,
18701    )
18702    .await;
18703
18704    cx.update_editor(|editor, window, cx| {
18705        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18706            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18707        });
18708    });
18709
18710    assert_indent_guides(
18711        0..3,
18712        vec![indent_guide(buffer_id, 1, 2, 0)],
18713        Some(vec![0]),
18714        &mut cx,
18715    );
18716}
18717
18718#[gpui::test]
18719async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
18720    init_test(cx, |_| {});
18721    let mut cx = EditorTestContext::new(cx).await;
18722    let text = indoc! {
18723        "
18724        impl A {
18725            fn b() {
18726                0;
18727                3;
18728                5;
18729                6;
18730                7;
18731            }
18732        }
18733        "
18734    };
18735    let base_text = indoc! {
18736        "
18737        impl A {
18738            fn b() {
18739                0;
18740                1;
18741                2;
18742                3;
18743                4;
18744            }
18745            fn c() {
18746                5;
18747                6;
18748                7;
18749            }
18750        }
18751        "
18752    };
18753
18754    cx.update_editor(|editor, window, cx| {
18755        editor.set_text(text, window, cx);
18756
18757        editor.buffer().update(cx, |multibuffer, cx| {
18758            let buffer = multibuffer.as_singleton().unwrap();
18759            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
18760
18761            multibuffer.set_all_diff_hunks_expanded(cx);
18762            multibuffer.add_diff(diff, cx);
18763
18764            buffer.read(cx).remote_id()
18765        })
18766    });
18767    cx.run_until_parked();
18768
18769    cx.assert_state_with_diff(
18770        indoc! { "
18771          impl A {
18772              fn b() {
18773                  0;
18774        -         1;
18775        -         2;
18776                  3;
18777        -         4;
18778        -     }
18779        -     fn c() {
18780                  5;
18781                  6;
18782                  7;
18783              }
18784          }
18785          ˇ"
18786        }
18787        .to_string(),
18788    );
18789
18790    let mut actual_guides = cx.update_editor(|editor, window, cx| {
18791        editor
18792            .snapshot(window, cx)
18793            .buffer_snapshot
18794            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
18795            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
18796            .collect::<Vec<_>>()
18797    });
18798    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
18799    assert_eq!(
18800        actual_guides,
18801        vec![
18802            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
18803            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
18804            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
18805        ]
18806    );
18807}
18808
18809#[gpui::test]
18810async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18811    init_test(cx, |_| {});
18812    let mut cx = EditorTestContext::new(cx).await;
18813
18814    let diff_base = r#"
18815        a
18816        b
18817        c
18818        "#
18819    .unindent();
18820
18821    cx.set_state(
18822        &r#"
18823        ˇA
18824        b
18825        C
18826        "#
18827        .unindent(),
18828    );
18829    cx.set_head_text(&diff_base);
18830    cx.update_editor(|editor, window, cx| {
18831        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18832    });
18833    executor.run_until_parked();
18834
18835    let both_hunks_expanded = r#"
18836        - a
18837        + ˇA
18838          b
18839        - c
18840        + C
18841        "#
18842    .unindent();
18843
18844    cx.assert_state_with_diff(both_hunks_expanded.clone());
18845
18846    let hunk_ranges = cx.update_editor(|editor, window, cx| {
18847        let snapshot = editor.snapshot(window, cx);
18848        let hunks = editor
18849            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18850            .collect::<Vec<_>>();
18851        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
18852        let buffer_id = hunks[0].buffer_id;
18853        hunks
18854            .into_iter()
18855            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
18856            .collect::<Vec<_>>()
18857    });
18858    assert_eq!(hunk_ranges.len(), 2);
18859
18860    cx.update_editor(|editor, _, cx| {
18861        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18862    });
18863    executor.run_until_parked();
18864
18865    let second_hunk_expanded = r#"
18866          ˇA
18867          b
18868        - c
18869        + C
18870        "#
18871    .unindent();
18872
18873    cx.assert_state_with_diff(second_hunk_expanded);
18874
18875    cx.update_editor(|editor, _, cx| {
18876        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18877    });
18878    executor.run_until_parked();
18879
18880    cx.assert_state_with_diff(both_hunks_expanded.clone());
18881
18882    cx.update_editor(|editor, _, cx| {
18883        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
18884    });
18885    executor.run_until_parked();
18886
18887    let first_hunk_expanded = r#"
18888        - a
18889        + ˇA
18890          b
18891          C
18892        "#
18893    .unindent();
18894
18895    cx.assert_state_with_diff(first_hunk_expanded);
18896
18897    cx.update_editor(|editor, _, cx| {
18898        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
18899    });
18900    executor.run_until_parked();
18901
18902    cx.assert_state_with_diff(both_hunks_expanded);
18903
18904    cx.set_state(
18905        &r#"
18906        ˇA
18907        b
18908        "#
18909        .unindent(),
18910    );
18911    cx.run_until_parked();
18912
18913    // TODO this cursor position seems bad
18914    cx.assert_state_with_diff(
18915        r#"
18916        - ˇa
18917        + A
18918          b
18919        "#
18920        .unindent(),
18921    );
18922
18923    cx.update_editor(|editor, window, cx| {
18924        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18925    });
18926
18927    cx.assert_state_with_diff(
18928        r#"
18929            - ˇa
18930            + A
18931              b
18932            - c
18933            "#
18934        .unindent(),
18935    );
18936
18937    let hunk_ranges = cx.update_editor(|editor, window, cx| {
18938        let snapshot = editor.snapshot(window, cx);
18939        let hunks = editor
18940            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18941            .collect::<Vec<_>>();
18942        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
18943        let buffer_id = hunks[0].buffer_id;
18944        hunks
18945            .into_iter()
18946            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
18947            .collect::<Vec<_>>()
18948    });
18949    assert_eq!(hunk_ranges.len(), 2);
18950
18951    cx.update_editor(|editor, _, cx| {
18952        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
18953    });
18954    executor.run_until_parked();
18955
18956    cx.assert_state_with_diff(
18957        r#"
18958        - ˇa
18959        + A
18960          b
18961        "#
18962        .unindent(),
18963    );
18964}
18965
18966#[gpui::test]
18967async fn test_toggle_deletion_hunk_at_start_of_file(
18968    executor: BackgroundExecutor,
18969    cx: &mut TestAppContext,
18970) {
18971    init_test(cx, |_| {});
18972    let mut cx = EditorTestContext::new(cx).await;
18973
18974    let diff_base = r#"
18975        a
18976        b
18977        c
18978        "#
18979    .unindent();
18980
18981    cx.set_state(
18982        &r#"
18983        ˇb
18984        c
18985        "#
18986        .unindent(),
18987    );
18988    cx.set_head_text(&diff_base);
18989    cx.update_editor(|editor, window, cx| {
18990        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18991    });
18992    executor.run_until_parked();
18993
18994    let hunk_expanded = r#"
18995        - a
18996          ˇb
18997          c
18998        "#
18999    .unindent();
19000
19001    cx.assert_state_with_diff(hunk_expanded.clone());
19002
19003    let hunk_ranges = cx.update_editor(|editor, window, cx| {
19004        let snapshot = editor.snapshot(window, cx);
19005        let hunks = editor
19006            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19007            .collect::<Vec<_>>();
19008        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
19009        let buffer_id = hunks[0].buffer_id;
19010        hunks
19011            .into_iter()
19012            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
19013            .collect::<Vec<_>>()
19014    });
19015    assert_eq!(hunk_ranges.len(), 1);
19016
19017    cx.update_editor(|editor, _, cx| {
19018        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19019    });
19020    executor.run_until_parked();
19021
19022    let hunk_collapsed = r#"
19023          ˇb
19024          c
19025        "#
19026    .unindent();
19027
19028    cx.assert_state_with_diff(hunk_collapsed);
19029
19030    cx.update_editor(|editor, _, cx| {
19031        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19032    });
19033    executor.run_until_parked();
19034
19035    cx.assert_state_with_diff(hunk_expanded.clone());
19036}
19037
19038#[gpui::test]
19039async fn test_display_diff_hunks(cx: &mut TestAppContext) {
19040    init_test(cx, |_| {});
19041
19042    let fs = FakeFs::new(cx.executor());
19043    fs.insert_tree(
19044        path!("/test"),
19045        json!({
19046            ".git": {},
19047            "file-1": "ONE\n",
19048            "file-2": "TWO\n",
19049            "file-3": "THREE\n",
19050        }),
19051    )
19052    .await;
19053
19054    fs.set_head_for_repo(
19055        path!("/test/.git").as_ref(),
19056        &[
19057            ("file-1".into(), "one\n".into()),
19058            ("file-2".into(), "two\n".into()),
19059            ("file-3".into(), "three\n".into()),
19060        ],
19061        "deadbeef",
19062    );
19063
19064    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
19065    let mut buffers = vec![];
19066    for i in 1..=3 {
19067        let buffer = project
19068            .update(cx, |project, cx| {
19069                let path = format!(path!("/test/file-{}"), i);
19070                project.open_local_buffer(path, cx)
19071            })
19072            .await
19073            .unwrap();
19074        buffers.push(buffer);
19075    }
19076
19077    let multibuffer = cx.new(|cx| {
19078        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
19079        multibuffer.set_all_diff_hunks_expanded(cx);
19080        for buffer in &buffers {
19081            let snapshot = buffer.read(cx).snapshot();
19082            multibuffer.set_excerpts_for_path(
19083                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
19084                buffer.clone(),
19085                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
19086                DEFAULT_MULTIBUFFER_CONTEXT,
19087                cx,
19088            );
19089        }
19090        multibuffer
19091    });
19092
19093    let editor = cx.add_window(|window, cx| {
19094        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
19095    });
19096    cx.run_until_parked();
19097
19098    let snapshot = editor
19099        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19100        .unwrap();
19101    let hunks = snapshot
19102        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
19103        .map(|hunk| match hunk {
19104            DisplayDiffHunk::Unfolded {
19105                display_row_range, ..
19106            } => display_row_range,
19107            DisplayDiffHunk::Folded { .. } => unreachable!(),
19108        })
19109        .collect::<Vec<_>>();
19110    assert_eq!(
19111        hunks,
19112        [
19113            DisplayRow(2)..DisplayRow(4),
19114            DisplayRow(7)..DisplayRow(9),
19115            DisplayRow(12)..DisplayRow(14),
19116        ]
19117    );
19118}
19119
19120#[gpui::test]
19121async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
19122    init_test(cx, |_| {});
19123
19124    let mut cx = EditorTestContext::new(cx).await;
19125    cx.set_head_text(indoc! { "
19126        one
19127        two
19128        three
19129        four
19130        five
19131        "
19132    });
19133    cx.set_index_text(indoc! { "
19134        one
19135        two
19136        three
19137        four
19138        five
19139        "
19140    });
19141    cx.set_state(indoc! {"
19142        one
19143        TWO
19144        ˇTHREE
19145        FOUR
19146        five
19147    "});
19148    cx.run_until_parked();
19149    cx.update_editor(|editor, window, cx| {
19150        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19151    });
19152    cx.run_until_parked();
19153    cx.assert_index_text(Some(indoc! {"
19154        one
19155        TWO
19156        THREE
19157        FOUR
19158        five
19159    "}));
19160    cx.set_state(indoc! { "
19161        one
19162        TWO
19163        ˇTHREE-HUNDRED
19164        FOUR
19165        five
19166    "});
19167    cx.run_until_parked();
19168    cx.update_editor(|editor, window, cx| {
19169        let snapshot = editor.snapshot(window, cx);
19170        let hunks = editor
19171            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19172            .collect::<Vec<_>>();
19173        assert_eq!(hunks.len(), 1);
19174        assert_eq!(
19175            hunks[0].status(),
19176            DiffHunkStatus {
19177                kind: DiffHunkStatusKind::Modified,
19178                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
19179            }
19180        );
19181
19182        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19183    });
19184    cx.run_until_parked();
19185    cx.assert_index_text(Some(indoc! {"
19186        one
19187        TWO
19188        THREE-HUNDRED
19189        FOUR
19190        five
19191    "}));
19192}
19193
19194#[gpui::test]
19195fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
19196    init_test(cx, |_| {});
19197
19198    let editor = cx.add_window(|window, cx| {
19199        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
19200        build_editor(buffer, window, cx)
19201    });
19202
19203    let render_args = Arc::new(Mutex::new(None));
19204    let snapshot = editor
19205        .update(cx, |editor, window, cx| {
19206            let snapshot = editor.buffer().read(cx).snapshot(cx);
19207            let range =
19208                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
19209
19210            struct RenderArgs {
19211                row: MultiBufferRow,
19212                folded: bool,
19213                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
19214            }
19215
19216            let crease = Crease::inline(
19217                range,
19218                FoldPlaceholder::test(),
19219                {
19220                    let toggle_callback = render_args.clone();
19221                    move |row, folded, callback, _window, _cx| {
19222                        *toggle_callback.lock() = Some(RenderArgs {
19223                            row,
19224                            folded,
19225                            callback,
19226                        });
19227                        div()
19228                    }
19229                },
19230                |_row, _folded, _window, _cx| div(),
19231            );
19232
19233            editor.insert_creases(Some(crease), cx);
19234            let snapshot = editor.snapshot(window, cx);
19235            let _div = snapshot.render_crease_toggle(
19236                MultiBufferRow(1),
19237                false,
19238                cx.entity().clone(),
19239                window,
19240                cx,
19241            );
19242            snapshot
19243        })
19244        .unwrap();
19245
19246    let render_args = render_args.lock().take().unwrap();
19247    assert_eq!(render_args.row, MultiBufferRow(1));
19248    assert!(!render_args.folded);
19249    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
19250
19251    cx.update_window(*editor, |_, window, cx| {
19252        (render_args.callback)(true, window, cx)
19253    })
19254    .unwrap();
19255    let snapshot = editor
19256        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19257        .unwrap();
19258    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
19259
19260    cx.update_window(*editor, |_, window, cx| {
19261        (render_args.callback)(false, window, cx)
19262    })
19263    .unwrap();
19264    let snapshot = editor
19265        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19266        .unwrap();
19267    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
19268}
19269
19270#[gpui::test]
19271async fn test_input_text(cx: &mut TestAppContext) {
19272    init_test(cx, |_| {});
19273    let mut cx = EditorTestContext::new(cx).await;
19274
19275    cx.set_state(
19276        &r#"ˇone
19277        two
19278
19279        three
19280        fourˇ
19281        five
19282
19283        siˇx"#
19284            .unindent(),
19285    );
19286
19287    cx.dispatch_action(HandleInput(String::new()));
19288    cx.assert_editor_state(
19289        &r#"ˇone
19290        two
19291
19292        three
19293        fourˇ
19294        five
19295
19296        siˇx"#
19297            .unindent(),
19298    );
19299
19300    cx.dispatch_action(HandleInput("AAAA".to_string()));
19301    cx.assert_editor_state(
19302        &r#"AAAAˇone
19303        two
19304
19305        three
19306        fourAAAAˇ
19307        five
19308
19309        siAAAAˇx"#
19310            .unindent(),
19311    );
19312}
19313
19314#[gpui::test]
19315async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
19316    init_test(cx, |_| {});
19317
19318    let mut cx = EditorTestContext::new(cx).await;
19319    cx.set_state(
19320        r#"let foo = 1;
19321let foo = 2;
19322let foo = 3;
19323let fooˇ = 4;
19324let foo = 5;
19325let foo = 6;
19326let foo = 7;
19327let foo = 8;
19328let foo = 9;
19329let foo = 10;
19330let foo = 11;
19331let foo = 12;
19332let foo = 13;
19333let foo = 14;
19334let foo = 15;"#,
19335    );
19336
19337    cx.update_editor(|e, window, cx| {
19338        assert_eq!(
19339            e.next_scroll_position,
19340            NextScrollCursorCenterTopBottom::Center,
19341            "Default next scroll direction is center",
19342        );
19343
19344        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19345        assert_eq!(
19346            e.next_scroll_position,
19347            NextScrollCursorCenterTopBottom::Top,
19348            "After center, next scroll direction should be top",
19349        );
19350
19351        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19352        assert_eq!(
19353            e.next_scroll_position,
19354            NextScrollCursorCenterTopBottom::Bottom,
19355            "After top, next scroll direction should be bottom",
19356        );
19357
19358        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19359        assert_eq!(
19360            e.next_scroll_position,
19361            NextScrollCursorCenterTopBottom::Center,
19362            "After bottom, scrolling should start over",
19363        );
19364
19365        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19366        assert_eq!(
19367            e.next_scroll_position,
19368            NextScrollCursorCenterTopBottom::Top,
19369            "Scrolling continues if retriggered fast enough"
19370        );
19371    });
19372
19373    cx.executor()
19374        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
19375    cx.executor().run_until_parked();
19376    cx.update_editor(|e, _, _| {
19377        assert_eq!(
19378            e.next_scroll_position,
19379            NextScrollCursorCenterTopBottom::Center,
19380            "If scrolling is not triggered fast enough, it should reset"
19381        );
19382    });
19383}
19384
19385#[gpui::test]
19386async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
19387    init_test(cx, |_| {});
19388    let mut cx = EditorLspTestContext::new_rust(
19389        lsp::ServerCapabilities {
19390            definition_provider: Some(lsp::OneOf::Left(true)),
19391            references_provider: Some(lsp::OneOf::Left(true)),
19392            ..lsp::ServerCapabilities::default()
19393        },
19394        cx,
19395    )
19396    .await;
19397
19398    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
19399        let go_to_definition = cx
19400            .lsp
19401            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
19402                move |params, _| async move {
19403                    if empty_go_to_definition {
19404                        Ok(None)
19405                    } else {
19406                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
19407                            uri: params.text_document_position_params.text_document.uri,
19408                            range: lsp::Range::new(
19409                                lsp::Position::new(4, 3),
19410                                lsp::Position::new(4, 6),
19411                            ),
19412                        })))
19413                    }
19414                },
19415            );
19416        let references = cx
19417            .lsp
19418            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
19419                Ok(Some(vec![lsp::Location {
19420                    uri: params.text_document_position.text_document.uri,
19421                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
19422                }]))
19423            });
19424        (go_to_definition, references)
19425    };
19426
19427    cx.set_state(
19428        &r#"fn one() {
19429            let mut a = ˇtwo();
19430        }
19431
19432        fn two() {}"#
19433            .unindent(),
19434    );
19435    set_up_lsp_handlers(false, &mut cx);
19436    let navigated = cx
19437        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19438        .await
19439        .expect("Failed to navigate to definition");
19440    assert_eq!(
19441        navigated,
19442        Navigated::Yes,
19443        "Should have navigated to definition from the GetDefinition response"
19444    );
19445    cx.assert_editor_state(
19446        &r#"fn one() {
19447            let mut a = two();
19448        }
19449
19450        fn «twoˇ»() {}"#
19451            .unindent(),
19452    );
19453
19454    let editors = cx.update_workspace(|workspace, _, cx| {
19455        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19456    });
19457    cx.update_editor(|_, _, test_editor_cx| {
19458        assert_eq!(
19459            editors.len(),
19460            1,
19461            "Initially, only one, test, editor should be open in the workspace"
19462        );
19463        assert_eq!(
19464            test_editor_cx.entity(),
19465            editors.last().expect("Asserted len is 1").clone()
19466        );
19467    });
19468
19469    set_up_lsp_handlers(true, &mut cx);
19470    let navigated = cx
19471        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19472        .await
19473        .expect("Failed to navigate to lookup references");
19474    assert_eq!(
19475        navigated,
19476        Navigated::Yes,
19477        "Should have navigated to references as a fallback after empty GoToDefinition response"
19478    );
19479    // We should not change the selections in the existing file,
19480    // if opening another milti buffer with the references
19481    cx.assert_editor_state(
19482        &r#"fn one() {
19483            let mut a = two();
19484        }
19485
19486        fn «twoˇ»() {}"#
19487            .unindent(),
19488    );
19489    let editors = cx.update_workspace(|workspace, _, cx| {
19490        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19491    });
19492    cx.update_editor(|_, _, test_editor_cx| {
19493        assert_eq!(
19494            editors.len(),
19495            2,
19496            "After falling back to references search, we open a new editor with the results"
19497        );
19498        let references_fallback_text = editors
19499            .into_iter()
19500            .find(|new_editor| *new_editor != test_editor_cx.entity())
19501            .expect("Should have one non-test editor now")
19502            .read(test_editor_cx)
19503            .text(test_editor_cx);
19504        assert_eq!(
19505            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
19506            "Should use the range from the references response and not the GoToDefinition one"
19507        );
19508    });
19509}
19510
19511#[gpui::test]
19512async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
19513    init_test(cx, |_| {});
19514    cx.update(|cx| {
19515        let mut editor_settings = EditorSettings::get_global(cx).clone();
19516        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
19517        EditorSettings::override_global(editor_settings, cx);
19518    });
19519    let mut cx = EditorLspTestContext::new_rust(
19520        lsp::ServerCapabilities {
19521            definition_provider: Some(lsp::OneOf::Left(true)),
19522            references_provider: Some(lsp::OneOf::Left(true)),
19523            ..lsp::ServerCapabilities::default()
19524        },
19525        cx,
19526    )
19527    .await;
19528    let original_state = r#"fn one() {
19529        let mut a = ˇtwo();
19530    }
19531
19532    fn two() {}"#
19533        .unindent();
19534    cx.set_state(&original_state);
19535
19536    let mut go_to_definition = cx
19537        .lsp
19538        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
19539            move |_, _| async move { Ok(None) },
19540        );
19541    let _references = cx
19542        .lsp
19543        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
19544            panic!("Should not call for references with no go to definition fallback")
19545        });
19546
19547    let navigated = cx
19548        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19549        .await
19550        .expect("Failed to navigate to lookup references");
19551    go_to_definition
19552        .next()
19553        .await
19554        .expect("Should have called the go_to_definition handler");
19555
19556    assert_eq!(
19557        navigated,
19558        Navigated::No,
19559        "Should have navigated to references as a fallback after empty GoToDefinition response"
19560    );
19561    cx.assert_editor_state(&original_state);
19562    let editors = cx.update_workspace(|workspace, _, cx| {
19563        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19564    });
19565    cx.update_editor(|_, _, _| {
19566        assert_eq!(
19567            editors.len(),
19568            1,
19569            "After unsuccessful fallback, no other editor should have been opened"
19570        );
19571    });
19572}
19573
19574#[gpui::test]
19575async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
19576    init_test(cx, |_| {});
19577
19578    let language = Arc::new(Language::new(
19579        LanguageConfig::default(),
19580        Some(tree_sitter_rust::LANGUAGE.into()),
19581    ));
19582
19583    let text = r#"
19584        #[cfg(test)]
19585        mod tests() {
19586            #[test]
19587            fn runnable_1() {
19588                let a = 1;
19589            }
19590
19591            #[test]
19592            fn runnable_2() {
19593                let a = 1;
19594                let b = 2;
19595            }
19596        }
19597    "#
19598    .unindent();
19599
19600    let fs = FakeFs::new(cx.executor());
19601    fs.insert_file("/file.rs", Default::default()).await;
19602
19603    let project = Project::test(fs, ["/a".as_ref()], cx).await;
19604    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19605    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19606    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
19607    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
19608
19609    let editor = cx.new_window_entity(|window, cx| {
19610        Editor::new(
19611            EditorMode::full(),
19612            multi_buffer,
19613            Some(project.clone()),
19614            window,
19615            cx,
19616        )
19617    });
19618
19619    editor.update_in(cx, |editor, window, cx| {
19620        let snapshot = editor.buffer().read(cx).snapshot(cx);
19621        editor.tasks.insert(
19622            (buffer.read(cx).remote_id(), 3),
19623            RunnableTasks {
19624                templates: vec![],
19625                offset: snapshot.anchor_before(43),
19626                column: 0,
19627                extra_variables: HashMap::default(),
19628                context_range: BufferOffset(43)..BufferOffset(85),
19629            },
19630        );
19631        editor.tasks.insert(
19632            (buffer.read(cx).remote_id(), 8),
19633            RunnableTasks {
19634                templates: vec![],
19635                offset: snapshot.anchor_before(86),
19636                column: 0,
19637                extra_variables: HashMap::default(),
19638                context_range: BufferOffset(86)..BufferOffset(191),
19639            },
19640        );
19641
19642        // Test finding task when cursor is inside function body
19643        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19644            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
19645        });
19646        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
19647        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
19648
19649        // Test finding task when cursor is on function name
19650        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19651            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
19652        });
19653        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
19654        assert_eq!(row, 8, "Should find task when cursor is on function name");
19655    });
19656}
19657
19658#[gpui::test]
19659async fn test_folding_buffers(cx: &mut TestAppContext) {
19660    init_test(cx, |_| {});
19661
19662    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
19663    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
19664    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
19665
19666    let fs = FakeFs::new(cx.executor());
19667    fs.insert_tree(
19668        path!("/a"),
19669        json!({
19670            "first.rs": sample_text_1,
19671            "second.rs": sample_text_2,
19672            "third.rs": sample_text_3,
19673        }),
19674    )
19675    .await;
19676    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19677    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19678    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19679    let worktree = project.update(cx, |project, cx| {
19680        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19681        assert_eq!(worktrees.len(), 1);
19682        worktrees.pop().unwrap()
19683    });
19684    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19685
19686    let buffer_1 = project
19687        .update(cx, |project, cx| {
19688            project.open_buffer((worktree_id, "first.rs"), cx)
19689        })
19690        .await
19691        .unwrap();
19692    let buffer_2 = project
19693        .update(cx, |project, cx| {
19694            project.open_buffer((worktree_id, "second.rs"), cx)
19695        })
19696        .await
19697        .unwrap();
19698    let buffer_3 = project
19699        .update(cx, |project, cx| {
19700            project.open_buffer((worktree_id, "third.rs"), cx)
19701        })
19702        .await
19703        .unwrap();
19704
19705    let multi_buffer = cx.new(|cx| {
19706        let mut multi_buffer = MultiBuffer::new(ReadWrite);
19707        multi_buffer.push_excerpts(
19708            buffer_1.clone(),
19709            [
19710                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19711                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19712                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19713            ],
19714            cx,
19715        );
19716        multi_buffer.push_excerpts(
19717            buffer_2.clone(),
19718            [
19719                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19720                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19721                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19722            ],
19723            cx,
19724        );
19725        multi_buffer.push_excerpts(
19726            buffer_3.clone(),
19727            [
19728                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19729                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19730                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19731            ],
19732            cx,
19733        );
19734        multi_buffer
19735    });
19736    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19737        Editor::new(
19738            EditorMode::full(),
19739            multi_buffer.clone(),
19740            Some(project.clone()),
19741            window,
19742            cx,
19743        )
19744    });
19745
19746    assert_eq!(
19747        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19748        "\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",
19749    );
19750
19751    multi_buffer_editor.update(cx, |editor, cx| {
19752        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
19753    });
19754    assert_eq!(
19755        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19756        "\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",
19757        "After folding the first buffer, its text should not be displayed"
19758    );
19759
19760    multi_buffer_editor.update(cx, |editor, cx| {
19761        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
19762    });
19763    assert_eq!(
19764        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19765        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
19766        "After folding the second buffer, its text should not be displayed"
19767    );
19768
19769    multi_buffer_editor.update(cx, |editor, cx| {
19770        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
19771    });
19772    assert_eq!(
19773        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19774        "\n\n\n\n\n",
19775        "After folding the third buffer, its text should not be displayed"
19776    );
19777
19778    // Emulate selection inside the fold logic, that should work
19779    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19780        editor
19781            .snapshot(window, cx)
19782            .next_line_boundary(Point::new(0, 4));
19783    });
19784
19785    multi_buffer_editor.update(cx, |editor, cx| {
19786        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
19787    });
19788    assert_eq!(
19789        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19790        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
19791        "After unfolding the second buffer, its text should be displayed"
19792    );
19793
19794    // Typing inside of buffer 1 causes that buffer to be unfolded.
19795    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19796        assert_eq!(
19797            multi_buffer
19798                .read(cx)
19799                .snapshot(cx)
19800                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
19801                .collect::<String>(),
19802            "bbbb"
19803        );
19804        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
19805            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
19806        });
19807        editor.handle_input("B", window, cx);
19808    });
19809
19810    assert_eq!(
19811        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19812        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
19813        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
19814    );
19815
19816    multi_buffer_editor.update(cx, |editor, cx| {
19817        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
19818    });
19819    assert_eq!(
19820        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19821        "\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",
19822        "After unfolding the all buffers, all original text should be displayed"
19823    );
19824}
19825
19826#[gpui::test]
19827async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
19828    init_test(cx, |_| {});
19829
19830    let sample_text_1 = "1111\n2222\n3333".to_string();
19831    let sample_text_2 = "4444\n5555\n6666".to_string();
19832    let sample_text_3 = "7777\n8888\n9999".to_string();
19833
19834    let fs = FakeFs::new(cx.executor());
19835    fs.insert_tree(
19836        path!("/a"),
19837        json!({
19838            "first.rs": sample_text_1,
19839            "second.rs": sample_text_2,
19840            "third.rs": sample_text_3,
19841        }),
19842    )
19843    .await;
19844    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19845    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19846    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19847    let worktree = project.update(cx, |project, cx| {
19848        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19849        assert_eq!(worktrees.len(), 1);
19850        worktrees.pop().unwrap()
19851    });
19852    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19853
19854    let buffer_1 = project
19855        .update(cx, |project, cx| {
19856            project.open_buffer((worktree_id, "first.rs"), cx)
19857        })
19858        .await
19859        .unwrap();
19860    let buffer_2 = project
19861        .update(cx, |project, cx| {
19862            project.open_buffer((worktree_id, "second.rs"), cx)
19863        })
19864        .await
19865        .unwrap();
19866    let buffer_3 = project
19867        .update(cx, |project, cx| {
19868            project.open_buffer((worktree_id, "third.rs"), cx)
19869        })
19870        .await
19871        .unwrap();
19872
19873    let multi_buffer = cx.new(|cx| {
19874        let mut multi_buffer = MultiBuffer::new(ReadWrite);
19875        multi_buffer.push_excerpts(
19876            buffer_1.clone(),
19877            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19878            cx,
19879        );
19880        multi_buffer.push_excerpts(
19881            buffer_2.clone(),
19882            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19883            cx,
19884        );
19885        multi_buffer.push_excerpts(
19886            buffer_3.clone(),
19887            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19888            cx,
19889        );
19890        multi_buffer
19891    });
19892
19893    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19894        Editor::new(
19895            EditorMode::full(),
19896            multi_buffer,
19897            Some(project.clone()),
19898            window,
19899            cx,
19900        )
19901    });
19902
19903    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
19904    assert_eq!(
19905        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19906        full_text,
19907    );
19908
19909    multi_buffer_editor.update(cx, |editor, cx| {
19910        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
19911    });
19912    assert_eq!(
19913        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19914        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
19915        "After folding the first buffer, its text should not be displayed"
19916    );
19917
19918    multi_buffer_editor.update(cx, |editor, cx| {
19919        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
19920    });
19921
19922    assert_eq!(
19923        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19924        "\n\n\n\n\n\n7777\n8888\n9999",
19925        "After folding the second buffer, its text should not be displayed"
19926    );
19927
19928    multi_buffer_editor.update(cx, |editor, cx| {
19929        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
19930    });
19931    assert_eq!(
19932        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19933        "\n\n\n\n\n",
19934        "After folding the third buffer, its text should not be displayed"
19935    );
19936
19937    multi_buffer_editor.update(cx, |editor, cx| {
19938        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
19939    });
19940    assert_eq!(
19941        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19942        "\n\n\n\n4444\n5555\n6666\n\n",
19943        "After unfolding the second buffer, its text should be displayed"
19944    );
19945
19946    multi_buffer_editor.update(cx, |editor, cx| {
19947        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
19948    });
19949    assert_eq!(
19950        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19951        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
19952        "After unfolding the first buffer, its text should be displayed"
19953    );
19954
19955    multi_buffer_editor.update(cx, |editor, cx| {
19956        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
19957    });
19958    assert_eq!(
19959        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19960        full_text,
19961        "After unfolding all buffers, all original text should be displayed"
19962    );
19963}
19964
19965#[gpui::test]
19966async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
19967    init_test(cx, |_| {});
19968
19969    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
19970
19971    let fs = FakeFs::new(cx.executor());
19972    fs.insert_tree(
19973        path!("/a"),
19974        json!({
19975            "main.rs": sample_text,
19976        }),
19977    )
19978    .await;
19979    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19980    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19981    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19982    let worktree = project.update(cx, |project, cx| {
19983        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19984        assert_eq!(worktrees.len(), 1);
19985        worktrees.pop().unwrap()
19986    });
19987    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19988
19989    let buffer_1 = project
19990        .update(cx, |project, cx| {
19991            project.open_buffer((worktree_id, "main.rs"), cx)
19992        })
19993        .await
19994        .unwrap();
19995
19996    let multi_buffer = cx.new(|cx| {
19997        let mut multi_buffer = MultiBuffer::new(ReadWrite);
19998        multi_buffer.push_excerpts(
19999            buffer_1.clone(),
20000            [ExcerptRange::new(
20001                Point::new(0, 0)
20002                    ..Point::new(
20003                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
20004                        0,
20005                    ),
20006            )],
20007            cx,
20008        );
20009        multi_buffer
20010    });
20011    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20012        Editor::new(
20013            EditorMode::full(),
20014            multi_buffer,
20015            Some(project.clone()),
20016            window,
20017            cx,
20018        )
20019    });
20020
20021    let selection_range = Point::new(1, 0)..Point::new(2, 0);
20022    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20023        enum TestHighlight {}
20024        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
20025        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
20026        editor.highlight_text::<TestHighlight>(
20027            vec![highlight_range.clone()],
20028            HighlightStyle::color(Hsla::green()),
20029            cx,
20030        );
20031        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20032            s.select_ranges(Some(highlight_range))
20033        });
20034    });
20035
20036    let full_text = format!("\n\n{sample_text}");
20037    assert_eq!(
20038        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20039        full_text,
20040    );
20041}
20042
20043#[gpui::test]
20044async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
20045    init_test(cx, |_| {});
20046    cx.update(|cx| {
20047        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
20048            "keymaps/default-linux.json",
20049            cx,
20050        )
20051        .unwrap();
20052        cx.bind_keys(default_key_bindings);
20053    });
20054
20055    let (editor, cx) = cx.add_window_view(|window, cx| {
20056        let multi_buffer = MultiBuffer::build_multi(
20057            [
20058                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
20059                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
20060                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
20061                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
20062            ],
20063            cx,
20064        );
20065        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
20066
20067        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
20068        // fold all but the second buffer, so that we test navigating between two
20069        // adjacent folded buffers, as well as folded buffers at the start and
20070        // end the multibuffer
20071        editor.fold_buffer(buffer_ids[0], cx);
20072        editor.fold_buffer(buffer_ids[2], cx);
20073        editor.fold_buffer(buffer_ids[3], cx);
20074
20075        editor
20076    });
20077    cx.simulate_resize(size(px(1000.), px(1000.)));
20078
20079    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
20080    cx.assert_excerpts_with_selections(indoc! {"
20081        [EXCERPT]
20082        ˇ[FOLDED]
20083        [EXCERPT]
20084        a1
20085        b1
20086        [EXCERPT]
20087        [FOLDED]
20088        [EXCERPT]
20089        [FOLDED]
20090        "
20091    });
20092    cx.simulate_keystroke("down");
20093    cx.assert_excerpts_with_selections(indoc! {"
20094        [EXCERPT]
20095        [FOLDED]
20096        [EXCERPT]
20097        ˇa1
20098        b1
20099        [EXCERPT]
20100        [FOLDED]
20101        [EXCERPT]
20102        [FOLDED]
20103        "
20104    });
20105    cx.simulate_keystroke("down");
20106    cx.assert_excerpts_with_selections(indoc! {"
20107        [EXCERPT]
20108        [FOLDED]
20109        [EXCERPT]
20110        a1
20111        ˇb1
20112        [EXCERPT]
20113        [FOLDED]
20114        [EXCERPT]
20115        [FOLDED]
20116        "
20117    });
20118    cx.simulate_keystroke("down");
20119    cx.assert_excerpts_with_selections(indoc! {"
20120        [EXCERPT]
20121        [FOLDED]
20122        [EXCERPT]
20123        a1
20124        b1
20125        ˇ[EXCERPT]
20126        [FOLDED]
20127        [EXCERPT]
20128        [FOLDED]
20129        "
20130    });
20131    cx.simulate_keystroke("down");
20132    cx.assert_excerpts_with_selections(indoc! {"
20133        [EXCERPT]
20134        [FOLDED]
20135        [EXCERPT]
20136        a1
20137        b1
20138        [EXCERPT]
20139        ˇ[FOLDED]
20140        [EXCERPT]
20141        [FOLDED]
20142        "
20143    });
20144    for _ in 0..5 {
20145        cx.simulate_keystroke("down");
20146        cx.assert_excerpts_with_selections(indoc! {"
20147            [EXCERPT]
20148            [FOLDED]
20149            [EXCERPT]
20150            a1
20151            b1
20152            [EXCERPT]
20153            [FOLDED]
20154            [EXCERPT]
20155            ˇ[FOLDED]
20156            "
20157        });
20158    }
20159
20160    cx.simulate_keystroke("up");
20161    cx.assert_excerpts_with_selections(indoc! {"
20162        [EXCERPT]
20163        [FOLDED]
20164        [EXCERPT]
20165        a1
20166        b1
20167        [EXCERPT]
20168        ˇ[FOLDED]
20169        [EXCERPT]
20170        [FOLDED]
20171        "
20172    });
20173    cx.simulate_keystroke("up");
20174    cx.assert_excerpts_with_selections(indoc! {"
20175        [EXCERPT]
20176        [FOLDED]
20177        [EXCERPT]
20178        a1
20179        b1
20180        ˇ[EXCERPT]
20181        [FOLDED]
20182        [EXCERPT]
20183        [FOLDED]
20184        "
20185    });
20186    cx.simulate_keystroke("up");
20187    cx.assert_excerpts_with_selections(indoc! {"
20188        [EXCERPT]
20189        [FOLDED]
20190        [EXCERPT]
20191        a1
20192        ˇb1
20193        [EXCERPT]
20194        [FOLDED]
20195        [EXCERPT]
20196        [FOLDED]
20197        "
20198    });
20199    cx.simulate_keystroke("up");
20200    cx.assert_excerpts_with_selections(indoc! {"
20201        [EXCERPT]
20202        [FOLDED]
20203        [EXCERPT]
20204        ˇa1
20205        b1
20206        [EXCERPT]
20207        [FOLDED]
20208        [EXCERPT]
20209        [FOLDED]
20210        "
20211    });
20212    for _ in 0..5 {
20213        cx.simulate_keystroke("up");
20214        cx.assert_excerpts_with_selections(indoc! {"
20215            [EXCERPT]
20216            ˇ[FOLDED]
20217            [EXCERPT]
20218            a1
20219            b1
20220            [EXCERPT]
20221            [FOLDED]
20222            [EXCERPT]
20223            [FOLDED]
20224            "
20225        });
20226    }
20227}
20228
20229#[gpui::test]
20230async fn test_inline_completion_text(cx: &mut TestAppContext) {
20231    init_test(cx, |_| {});
20232
20233    // Simple insertion
20234    assert_highlighted_edits(
20235        "Hello, world!",
20236        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
20237        true,
20238        cx,
20239        |highlighted_edits, cx| {
20240            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
20241            assert_eq!(highlighted_edits.highlights.len(), 1);
20242            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
20243            assert_eq!(
20244                highlighted_edits.highlights[0].1.background_color,
20245                Some(cx.theme().status().created_background)
20246            );
20247        },
20248    )
20249    .await;
20250
20251    // Replacement
20252    assert_highlighted_edits(
20253        "This is a test.",
20254        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
20255        false,
20256        cx,
20257        |highlighted_edits, cx| {
20258            assert_eq!(highlighted_edits.text, "That is a test.");
20259            assert_eq!(highlighted_edits.highlights.len(), 1);
20260            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
20261            assert_eq!(
20262                highlighted_edits.highlights[0].1.background_color,
20263                Some(cx.theme().status().created_background)
20264            );
20265        },
20266    )
20267    .await;
20268
20269    // Multiple edits
20270    assert_highlighted_edits(
20271        "Hello, world!",
20272        vec![
20273            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
20274            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
20275        ],
20276        false,
20277        cx,
20278        |highlighted_edits, cx| {
20279            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
20280            assert_eq!(highlighted_edits.highlights.len(), 2);
20281            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
20282            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
20283            assert_eq!(
20284                highlighted_edits.highlights[0].1.background_color,
20285                Some(cx.theme().status().created_background)
20286            );
20287            assert_eq!(
20288                highlighted_edits.highlights[1].1.background_color,
20289                Some(cx.theme().status().created_background)
20290            );
20291        },
20292    )
20293    .await;
20294
20295    // Multiple lines with edits
20296    assert_highlighted_edits(
20297        "First line\nSecond line\nThird line\nFourth line",
20298        vec![
20299            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
20300            (
20301                Point::new(2, 0)..Point::new(2, 10),
20302                "New third line".to_string(),
20303            ),
20304            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
20305        ],
20306        false,
20307        cx,
20308        |highlighted_edits, cx| {
20309            assert_eq!(
20310                highlighted_edits.text,
20311                "Second modified\nNew third line\nFourth updated line"
20312            );
20313            assert_eq!(highlighted_edits.highlights.len(), 3);
20314            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
20315            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
20316            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
20317            for highlight in &highlighted_edits.highlights {
20318                assert_eq!(
20319                    highlight.1.background_color,
20320                    Some(cx.theme().status().created_background)
20321                );
20322            }
20323        },
20324    )
20325    .await;
20326}
20327
20328#[gpui::test]
20329async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
20330    init_test(cx, |_| {});
20331
20332    // Deletion
20333    assert_highlighted_edits(
20334        "Hello, world!",
20335        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
20336        true,
20337        cx,
20338        |highlighted_edits, cx| {
20339            assert_eq!(highlighted_edits.text, "Hello, world!");
20340            assert_eq!(highlighted_edits.highlights.len(), 1);
20341            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
20342            assert_eq!(
20343                highlighted_edits.highlights[0].1.background_color,
20344                Some(cx.theme().status().deleted_background)
20345            );
20346        },
20347    )
20348    .await;
20349
20350    // Insertion
20351    assert_highlighted_edits(
20352        "Hello, world!",
20353        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
20354        true,
20355        cx,
20356        |highlighted_edits, cx| {
20357            assert_eq!(highlighted_edits.highlights.len(), 1);
20358            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
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
20368async fn assert_highlighted_edits(
20369    text: &str,
20370    edits: Vec<(Range<Point>, String)>,
20371    include_deletions: bool,
20372    cx: &mut TestAppContext,
20373    assertion_fn: impl Fn(HighlightedText, &App),
20374) {
20375    let window = cx.add_window(|window, cx| {
20376        let buffer = MultiBuffer::build_simple(text, cx);
20377        Editor::new(EditorMode::full(), buffer, None, window, cx)
20378    });
20379    let cx = &mut VisualTestContext::from_window(*window, cx);
20380
20381    let (buffer, snapshot) = window
20382        .update(cx, |editor, _window, cx| {
20383            (
20384                editor.buffer().clone(),
20385                editor.buffer().read(cx).snapshot(cx),
20386            )
20387        })
20388        .unwrap();
20389
20390    let edits = edits
20391        .into_iter()
20392        .map(|(range, edit)| {
20393            (
20394                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
20395                edit,
20396            )
20397        })
20398        .collect::<Vec<_>>();
20399
20400    let text_anchor_edits = edits
20401        .clone()
20402        .into_iter()
20403        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
20404        .collect::<Vec<_>>();
20405
20406    let edit_preview = window
20407        .update(cx, |_, _window, cx| {
20408            buffer
20409                .read(cx)
20410                .as_singleton()
20411                .unwrap()
20412                .read(cx)
20413                .preview_edits(text_anchor_edits.into(), cx)
20414        })
20415        .unwrap()
20416        .await;
20417
20418    cx.update(|_window, cx| {
20419        let highlighted_edits = inline_completion_edit_text(
20420            &snapshot.as_singleton().unwrap().2,
20421            &edits,
20422            &edit_preview,
20423            include_deletions,
20424            cx,
20425        );
20426        assertion_fn(highlighted_edits, cx)
20427    });
20428}
20429
20430#[track_caller]
20431fn assert_breakpoint(
20432    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
20433    path: &Arc<Path>,
20434    expected: Vec<(u32, Breakpoint)>,
20435) {
20436    if expected.len() == 0usize {
20437        assert!(!breakpoints.contains_key(path), "{}", path.display());
20438    } else {
20439        let mut breakpoint = breakpoints
20440            .get(path)
20441            .unwrap()
20442            .into_iter()
20443            .map(|breakpoint| {
20444                (
20445                    breakpoint.row,
20446                    Breakpoint {
20447                        message: breakpoint.message.clone(),
20448                        state: breakpoint.state,
20449                        condition: breakpoint.condition.clone(),
20450                        hit_condition: breakpoint.hit_condition.clone(),
20451                    },
20452                )
20453            })
20454            .collect::<Vec<_>>();
20455
20456        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
20457
20458        assert_eq!(expected, breakpoint);
20459    }
20460}
20461
20462fn add_log_breakpoint_at_cursor(
20463    editor: &mut Editor,
20464    log_message: &str,
20465    window: &mut Window,
20466    cx: &mut Context<Editor>,
20467) {
20468    let (anchor, bp) = editor
20469        .breakpoints_at_cursors(window, cx)
20470        .first()
20471        .and_then(|(anchor, bp)| {
20472            if let Some(bp) = bp {
20473                Some((*anchor, bp.clone()))
20474            } else {
20475                None
20476            }
20477        })
20478        .unwrap_or_else(|| {
20479            let cursor_position: Point = editor.selections.newest(cx).head();
20480
20481            let breakpoint_position = editor
20482                .snapshot(window, cx)
20483                .display_snapshot
20484                .buffer_snapshot
20485                .anchor_before(Point::new(cursor_position.row, 0));
20486
20487            (breakpoint_position, Breakpoint::new_log(&log_message))
20488        });
20489
20490    editor.edit_breakpoint_at_anchor(
20491        anchor,
20492        bp,
20493        BreakpointEditAction::EditLogMessage(log_message.into()),
20494        cx,
20495    );
20496}
20497
20498#[gpui::test]
20499async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
20500    init_test(cx, |_| {});
20501
20502    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20503    let fs = FakeFs::new(cx.executor());
20504    fs.insert_tree(
20505        path!("/a"),
20506        json!({
20507            "main.rs": sample_text,
20508        }),
20509    )
20510    .await;
20511    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20512    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20513    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20514
20515    let fs = FakeFs::new(cx.executor());
20516    fs.insert_tree(
20517        path!("/a"),
20518        json!({
20519            "main.rs": sample_text,
20520        }),
20521    )
20522    .await;
20523    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20524    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20525    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20526    let worktree_id = workspace
20527        .update(cx, |workspace, _window, cx| {
20528            workspace.project().update(cx, |project, cx| {
20529                project.worktrees(cx).next().unwrap().read(cx).id()
20530            })
20531        })
20532        .unwrap();
20533
20534    let buffer = project
20535        .update(cx, |project, cx| {
20536            project.open_buffer((worktree_id, "main.rs"), cx)
20537        })
20538        .await
20539        .unwrap();
20540
20541    let (editor, cx) = cx.add_window_view(|window, cx| {
20542        Editor::new(
20543            EditorMode::full(),
20544            MultiBuffer::build_from_buffer(buffer, cx),
20545            Some(project.clone()),
20546            window,
20547            cx,
20548        )
20549    });
20550
20551    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20552    let abs_path = project.read_with(cx, |project, cx| {
20553        project
20554            .absolute_path(&project_path, cx)
20555            .map(|path_buf| Arc::from(path_buf.to_owned()))
20556            .unwrap()
20557    });
20558
20559    // assert we can add breakpoint on the first line
20560    editor.update_in(cx, |editor, window, cx| {
20561        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20562        editor.move_to_end(&MoveToEnd, window, cx);
20563        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20564    });
20565
20566    let breakpoints = editor.update(cx, |editor, cx| {
20567        editor
20568            .breakpoint_store()
20569            .as_ref()
20570            .unwrap()
20571            .read(cx)
20572            .all_source_breakpoints(cx)
20573            .clone()
20574    });
20575
20576    assert_eq!(1, breakpoints.len());
20577    assert_breakpoint(
20578        &breakpoints,
20579        &abs_path,
20580        vec![
20581            (0, Breakpoint::new_standard()),
20582            (3, Breakpoint::new_standard()),
20583        ],
20584    );
20585
20586    editor.update_in(cx, |editor, window, cx| {
20587        editor.move_to_beginning(&MoveToBeginning, window, cx);
20588        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20589    });
20590
20591    let breakpoints = editor.update(cx, |editor, cx| {
20592        editor
20593            .breakpoint_store()
20594            .as_ref()
20595            .unwrap()
20596            .read(cx)
20597            .all_source_breakpoints(cx)
20598            .clone()
20599    });
20600
20601    assert_eq!(1, breakpoints.len());
20602    assert_breakpoint(
20603        &breakpoints,
20604        &abs_path,
20605        vec![(3, Breakpoint::new_standard())],
20606    );
20607
20608    editor.update_in(cx, |editor, window, cx| {
20609        editor.move_to_end(&MoveToEnd, window, cx);
20610        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20611    });
20612
20613    let breakpoints = editor.update(cx, |editor, cx| {
20614        editor
20615            .breakpoint_store()
20616            .as_ref()
20617            .unwrap()
20618            .read(cx)
20619            .all_source_breakpoints(cx)
20620            .clone()
20621    });
20622
20623    assert_eq!(0, breakpoints.len());
20624    assert_breakpoint(&breakpoints, &abs_path, vec![]);
20625}
20626
20627#[gpui::test]
20628async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
20629    init_test(cx, |_| {});
20630
20631    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20632
20633    let fs = FakeFs::new(cx.executor());
20634    fs.insert_tree(
20635        path!("/a"),
20636        json!({
20637            "main.rs": sample_text,
20638        }),
20639    )
20640    .await;
20641    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20642    let (workspace, cx) =
20643        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
20644
20645    let worktree_id = workspace.update(cx, |workspace, cx| {
20646        workspace.project().update(cx, |project, cx| {
20647            project.worktrees(cx).next().unwrap().read(cx).id()
20648        })
20649    });
20650
20651    let buffer = project
20652        .update(cx, |project, cx| {
20653            project.open_buffer((worktree_id, "main.rs"), cx)
20654        })
20655        .await
20656        .unwrap();
20657
20658    let (editor, cx) = cx.add_window_view(|window, cx| {
20659        Editor::new(
20660            EditorMode::full(),
20661            MultiBuffer::build_from_buffer(buffer, cx),
20662            Some(project.clone()),
20663            window,
20664            cx,
20665        )
20666    });
20667
20668    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20669    let abs_path = project.read_with(cx, |project, cx| {
20670        project
20671            .absolute_path(&project_path, cx)
20672            .map(|path_buf| Arc::from(path_buf.to_owned()))
20673            .unwrap()
20674    });
20675
20676    editor.update_in(cx, |editor, window, cx| {
20677        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
20678    });
20679
20680    let breakpoints = editor.update(cx, |editor, cx| {
20681        editor
20682            .breakpoint_store()
20683            .as_ref()
20684            .unwrap()
20685            .read(cx)
20686            .all_source_breakpoints(cx)
20687            .clone()
20688    });
20689
20690    assert_breakpoint(
20691        &breakpoints,
20692        &abs_path,
20693        vec![(0, Breakpoint::new_log("hello world"))],
20694    );
20695
20696    // Removing a log message from a log breakpoint should remove it
20697    editor.update_in(cx, |editor, window, cx| {
20698        add_log_breakpoint_at_cursor(editor, "", window, cx);
20699    });
20700
20701    let breakpoints = editor.update(cx, |editor, cx| {
20702        editor
20703            .breakpoint_store()
20704            .as_ref()
20705            .unwrap()
20706            .read(cx)
20707            .all_source_breakpoints(cx)
20708            .clone()
20709    });
20710
20711    assert_breakpoint(&breakpoints, &abs_path, vec![]);
20712
20713    editor.update_in(cx, |editor, window, cx| {
20714        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20715        editor.move_to_end(&MoveToEnd, window, cx);
20716        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20717        // Not adding a log message to a standard breakpoint shouldn't remove it
20718        add_log_breakpoint_at_cursor(editor, "", window, cx);
20719    });
20720
20721    let breakpoints = editor.update(cx, |editor, cx| {
20722        editor
20723            .breakpoint_store()
20724            .as_ref()
20725            .unwrap()
20726            .read(cx)
20727            .all_source_breakpoints(cx)
20728            .clone()
20729    });
20730
20731    assert_breakpoint(
20732        &breakpoints,
20733        &abs_path,
20734        vec![
20735            (0, Breakpoint::new_standard()),
20736            (3, Breakpoint::new_standard()),
20737        ],
20738    );
20739
20740    editor.update_in(cx, |editor, window, cx| {
20741        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
20742    });
20743
20744    let breakpoints = editor.update(cx, |editor, cx| {
20745        editor
20746            .breakpoint_store()
20747            .as_ref()
20748            .unwrap()
20749            .read(cx)
20750            .all_source_breakpoints(cx)
20751            .clone()
20752    });
20753
20754    assert_breakpoint(
20755        &breakpoints,
20756        &abs_path,
20757        vec![
20758            (0, Breakpoint::new_standard()),
20759            (3, Breakpoint::new_log("hello world")),
20760        ],
20761    );
20762
20763    editor.update_in(cx, |editor, window, cx| {
20764        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
20765    });
20766
20767    let breakpoints = editor.update(cx, |editor, cx| {
20768        editor
20769            .breakpoint_store()
20770            .as_ref()
20771            .unwrap()
20772            .read(cx)
20773            .all_source_breakpoints(cx)
20774            .clone()
20775    });
20776
20777    assert_breakpoint(
20778        &breakpoints,
20779        &abs_path,
20780        vec![
20781            (0, Breakpoint::new_standard()),
20782            (3, Breakpoint::new_log("hello Earth!!")),
20783        ],
20784    );
20785}
20786
20787/// This also tests that Editor::breakpoint_at_cursor_head is working properly
20788/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
20789/// or when breakpoints were placed out of order. This tests for a regression too
20790#[gpui::test]
20791async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
20792    init_test(cx, |_| {});
20793
20794    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20795    let fs = FakeFs::new(cx.executor());
20796    fs.insert_tree(
20797        path!("/a"),
20798        json!({
20799            "main.rs": sample_text,
20800        }),
20801    )
20802    .await;
20803    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20804    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20805    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20806
20807    let fs = FakeFs::new(cx.executor());
20808    fs.insert_tree(
20809        path!("/a"),
20810        json!({
20811            "main.rs": sample_text,
20812        }),
20813    )
20814    .await;
20815    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20816    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20817    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20818    let worktree_id = workspace
20819        .update(cx, |workspace, _window, cx| {
20820            workspace.project().update(cx, |project, cx| {
20821                project.worktrees(cx).next().unwrap().read(cx).id()
20822            })
20823        })
20824        .unwrap();
20825
20826    let buffer = project
20827        .update(cx, |project, cx| {
20828            project.open_buffer((worktree_id, "main.rs"), cx)
20829        })
20830        .await
20831        .unwrap();
20832
20833    let (editor, cx) = cx.add_window_view(|window, cx| {
20834        Editor::new(
20835            EditorMode::full(),
20836            MultiBuffer::build_from_buffer(buffer, cx),
20837            Some(project.clone()),
20838            window,
20839            cx,
20840        )
20841    });
20842
20843    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20844    let abs_path = project.read_with(cx, |project, cx| {
20845        project
20846            .absolute_path(&project_path, cx)
20847            .map(|path_buf| Arc::from(path_buf.to_owned()))
20848            .unwrap()
20849    });
20850
20851    // assert we can add breakpoint on the first line
20852    editor.update_in(cx, |editor, window, cx| {
20853        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20854        editor.move_to_end(&MoveToEnd, window, cx);
20855        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20856        editor.move_up(&MoveUp, window, cx);
20857        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20858    });
20859
20860    let breakpoints = editor.update(cx, |editor, cx| {
20861        editor
20862            .breakpoint_store()
20863            .as_ref()
20864            .unwrap()
20865            .read(cx)
20866            .all_source_breakpoints(cx)
20867            .clone()
20868    });
20869
20870    assert_eq!(1, breakpoints.len());
20871    assert_breakpoint(
20872        &breakpoints,
20873        &abs_path,
20874        vec![
20875            (0, Breakpoint::new_standard()),
20876            (2, Breakpoint::new_standard()),
20877            (3, Breakpoint::new_standard()),
20878        ],
20879    );
20880
20881    editor.update_in(cx, |editor, window, cx| {
20882        editor.move_to_beginning(&MoveToBeginning, window, cx);
20883        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20884        editor.move_to_end(&MoveToEnd, window, cx);
20885        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20886        // Disabling a breakpoint that doesn't exist should do nothing
20887        editor.move_up(&MoveUp, window, cx);
20888        editor.move_up(&MoveUp, window, cx);
20889        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20890    });
20891
20892    let breakpoints = editor.update(cx, |editor, cx| {
20893        editor
20894            .breakpoint_store()
20895            .as_ref()
20896            .unwrap()
20897            .read(cx)
20898            .all_source_breakpoints(cx)
20899            .clone()
20900    });
20901
20902    let disable_breakpoint = {
20903        let mut bp = Breakpoint::new_standard();
20904        bp.state = BreakpointState::Disabled;
20905        bp
20906    };
20907
20908    assert_eq!(1, breakpoints.len());
20909    assert_breakpoint(
20910        &breakpoints,
20911        &abs_path,
20912        vec![
20913            (0, disable_breakpoint.clone()),
20914            (2, Breakpoint::new_standard()),
20915            (3, disable_breakpoint.clone()),
20916        ],
20917    );
20918
20919    editor.update_in(cx, |editor, window, cx| {
20920        editor.move_to_beginning(&MoveToBeginning, window, cx);
20921        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
20922        editor.move_to_end(&MoveToEnd, window, cx);
20923        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
20924        editor.move_up(&MoveUp, window, cx);
20925        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20926    });
20927
20928    let breakpoints = editor.update(cx, |editor, cx| {
20929        editor
20930            .breakpoint_store()
20931            .as_ref()
20932            .unwrap()
20933            .read(cx)
20934            .all_source_breakpoints(cx)
20935            .clone()
20936    });
20937
20938    assert_eq!(1, breakpoints.len());
20939    assert_breakpoint(
20940        &breakpoints,
20941        &abs_path,
20942        vec![
20943            (0, Breakpoint::new_standard()),
20944            (2, disable_breakpoint),
20945            (3, Breakpoint::new_standard()),
20946        ],
20947    );
20948}
20949
20950#[gpui::test]
20951async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
20952    init_test(cx, |_| {});
20953    let capabilities = lsp::ServerCapabilities {
20954        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
20955            prepare_provider: Some(true),
20956            work_done_progress_options: Default::default(),
20957        })),
20958        ..Default::default()
20959    };
20960    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
20961
20962    cx.set_state(indoc! {"
20963        struct Fˇoo {}
20964    "});
20965
20966    cx.update_editor(|editor, _, cx| {
20967        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
20968        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
20969        editor.highlight_background::<DocumentHighlightRead>(
20970            &[highlight_range],
20971            |theme| theme.colors().editor_document_highlight_read_background,
20972            cx,
20973        );
20974    });
20975
20976    let mut prepare_rename_handler = cx
20977        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
20978            move |_, _, _| async move {
20979                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
20980                    start: lsp::Position {
20981                        line: 0,
20982                        character: 7,
20983                    },
20984                    end: lsp::Position {
20985                        line: 0,
20986                        character: 10,
20987                    },
20988                })))
20989            },
20990        );
20991    let prepare_rename_task = cx
20992        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
20993        .expect("Prepare rename was not started");
20994    prepare_rename_handler.next().await.unwrap();
20995    prepare_rename_task.await.expect("Prepare rename failed");
20996
20997    let mut rename_handler =
20998        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
20999            let edit = lsp::TextEdit {
21000                range: lsp::Range {
21001                    start: lsp::Position {
21002                        line: 0,
21003                        character: 7,
21004                    },
21005                    end: lsp::Position {
21006                        line: 0,
21007                        character: 10,
21008                    },
21009                },
21010                new_text: "FooRenamed".to_string(),
21011            };
21012            Ok(Some(lsp::WorkspaceEdit::new(
21013                // Specify the same edit twice
21014                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
21015            )))
21016        });
21017    let rename_task = cx
21018        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
21019        .expect("Confirm rename was not started");
21020    rename_handler.next().await.unwrap();
21021    rename_task.await.expect("Confirm rename failed");
21022    cx.run_until_parked();
21023
21024    // Despite two edits, only one is actually applied as those are identical
21025    cx.assert_editor_state(indoc! {"
21026        struct FooRenamedˇ {}
21027    "});
21028}
21029
21030#[gpui::test]
21031async fn test_rename_without_prepare(cx: &mut TestAppContext) {
21032    init_test(cx, |_| {});
21033    // These capabilities indicate that the server does not support prepare rename.
21034    let capabilities = lsp::ServerCapabilities {
21035        rename_provider: Some(lsp::OneOf::Left(true)),
21036        ..Default::default()
21037    };
21038    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
21039
21040    cx.set_state(indoc! {"
21041        struct Fˇoo {}
21042    "});
21043
21044    cx.update_editor(|editor, _window, cx| {
21045        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
21046        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
21047        editor.highlight_background::<DocumentHighlightRead>(
21048            &[highlight_range],
21049            |theme| theme.colors().editor_document_highlight_read_background,
21050            cx,
21051        );
21052    });
21053
21054    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
21055        .expect("Prepare rename was not started")
21056        .await
21057        .expect("Prepare rename failed");
21058
21059    let mut rename_handler =
21060        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
21061            let edit = lsp::TextEdit {
21062                range: lsp::Range {
21063                    start: lsp::Position {
21064                        line: 0,
21065                        character: 7,
21066                    },
21067                    end: lsp::Position {
21068                        line: 0,
21069                        character: 10,
21070                    },
21071                },
21072                new_text: "FooRenamed".to_string(),
21073            };
21074            Ok(Some(lsp::WorkspaceEdit::new(
21075                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
21076            )))
21077        });
21078    let rename_task = cx
21079        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
21080        .expect("Confirm rename was not started");
21081    rename_handler.next().await.unwrap();
21082    rename_task.await.expect("Confirm rename failed");
21083    cx.run_until_parked();
21084
21085    // Correct range is renamed, as `surrounding_word` is used to find it.
21086    cx.assert_editor_state(indoc! {"
21087        struct FooRenamedˇ {}
21088    "});
21089}
21090
21091#[gpui::test]
21092async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
21093    init_test(cx, |_| {});
21094    let mut cx = EditorTestContext::new(cx).await;
21095
21096    let language = Arc::new(
21097        Language::new(
21098            LanguageConfig::default(),
21099            Some(tree_sitter_html::LANGUAGE.into()),
21100        )
21101        .with_brackets_query(
21102            r#"
21103            ("<" @open "/>" @close)
21104            ("</" @open ">" @close)
21105            ("<" @open ">" @close)
21106            ("\"" @open "\"" @close)
21107            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
21108        "#,
21109        )
21110        .unwrap(),
21111    );
21112    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
21113
21114    cx.set_state(indoc! {"
21115        <span>ˇ</span>
21116    "});
21117    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21118    cx.assert_editor_state(indoc! {"
21119        <span>
21120        ˇ
21121        </span>
21122    "});
21123
21124    cx.set_state(indoc! {"
21125        <span><span></span>ˇ</span>
21126    "});
21127    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21128    cx.assert_editor_state(indoc! {"
21129        <span><span></span>
21130        ˇ</span>
21131    "});
21132
21133    cx.set_state(indoc! {"
21134        <span>ˇ
21135        </span>
21136    "});
21137    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21138    cx.assert_editor_state(indoc! {"
21139        <span>
21140        ˇ
21141        </span>
21142    "});
21143}
21144
21145#[gpui::test(iterations = 10)]
21146async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
21147    init_test(cx, |_| {});
21148
21149    let fs = FakeFs::new(cx.executor());
21150    fs.insert_tree(
21151        path!("/dir"),
21152        json!({
21153            "a.ts": "a",
21154        }),
21155    )
21156    .await;
21157
21158    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
21159    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21160    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21161
21162    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21163    language_registry.add(Arc::new(Language::new(
21164        LanguageConfig {
21165            name: "TypeScript".into(),
21166            matcher: LanguageMatcher {
21167                path_suffixes: vec!["ts".to_string()],
21168                ..Default::default()
21169            },
21170            ..Default::default()
21171        },
21172        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
21173    )));
21174    let mut fake_language_servers = language_registry.register_fake_lsp(
21175        "TypeScript",
21176        FakeLspAdapter {
21177            capabilities: lsp::ServerCapabilities {
21178                code_lens_provider: Some(lsp::CodeLensOptions {
21179                    resolve_provider: Some(true),
21180                }),
21181                execute_command_provider: Some(lsp::ExecuteCommandOptions {
21182                    commands: vec!["_the/command".to_string()],
21183                    ..lsp::ExecuteCommandOptions::default()
21184                }),
21185                ..lsp::ServerCapabilities::default()
21186            },
21187            ..FakeLspAdapter::default()
21188        },
21189    );
21190
21191    let (buffer, _handle) = project
21192        .update(cx, |p, cx| {
21193            p.open_local_buffer_with_lsp(path!("/dir/a.ts"), cx)
21194        })
21195        .await
21196        .unwrap();
21197    cx.executor().run_until_parked();
21198
21199    let fake_server = fake_language_servers.next().await.unwrap();
21200
21201    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
21202    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
21203    drop(buffer_snapshot);
21204    let actions = cx
21205        .update_window(*workspace, |_, window, cx| {
21206            project.code_actions(&buffer, anchor..anchor, window, cx)
21207        })
21208        .unwrap();
21209
21210    fake_server
21211        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
21212            Ok(Some(vec![
21213                lsp::CodeLens {
21214                    range: lsp::Range::default(),
21215                    command: Some(lsp::Command {
21216                        title: "Code lens command".to_owned(),
21217                        command: "_the/command".to_owned(),
21218                        arguments: None,
21219                    }),
21220                    data: None,
21221                },
21222                lsp::CodeLens {
21223                    range: lsp::Range::default(),
21224                    command: Some(lsp::Command {
21225                        title: "Command not in capabilities".to_owned(),
21226                        command: "not in capabilities".to_owned(),
21227                        arguments: None,
21228                    }),
21229                    data: None,
21230                },
21231                lsp::CodeLens {
21232                    range: lsp::Range {
21233                        start: lsp::Position {
21234                            line: 1,
21235                            character: 1,
21236                        },
21237                        end: lsp::Position {
21238                            line: 1,
21239                            character: 1,
21240                        },
21241                    },
21242                    command: Some(lsp::Command {
21243                        title: "Command not in range".to_owned(),
21244                        command: "_the/command".to_owned(),
21245                        arguments: None,
21246                    }),
21247                    data: None,
21248                },
21249            ]))
21250        })
21251        .next()
21252        .await;
21253
21254    let actions = actions.await.unwrap();
21255    assert_eq!(
21256        actions.len(),
21257        1,
21258        "Should have only one valid action for the 0..0 range"
21259    );
21260    let action = actions[0].clone();
21261    let apply = project.update(cx, |project, cx| {
21262        project.apply_code_action(buffer.clone(), action, true, cx)
21263    });
21264
21265    // Resolving the code action does not populate its edits. In absence of
21266    // edits, we must execute the given command.
21267    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
21268        |mut lens, _| async move {
21269            let lens_command = lens.command.as_mut().expect("should have a command");
21270            assert_eq!(lens_command.title, "Code lens command");
21271            lens_command.arguments = Some(vec![json!("the-argument")]);
21272            Ok(lens)
21273        },
21274    );
21275
21276    // While executing the command, the language server sends the editor
21277    // a `workspaceEdit` request.
21278    fake_server
21279        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
21280            let fake = fake_server.clone();
21281            move |params, _| {
21282                assert_eq!(params.command, "_the/command");
21283                let fake = fake.clone();
21284                async move {
21285                    fake.server
21286                        .request::<lsp::request::ApplyWorkspaceEdit>(
21287                            lsp::ApplyWorkspaceEditParams {
21288                                label: None,
21289                                edit: lsp::WorkspaceEdit {
21290                                    changes: Some(
21291                                        [(
21292                                            lsp::Url::from_file_path(path!("/dir/a.ts")).unwrap(),
21293                                            vec![lsp::TextEdit {
21294                                                range: lsp::Range::new(
21295                                                    lsp::Position::new(0, 0),
21296                                                    lsp::Position::new(0, 0),
21297                                                ),
21298                                                new_text: "X".into(),
21299                                            }],
21300                                        )]
21301                                        .into_iter()
21302                                        .collect(),
21303                                    ),
21304                                    ..Default::default()
21305                                },
21306                            },
21307                        )
21308                        .await
21309                        .into_response()
21310                        .unwrap();
21311                    Ok(Some(json!(null)))
21312                }
21313            }
21314        })
21315        .next()
21316        .await;
21317
21318    // Applying the code lens command returns a project transaction containing the edits
21319    // sent by the language server in its `workspaceEdit` request.
21320    let transaction = apply.await.unwrap();
21321    assert!(transaction.0.contains_key(&buffer));
21322    buffer.update(cx, |buffer, cx| {
21323        assert_eq!(buffer.text(), "Xa");
21324        buffer.undo(cx);
21325        assert_eq!(buffer.text(), "a");
21326    });
21327}
21328
21329#[gpui::test]
21330async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
21331    init_test(cx, |_| {});
21332
21333    let fs = FakeFs::new(cx.executor());
21334    let main_text = r#"fn main() {
21335println!("1");
21336println!("2");
21337println!("3");
21338println!("4");
21339println!("5");
21340}"#;
21341    let lib_text = "mod foo {}";
21342    fs.insert_tree(
21343        path!("/a"),
21344        json!({
21345            "lib.rs": lib_text,
21346            "main.rs": main_text,
21347        }),
21348    )
21349    .await;
21350
21351    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21352    let (workspace, cx) =
21353        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21354    let worktree_id = workspace.update(cx, |workspace, cx| {
21355        workspace.project().update(cx, |project, cx| {
21356            project.worktrees(cx).next().unwrap().read(cx).id()
21357        })
21358    });
21359
21360    let expected_ranges = vec![
21361        Point::new(0, 0)..Point::new(0, 0),
21362        Point::new(1, 0)..Point::new(1, 1),
21363        Point::new(2, 0)..Point::new(2, 2),
21364        Point::new(3, 0)..Point::new(3, 3),
21365    ];
21366
21367    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21368    let editor_1 = workspace
21369        .update_in(cx, |workspace, window, cx| {
21370            workspace.open_path(
21371                (worktree_id, "main.rs"),
21372                Some(pane_1.downgrade()),
21373                true,
21374                window,
21375                cx,
21376            )
21377        })
21378        .unwrap()
21379        .await
21380        .downcast::<Editor>()
21381        .unwrap();
21382    pane_1.update(cx, |pane, cx| {
21383        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21384        open_editor.update(cx, |editor, cx| {
21385            assert_eq!(
21386                editor.display_text(cx),
21387                main_text,
21388                "Original main.rs text on initial open",
21389            );
21390            assert_eq!(
21391                editor
21392                    .selections
21393                    .all::<Point>(cx)
21394                    .into_iter()
21395                    .map(|s| s.range())
21396                    .collect::<Vec<_>>(),
21397                vec![Point::zero()..Point::zero()],
21398                "Default selections on initial open",
21399            );
21400        })
21401    });
21402    editor_1.update_in(cx, |editor, window, cx| {
21403        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21404            s.select_ranges(expected_ranges.clone());
21405        });
21406    });
21407
21408    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
21409        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
21410    });
21411    let editor_2 = workspace
21412        .update_in(cx, |workspace, window, cx| {
21413            workspace.open_path(
21414                (worktree_id, "main.rs"),
21415                Some(pane_2.downgrade()),
21416                true,
21417                window,
21418                cx,
21419            )
21420        })
21421        .unwrap()
21422        .await
21423        .downcast::<Editor>()
21424        .unwrap();
21425    pane_2.update(cx, |pane, cx| {
21426        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21427        open_editor.update(cx, |editor, cx| {
21428            assert_eq!(
21429                editor.display_text(cx),
21430                main_text,
21431                "Original main.rs text on initial open in another panel",
21432            );
21433            assert_eq!(
21434                editor
21435                    .selections
21436                    .all::<Point>(cx)
21437                    .into_iter()
21438                    .map(|s| s.range())
21439                    .collect::<Vec<_>>(),
21440                vec![Point::zero()..Point::zero()],
21441                "Default selections on initial open in another panel",
21442            );
21443        })
21444    });
21445
21446    editor_2.update_in(cx, |editor, window, cx| {
21447        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
21448    });
21449
21450    let _other_editor_1 = workspace
21451        .update_in(cx, |workspace, window, cx| {
21452            workspace.open_path(
21453                (worktree_id, "lib.rs"),
21454                Some(pane_1.downgrade()),
21455                true,
21456                window,
21457                cx,
21458            )
21459        })
21460        .unwrap()
21461        .await
21462        .downcast::<Editor>()
21463        .unwrap();
21464    pane_1
21465        .update_in(cx, |pane, window, cx| {
21466            pane.close_inactive_items(&CloseInactiveItems::default(), None, window, cx)
21467        })
21468        .await
21469        .unwrap();
21470    drop(editor_1);
21471    pane_1.update(cx, |pane, cx| {
21472        pane.active_item()
21473            .unwrap()
21474            .downcast::<Editor>()
21475            .unwrap()
21476            .update(cx, |editor, cx| {
21477                assert_eq!(
21478                    editor.display_text(cx),
21479                    lib_text,
21480                    "Other file should be open and active",
21481                );
21482            });
21483        assert_eq!(pane.items().count(), 1, "No other editors should be open");
21484    });
21485
21486    let _other_editor_2 = workspace
21487        .update_in(cx, |workspace, window, cx| {
21488            workspace.open_path(
21489                (worktree_id, "lib.rs"),
21490                Some(pane_2.downgrade()),
21491                true,
21492                window,
21493                cx,
21494            )
21495        })
21496        .unwrap()
21497        .await
21498        .downcast::<Editor>()
21499        .unwrap();
21500    pane_2
21501        .update_in(cx, |pane, window, cx| {
21502            pane.close_inactive_items(&CloseInactiveItems::default(), None, window, cx)
21503        })
21504        .await
21505        .unwrap();
21506    drop(editor_2);
21507    pane_2.update(cx, |pane, cx| {
21508        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21509        open_editor.update(cx, |editor, cx| {
21510            assert_eq!(
21511                editor.display_text(cx),
21512                lib_text,
21513                "Other file should be open and active in another panel too",
21514            );
21515        });
21516        assert_eq!(
21517            pane.items().count(),
21518            1,
21519            "No other editors should be open in another pane",
21520        );
21521    });
21522
21523    let _editor_1_reopened = workspace
21524        .update_in(cx, |workspace, window, cx| {
21525            workspace.open_path(
21526                (worktree_id, "main.rs"),
21527                Some(pane_1.downgrade()),
21528                true,
21529                window,
21530                cx,
21531            )
21532        })
21533        .unwrap()
21534        .await
21535        .downcast::<Editor>()
21536        .unwrap();
21537    let _editor_2_reopened = workspace
21538        .update_in(cx, |workspace, window, cx| {
21539            workspace.open_path(
21540                (worktree_id, "main.rs"),
21541                Some(pane_2.downgrade()),
21542                true,
21543                window,
21544                cx,
21545            )
21546        })
21547        .unwrap()
21548        .await
21549        .downcast::<Editor>()
21550        .unwrap();
21551    pane_1.update(cx, |pane, cx| {
21552        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21553        open_editor.update(cx, |editor, cx| {
21554            assert_eq!(
21555                editor.display_text(cx),
21556                main_text,
21557                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
21558            );
21559            assert_eq!(
21560                editor
21561                    .selections
21562                    .all::<Point>(cx)
21563                    .into_iter()
21564                    .map(|s| s.range())
21565                    .collect::<Vec<_>>(),
21566                expected_ranges,
21567                "Previous editor in the 1st panel had selections and should get them restored on reopen",
21568            );
21569        })
21570    });
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                r#"fn main() {
21577⋯rintln!("1");
21578⋯intln!("2");
21579⋯ntln!("3");
21580println!("4");
21581println!("5");
21582}"#,
21583                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
21584            );
21585            assert_eq!(
21586                editor
21587                    .selections
21588                    .all::<Point>(cx)
21589                    .into_iter()
21590                    .map(|s| s.range())
21591                    .collect::<Vec<_>>(),
21592                vec![Point::zero()..Point::zero()],
21593                "Previous editor in the 2nd pane had no selections changed hence should restore none",
21594            );
21595        })
21596    });
21597}
21598
21599#[gpui::test]
21600async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
21601    init_test(cx, |_| {});
21602
21603    let fs = FakeFs::new(cx.executor());
21604    let main_text = r#"fn main() {
21605println!("1");
21606println!("2");
21607println!("3");
21608println!("4");
21609println!("5");
21610}"#;
21611    let lib_text = "mod foo {}";
21612    fs.insert_tree(
21613        path!("/a"),
21614        json!({
21615            "lib.rs": lib_text,
21616            "main.rs": main_text,
21617        }),
21618    )
21619    .await;
21620
21621    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21622    let (workspace, cx) =
21623        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21624    let worktree_id = workspace.update(cx, |workspace, cx| {
21625        workspace.project().update(cx, |project, cx| {
21626            project.worktrees(cx).next().unwrap().read(cx).id()
21627        })
21628    });
21629
21630    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21631    let editor = workspace
21632        .update_in(cx, |workspace, window, cx| {
21633            workspace.open_path(
21634                (worktree_id, "main.rs"),
21635                Some(pane.downgrade()),
21636                true,
21637                window,
21638                cx,
21639            )
21640        })
21641        .unwrap()
21642        .await
21643        .downcast::<Editor>()
21644        .unwrap();
21645    pane.update(cx, |pane, cx| {
21646        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21647        open_editor.update(cx, |editor, cx| {
21648            assert_eq!(
21649                editor.display_text(cx),
21650                main_text,
21651                "Original main.rs text on initial open",
21652            );
21653        })
21654    });
21655    editor.update_in(cx, |editor, window, cx| {
21656        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
21657    });
21658
21659    cx.update_global(|store: &mut SettingsStore, cx| {
21660        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
21661            s.restore_on_file_reopen = Some(false);
21662        });
21663    });
21664    editor.update_in(cx, |editor, window, cx| {
21665        editor.fold_ranges(
21666            vec![
21667                Point::new(1, 0)..Point::new(1, 1),
21668                Point::new(2, 0)..Point::new(2, 2),
21669                Point::new(3, 0)..Point::new(3, 3),
21670            ],
21671            false,
21672            window,
21673            cx,
21674        );
21675    });
21676    pane.update_in(cx, |pane, window, cx| {
21677        pane.close_all_items(&CloseAllItems::default(), window, cx)
21678    })
21679    .await
21680    .unwrap();
21681    pane.update(cx, |pane, _| {
21682        assert!(pane.active_item().is_none());
21683    });
21684    cx.update_global(|store: &mut SettingsStore, cx| {
21685        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
21686            s.restore_on_file_reopen = Some(true);
21687        });
21688    });
21689
21690    let _editor_reopened = workspace
21691        .update_in(cx, |workspace, window, cx| {
21692            workspace.open_path(
21693                (worktree_id, "main.rs"),
21694                Some(pane.downgrade()),
21695                true,
21696                window,
21697                cx,
21698            )
21699        })
21700        .unwrap()
21701        .await
21702        .downcast::<Editor>()
21703        .unwrap();
21704    pane.update(cx, |pane, cx| {
21705        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21706        open_editor.update(cx, |editor, cx| {
21707            assert_eq!(
21708                editor.display_text(cx),
21709                main_text,
21710                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
21711            );
21712        })
21713    });
21714}
21715
21716#[gpui::test]
21717async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
21718    struct EmptyModalView {
21719        focus_handle: gpui::FocusHandle,
21720    }
21721    impl EventEmitter<DismissEvent> for EmptyModalView {}
21722    impl Render for EmptyModalView {
21723        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
21724            div()
21725        }
21726    }
21727    impl Focusable for EmptyModalView {
21728        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
21729            self.focus_handle.clone()
21730        }
21731    }
21732    impl workspace::ModalView for EmptyModalView {}
21733    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
21734        EmptyModalView {
21735            focus_handle: cx.focus_handle(),
21736        }
21737    }
21738
21739    init_test(cx, |_| {});
21740
21741    let fs = FakeFs::new(cx.executor());
21742    let project = Project::test(fs, [], cx).await;
21743    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21744    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
21745    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21746    let editor = cx.new_window_entity(|window, cx| {
21747        Editor::new(
21748            EditorMode::full(),
21749            buffer,
21750            Some(project.clone()),
21751            window,
21752            cx,
21753        )
21754    });
21755    workspace
21756        .update(cx, |workspace, window, cx| {
21757            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
21758        })
21759        .unwrap();
21760    editor.update_in(cx, |editor, window, cx| {
21761        editor.open_context_menu(&OpenContextMenu, window, cx);
21762        assert!(editor.mouse_context_menu.is_some());
21763    });
21764    workspace
21765        .update(cx, |workspace, window, cx| {
21766            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
21767        })
21768        .unwrap();
21769    cx.read(|cx| {
21770        assert!(editor.read(cx).mouse_context_menu.is_none());
21771    });
21772}
21773
21774#[gpui::test]
21775async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
21776    init_test(cx, |_| {});
21777
21778    let fs = FakeFs::new(cx.executor());
21779    fs.insert_file(path!("/file.html"), Default::default())
21780        .await;
21781
21782    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
21783
21784    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21785    let html_language = Arc::new(Language::new(
21786        LanguageConfig {
21787            name: "HTML".into(),
21788            matcher: LanguageMatcher {
21789                path_suffixes: vec!["html".to_string()],
21790                ..LanguageMatcher::default()
21791            },
21792            brackets: BracketPairConfig {
21793                pairs: vec![BracketPair {
21794                    start: "<".into(),
21795                    end: ">".into(),
21796                    close: true,
21797                    ..Default::default()
21798                }],
21799                ..Default::default()
21800            },
21801            ..Default::default()
21802        },
21803        Some(tree_sitter_html::LANGUAGE.into()),
21804    ));
21805    language_registry.add(html_language);
21806    let mut fake_servers = language_registry.register_fake_lsp(
21807        "HTML",
21808        FakeLspAdapter {
21809            capabilities: lsp::ServerCapabilities {
21810                completion_provider: Some(lsp::CompletionOptions {
21811                    resolve_provider: Some(true),
21812                    ..Default::default()
21813                }),
21814                ..Default::default()
21815            },
21816            ..Default::default()
21817        },
21818    );
21819
21820    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21821    let cx = &mut VisualTestContext::from_window(*workspace, cx);
21822
21823    let worktree_id = workspace
21824        .update(cx, |workspace, _window, cx| {
21825            workspace.project().update(cx, |project, cx| {
21826                project.worktrees(cx).next().unwrap().read(cx).id()
21827            })
21828        })
21829        .unwrap();
21830    project
21831        .update(cx, |project, cx| {
21832            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
21833        })
21834        .await
21835        .unwrap();
21836    let editor = workspace
21837        .update(cx, |workspace, window, cx| {
21838            workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
21839        })
21840        .unwrap()
21841        .await
21842        .unwrap()
21843        .downcast::<Editor>()
21844        .unwrap();
21845
21846    let fake_server = fake_servers.next().await.unwrap();
21847    editor.update_in(cx, |editor, window, cx| {
21848        editor.set_text("<ad></ad>", window, cx);
21849        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21850            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
21851        });
21852        let Some((buffer, _)) = editor
21853            .buffer
21854            .read(cx)
21855            .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
21856        else {
21857            panic!("Failed to get buffer for selection position");
21858        };
21859        let buffer = buffer.read(cx);
21860        let buffer_id = buffer.remote_id();
21861        let opening_range =
21862            buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3));
21863        let closing_range =
21864            buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8));
21865        let mut linked_ranges = HashMap::default();
21866        linked_ranges.insert(
21867            buffer_id,
21868            vec![(opening_range.clone(), vec![closing_range.clone()])],
21869        );
21870        editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
21871    });
21872    let mut completion_handle =
21873        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
21874            Ok(Some(lsp::CompletionResponse::Array(vec![
21875                lsp::CompletionItem {
21876                    label: "head".to_string(),
21877                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21878                        lsp::InsertReplaceEdit {
21879                            new_text: "head".to_string(),
21880                            insert: lsp::Range::new(
21881                                lsp::Position::new(0, 1),
21882                                lsp::Position::new(0, 3),
21883                            ),
21884                            replace: lsp::Range::new(
21885                                lsp::Position::new(0, 1),
21886                                lsp::Position::new(0, 3),
21887                            ),
21888                        },
21889                    )),
21890                    ..Default::default()
21891                },
21892            ])))
21893        });
21894    editor.update_in(cx, |editor, window, cx| {
21895        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
21896    });
21897    cx.run_until_parked();
21898    completion_handle.next().await.unwrap();
21899    editor.update(cx, |editor, _| {
21900        assert!(
21901            editor.context_menu_visible(),
21902            "Completion menu should be visible"
21903        );
21904    });
21905    editor.update_in(cx, |editor, window, cx| {
21906        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
21907    });
21908    cx.executor().run_until_parked();
21909    editor.update(cx, |editor, cx| {
21910        assert_eq!(editor.text(cx), "<head></head>");
21911    });
21912}
21913
21914#[gpui::test]
21915async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
21916    init_test(cx, |_| {});
21917
21918    let fs = FakeFs::new(cx.executor());
21919    fs.insert_tree(
21920        path!("/root"),
21921        json!({
21922            "a": {
21923                "main.rs": "fn main() {}",
21924            },
21925            "foo": {
21926                "bar": {
21927                    "external_file.rs": "pub mod external {}",
21928                }
21929            }
21930        }),
21931    )
21932    .await;
21933
21934    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
21935    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21936    language_registry.add(rust_lang());
21937    let _fake_servers = language_registry.register_fake_lsp(
21938        "Rust",
21939        FakeLspAdapter {
21940            ..FakeLspAdapter::default()
21941        },
21942    );
21943    let (workspace, cx) =
21944        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21945    let worktree_id = workspace.update(cx, |workspace, cx| {
21946        workspace.project().update(cx, |project, cx| {
21947            project.worktrees(cx).next().unwrap().read(cx).id()
21948        })
21949    });
21950
21951    let assert_language_servers_count =
21952        |expected: usize, context: &str, cx: &mut VisualTestContext| {
21953            project.update(cx, |project, cx| {
21954                let current = project
21955                    .lsp_store()
21956                    .read(cx)
21957                    .as_local()
21958                    .unwrap()
21959                    .language_servers
21960                    .len();
21961                assert_eq!(expected, current, "{context}");
21962            });
21963        };
21964
21965    assert_language_servers_count(
21966        0,
21967        "No servers should be running before any file is open",
21968        cx,
21969    );
21970    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21971    let main_editor = workspace
21972        .update_in(cx, |workspace, window, cx| {
21973            workspace.open_path(
21974                (worktree_id, "main.rs"),
21975                Some(pane.downgrade()),
21976                true,
21977                window,
21978                cx,
21979            )
21980        })
21981        .unwrap()
21982        .await
21983        .downcast::<Editor>()
21984        .unwrap();
21985    pane.update(cx, |pane, cx| {
21986        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21987        open_editor.update(cx, |editor, cx| {
21988            assert_eq!(
21989                editor.display_text(cx),
21990                "fn main() {}",
21991                "Original main.rs text on initial open",
21992            );
21993        });
21994        assert_eq!(open_editor, main_editor);
21995    });
21996    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
21997
21998    let external_editor = workspace
21999        .update_in(cx, |workspace, window, cx| {
22000            workspace.open_abs_path(
22001                PathBuf::from("/root/foo/bar/external_file.rs"),
22002                OpenOptions::default(),
22003                window,
22004                cx,
22005            )
22006        })
22007        .await
22008        .expect("opening external file")
22009        .downcast::<Editor>()
22010        .expect("downcasted external file's open element to editor");
22011    pane.update(cx, |pane, cx| {
22012        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22013        open_editor.update(cx, |editor, cx| {
22014            assert_eq!(
22015                editor.display_text(cx),
22016                "pub mod external {}",
22017                "External file is open now",
22018            );
22019        });
22020        assert_eq!(open_editor, external_editor);
22021    });
22022    assert_language_servers_count(
22023        1,
22024        "Second, external, *.rs file should join the existing server",
22025        cx,
22026    );
22027
22028    pane.update_in(cx, |pane, window, cx| {
22029        pane.close_active_item(&CloseActiveItem::default(), window, cx)
22030    })
22031    .await
22032    .unwrap();
22033    pane.update_in(cx, |pane, window, cx| {
22034        pane.navigate_backward(window, cx);
22035    });
22036    cx.run_until_parked();
22037    pane.update(cx, |pane, cx| {
22038        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22039        open_editor.update(cx, |editor, cx| {
22040            assert_eq!(
22041                editor.display_text(cx),
22042                "pub mod external {}",
22043                "External file is open now",
22044            );
22045        });
22046    });
22047    assert_language_servers_count(
22048        1,
22049        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
22050        cx,
22051    );
22052
22053    cx.update(|_, cx| {
22054        workspace::reload(&workspace::Reload::default(), cx);
22055    });
22056    assert_language_servers_count(
22057        1,
22058        "After reloading the worktree with local and external files opened, only one project should be started",
22059        cx,
22060    );
22061}
22062
22063#[gpui::test]
22064async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
22065    init_test(cx, |_| {});
22066
22067    let mut cx = EditorTestContext::new(cx).await;
22068    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22069    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22070
22071    // test cursor move to start of each line on tab
22072    // for `if`, `elif`, `else`, `while`, `with` and `for`
22073    cx.set_state(indoc! {"
22074        def main():
22075        ˇ    for item in items:
22076        ˇ        while item.active:
22077        ˇ            if item.value > 10:
22078        ˇ                continue
22079        ˇ            elif item.value < 0:
22080        ˇ                break
22081        ˇ            else:
22082        ˇ                with item.context() as ctx:
22083        ˇ                    yield count
22084        ˇ        else:
22085        ˇ            log('while else')
22086        ˇ    else:
22087        ˇ        log('for else')
22088    "});
22089    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22090    cx.assert_editor_state(indoc! {"
22091        def main():
22092            ˇfor item in items:
22093                ˇwhile item.active:
22094                    ˇif item.value > 10:
22095                        ˇcontinue
22096                    ˇelif item.value < 0:
22097                        ˇbreak
22098                    ˇelse:
22099                        ˇwith item.context() as ctx:
22100                            ˇyield count
22101                ˇelse:
22102                    ˇlog('while else')
22103            ˇelse:
22104                ˇlog('for else')
22105    "});
22106    // test relative indent is preserved when tab
22107    // for `if`, `elif`, `else`, `while`, `with` and `for`
22108    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22109    cx.assert_editor_state(indoc! {"
22110        def main():
22111                ˇfor item in items:
22112                    ˇwhile item.active:
22113                        ˇif item.value > 10:
22114                            ˇcontinue
22115                        ˇelif item.value < 0:
22116                            ˇbreak
22117                        ˇelse:
22118                            ˇwith item.context() as ctx:
22119                                ˇyield count
22120                    ˇelse:
22121                        ˇlog('while else')
22122                ˇelse:
22123                    ˇlog('for else')
22124    "});
22125
22126    // test cursor move to start of each line on tab
22127    // for `try`, `except`, `else`, `finally`, `match` and `def`
22128    cx.set_state(indoc! {"
22129        def main():
22130        ˇ    try:
22131        ˇ        fetch()
22132        ˇ    except ValueError:
22133        ˇ        handle_error()
22134        ˇ    else:
22135        ˇ        match value:
22136        ˇ            case _:
22137        ˇ    finally:
22138        ˇ        def status():
22139        ˇ            return 0
22140    "});
22141    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22142    cx.assert_editor_state(indoc! {"
22143        def main():
22144            ˇtry:
22145                ˇfetch()
22146            ˇexcept ValueError:
22147                ˇhandle_error()
22148            ˇelse:
22149                ˇmatch value:
22150                    ˇcase _:
22151            ˇfinally:
22152                ˇdef status():
22153                    ˇreturn 0
22154    "});
22155    // test relative indent is preserved when tab
22156    // for `try`, `except`, `else`, `finally`, `match` and `def`
22157    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22158    cx.assert_editor_state(indoc! {"
22159        def main():
22160                ˇtry:
22161                    ˇfetch()
22162                ˇexcept ValueError:
22163                    ˇhandle_error()
22164                ˇelse:
22165                    ˇmatch value:
22166                        ˇcase _:
22167                ˇfinally:
22168                    ˇdef status():
22169                        ˇreturn 0
22170    "});
22171}
22172
22173#[gpui::test]
22174async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
22175    init_test(cx, |_| {});
22176
22177    let mut cx = EditorTestContext::new(cx).await;
22178    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22179    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22180
22181    // test `else` auto outdents when typed inside `if` block
22182    cx.set_state(indoc! {"
22183        def main():
22184            if i == 2:
22185                return
22186                ˇ
22187    "});
22188    cx.update_editor(|editor, window, cx| {
22189        editor.handle_input("else:", window, cx);
22190    });
22191    cx.assert_editor_state(indoc! {"
22192        def main():
22193            if i == 2:
22194                return
22195            else:ˇ
22196    "});
22197
22198    // test `except` auto outdents when typed inside `try` block
22199    cx.set_state(indoc! {"
22200        def main():
22201            try:
22202                i = 2
22203                ˇ
22204    "});
22205    cx.update_editor(|editor, window, cx| {
22206        editor.handle_input("except:", window, cx);
22207    });
22208    cx.assert_editor_state(indoc! {"
22209        def main():
22210            try:
22211                i = 2
22212            except:ˇ
22213    "});
22214
22215    // test `else` auto outdents when typed inside `except` block
22216    cx.set_state(indoc! {"
22217        def main():
22218            try:
22219                i = 2
22220            except:
22221                j = 2
22222                ˇ
22223    "});
22224    cx.update_editor(|editor, window, cx| {
22225        editor.handle_input("else:", window, cx);
22226    });
22227    cx.assert_editor_state(indoc! {"
22228        def main():
22229            try:
22230                i = 2
22231            except:
22232                j = 2
22233            else:ˇ
22234    "});
22235
22236    // test `finally` auto outdents when typed inside `else` block
22237    cx.set_state(indoc! {"
22238        def main():
22239            try:
22240                i = 2
22241            except:
22242                j = 2
22243            else:
22244                k = 2
22245                ˇ
22246    "});
22247    cx.update_editor(|editor, window, cx| {
22248        editor.handle_input("finally:", window, cx);
22249    });
22250    cx.assert_editor_state(indoc! {"
22251        def main():
22252            try:
22253                i = 2
22254            except:
22255                j = 2
22256            else:
22257                k = 2
22258            finally:ˇ
22259    "});
22260
22261    // test `else` does not outdents when typed inside `except` block right after for block
22262    cx.set_state(indoc! {"
22263        def main():
22264            try:
22265                i = 2
22266            except:
22267                for i in range(n):
22268                    pass
22269                ˇ
22270    "});
22271    cx.update_editor(|editor, window, cx| {
22272        editor.handle_input("else:", window, cx);
22273    });
22274    cx.assert_editor_state(indoc! {"
22275        def main():
22276            try:
22277                i = 2
22278            except:
22279                for i in range(n):
22280                    pass
22281                else:ˇ
22282    "});
22283
22284    // test `finally` auto outdents when typed inside `else` block right after for block
22285    cx.set_state(indoc! {"
22286        def main():
22287            try:
22288                i = 2
22289            except:
22290                j = 2
22291            else:
22292                for i in range(n):
22293                    pass
22294                ˇ
22295    "});
22296    cx.update_editor(|editor, window, cx| {
22297        editor.handle_input("finally:", window, cx);
22298    });
22299    cx.assert_editor_state(indoc! {"
22300        def main():
22301            try:
22302                i = 2
22303            except:
22304                j = 2
22305            else:
22306                for i in range(n):
22307                    pass
22308            finally:ˇ
22309    "});
22310
22311    // test `except` outdents to inner "try" block
22312    cx.set_state(indoc! {"
22313        def main():
22314            try:
22315                i = 2
22316                if i == 2:
22317                    try:
22318                        i = 3
22319                        ˇ
22320    "});
22321    cx.update_editor(|editor, window, cx| {
22322        editor.handle_input("except:", window, cx);
22323    });
22324    cx.assert_editor_state(indoc! {"
22325        def main():
22326            try:
22327                i = 2
22328                if i == 2:
22329                    try:
22330                        i = 3
22331                    except:ˇ
22332    "});
22333
22334    // test `except` outdents to outer "try" block
22335    cx.set_state(indoc! {"
22336        def main():
22337            try:
22338                i = 2
22339                if i == 2:
22340                    try:
22341                        i = 3
22342                ˇ
22343    "});
22344    cx.update_editor(|editor, window, cx| {
22345        editor.handle_input("except:", window, cx);
22346    });
22347    cx.assert_editor_state(indoc! {"
22348        def main():
22349            try:
22350                i = 2
22351                if i == 2:
22352                    try:
22353                        i = 3
22354            except:ˇ
22355    "});
22356
22357    // test `else` stays at correct indent when typed after `for` block
22358    cx.set_state(indoc! {"
22359        def main():
22360            for i in range(10):
22361                if i == 3:
22362                    break
22363            ˇ
22364    "});
22365    cx.update_editor(|editor, window, cx| {
22366        editor.handle_input("else:", window, cx);
22367    });
22368    cx.assert_editor_state(indoc! {"
22369        def main():
22370            for i in range(10):
22371                if i == 3:
22372                    break
22373            else:ˇ
22374    "});
22375
22376    // test does not outdent on typing after line with square brackets
22377    cx.set_state(indoc! {"
22378        def f() -> list[str]:
22379            ˇ
22380    "});
22381    cx.update_editor(|editor, window, cx| {
22382        editor.handle_input("a", window, cx);
22383    });
22384    cx.assert_editor_state(indoc! {"
22385        def f() -> list[str]:
2238622387    "});
22388
22389    // test does not outdent on typing : after case keyword
22390    cx.set_state(indoc! {"
22391        match 1:
22392            caseˇ
22393    "});
22394    cx.update_editor(|editor, window, cx| {
22395        editor.handle_input(":", window, cx);
22396    });
22397    cx.assert_editor_state(indoc! {"
22398        match 1:
22399            case:ˇ
22400    "});
22401}
22402
22403#[gpui::test]
22404async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
22405    init_test(cx, |_| {});
22406    update_test_language_settings(cx, |settings| {
22407        settings.defaults.extend_comment_on_newline = Some(false);
22408    });
22409    let mut cx = EditorTestContext::new(cx).await;
22410    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22411    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22412
22413    // test correct indent after newline on comment
22414    cx.set_state(indoc! {"
22415        # COMMENT:ˇ
22416    "});
22417    cx.update_editor(|editor, window, cx| {
22418        editor.newline(&Newline, window, cx);
22419    });
22420    cx.assert_editor_state(indoc! {"
22421        # COMMENT:
22422        ˇ
22423    "});
22424
22425    // test correct indent after newline in brackets
22426    cx.set_state(indoc! {"
22427        {ˇ}
22428    "});
22429    cx.update_editor(|editor, window, cx| {
22430        editor.newline(&Newline, window, cx);
22431    });
22432    cx.run_until_parked();
22433    cx.assert_editor_state(indoc! {"
22434        {
22435            ˇ
22436        }
22437    "});
22438
22439    cx.set_state(indoc! {"
22440        (ˇ)
22441    "});
22442    cx.update_editor(|editor, window, cx| {
22443        editor.newline(&Newline, window, cx);
22444    });
22445    cx.run_until_parked();
22446    cx.assert_editor_state(indoc! {"
22447        (
22448            ˇ
22449        )
22450    "});
22451
22452    // do not indent after empty lists or dictionaries
22453    cx.set_state(indoc! {"
22454        a = []ˇ
22455    "});
22456    cx.update_editor(|editor, window, cx| {
22457        editor.newline(&Newline, window, cx);
22458    });
22459    cx.run_until_parked();
22460    cx.assert_editor_state(indoc! {"
22461        a = []
22462        ˇ
22463    "});
22464}
22465
22466fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
22467    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
22468    point..point
22469}
22470
22471#[track_caller]
22472fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
22473    let (text, ranges) = marked_text_ranges(marked_text, true);
22474    assert_eq!(editor.text(cx), text);
22475    assert_eq!(
22476        editor.selections.ranges(cx),
22477        ranges,
22478        "Assert selections are {}",
22479        marked_text
22480    );
22481}
22482
22483pub fn handle_signature_help_request(
22484    cx: &mut EditorLspTestContext,
22485    mocked_response: lsp::SignatureHelp,
22486) -> impl Future<Output = ()> + use<> {
22487    let mut request =
22488        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
22489            let mocked_response = mocked_response.clone();
22490            async move { Ok(Some(mocked_response)) }
22491        });
22492
22493    async move {
22494        request.next().await;
22495    }
22496}
22497
22498#[track_caller]
22499pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
22500    cx.update_editor(|editor, _, _| {
22501        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
22502            let entries = menu.entries.borrow();
22503            let entries = entries
22504                .iter()
22505                .map(|entry| entry.string.as_str())
22506                .collect::<Vec<_>>();
22507            assert_eq!(entries, expected);
22508        } else {
22509            panic!("Expected completions menu");
22510        }
22511    });
22512}
22513
22514/// Handle completion request passing a marked string specifying where the completion
22515/// should be triggered from using '|' character, what range should be replaced, and what completions
22516/// should be returned using '<' and '>' to delimit the range.
22517///
22518/// Also see `handle_completion_request_with_insert_and_replace`.
22519#[track_caller]
22520pub fn handle_completion_request(
22521    marked_string: &str,
22522    completions: Vec<&'static str>,
22523    is_incomplete: bool,
22524    counter: Arc<AtomicUsize>,
22525    cx: &mut EditorLspTestContext,
22526) -> impl Future<Output = ()> {
22527    let complete_from_marker: TextRangeMarker = '|'.into();
22528    let replace_range_marker: TextRangeMarker = ('<', '>').into();
22529    let (_, mut marked_ranges) = marked_text_ranges_by(
22530        marked_string,
22531        vec![complete_from_marker.clone(), replace_range_marker.clone()],
22532    );
22533
22534    let complete_from_position =
22535        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
22536    let replace_range =
22537        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
22538
22539    let mut request =
22540        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
22541            let completions = completions.clone();
22542            counter.fetch_add(1, atomic::Ordering::Release);
22543            async move {
22544                assert_eq!(params.text_document_position.text_document.uri, url.clone());
22545                assert_eq!(
22546                    params.text_document_position.position,
22547                    complete_from_position
22548                );
22549                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
22550                    is_incomplete: is_incomplete,
22551                    item_defaults: None,
22552                    items: completions
22553                        .iter()
22554                        .map(|completion_text| lsp::CompletionItem {
22555                            label: completion_text.to_string(),
22556                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
22557                                range: replace_range,
22558                                new_text: completion_text.to_string(),
22559                            })),
22560                            ..Default::default()
22561                        })
22562                        .collect(),
22563                })))
22564            }
22565        });
22566
22567    async move {
22568        request.next().await;
22569    }
22570}
22571
22572/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
22573/// given instead, which also contains an `insert` range.
22574///
22575/// This function uses markers to define ranges:
22576/// - `|` marks the cursor position
22577/// - `<>` marks the replace range
22578/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
22579pub fn handle_completion_request_with_insert_and_replace(
22580    cx: &mut EditorLspTestContext,
22581    marked_string: &str,
22582    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
22583    counter: Arc<AtomicUsize>,
22584) -> impl Future<Output = ()> {
22585    let complete_from_marker: TextRangeMarker = '|'.into();
22586    let replace_range_marker: TextRangeMarker = ('<', '>').into();
22587    let insert_range_marker: TextRangeMarker = ('{', '}').into();
22588
22589    let (_, mut marked_ranges) = marked_text_ranges_by(
22590        marked_string,
22591        vec![
22592            complete_from_marker.clone(),
22593            replace_range_marker.clone(),
22594            insert_range_marker.clone(),
22595        ],
22596    );
22597
22598    let complete_from_position =
22599        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
22600    let replace_range =
22601        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
22602
22603    let insert_range = match marked_ranges.remove(&insert_range_marker) {
22604        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
22605        _ => lsp::Range {
22606            start: replace_range.start,
22607            end: complete_from_position,
22608        },
22609    };
22610
22611    let mut request =
22612        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
22613            let completions = completions.clone();
22614            counter.fetch_add(1, atomic::Ordering::Release);
22615            async move {
22616                assert_eq!(params.text_document_position.text_document.uri, url.clone());
22617                assert_eq!(
22618                    params.text_document_position.position, complete_from_position,
22619                    "marker `|` position doesn't match",
22620                );
22621                Ok(Some(lsp::CompletionResponse::Array(
22622                    completions
22623                        .iter()
22624                        .map(|(label, new_text)| lsp::CompletionItem {
22625                            label: label.to_string(),
22626                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22627                                lsp::InsertReplaceEdit {
22628                                    insert: insert_range,
22629                                    replace: replace_range,
22630                                    new_text: new_text.to_string(),
22631                                },
22632                            )),
22633                            ..Default::default()
22634                        })
22635                        .collect(),
22636                )))
22637            }
22638        });
22639
22640    async move {
22641        request.next().await;
22642    }
22643}
22644
22645fn handle_resolve_completion_request(
22646    cx: &mut EditorLspTestContext,
22647    edits: Option<Vec<(&'static str, &'static str)>>,
22648) -> impl Future<Output = ()> {
22649    let edits = edits.map(|edits| {
22650        edits
22651            .iter()
22652            .map(|(marked_string, new_text)| {
22653                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
22654                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
22655                lsp::TextEdit::new(replace_range, new_text.to_string())
22656            })
22657            .collect::<Vec<_>>()
22658    });
22659
22660    let mut request =
22661        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
22662            let edits = edits.clone();
22663            async move {
22664                Ok(lsp::CompletionItem {
22665                    additional_text_edits: edits,
22666                    ..Default::default()
22667                })
22668            }
22669        });
22670
22671    async move {
22672        request.next().await;
22673    }
22674}
22675
22676pub(crate) fn update_test_language_settings(
22677    cx: &mut TestAppContext,
22678    f: impl Fn(&mut AllLanguageSettingsContent),
22679) {
22680    cx.update(|cx| {
22681        SettingsStore::update_global(cx, |store, cx| {
22682            store.update_user_settings::<AllLanguageSettings>(cx, f);
22683        });
22684    });
22685}
22686
22687pub(crate) fn update_test_project_settings(
22688    cx: &mut TestAppContext,
22689    f: impl Fn(&mut ProjectSettings),
22690) {
22691    cx.update(|cx| {
22692        SettingsStore::update_global(cx, |store, cx| {
22693            store.update_user_settings::<ProjectSettings>(cx, f);
22694        });
22695    });
22696}
22697
22698pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
22699    cx.update(|cx| {
22700        assets::Assets.load_test_fonts(cx);
22701        let store = SettingsStore::test(cx);
22702        cx.set_global(store);
22703        theme::init(theme::LoadThemes::JustBase, cx);
22704        release_channel::init(SemanticVersion::default(), cx);
22705        client::init_settings(cx);
22706        language::init(cx);
22707        Project::init_settings(cx);
22708        workspace::init_settings(cx);
22709        crate::init(cx);
22710    });
22711
22712    update_test_language_settings(cx, f);
22713}
22714
22715#[track_caller]
22716fn assert_hunk_revert(
22717    not_reverted_text_with_selections: &str,
22718    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
22719    expected_reverted_text_with_selections: &str,
22720    base_text: &str,
22721    cx: &mut EditorLspTestContext,
22722) {
22723    cx.set_state(not_reverted_text_with_selections);
22724    cx.set_head_text(base_text);
22725    cx.executor().run_until_parked();
22726
22727    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
22728        let snapshot = editor.snapshot(window, cx);
22729        let reverted_hunk_statuses = snapshot
22730            .buffer_snapshot
22731            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
22732            .map(|hunk| hunk.status().kind)
22733            .collect::<Vec<_>>();
22734
22735        editor.git_restore(&Default::default(), window, cx);
22736        reverted_hunk_statuses
22737    });
22738    cx.executor().run_until_parked();
22739    cx.assert_editor_state(expected_reverted_text_with_selections);
22740    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
22741}
22742
22743#[gpui::test(iterations = 10)]
22744async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
22745    init_test(cx, |_| {});
22746
22747    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
22748    let counter = diagnostic_requests.clone();
22749
22750    let fs = FakeFs::new(cx.executor());
22751    fs.insert_tree(
22752        path!("/a"),
22753        json!({
22754            "first.rs": "fn main() { let a = 5; }",
22755            "second.rs": "// Test file",
22756        }),
22757    )
22758    .await;
22759
22760    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22761    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22762    let cx = &mut VisualTestContext::from_window(*workspace, cx);
22763
22764    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22765    language_registry.add(rust_lang());
22766    let mut fake_servers = language_registry.register_fake_lsp(
22767        "Rust",
22768        FakeLspAdapter {
22769            capabilities: lsp::ServerCapabilities {
22770                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
22771                    lsp::DiagnosticOptions {
22772                        identifier: None,
22773                        inter_file_dependencies: true,
22774                        workspace_diagnostics: true,
22775                        work_done_progress_options: Default::default(),
22776                    },
22777                )),
22778                ..Default::default()
22779            },
22780            ..Default::default()
22781        },
22782    );
22783
22784    let editor = workspace
22785        .update(cx, |workspace, window, cx| {
22786            workspace.open_abs_path(
22787                PathBuf::from(path!("/a/first.rs")),
22788                OpenOptions::default(),
22789                window,
22790                cx,
22791            )
22792        })
22793        .unwrap()
22794        .await
22795        .unwrap()
22796        .downcast::<Editor>()
22797        .unwrap();
22798    let fake_server = fake_servers.next().await.unwrap();
22799    let server_id = fake_server.server.server_id();
22800    let mut first_request = fake_server
22801        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
22802            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
22803            let result_id = Some(new_result_id.to_string());
22804            assert_eq!(
22805                params.text_document.uri,
22806                lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
22807            );
22808            async move {
22809                Ok(lsp::DocumentDiagnosticReportResult::Report(
22810                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
22811                        related_documents: None,
22812                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
22813                            items: Vec::new(),
22814                            result_id,
22815                        },
22816                    }),
22817                ))
22818            }
22819        });
22820
22821    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
22822        project.update(cx, |project, cx| {
22823            let buffer_id = editor
22824                .read(cx)
22825                .buffer()
22826                .read(cx)
22827                .as_singleton()
22828                .expect("created a singleton buffer")
22829                .read(cx)
22830                .remote_id();
22831            let buffer_result_id = project
22832                .lsp_store()
22833                .read(cx)
22834                .result_id(server_id, buffer_id, cx);
22835            assert_eq!(expected, buffer_result_id);
22836        });
22837    };
22838
22839    ensure_result_id(None, cx);
22840    cx.executor().advance_clock(Duration::from_millis(60));
22841    cx.executor().run_until_parked();
22842    assert_eq!(
22843        diagnostic_requests.load(atomic::Ordering::Acquire),
22844        1,
22845        "Opening file should trigger diagnostic request"
22846    );
22847    first_request
22848        .next()
22849        .await
22850        .expect("should have sent the first diagnostics pull request");
22851    ensure_result_id(Some("1".to_string()), cx);
22852
22853    // Editing should trigger diagnostics
22854    editor.update_in(cx, |editor, window, cx| {
22855        editor.handle_input("2", window, cx)
22856    });
22857    cx.executor().advance_clock(Duration::from_millis(60));
22858    cx.executor().run_until_parked();
22859    assert_eq!(
22860        diagnostic_requests.load(atomic::Ordering::Acquire),
22861        2,
22862        "Editing should trigger diagnostic request"
22863    );
22864    ensure_result_id(Some("2".to_string()), cx);
22865
22866    // Moving cursor should not trigger diagnostic request
22867    editor.update_in(cx, |editor, window, cx| {
22868        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22869            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
22870        });
22871    });
22872    cx.executor().advance_clock(Duration::from_millis(60));
22873    cx.executor().run_until_parked();
22874    assert_eq!(
22875        diagnostic_requests.load(atomic::Ordering::Acquire),
22876        2,
22877        "Cursor movement should not trigger diagnostic request"
22878    );
22879    ensure_result_id(Some("2".to_string()), cx);
22880    // Multiple rapid edits should be debounced
22881    for _ in 0..5 {
22882        editor.update_in(cx, |editor, window, cx| {
22883            editor.handle_input("x", window, cx)
22884        });
22885    }
22886    cx.executor().advance_clock(Duration::from_millis(60));
22887    cx.executor().run_until_parked();
22888
22889    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
22890    assert!(
22891        final_requests <= 4,
22892        "Multiple rapid edits should be debounced (got {final_requests} requests)",
22893    );
22894    ensure_result_id(Some(final_requests.to_string()), cx);
22895}
22896
22897#[gpui::test]
22898async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
22899    // Regression test for issue #11671
22900    // Previously, adding a cursor after moving multiple cursors would reset
22901    // the cursor count instead of adding to the existing cursors.
22902    init_test(cx, |_| {});
22903    let mut cx = EditorTestContext::new(cx).await;
22904
22905    // Create a simple buffer with cursor at start
22906    cx.set_state(indoc! {"
22907        ˇaaaa
22908        bbbb
22909        cccc
22910        dddd
22911        eeee
22912        ffff
22913        gggg
22914        hhhh"});
22915
22916    // Add 2 cursors below (so we have 3 total)
22917    cx.update_editor(|editor, window, cx| {
22918        editor.add_selection_below(&Default::default(), window, cx);
22919        editor.add_selection_below(&Default::default(), window, cx);
22920    });
22921
22922    // Verify we have 3 cursors
22923    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
22924    assert_eq!(
22925        initial_count, 3,
22926        "Should have 3 cursors after adding 2 below"
22927    );
22928
22929    // Move down one line
22930    cx.update_editor(|editor, window, cx| {
22931        editor.move_down(&MoveDown, window, cx);
22932    });
22933
22934    // Add another cursor below
22935    cx.update_editor(|editor, window, cx| {
22936        editor.add_selection_below(&Default::default(), window, cx);
22937    });
22938
22939    // Should now have 4 cursors (3 original + 1 new)
22940    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
22941    assert_eq!(
22942        final_count, 4,
22943        "Should have 4 cursors after moving and adding another"
22944    );
22945}
22946
22947#[gpui::test(iterations = 10)]
22948async fn test_document_colors(cx: &mut TestAppContext) {
22949    let expected_color = Rgba {
22950        r: 0.33,
22951        g: 0.33,
22952        b: 0.33,
22953        a: 0.33,
22954    };
22955
22956    init_test(cx, |_| {});
22957
22958    let fs = FakeFs::new(cx.executor());
22959    fs.insert_tree(
22960        path!("/a"),
22961        json!({
22962            "first.rs": "fn main() { let a = 5; }",
22963        }),
22964    )
22965    .await;
22966
22967    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22968    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22969    let cx = &mut VisualTestContext::from_window(*workspace, cx);
22970
22971    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22972    language_registry.add(rust_lang());
22973    let mut fake_servers = language_registry.register_fake_lsp(
22974        "Rust",
22975        FakeLspAdapter {
22976            capabilities: lsp::ServerCapabilities {
22977                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
22978                ..lsp::ServerCapabilities::default()
22979            },
22980            name: "rust-analyzer",
22981            ..FakeLspAdapter::default()
22982        },
22983    );
22984    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
22985        "Rust",
22986        FakeLspAdapter {
22987            capabilities: lsp::ServerCapabilities {
22988                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
22989                ..lsp::ServerCapabilities::default()
22990            },
22991            name: "not-rust-analyzer",
22992            ..FakeLspAdapter::default()
22993        },
22994    );
22995
22996    let editor = workspace
22997        .update(cx, |workspace, window, cx| {
22998            workspace.open_abs_path(
22999                PathBuf::from(path!("/a/first.rs")),
23000                OpenOptions::default(),
23001                window,
23002                cx,
23003            )
23004        })
23005        .unwrap()
23006        .await
23007        .unwrap()
23008        .downcast::<Editor>()
23009        .unwrap();
23010    let fake_language_server = fake_servers.next().await.unwrap();
23011    let fake_language_server_without_capabilities =
23012        fake_servers_without_capabilities.next().await.unwrap();
23013    let requests_made = Arc::new(AtomicUsize::new(0));
23014    let closure_requests_made = Arc::clone(&requests_made);
23015    let mut color_request_handle = fake_language_server
23016        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
23017            let requests_made = Arc::clone(&closure_requests_made);
23018            async move {
23019                assert_eq!(
23020                    params.text_document.uri,
23021                    lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
23022                );
23023                requests_made.fetch_add(1, atomic::Ordering::Release);
23024                Ok(vec![
23025                    lsp::ColorInformation {
23026                        range: lsp::Range {
23027                            start: lsp::Position {
23028                                line: 0,
23029                                character: 0,
23030                            },
23031                            end: lsp::Position {
23032                                line: 0,
23033                                character: 1,
23034                            },
23035                        },
23036                        color: lsp::Color {
23037                            red: 0.33,
23038                            green: 0.33,
23039                            blue: 0.33,
23040                            alpha: 0.33,
23041                        },
23042                    },
23043                    lsp::ColorInformation {
23044                        range: lsp::Range {
23045                            start: lsp::Position {
23046                                line: 0,
23047                                character: 0,
23048                            },
23049                            end: lsp::Position {
23050                                line: 0,
23051                                character: 1,
23052                            },
23053                        },
23054                        color: lsp::Color {
23055                            red: 0.33,
23056                            green: 0.33,
23057                            blue: 0.33,
23058                            alpha: 0.33,
23059                        },
23060                    },
23061                ])
23062            }
23063        });
23064
23065    let _handle = fake_language_server_without_capabilities
23066        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
23067            panic!("Should not be called");
23068        });
23069    cx.executor().advance_clock(Duration::from_millis(100));
23070    color_request_handle.next().await.unwrap();
23071    cx.run_until_parked();
23072    assert_eq!(
23073        1,
23074        requests_made.load(atomic::Ordering::Acquire),
23075        "Should query for colors once per editor open"
23076    );
23077    editor.update_in(cx, |editor, _, cx| {
23078        assert_eq!(
23079            vec![expected_color],
23080            extract_color_inlays(editor, cx),
23081            "Should have an initial inlay"
23082        );
23083    });
23084
23085    // opening another file in a split should not influence the LSP query counter
23086    workspace
23087        .update(cx, |workspace, window, cx| {
23088            assert_eq!(
23089                workspace.panes().len(),
23090                1,
23091                "Should have one pane with one editor"
23092            );
23093            workspace.move_item_to_pane_in_direction(
23094                &MoveItemToPaneInDirection {
23095                    direction: SplitDirection::Right,
23096                    focus: false,
23097                    clone: true,
23098                },
23099                window,
23100                cx,
23101            );
23102        })
23103        .unwrap();
23104    cx.run_until_parked();
23105    workspace
23106        .update(cx, |workspace, _, cx| {
23107            let panes = workspace.panes();
23108            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
23109            for pane in panes {
23110                let editor = pane
23111                    .read(cx)
23112                    .active_item()
23113                    .and_then(|item| item.downcast::<Editor>())
23114                    .expect("Should have opened an editor in each split");
23115                let editor_file = editor
23116                    .read(cx)
23117                    .buffer()
23118                    .read(cx)
23119                    .as_singleton()
23120                    .expect("test deals with singleton buffers")
23121                    .read(cx)
23122                    .file()
23123                    .expect("test buffese should have a file")
23124                    .path();
23125                assert_eq!(
23126                    editor_file.as_ref(),
23127                    Path::new("first.rs"),
23128                    "Both editors should be opened for the same file"
23129                )
23130            }
23131        })
23132        .unwrap();
23133
23134    cx.executor().advance_clock(Duration::from_millis(500));
23135    let save = editor.update_in(cx, |editor, window, cx| {
23136        editor.move_to_end(&MoveToEnd, window, cx);
23137        editor.handle_input("dirty", window, cx);
23138        editor.save(
23139            SaveOptions {
23140                format: true,
23141                autosave: true,
23142            },
23143            project.clone(),
23144            window,
23145            cx,
23146        )
23147    });
23148    save.await.unwrap();
23149
23150    color_request_handle.next().await.unwrap();
23151    cx.run_until_parked();
23152    assert_eq!(
23153        3,
23154        requests_made.load(atomic::Ordering::Acquire),
23155        "Should query for colors once per save and once per formatting after save"
23156    );
23157
23158    drop(editor);
23159    let close = workspace
23160        .update(cx, |workspace, window, cx| {
23161            workspace.active_pane().update(cx, |pane, cx| {
23162                pane.close_active_item(&CloseActiveItem::default(), window, cx)
23163            })
23164        })
23165        .unwrap();
23166    close.await.unwrap();
23167    let close = workspace
23168        .update(cx, |workspace, window, cx| {
23169            workspace.active_pane().update(cx, |pane, cx| {
23170                pane.close_active_item(&CloseActiveItem::default(), window, cx)
23171            })
23172        })
23173        .unwrap();
23174    close.await.unwrap();
23175    assert_eq!(
23176        3,
23177        requests_made.load(atomic::Ordering::Acquire),
23178        "After saving and closing all editors, no extra requests should be made"
23179    );
23180    workspace
23181        .update(cx, |workspace, _, cx| {
23182            assert!(
23183                workspace.active_item(cx).is_none(),
23184                "Should close all editors"
23185            )
23186        })
23187        .unwrap();
23188
23189    workspace
23190        .update(cx, |workspace, window, cx| {
23191            workspace.active_pane().update(cx, |pane, cx| {
23192                pane.navigate_backward(window, cx);
23193            })
23194        })
23195        .unwrap();
23196    cx.executor().advance_clock(Duration::from_millis(100));
23197    cx.run_until_parked();
23198    let editor = workspace
23199        .update(cx, |workspace, _, cx| {
23200            workspace
23201                .active_item(cx)
23202                .expect("Should have reopened the editor again after navigating back")
23203                .downcast::<Editor>()
23204                .expect("Should be an editor")
23205        })
23206        .unwrap();
23207    color_request_handle.next().await.unwrap();
23208    assert_eq!(
23209        3,
23210        requests_made.load(atomic::Ordering::Acquire),
23211        "Cache should be reused on buffer close and reopen"
23212    );
23213    editor.update(cx, |editor, cx| {
23214        assert_eq!(
23215            vec![expected_color],
23216            extract_color_inlays(editor, cx),
23217            "Should have an initial inlay"
23218        );
23219    });
23220}
23221
23222#[gpui::test]
23223async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
23224    init_test(cx, |_| {});
23225    let (editor, cx) = cx.add_window_view(Editor::single_line);
23226    editor.update_in(cx, |editor, window, cx| {
23227        editor.set_text("oops\n\nwow\n", window, cx)
23228    });
23229    cx.run_until_parked();
23230    editor.update(cx, |editor, cx| {
23231        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
23232    });
23233    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
23234    cx.run_until_parked();
23235    editor.update(cx, |editor, cx| {
23236        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
23237    });
23238}
23239
23240#[track_caller]
23241fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
23242    editor
23243        .all_inlays(cx)
23244        .into_iter()
23245        .filter_map(|inlay| inlay.get_color())
23246        .map(Rgba::from)
23247        .collect()
23248}