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]
 3084fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3085    init_test(cx, |_| {});
 3086
 3087    let editor = cx.add_window(|window, cx| {
 3088        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3089        let mut editor = build_editor(buffer.clone(), window, cx);
 3090        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3091            s.select_ranges([3..4, 11..12, 19..20])
 3092        });
 3093        editor
 3094    });
 3095
 3096    _ = editor.update(cx, |editor, window, cx| {
 3097        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3098        editor.buffer.update(cx, |buffer, cx| {
 3099            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 3100            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3101        });
 3102        assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
 3103
 3104        editor.insert("Z", window, cx);
 3105        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3106
 3107        // The selections are moved after the inserted characters
 3108        assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
 3109    });
 3110}
 3111
 3112#[gpui::test]
 3113async fn test_tab(cx: &mut TestAppContext) {
 3114    init_test(cx, |settings| {
 3115        settings.defaults.tab_size = NonZeroU32::new(3)
 3116    });
 3117
 3118    let mut cx = EditorTestContext::new(cx).await;
 3119    cx.set_state(indoc! {"
 3120        ˇabˇc
 3121        ˇ🏀ˇ🏀ˇefg
 3122 3123    "});
 3124    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3125    cx.assert_editor_state(indoc! {"
 3126           ˇab ˇc
 3127           ˇ🏀  ˇ🏀  ˇefg
 3128        d  ˇ
 3129    "});
 3130
 3131    cx.set_state(indoc! {"
 3132        a
 3133        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3134    "});
 3135    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3136    cx.assert_editor_state(indoc! {"
 3137        a
 3138           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3139    "});
 3140}
 3141
 3142#[gpui::test]
 3143async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3144    init_test(cx, |_| {});
 3145
 3146    let mut cx = EditorTestContext::new(cx).await;
 3147    let language = Arc::new(
 3148        Language::new(
 3149            LanguageConfig::default(),
 3150            Some(tree_sitter_rust::LANGUAGE.into()),
 3151        )
 3152        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3153        .unwrap(),
 3154    );
 3155    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3156
 3157    // test when all cursors are not at suggested indent
 3158    // then simply move to their suggested indent location
 3159    cx.set_state(indoc! {"
 3160        const a: B = (
 3161            c(
 3162        ˇ
 3163        ˇ    )
 3164        );
 3165    "});
 3166    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3167    cx.assert_editor_state(indoc! {"
 3168        const a: B = (
 3169            c(
 3170                ˇ
 3171            ˇ)
 3172        );
 3173    "});
 3174
 3175    // test cursor already at suggested indent not moving when
 3176    // other cursors are yet to reach their suggested indents
 3177    cx.set_state(indoc! {"
 3178        ˇ
 3179        const a: B = (
 3180            c(
 3181                d(
 3182        ˇ
 3183                )
 3184        ˇ
 3185        ˇ    )
 3186        );
 3187    "});
 3188    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3189    cx.assert_editor_state(indoc! {"
 3190        ˇ
 3191        const a: B = (
 3192            c(
 3193                d(
 3194                    ˇ
 3195                )
 3196                ˇ
 3197            ˇ)
 3198        );
 3199    "});
 3200    // test when all cursors are at suggested indent then tab is inserted
 3201    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3202    cx.assert_editor_state(indoc! {"
 3203            ˇ
 3204        const a: B = (
 3205            c(
 3206                d(
 3207                        ˇ
 3208                )
 3209                    ˇ
 3210                ˇ)
 3211        );
 3212    "});
 3213
 3214    // test when current indent is less than suggested indent,
 3215    // we adjust line to match suggested indent and move cursor to it
 3216    //
 3217    // when no other cursor is at word boundary, all of them should move
 3218    cx.set_state(indoc! {"
 3219        const a: B = (
 3220            c(
 3221                d(
 3222        ˇ
 3223        ˇ   )
 3224        ˇ   )
 3225        );
 3226    "});
 3227    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3228    cx.assert_editor_state(indoc! {"
 3229        const a: B = (
 3230            c(
 3231                d(
 3232                    ˇ
 3233                ˇ)
 3234            ˇ)
 3235        );
 3236    "});
 3237
 3238    // test when current indent is less than suggested indent,
 3239    // we adjust line to match suggested indent and move cursor to it
 3240    //
 3241    // when some other cursor is at word boundary, it should not move
 3242    cx.set_state(indoc! {"
 3243        const a: B = (
 3244            c(
 3245                d(
 3246        ˇ
 3247        ˇ   )
 3248           ˇ)
 3249        );
 3250    "});
 3251    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3252    cx.assert_editor_state(indoc! {"
 3253        const a: B = (
 3254            c(
 3255                d(
 3256                    ˇ
 3257                ˇ)
 3258            ˇ)
 3259        );
 3260    "});
 3261
 3262    // test when current indent is more than suggested indent,
 3263    // we just move cursor to current indent instead of suggested indent
 3264    //
 3265    // when no other cursor is at word boundary, all of them should move
 3266    cx.set_state(indoc! {"
 3267        const a: B = (
 3268            c(
 3269                d(
 3270        ˇ
 3271        ˇ                )
 3272        ˇ   )
 3273        );
 3274    "});
 3275    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3276    cx.assert_editor_state(indoc! {"
 3277        const a: B = (
 3278            c(
 3279                d(
 3280                    ˇ
 3281                        ˇ)
 3282            ˇ)
 3283        );
 3284    "});
 3285    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3286    cx.assert_editor_state(indoc! {"
 3287        const a: B = (
 3288            c(
 3289                d(
 3290                        ˇ
 3291                            ˇ)
 3292                ˇ)
 3293        );
 3294    "});
 3295
 3296    // test when current indent is more than suggested indent,
 3297    // we just move cursor to current indent instead of suggested indent
 3298    //
 3299    // when some other cursor is at word boundary, it doesn't move
 3300    cx.set_state(indoc! {"
 3301        const a: B = (
 3302            c(
 3303                d(
 3304        ˇ
 3305        ˇ                )
 3306            ˇ)
 3307        );
 3308    "});
 3309    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3310    cx.assert_editor_state(indoc! {"
 3311        const a: B = (
 3312            c(
 3313                d(
 3314                    ˇ
 3315                        ˇ)
 3316            ˇ)
 3317        );
 3318    "});
 3319
 3320    // handle auto-indent when there are multiple cursors on the same line
 3321    cx.set_state(indoc! {"
 3322        const a: B = (
 3323            c(
 3324        ˇ    ˇ
 3325        ˇ    )
 3326        );
 3327    "});
 3328    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3329    cx.assert_editor_state(indoc! {"
 3330        const a: B = (
 3331            c(
 3332                ˇ
 3333            ˇ)
 3334        );
 3335    "});
 3336}
 3337
 3338#[gpui::test]
 3339async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 3340    init_test(cx, |settings| {
 3341        settings.defaults.tab_size = NonZeroU32::new(3)
 3342    });
 3343
 3344    let mut cx = EditorTestContext::new(cx).await;
 3345    cx.set_state(indoc! {"
 3346         ˇ
 3347        \t ˇ
 3348        \t  ˇ
 3349        \t   ˇ
 3350         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 3351    "});
 3352
 3353    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3354    cx.assert_editor_state(indoc! {"
 3355           ˇ
 3356        \t   ˇ
 3357        \t   ˇ
 3358        \t      ˇ
 3359         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 3360    "});
 3361}
 3362
 3363#[gpui::test]
 3364async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 3365    init_test(cx, |settings| {
 3366        settings.defaults.tab_size = NonZeroU32::new(4)
 3367    });
 3368
 3369    let language = Arc::new(
 3370        Language::new(
 3371            LanguageConfig::default(),
 3372            Some(tree_sitter_rust::LANGUAGE.into()),
 3373        )
 3374        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 3375        .unwrap(),
 3376    );
 3377
 3378    let mut cx = EditorTestContext::new(cx).await;
 3379    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3380    cx.set_state(indoc! {"
 3381        fn a() {
 3382            if b {
 3383        \t ˇc
 3384            }
 3385        }
 3386    "});
 3387
 3388    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3389    cx.assert_editor_state(indoc! {"
 3390        fn a() {
 3391            if b {
 3392                ˇc
 3393            }
 3394        }
 3395    "});
 3396}
 3397
 3398#[gpui::test]
 3399async fn test_indent_outdent(cx: &mut TestAppContext) {
 3400    init_test(cx, |settings| {
 3401        settings.defaults.tab_size = NonZeroU32::new(4);
 3402    });
 3403
 3404    let mut cx = EditorTestContext::new(cx).await;
 3405
 3406    cx.set_state(indoc! {"
 3407          «oneˇ» «twoˇ»
 3408        three
 3409         four
 3410    "});
 3411    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3412    cx.assert_editor_state(indoc! {"
 3413            «oneˇ» «twoˇ»
 3414        three
 3415         four
 3416    "});
 3417
 3418    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3419    cx.assert_editor_state(indoc! {"
 3420        «oneˇ» «twoˇ»
 3421        three
 3422         four
 3423    "});
 3424
 3425    // select across line ending
 3426    cx.set_state(indoc! {"
 3427        one two
 3428        t«hree
 3429        ˇ» four
 3430    "});
 3431    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3432    cx.assert_editor_state(indoc! {"
 3433        one two
 3434            t«hree
 3435        ˇ» four
 3436    "});
 3437
 3438    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3439    cx.assert_editor_state(indoc! {"
 3440        one two
 3441        t«hree
 3442        ˇ» four
 3443    "});
 3444
 3445    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3446    cx.set_state(indoc! {"
 3447        one two
 3448        ˇthree
 3449            four
 3450    "});
 3451    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3452    cx.assert_editor_state(indoc! {"
 3453        one two
 3454            ˇthree
 3455            four
 3456    "});
 3457
 3458    cx.set_state(indoc! {"
 3459        one two
 3460        ˇ    three
 3461            four
 3462    "});
 3463    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3464    cx.assert_editor_state(indoc! {"
 3465        one two
 3466        ˇthree
 3467            four
 3468    "});
 3469}
 3470
 3471#[gpui::test]
 3472async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3473    // This is a regression test for issue #33761
 3474    init_test(cx, |_| {});
 3475
 3476    let mut cx = EditorTestContext::new(cx).await;
 3477    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3478    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3479
 3480    cx.set_state(
 3481        r#"ˇ#     ingress:
 3482ˇ#         api:
 3483ˇ#             enabled: false
 3484ˇ#             pathType: Prefix
 3485ˇ#           console:
 3486ˇ#               enabled: false
 3487ˇ#               pathType: Prefix
 3488"#,
 3489    );
 3490
 3491    // Press tab to indent all lines
 3492    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3493
 3494    cx.assert_editor_state(
 3495        r#"    ˇ#     ingress:
 3496    ˇ#         api:
 3497    ˇ#             enabled: false
 3498    ˇ#             pathType: Prefix
 3499    ˇ#           console:
 3500    ˇ#               enabled: false
 3501    ˇ#               pathType: Prefix
 3502"#,
 3503    );
 3504}
 3505
 3506#[gpui::test]
 3507async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3508    // This is a test to make sure our fix for issue #33761 didn't break anything
 3509    init_test(cx, |_| {});
 3510
 3511    let mut cx = EditorTestContext::new(cx).await;
 3512    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3513    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3514
 3515    cx.set_state(
 3516        r#"ˇingress:
 3517ˇ  api:
 3518ˇ    enabled: false
 3519ˇ    pathType: Prefix
 3520"#,
 3521    );
 3522
 3523    // Press tab to indent all lines
 3524    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3525
 3526    cx.assert_editor_state(
 3527        r#"ˇingress:
 3528    ˇapi:
 3529        ˇenabled: false
 3530        ˇpathType: Prefix
 3531"#,
 3532    );
 3533}
 3534
 3535#[gpui::test]
 3536async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 3537    init_test(cx, |settings| {
 3538        settings.defaults.hard_tabs = Some(true);
 3539    });
 3540
 3541    let mut cx = EditorTestContext::new(cx).await;
 3542
 3543    // select two ranges on one line
 3544    cx.set_state(indoc! {"
 3545        «oneˇ» «twoˇ»
 3546        three
 3547        four
 3548    "});
 3549    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3550    cx.assert_editor_state(indoc! {"
 3551        \t«oneˇ» «twoˇ»
 3552        three
 3553        four
 3554    "});
 3555    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3556    cx.assert_editor_state(indoc! {"
 3557        \t\t«oneˇ» «twoˇ»
 3558        three
 3559        four
 3560    "});
 3561    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3562    cx.assert_editor_state(indoc! {"
 3563        \t«oneˇ» «twoˇ»
 3564        three
 3565        four
 3566    "});
 3567    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3568    cx.assert_editor_state(indoc! {"
 3569        «oneˇ» «twoˇ»
 3570        three
 3571        four
 3572    "});
 3573
 3574    // select across a line ending
 3575    cx.set_state(indoc! {"
 3576        one two
 3577        t«hree
 3578        ˇ»four
 3579    "});
 3580    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3581    cx.assert_editor_state(indoc! {"
 3582        one two
 3583        \tt«hree
 3584        ˇ»four
 3585    "});
 3586    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3587    cx.assert_editor_state(indoc! {"
 3588        one two
 3589        \t\tt«hree
 3590        ˇ»four
 3591    "});
 3592    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3593    cx.assert_editor_state(indoc! {"
 3594        one two
 3595        \tt«hree
 3596        ˇ»four
 3597    "});
 3598    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3599    cx.assert_editor_state(indoc! {"
 3600        one two
 3601        t«hree
 3602        ˇ»four
 3603    "});
 3604
 3605    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3606    cx.set_state(indoc! {"
 3607        one two
 3608        ˇthree
 3609        four
 3610    "});
 3611    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3612    cx.assert_editor_state(indoc! {"
 3613        one two
 3614        ˇthree
 3615        four
 3616    "});
 3617    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3618    cx.assert_editor_state(indoc! {"
 3619        one two
 3620        \tˇthree
 3621        four
 3622    "});
 3623    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3624    cx.assert_editor_state(indoc! {"
 3625        one two
 3626        ˇthree
 3627        four
 3628    "});
 3629}
 3630
 3631#[gpui::test]
 3632fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 3633    init_test(cx, |settings| {
 3634        settings.languages.0.extend([
 3635            (
 3636                "TOML".into(),
 3637                LanguageSettingsContent {
 3638                    tab_size: NonZeroU32::new(2),
 3639                    ..Default::default()
 3640                },
 3641            ),
 3642            (
 3643                "Rust".into(),
 3644                LanguageSettingsContent {
 3645                    tab_size: NonZeroU32::new(4),
 3646                    ..Default::default()
 3647                },
 3648            ),
 3649        ]);
 3650    });
 3651
 3652    let toml_language = Arc::new(Language::new(
 3653        LanguageConfig {
 3654            name: "TOML".into(),
 3655            ..Default::default()
 3656        },
 3657        None,
 3658    ));
 3659    let rust_language = Arc::new(Language::new(
 3660        LanguageConfig {
 3661            name: "Rust".into(),
 3662            ..Default::default()
 3663        },
 3664        None,
 3665    ));
 3666
 3667    let toml_buffer =
 3668        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 3669    let rust_buffer =
 3670        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 3671    let multibuffer = cx.new(|cx| {
 3672        let mut multibuffer = MultiBuffer::new(ReadWrite);
 3673        multibuffer.push_excerpts(
 3674            toml_buffer.clone(),
 3675            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 3676            cx,
 3677        );
 3678        multibuffer.push_excerpts(
 3679            rust_buffer.clone(),
 3680            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 3681            cx,
 3682        );
 3683        multibuffer
 3684    });
 3685
 3686    cx.add_window(|window, cx| {
 3687        let mut editor = build_editor(multibuffer, window, cx);
 3688
 3689        assert_eq!(
 3690            editor.text(cx),
 3691            indoc! {"
 3692                a = 1
 3693                b = 2
 3694
 3695                const c: usize = 3;
 3696            "}
 3697        );
 3698
 3699        select_ranges(
 3700            &mut editor,
 3701            indoc! {"
 3702                «aˇ» = 1
 3703                b = 2
 3704
 3705                «const c:ˇ» usize = 3;
 3706            "},
 3707            window,
 3708            cx,
 3709        );
 3710
 3711        editor.tab(&Tab, window, cx);
 3712        assert_text_with_selections(
 3713            &mut editor,
 3714            indoc! {"
 3715                  «aˇ» = 1
 3716                b = 2
 3717
 3718                    «const c:ˇ» usize = 3;
 3719            "},
 3720            cx,
 3721        );
 3722        editor.backtab(&Backtab, window, cx);
 3723        assert_text_with_selections(
 3724            &mut editor,
 3725            indoc! {"
 3726                «aˇ» = 1
 3727                b = 2
 3728
 3729                «const c:ˇ» usize = 3;
 3730            "},
 3731            cx,
 3732        );
 3733
 3734        editor
 3735    });
 3736}
 3737
 3738#[gpui::test]
 3739async fn test_backspace(cx: &mut TestAppContext) {
 3740    init_test(cx, |_| {});
 3741
 3742    let mut cx = EditorTestContext::new(cx).await;
 3743
 3744    // Basic backspace
 3745    cx.set_state(indoc! {"
 3746        onˇe two three
 3747        fou«rˇ» five six
 3748        seven «ˇeight nine
 3749        »ten
 3750    "});
 3751    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3752    cx.assert_editor_state(indoc! {"
 3753        oˇe two three
 3754        fouˇ five six
 3755        seven ˇten
 3756    "});
 3757
 3758    // Test backspace inside and around indents
 3759    cx.set_state(indoc! {"
 3760        zero
 3761            ˇone
 3762                ˇtwo
 3763            ˇ ˇ ˇ  three
 3764        ˇ  ˇ  four
 3765    "});
 3766    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3767    cx.assert_editor_state(indoc! {"
 3768        zero
 3769        ˇone
 3770            ˇtwo
 3771        ˇ  threeˇ  four
 3772    "});
 3773}
 3774
 3775#[gpui::test]
 3776async fn test_delete(cx: &mut TestAppContext) {
 3777    init_test(cx, |_| {});
 3778
 3779    let mut cx = EditorTestContext::new(cx).await;
 3780    cx.set_state(indoc! {"
 3781        onˇe two three
 3782        fou«rˇ» five six
 3783        seven «ˇeight nine
 3784        »ten
 3785    "});
 3786    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 3787    cx.assert_editor_state(indoc! {"
 3788        onˇ two three
 3789        fouˇ five six
 3790        seven ˇten
 3791    "});
 3792}
 3793
 3794#[gpui::test]
 3795fn test_delete_line(cx: &mut TestAppContext) {
 3796    init_test(cx, |_| {});
 3797
 3798    let editor = cx.add_window(|window, cx| {
 3799        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3800        build_editor(buffer, window, cx)
 3801    });
 3802    _ = editor.update(cx, |editor, window, cx| {
 3803        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3804            s.select_display_ranges([
 3805                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 3806                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 3807                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 3808            ])
 3809        });
 3810        editor.delete_line(&DeleteLine, window, cx);
 3811        assert_eq!(editor.display_text(cx), "ghi");
 3812        assert_eq!(
 3813            editor.selections.display_ranges(cx),
 3814            vec![
 3815                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 3816                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
 3817            ]
 3818        );
 3819    });
 3820
 3821    let editor = cx.add_window(|window, cx| {
 3822        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3823        build_editor(buffer, window, cx)
 3824    });
 3825    _ = editor.update(cx, |editor, window, cx| {
 3826        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3827            s.select_display_ranges([
 3828                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 3829            ])
 3830        });
 3831        editor.delete_line(&DeleteLine, window, cx);
 3832        assert_eq!(editor.display_text(cx), "ghi\n");
 3833        assert_eq!(
 3834            editor.selections.display_ranges(cx),
 3835            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 3836        );
 3837    });
 3838}
 3839
 3840#[gpui::test]
 3841fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 3842    init_test(cx, |_| {});
 3843
 3844    cx.add_window(|window, cx| {
 3845        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3846        let mut editor = build_editor(buffer.clone(), window, cx);
 3847        let buffer = buffer.read(cx).as_singleton().unwrap();
 3848
 3849        assert_eq!(
 3850            editor.selections.ranges::<Point>(cx),
 3851            &[Point::new(0, 0)..Point::new(0, 0)]
 3852        );
 3853
 3854        // When on single line, replace newline at end by space
 3855        editor.join_lines(&JoinLines, window, cx);
 3856        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3857        assert_eq!(
 3858            editor.selections.ranges::<Point>(cx),
 3859            &[Point::new(0, 3)..Point::new(0, 3)]
 3860        );
 3861
 3862        // When multiple lines are selected, remove newlines that are spanned by the selection
 3863        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3864            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 3865        });
 3866        editor.join_lines(&JoinLines, window, cx);
 3867        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 3868        assert_eq!(
 3869            editor.selections.ranges::<Point>(cx),
 3870            &[Point::new(0, 11)..Point::new(0, 11)]
 3871        );
 3872
 3873        // Undo should be transactional
 3874        editor.undo(&Undo, window, cx);
 3875        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3876        assert_eq!(
 3877            editor.selections.ranges::<Point>(cx),
 3878            &[Point::new(0, 5)..Point::new(2, 2)]
 3879        );
 3880
 3881        // When joining an empty line don't insert a space
 3882        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3883            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 3884        });
 3885        editor.join_lines(&JoinLines, window, cx);
 3886        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 3887        assert_eq!(
 3888            editor.selections.ranges::<Point>(cx),
 3889            [Point::new(2, 3)..Point::new(2, 3)]
 3890        );
 3891
 3892        // We can remove trailing newlines
 3893        editor.join_lines(&JoinLines, window, cx);
 3894        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3895        assert_eq!(
 3896            editor.selections.ranges::<Point>(cx),
 3897            [Point::new(2, 3)..Point::new(2, 3)]
 3898        );
 3899
 3900        // We don't blow up on the last line
 3901        editor.join_lines(&JoinLines, window, cx);
 3902        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3903        assert_eq!(
 3904            editor.selections.ranges::<Point>(cx),
 3905            [Point::new(2, 3)..Point::new(2, 3)]
 3906        );
 3907
 3908        // reset to test indentation
 3909        editor.buffer.update(cx, |buffer, cx| {
 3910            buffer.edit(
 3911                [
 3912                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 3913                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 3914                ],
 3915                None,
 3916                cx,
 3917            )
 3918        });
 3919
 3920        // We remove any leading spaces
 3921        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 3922        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3923            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 3924        });
 3925        editor.join_lines(&JoinLines, window, cx);
 3926        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 3927
 3928        // We don't insert a space for a line containing only spaces
 3929        editor.join_lines(&JoinLines, window, cx);
 3930        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 3931
 3932        // We ignore any leading tabs
 3933        editor.join_lines(&JoinLines, window, cx);
 3934        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 3935
 3936        editor
 3937    });
 3938}
 3939
 3940#[gpui::test]
 3941fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 3942    init_test(cx, |_| {});
 3943
 3944    cx.add_window(|window, cx| {
 3945        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3946        let mut editor = build_editor(buffer.clone(), window, cx);
 3947        let buffer = buffer.read(cx).as_singleton().unwrap();
 3948
 3949        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3950            s.select_ranges([
 3951                Point::new(0, 2)..Point::new(1, 1),
 3952                Point::new(1, 2)..Point::new(1, 2),
 3953                Point::new(3, 1)..Point::new(3, 2),
 3954            ])
 3955        });
 3956
 3957        editor.join_lines(&JoinLines, window, cx);
 3958        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 3959
 3960        assert_eq!(
 3961            editor.selections.ranges::<Point>(cx),
 3962            [
 3963                Point::new(0, 7)..Point::new(0, 7),
 3964                Point::new(1, 3)..Point::new(1, 3)
 3965            ]
 3966        );
 3967        editor
 3968    });
 3969}
 3970
 3971#[gpui::test]
 3972async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 3973    init_test(cx, |_| {});
 3974
 3975    let mut cx = EditorTestContext::new(cx).await;
 3976
 3977    let diff_base = r#"
 3978        Line 0
 3979        Line 1
 3980        Line 2
 3981        Line 3
 3982        "#
 3983    .unindent();
 3984
 3985    cx.set_state(
 3986        &r#"
 3987        ˇLine 0
 3988        Line 1
 3989        Line 2
 3990        Line 3
 3991        "#
 3992        .unindent(),
 3993    );
 3994
 3995    cx.set_head_text(&diff_base);
 3996    executor.run_until_parked();
 3997
 3998    // Join lines
 3999    cx.update_editor(|editor, window, cx| {
 4000        editor.join_lines(&JoinLines, window, cx);
 4001    });
 4002    executor.run_until_parked();
 4003
 4004    cx.assert_editor_state(
 4005        &r#"
 4006        Line 0ˇ Line 1
 4007        Line 2
 4008        Line 3
 4009        "#
 4010        .unindent(),
 4011    );
 4012    // Join again
 4013    cx.update_editor(|editor, window, cx| {
 4014        editor.join_lines(&JoinLines, window, cx);
 4015    });
 4016    executor.run_until_parked();
 4017
 4018    cx.assert_editor_state(
 4019        &r#"
 4020        Line 0 Line 1ˇ Line 2
 4021        Line 3
 4022        "#
 4023        .unindent(),
 4024    );
 4025}
 4026
 4027#[gpui::test]
 4028async fn test_custom_newlines_cause_no_false_positive_diffs(
 4029    executor: BackgroundExecutor,
 4030    cx: &mut TestAppContext,
 4031) {
 4032    init_test(cx, |_| {});
 4033    let mut cx = EditorTestContext::new(cx).await;
 4034    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 4035    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 4036    executor.run_until_parked();
 4037
 4038    cx.update_editor(|editor, window, cx| {
 4039        let snapshot = editor.snapshot(window, cx);
 4040        assert_eq!(
 4041            snapshot
 4042                .buffer_snapshot
 4043                .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
 4044                .collect::<Vec<_>>(),
 4045            Vec::new(),
 4046            "Should not have any diffs for files with custom newlines"
 4047        );
 4048    });
 4049}
 4050
 4051#[gpui::test]
 4052async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 4053    init_test(cx, |_| {});
 4054
 4055    let mut cx = EditorTestContext::new(cx).await;
 4056
 4057    // Test sort_lines_case_insensitive()
 4058    cx.set_state(indoc! {"
 4059        «z
 4060        y
 4061        x
 4062        Z
 4063        Y
 4064        Xˇ»
 4065    "});
 4066    cx.update_editor(|e, window, cx| {
 4067        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4068    });
 4069    cx.assert_editor_state(indoc! {"
 4070        «x
 4071        X
 4072        y
 4073        Y
 4074        z
 4075        Zˇ»
 4076    "});
 4077
 4078    // Test sort_lines_by_length()
 4079    //
 4080    // Demonstrates:
 4081    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 4082    // - sort is stable
 4083    cx.set_state(indoc! {"
 4084        «123
 4085        æ
 4086        12
 4087 4088        1
 4089        æˇ»
 4090    "});
 4091    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 4092    cx.assert_editor_state(indoc! {"
 4093        «æ
 4094 4095        1
 4096        æ
 4097        12
 4098        123ˇ»
 4099    "});
 4100
 4101    // Test reverse_lines()
 4102    cx.set_state(indoc! {"
 4103        «5
 4104        4
 4105        3
 4106        2
 4107        1ˇ»
 4108    "});
 4109    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4110    cx.assert_editor_state(indoc! {"
 4111        «1
 4112        2
 4113        3
 4114        4
 4115        5ˇ»
 4116    "});
 4117
 4118    // Skip testing shuffle_line()
 4119
 4120    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4121    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4122
 4123    // Don't manipulate when cursor is on single line, but expand the selection
 4124    cx.set_state(indoc! {"
 4125        ddˇdd
 4126        ccc
 4127        bb
 4128        a
 4129    "});
 4130    cx.update_editor(|e, window, cx| {
 4131        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4132    });
 4133    cx.assert_editor_state(indoc! {"
 4134        «ddddˇ»
 4135        ccc
 4136        bb
 4137        a
 4138    "});
 4139
 4140    // Basic manipulate case
 4141    // Start selection moves to column 0
 4142    // End of selection shrinks to fit shorter line
 4143    cx.set_state(indoc! {"
 4144        dd«d
 4145        ccc
 4146        bb
 4147        aaaaaˇ»
 4148    "});
 4149    cx.update_editor(|e, window, cx| {
 4150        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4151    });
 4152    cx.assert_editor_state(indoc! {"
 4153        «aaaaa
 4154        bb
 4155        ccc
 4156        dddˇ»
 4157    "});
 4158
 4159    // Manipulate case with newlines
 4160    cx.set_state(indoc! {"
 4161        dd«d
 4162        ccc
 4163
 4164        bb
 4165        aaaaa
 4166
 4167        ˇ»
 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        «
 4174
 4175        aaaaa
 4176        bb
 4177        ccc
 4178        dddˇ»
 4179
 4180    "});
 4181
 4182    // Adding new line
 4183    cx.set_state(indoc! {"
 4184        aa«a
 4185        bbˇ»b
 4186    "});
 4187    cx.update_editor(|e, window, cx| {
 4188        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4189    });
 4190    cx.assert_editor_state(indoc! {"
 4191        «aaa
 4192        bbb
 4193        added_lineˇ»
 4194    "});
 4195
 4196    // Removing line
 4197    cx.set_state(indoc! {"
 4198        aa«a
 4199        bbbˇ»
 4200    "});
 4201    cx.update_editor(|e, window, cx| {
 4202        e.manipulate_immutable_lines(window, cx, |lines| {
 4203            lines.pop();
 4204        })
 4205    });
 4206    cx.assert_editor_state(indoc! {"
 4207        «aaaˇ»
 4208    "});
 4209
 4210    // Removing all lines
 4211    cx.set_state(indoc! {"
 4212        aa«a
 4213        bbbˇ»
 4214    "});
 4215    cx.update_editor(|e, window, cx| {
 4216        e.manipulate_immutable_lines(window, cx, |lines| {
 4217            lines.drain(..);
 4218        })
 4219    });
 4220    cx.assert_editor_state(indoc! {"
 4221        ˇ
 4222    "});
 4223}
 4224
 4225#[gpui::test]
 4226async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4227    init_test(cx, |_| {});
 4228
 4229    let mut cx = EditorTestContext::new(cx).await;
 4230
 4231    // Consider continuous selection as single selection
 4232    cx.set_state(indoc! {"
 4233        Aaa«aa
 4234        cˇ»c«c
 4235        bb
 4236        aaaˇ»aa
 4237    "});
 4238    cx.update_editor(|e, window, cx| {
 4239        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4240    });
 4241    cx.assert_editor_state(indoc! {"
 4242        «Aaaaa
 4243        ccc
 4244        bb
 4245        aaaaaˇ»
 4246    "});
 4247
 4248    cx.set_state(indoc! {"
 4249        Aaa«aa
 4250        cˇ»c«c
 4251        bb
 4252        aaaˇ»aa
 4253    "});
 4254    cx.update_editor(|e, window, cx| {
 4255        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4256    });
 4257    cx.assert_editor_state(indoc! {"
 4258        «Aaaaa
 4259        ccc
 4260        bbˇ»
 4261    "});
 4262
 4263    // Consider non continuous selection as distinct dedup operations
 4264    cx.set_state(indoc! {"
 4265        «aaaaa
 4266        bb
 4267        aaaaa
 4268        aaaaaˇ»
 4269
 4270        aaa«aaˇ»
 4271    "});
 4272    cx.update_editor(|e, window, cx| {
 4273        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4274    });
 4275    cx.assert_editor_state(indoc! {"
 4276        «aaaaa
 4277        bbˇ»
 4278
 4279        «aaaaaˇ»
 4280    "});
 4281}
 4282
 4283#[gpui::test]
 4284async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4285    init_test(cx, |_| {});
 4286
 4287    let mut cx = EditorTestContext::new(cx).await;
 4288
 4289    cx.set_state(indoc! {"
 4290        «Aaa
 4291        aAa
 4292        Aaaˇ»
 4293    "});
 4294    cx.update_editor(|e, window, cx| {
 4295        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4296    });
 4297    cx.assert_editor_state(indoc! {"
 4298        «Aaa
 4299        aAaˇ»
 4300    "});
 4301
 4302    cx.set_state(indoc! {"
 4303        «Aaa
 4304        aAa
 4305        aaAˇ»
 4306    "});
 4307    cx.update_editor(|e, window, cx| {
 4308        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4309    });
 4310    cx.assert_editor_state(indoc! {"
 4311        «Aaaˇ»
 4312    "});
 4313}
 4314
 4315#[gpui::test]
 4316async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 4317    init_test(cx, |_| {});
 4318
 4319    let mut cx = EditorTestContext::new(cx).await;
 4320
 4321    // Manipulate with multiple selections on a single line
 4322    cx.set_state(indoc! {"
 4323        dd«dd
 4324        cˇ»c«c
 4325        bb
 4326        aaaˇ»aa
 4327    "});
 4328    cx.update_editor(|e, window, cx| {
 4329        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4330    });
 4331    cx.assert_editor_state(indoc! {"
 4332        «aaaaa
 4333        bb
 4334        ccc
 4335        ddddˇ»
 4336    "});
 4337
 4338    // Manipulate with multiple disjoin selections
 4339    cx.set_state(indoc! {"
 4340 4341        4
 4342        3
 4343        2
 4344        1ˇ»
 4345
 4346        dd«dd
 4347        ccc
 4348        bb
 4349        aaaˇ»aa
 4350    "});
 4351    cx.update_editor(|e, window, cx| {
 4352        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4353    });
 4354    cx.assert_editor_state(indoc! {"
 4355        «1
 4356        2
 4357        3
 4358        4
 4359        5ˇ»
 4360
 4361        «aaaaa
 4362        bb
 4363        ccc
 4364        ddddˇ»
 4365    "});
 4366
 4367    // Adding lines on each selection
 4368    cx.set_state(indoc! {"
 4369 4370        1ˇ»
 4371
 4372        bb«bb
 4373        aaaˇ»aa
 4374    "});
 4375    cx.update_editor(|e, window, cx| {
 4376        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 4377    });
 4378    cx.assert_editor_state(indoc! {"
 4379        «2
 4380        1
 4381        added lineˇ»
 4382
 4383        «bbbb
 4384        aaaaa
 4385        added lineˇ»
 4386    "});
 4387
 4388    // Removing lines on each selection
 4389    cx.set_state(indoc! {"
 4390 4391        1ˇ»
 4392
 4393        bb«bb
 4394        aaaˇ»aa
 4395    "});
 4396    cx.update_editor(|e, window, cx| {
 4397        e.manipulate_immutable_lines(window, cx, |lines| {
 4398            lines.pop();
 4399        })
 4400    });
 4401    cx.assert_editor_state(indoc! {"
 4402        «2ˇ»
 4403
 4404        «bbbbˇ»
 4405    "});
 4406}
 4407
 4408#[gpui::test]
 4409async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 4410    init_test(cx, |settings| {
 4411        settings.defaults.tab_size = NonZeroU32::new(3)
 4412    });
 4413
 4414    let mut cx = EditorTestContext::new(cx).await;
 4415
 4416    // MULTI SELECTION
 4417    // Ln.1 "«" tests empty lines
 4418    // Ln.9 tests just leading whitespace
 4419    cx.set_state(indoc! {"
 4420        «
 4421        abc                 // No indentationˇ»
 4422        «\tabc              // 1 tabˇ»
 4423        \t\tabc «      ˇ»   // 2 tabs
 4424        \t ab«c             // Tab followed by space
 4425         \tabc              // Space followed by tab (3 spaces should be the result)
 4426        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4427           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 4428        \t
 4429        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4430    "});
 4431    cx.update_editor(|e, window, cx| {
 4432        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4433    });
 4434    cx.assert_editor_state(
 4435        indoc! {"
 4436            «
 4437            abc                 // No indentation
 4438               abc              // 1 tab
 4439                  abc          // 2 tabs
 4440                abc             // Tab followed by space
 4441               abc              // Space followed by tab (3 spaces should be the result)
 4442                           abc   // Mixed indentation (tab conversion depends on the column)
 4443               abc         // Already space indented
 4444               ·
 4445               abc\tdef          // Only the leading tab is manipulatedˇ»
 4446        "}
 4447        .replace("·", "")
 4448        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4449    );
 4450
 4451    // Test on just a few lines, the others should remain unchanged
 4452    // Only lines (3, 5, 10, 11) should change
 4453    cx.set_state(
 4454        indoc! {"
 4455            ·
 4456            abc                 // No indentation
 4457            \tabcˇ               // 1 tab
 4458            \t\tabc             // 2 tabs
 4459            \t abcˇ              // Tab followed by space
 4460             \tabc              // Space followed by tab (3 spaces should be the result)
 4461            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4462               abc              // Already space indented
 4463            «\t
 4464            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4465        "}
 4466        .replace("·", "")
 4467        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4468    );
 4469    cx.update_editor(|e, window, cx| {
 4470        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4471    });
 4472    cx.assert_editor_state(
 4473        indoc! {"
 4474            ·
 4475            abc                 // No indentation
 4476            «   abc               // 1 tabˇ»
 4477            \t\tabc             // 2 tabs
 4478            «    abc              // Tab followed by spaceˇ»
 4479             \tabc              // Space followed by tab (3 spaces should be the result)
 4480            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4481               abc              // Already space indented
 4482            «   ·
 4483               abc\tdef          // Only the leading tab is manipulatedˇ»
 4484        "}
 4485        .replace("·", "")
 4486        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4487    );
 4488
 4489    // SINGLE SELECTION
 4490    // Ln.1 "«" tests empty lines
 4491    // Ln.9 tests just leading whitespace
 4492    cx.set_state(indoc! {"
 4493        «
 4494        abc                 // No indentation
 4495        \tabc               // 1 tab
 4496        \t\tabc             // 2 tabs
 4497        \t abc              // Tab followed by space
 4498         \tabc              // Space followed by tab (3 spaces should be the result)
 4499        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4500           abc              // Already space indented
 4501        \t
 4502        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4503    "});
 4504    cx.update_editor(|e, window, cx| {
 4505        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4506    });
 4507    cx.assert_editor_state(
 4508        indoc! {"
 4509            «
 4510            abc                 // No indentation
 4511               abc               // 1 tab
 4512                  abc             // 2 tabs
 4513                abc              // Tab followed by space
 4514               abc              // Space followed by tab (3 spaces should be the result)
 4515                           abc   // Mixed indentation (tab conversion depends on the column)
 4516               abc              // Already space indented
 4517               ·
 4518               abc\tdef          // Only the leading tab is manipulatedˇ»
 4519        "}
 4520        .replace("·", "")
 4521        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4522    );
 4523}
 4524
 4525#[gpui::test]
 4526async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 4527    init_test(cx, |settings| {
 4528        settings.defaults.tab_size = NonZeroU32::new(3)
 4529    });
 4530
 4531    let mut cx = EditorTestContext::new(cx).await;
 4532
 4533    // MULTI SELECTION
 4534    // Ln.1 "«" tests empty lines
 4535    // Ln.11 tests just leading whitespace
 4536    cx.set_state(indoc! {"
 4537        «
 4538        abˇ»ˇc                 // No indentation
 4539         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 4540          abc  «             // 2 spaces (< 3 so dont convert)
 4541           abc              // 3 spaces (convert)
 4542             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 4543        «\tˇ»\t«\tˇ»abc           // Already tab indented
 4544        «\t abc              // Tab followed by space
 4545         \tabc              // Space followed by tab (should be consumed due to tab)
 4546        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4547           \tˇ»  «\t
 4548           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 4549    "});
 4550    cx.update_editor(|e, window, cx| {
 4551        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4552    });
 4553    cx.assert_editor_state(indoc! {"
 4554        «
 4555        abc                 // No indentation
 4556         abc                // 1 space (< 3 so dont convert)
 4557          abc               // 2 spaces (< 3 so dont convert)
 4558        \tabc              // 3 spaces (convert)
 4559        \t  abc            // 5 spaces (1 tab + 2 spaces)
 4560        \t\t\tabc           // Already tab indented
 4561        \t abc              // Tab followed by space
 4562        \tabc              // Space followed by tab (should be consumed due to tab)
 4563        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4564        \t\t\t
 4565        \tabc   \t         // Only the leading spaces should be convertedˇ»
 4566    "});
 4567
 4568    // Test on just a few lines, the other should remain unchanged
 4569    // Only lines (4, 8, 11, 12) should change
 4570    cx.set_state(
 4571        indoc! {"
 4572            ·
 4573            abc                 // No indentation
 4574             abc                // 1 space (< 3 so dont convert)
 4575              abc               // 2 spaces (< 3 so dont convert)
 4576            «   abc              // 3 spaces (convert)ˇ»
 4577                 abc            // 5 spaces (1 tab + 2 spaces)
 4578            \t\t\tabc           // Already tab indented
 4579            \t abc              // Tab followed by space
 4580             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 4581               \t\t  \tabc      // Mixed indentation
 4582            \t \t  \t   \tabc   // Mixed indentation
 4583               \t  \tˇ
 4584            «   abc   \t         // Only the leading spaces should be convertedˇ»
 4585        "}
 4586        .replace("·", "")
 4587        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4588    );
 4589    cx.update_editor(|e, window, cx| {
 4590        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4591    });
 4592    cx.assert_editor_state(
 4593        indoc! {"
 4594            ·
 4595            abc                 // No indentation
 4596             abc                // 1 space (< 3 so dont convert)
 4597              abc               // 2 spaces (< 3 so dont convert)
 4598            «\tabc              // 3 spaces (convert)ˇ»
 4599                 abc            // 5 spaces (1 tab + 2 spaces)
 4600            \t\t\tabc           // Already tab indented
 4601            \t abc              // Tab followed by space
 4602            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 4603               \t\t  \tabc      // Mixed indentation
 4604            \t \t  \t   \tabc   // Mixed indentation
 4605            «\t\t\t
 4606            \tabc   \t         // Only the leading spaces should be convertedˇ»
 4607        "}
 4608        .replace("·", "")
 4609        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4610    );
 4611
 4612    // SINGLE SELECTION
 4613    // Ln.1 "«" tests empty lines
 4614    // Ln.11 tests just leading whitespace
 4615    cx.set_state(indoc! {"
 4616        «
 4617        abc                 // No indentation
 4618         abc                // 1 space (< 3 so dont convert)
 4619          abc               // 2 spaces (< 3 so dont convert)
 4620           abc              // 3 spaces (convert)
 4621             abc            // 5 spaces (1 tab + 2 spaces)
 4622        \t\t\tabc           // Already tab indented
 4623        \t abc              // Tab followed by space
 4624         \tabc              // Space followed by tab (should be consumed due to tab)
 4625        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4626           \t  \t
 4627           abc   \t         // Only the leading spaces should be convertedˇ»
 4628    "});
 4629    cx.update_editor(|e, window, cx| {
 4630        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4631    });
 4632    cx.assert_editor_state(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        \t  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\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4643        \t\t\t
 4644        \tabc   \t         // Only the leading spaces should be convertedˇ»
 4645    "});
 4646}
 4647
 4648#[gpui::test]
 4649async fn test_toggle_case(cx: &mut TestAppContext) {
 4650    init_test(cx, |_| {});
 4651
 4652    let mut cx = EditorTestContext::new(cx).await;
 4653
 4654    // If all lower case -> upper case
 4655    cx.set_state(indoc! {"
 4656        «hello worldˇ»
 4657    "});
 4658    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4659    cx.assert_editor_state(indoc! {"
 4660        «HELLO WORLDˇ»
 4661    "});
 4662
 4663    // If all upper case -> lower case
 4664    cx.set_state(indoc! {"
 4665        «HELLO WORLDˇ»
 4666    "});
 4667    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4668    cx.assert_editor_state(indoc! {"
 4669        «hello worldˇ»
 4670    "});
 4671
 4672    // If any upper case characters are identified -> lower case
 4673    // This matches JetBrains IDEs
 4674    cx.set_state(indoc! {"
 4675        «hEllo worldˇ»
 4676    "});
 4677    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4678    cx.assert_editor_state(indoc! {"
 4679        «hello worldˇ»
 4680    "});
 4681}
 4682
 4683#[gpui::test]
 4684async fn test_manipulate_text(cx: &mut TestAppContext) {
 4685    init_test(cx, |_| {});
 4686
 4687    let mut cx = EditorTestContext::new(cx).await;
 4688
 4689    // Test convert_to_upper_case()
 4690    cx.set_state(indoc! {"
 4691        «hello worldˇ»
 4692    "});
 4693    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4694    cx.assert_editor_state(indoc! {"
 4695        «HELLO WORLDˇ»
 4696    "});
 4697
 4698    // Test convert_to_lower_case()
 4699    cx.set_state(indoc! {"
 4700        «HELLO WORLDˇ»
 4701    "});
 4702    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 4703    cx.assert_editor_state(indoc! {"
 4704        «hello worldˇ»
 4705    "});
 4706
 4707    // Test multiple line, single selection case
 4708    cx.set_state(indoc! {"
 4709        «The quick brown
 4710        fox jumps over
 4711        the lazy dogˇ»
 4712    "});
 4713    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 4714    cx.assert_editor_state(indoc! {"
 4715        «The Quick Brown
 4716        Fox Jumps Over
 4717        The Lazy Dogˇ»
 4718    "});
 4719
 4720    // Test multiple line, single selection case
 4721    cx.set_state(indoc! {"
 4722        «The quick brown
 4723        fox jumps over
 4724        the lazy dogˇ»
 4725    "});
 4726    cx.update_editor(|e, window, cx| {
 4727        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 4728    });
 4729    cx.assert_editor_state(indoc! {"
 4730        «TheQuickBrown
 4731        FoxJumpsOver
 4732        TheLazyDogˇ»
 4733    "});
 4734
 4735    // From here on out, test more complex cases of manipulate_text()
 4736
 4737    // Test no selection case - should affect words cursors are in
 4738    // Cursor at beginning, middle, and end of word
 4739    cx.set_state(indoc! {"
 4740        ˇhello big beauˇtiful worldˇ
 4741    "});
 4742    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4743    cx.assert_editor_state(indoc! {"
 4744        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 4745    "});
 4746
 4747    // Test multiple selections on a single line and across multiple lines
 4748    cx.set_state(indoc! {"
 4749        «Theˇ» quick «brown
 4750        foxˇ» jumps «overˇ»
 4751        the «lazyˇ» dog
 4752    "});
 4753    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4754    cx.assert_editor_state(indoc! {"
 4755        «THEˇ» quick «BROWN
 4756        FOXˇ» jumps «OVERˇ»
 4757        the «LAZYˇ» dog
 4758    "});
 4759
 4760    // Test case where text length grows
 4761    cx.set_state(indoc! {"
 4762        «tschüߡ»
 4763    "});
 4764    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4765    cx.assert_editor_state(indoc! {"
 4766        «TSCHÜSSˇ»
 4767    "});
 4768
 4769    // Test to make sure we don't crash when text shrinks
 4770    cx.set_state(indoc! {"
 4771        aaa_bbbˇ
 4772    "});
 4773    cx.update_editor(|e, window, cx| {
 4774        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4775    });
 4776    cx.assert_editor_state(indoc! {"
 4777        «aaaBbbˇ»
 4778    "});
 4779
 4780    // Test to make sure we all aware of the fact that each word can grow and shrink
 4781    // Final selections should be aware of this fact
 4782    cx.set_state(indoc! {"
 4783        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 4784    "});
 4785    cx.update_editor(|e, window, cx| {
 4786        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4787    });
 4788    cx.assert_editor_state(indoc! {"
 4789        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 4790    "});
 4791
 4792    cx.set_state(indoc! {"
 4793        «hElLo, WoRld!ˇ»
 4794    "});
 4795    cx.update_editor(|e, window, cx| {
 4796        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 4797    });
 4798    cx.assert_editor_state(indoc! {"
 4799        «HeLlO, wOrLD!ˇ»
 4800    "});
 4801}
 4802
 4803#[gpui::test]
 4804fn test_duplicate_line(cx: &mut TestAppContext) {
 4805    init_test(cx, |_| {});
 4806
 4807    let editor = cx.add_window(|window, cx| {
 4808        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4809        build_editor(buffer, window, cx)
 4810    });
 4811    _ = editor.update(cx, |editor, window, cx| {
 4812        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4813            s.select_display_ranges([
 4814                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4815                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4816                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4817                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4818            ])
 4819        });
 4820        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4821        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4822        assert_eq!(
 4823            editor.selections.display_ranges(cx),
 4824            vec![
 4825                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4826                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 4827                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4828                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4829            ]
 4830        );
 4831    });
 4832
 4833    let editor = cx.add_window(|window, cx| {
 4834        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4835        build_editor(buffer, window, cx)
 4836    });
 4837    _ = editor.update(cx, |editor, window, cx| {
 4838        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4839            s.select_display_ranges([
 4840                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4841                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4842            ])
 4843        });
 4844        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4845        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4846        assert_eq!(
 4847            editor.selections.display_ranges(cx),
 4848            vec![
 4849                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 4850                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 4851            ]
 4852        );
 4853    });
 4854
 4855    // With `move_upwards` the selections stay in place, except for
 4856    // the lines inserted above them
 4857    let editor = cx.add_window(|window, cx| {
 4858        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4859        build_editor(buffer, window, cx)
 4860    });
 4861    _ = editor.update(cx, |editor, window, cx| {
 4862        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4863            s.select_display_ranges([
 4864                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4865                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4866                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4867                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4868            ])
 4869        });
 4870        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4871        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4872        assert_eq!(
 4873            editor.selections.display_ranges(cx),
 4874            vec![
 4875                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4876                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4877                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 4878                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4879            ]
 4880        );
 4881    });
 4882
 4883    let editor = cx.add_window(|window, cx| {
 4884        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4885        build_editor(buffer, window, cx)
 4886    });
 4887    _ = editor.update(cx, |editor, window, cx| {
 4888        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4889            s.select_display_ranges([
 4890                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4891                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4892            ])
 4893        });
 4894        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4895        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4896        assert_eq!(
 4897            editor.selections.display_ranges(cx),
 4898            vec![
 4899                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4900                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4901            ]
 4902        );
 4903    });
 4904
 4905    let editor = cx.add_window(|window, cx| {
 4906        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4907        build_editor(buffer, window, cx)
 4908    });
 4909    _ = editor.update(cx, |editor, window, cx| {
 4910        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4911            s.select_display_ranges([
 4912                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4913                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4914            ])
 4915        });
 4916        editor.duplicate_selection(&DuplicateSelection, window, cx);
 4917        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 4918        assert_eq!(
 4919            editor.selections.display_ranges(cx),
 4920            vec![
 4921                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4922                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 4923            ]
 4924        );
 4925    });
 4926}
 4927
 4928#[gpui::test]
 4929fn test_move_line_up_down(cx: &mut TestAppContext) {
 4930    init_test(cx, |_| {});
 4931
 4932    let editor = cx.add_window(|window, cx| {
 4933        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 4934        build_editor(buffer, window, cx)
 4935    });
 4936    _ = editor.update(cx, |editor, window, cx| {
 4937        editor.fold_creases(
 4938            vec![
 4939                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 4940                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 4941                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 4942            ],
 4943            true,
 4944            window,
 4945            cx,
 4946        );
 4947        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4948            s.select_display_ranges([
 4949                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4950                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4951                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4952                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 4953            ])
 4954        });
 4955        assert_eq!(
 4956            editor.display_text(cx),
 4957            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 4958        );
 4959
 4960        editor.move_line_up(&MoveLineUp, window, cx);
 4961        assert_eq!(
 4962            editor.display_text(cx),
 4963            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 4964        );
 4965        assert_eq!(
 4966            editor.selections.display_ranges(cx),
 4967            vec![
 4968                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4969                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4970                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 4971                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 4972            ]
 4973        );
 4974    });
 4975
 4976    _ = editor.update(cx, |editor, window, cx| {
 4977        editor.move_line_down(&MoveLineDown, window, cx);
 4978        assert_eq!(
 4979            editor.display_text(cx),
 4980            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 4981        );
 4982        assert_eq!(
 4983            editor.selections.display_ranges(cx),
 4984            vec![
 4985                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4986                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4987                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4988                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 4989            ]
 4990        );
 4991    });
 4992
 4993    _ = editor.update(cx, |editor, window, cx| {
 4994        editor.move_line_down(&MoveLineDown, window, cx);
 4995        assert_eq!(
 4996            editor.display_text(cx),
 4997            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 4998        );
 4999        assert_eq!(
 5000            editor.selections.display_ranges(cx),
 5001            vec![
 5002                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5003                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5004                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5005                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5006            ]
 5007        );
 5008    });
 5009
 5010    _ = editor.update(cx, |editor, window, cx| {
 5011        editor.move_line_up(&MoveLineUp, window, cx);
 5012        assert_eq!(
 5013            editor.display_text(cx),
 5014            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 5015        );
 5016        assert_eq!(
 5017            editor.selections.display_ranges(cx),
 5018            vec![
 5019                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5020                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5021                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5022                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5023            ]
 5024        );
 5025    });
 5026}
 5027
 5028#[gpui::test]
 5029fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 5030    init_test(cx, |_| {});
 5031
 5032    let editor = cx.add_window(|window, cx| {
 5033        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5034        build_editor(buffer, window, cx)
 5035    });
 5036    _ = editor.update(cx, |editor, window, cx| {
 5037        let snapshot = editor.buffer.read(cx).snapshot(cx);
 5038        editor.insert_blocks(
 5039            [BlockProperties {
 5040                style: BlockStyle::Fixed,
 5041                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 5042                height: Some(1),
 5043                render: Arc::new(|_| div().into_any()),
 5044                priority: 0,
 5045                render_in_minimap: true,
 5046            }],
 5047            Some(Autoscroll::fit()),
 5048            cx,
 5049        );
 5050        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5051            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 5052        });
 5053        editor.move_line_down(&MoveLineDown, window, cx);
 5054    });
 5055}
 5056
 5057#[gpui::test]
 5058async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 5059    init_test(cx, |_| {});
 5060
 5061    let mut cx = EditorTestContext::new(cx).await;
 5062    cx.set_state(
 5063        &"
 5064            ˇzero
 5065            one
 5066            two
 5067            three
 5068            four
 5069            five
 5070        "
 5071        .unindent(),
 5072    );
 5073
 5074    // Create a four-line block that replaces three lines of text.
 5075    cx.update_editor(|editor, window, cx| {
 5076        let snapshot = editor.snapshot(window, cx);
 5077        let snapshot = &snapshot.buffer_snapshot;
 5078        let placement = BlockPlacement::Replace(
 5079            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 5080        );
 5081        editor.insert_blocks(
 5082            [BlockProperties {
 5083                placement,
 5084                height: Some(4),
 5085                style: BlockStyle::Sticky,
 5086                render: Arc::new(|_| gpui::div().into_any_element()),
 5087                priority: 0,
 5088                render_in_minimap: true,
 5089            }],
 5090            None,
 5091            cx,
 5092        );
 5093    });
 5094
 5095    // Move down so that the cursor touches the block.
 5096    cx.update_editor(|editor, window, cx| {
 5097        editor.move_down(&Default::default(), window, cx);
 5098    });
 5099    cx.assert_editor_state(
 5100        &"
 5101            zero
 5102            «one
 5103            two
 5104            threeˇ»
 5105            four
 5106            five
 5107        "
 5108        .unindent(),
 5109    );
 5110
 5111    // Move down past the block.
 5112    cx.update_editor(|editor, window, cx| {
 5113        editor.move_down(&Default::default(), window, cx);
 5114    });
 5115    cx.assert_editor_state(
 5116        &"
 5117            zero
 5118            one
 5119            two
 5120            three
 5121            ˇfour
 5122            five
 5123        "
 5124        .unindent(),
 5125    );
 5126}
 5127
 5128#[gpui::test]
 5129fn test_transpose(cx: &mut TestAppContext) {
 5130    init_test(cx, |_| {});
 5131
 5132    _ = cx.add_window(|window, cx| {
 5133        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 5134        editor.set_style(EditorStyle::default(), window, cx);
 5135        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5136            s.select_ranges([1..1])
 5137        });
 5138        editor.transpose(&Default::default(), window, cx);
 5139        assert_eq!(editor.text(cx), "bac");
 5140        assert_eq!(editor.selections.ranges(cx), [2..2]);
 5141
 5142        editor.transpose(&Default::default(), window, cx);
 5143        assert_eq!(editor.text(cx), "bca");
 5144        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5145
 5146        editor.transpose(&Default::default(), window, cx);
 5147        assert_eq!(editor.text(cx), "bac");
 5148        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5149
 5150        editor
 5151    });
 5152
 5153    _ = cx.add_window(|window, cx| {
 5154        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5155        editor.set_style(EditorStyle::default(), window, cx);
 5156        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5157            s.select_ranges([3..3])
 5158        });
 5159        editor.transpose(&Default::default(), window, cx);
 5160        assert_eq!(editor.text(cx), "acb\nde");
 5161        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5162
 5163        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5164            s.select_ranges([4..4])
 5165        });
 5166        editor.transpose(&Default::default(), window, cx);
 5167        assert_eq!(editor.text(cx), "acbd\ne");
 5168        assert_eq!(editor.selections.ranges(cx), [5..5]);
 5169
 5170        editor.transpose(&Default::default(), window, cx);
 5171        assert_eq!(editor.text(cx), "acbde\n");
 5172        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5173
 5174        editor.transpose(&Default::default(), window, cx);
 5175        assert_eq!(editor.text(cx), "acbd\ne");
 5176        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5177
 5178        editor
 5179    });
 5180
 5181    _ = cx.add_window(|window, cx| {
 5182        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5183        editor.set_style(EditorStyle::default(), window, cx);
 5184        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5185            s.select_ranges([1..1, 2..2, 4..4])
 5186        });
 5187        editor.transpose(&Default::default(), window, cx);
 5188        assert_eq!(editor.text(cx), "bacd\ne");
 5189        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 5190
 5191        editor.transpose(&Default::default(), window, cx);
 5192        assert_eq!(editor.text(cx), "bcade\n");
 5193        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 5194
 5195        editor.transpose(&Default::default(), window, cx);
 5196        assert_eq!(editor.text(cx), "bcda\ne");
 5197        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5198
 5199        editor.transpose(&Default::default(), window, cx);
 5200        assert_eq!(editor.text(cx), "bcade\n");
 5201        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5202
 5203        editor.transpose(&Default::default(), window, cx);
 5204        assert_eq!(editor.text(cx), "bcaed\n");
 5205        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 5206
 5207        editor
 5208    });
 5209
 5210    _ = cx.add_window(|window, cx| {
 5211        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 5212        editor.set_style(EditorStyle::default(), window, cx);
 5213        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5214            s.select_ranges([4..4])
 5215        });
 5216        editor.transpose(&Default::default(), window, cx);
 5217        assert_eq!(editor.text(cx), "🏀🍐✋");
 5218        assert_eq!(editor.selections.ranges(cx), [8..8]);
 5219
 5220        editor.transpose(&Default::default(), window, cx);
 5221        assert_eq!(editor.text(cx), "🏀✋🍐");
 5222        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5223
 5224        editor.transpose(&Default::default(), window, cx);
 5225        assert_eq!(editor.text(cx), "🏀🍐✋");
 5226        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5227
 5228        editor
 5229    });
 5230}
 5231
 5232#[gpui::test]
 5233async fn test_rewrap(cx: &mut TestAppContext) {
 5234    init_test(cx, |settings| {
 5235        settings.languages.0.extend([
 5236            (
 5237                "Markdown".into(),
 5238                LanguageSettingsContent {
 5239                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5240                    preferred_line_length: Some(40),
 5241                    ..Default::default()
 5242                },
 5243            ),
 5244            (
 5245                "Plain Text".into(),
 5246                LanguageSettingsContent {
 5247                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5248                    preferred_line_length: Some(40),
 5249                    ..Default::default()
 5250                },
 5251            ),
 5252            (
 5253                "C++".into(),
 5254                LanguageSettingsContent {
 5255                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5256                    preferred_line_length: Some(40),
 5257                    ..Default::default()
 5258                },
 5259            ),
 5260            (
 5261                "Python".into(),
 5262                LanguageSettingsContent {
 5263                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5264                    preferred_line_length: Some(40),
 5265                    ..Default::default()
 5266                },
 5267            ),
 5268            (
 5269                "Rust".into(),
 5270                LanguageSettingsContent {
 5271                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5272                    preferred_line_length: Some(40),
 5273                    ..Default::default()
 5274                },
 5275            ),
 5276        ])
 5277    });
 5278
 5279    let mut cx = EditorTestContext::new(cx).await;
 5280
 5281    let cpp_language = Arc::new(Language::new(
 5282        LanguageConfig {
 5283            name: "C++".into(),
 5284            line_comments: vec!["// ".into()],
 5285            ..LanguageConfig::default()
 5286        },
 5287        None,
 5288    ));
 5289    let python_language = Arc::new(Language::new(
 5290        LanguageConfig {
 5291            name: "Python".into(),
 5292            line_comments: vec!["# ".into()],
 5293            ..LanguageConfig::default()
 5294        },
 5295        None,
 5296    ));
 5297    let markdown_language = Arc::new(Language::new(
 5298        LanguageConfig {
 5299            name: "Markdown".into(),
 5300            rewrap_prefixes: vec![
 5301                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 5302                regex::Regex::new("[-*+]\\s+").unwrap(),
 5303            ],
 5304            ..LanguageConfig::default()
 5305        },
 5306        None,
 5307    ));
 5308    let rust_language = Arc::new(Language::new(
 5309        LanguageConfig {
 5310            name: "Rust".into(),
 5311            line_comments: vec!["// ".into(), "/// ".into()],
 5312            ..LanguageConfig::default()
 5313        },
 5314        Some(tree_sitter_rust::LANGUAGE.into()),
 5315    ));
 5316
 5317    let plaintext_language = Arc::new(Language::new(
 5318        LanguageConfig {
 5319            name: "Plain Text".into(),
 5320            ..LanguageConfig::default()
 5321        },
 5322        None,
 5323    ));
 5324
 5325    // Test basic rewrapping of a long line with a cursor
 5326    assert_rewrap(
 5327        indoc! {"
 5328            // ˇThis is a long comment that needs to be wrapped.
 5329        "},
 5330        indoc! {"
 5331            // ˇThis is a long comment that needs to
 5332            // be wrapped.
 5333        "},
 5334        cpp_language.clone(),
 5335        &mut cx,
 5336    );
 5337
 5338    // Test rewrapping a full selection
 5339    assert_rewrap(
 5340        indoc! {"
 5341            «// This selected long comment needs to be wrapped.ˇ»"
 5342        },
 5343        indoc! {"
 5344            «// This selected long comment needs to
 5345            // be wrapped.ˇ»"
 5346        },
 5347        cpp_language.clone(),
 5348        &mut cx,
 5349    );
 5350
 5351    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 5352    assert_rewrap(
 5353        indoc! {"
 5354            // ˇThis is the first line.
 5355            // Thisˇ is the second line.
 5356            // This is the thirdˇ line, all part of one paragraph.
 5357         "},
 5358        indoc! {"
 5359            // ˇThis is the first line. Thisˇ is the
 5360            // second line. This is the thirdˇ line,
 5361            // all part of one paragraph.
 5362         "},
 5363        cpp_language.clone(),
 5364        &mut cx,
 5365    );
 5366
 5367    // Test multiple cursors in different paragraphs trigger separate rewraps
 5368    assert_rewrap(
 5369        indoc! {"
 5370            // ˇThis is the first paragraph, first line.
 5371            // ˇThis is the first paragraph, second line.
 5372
 5373            // ˇThis is the second paragraph, first line.
 5374            // ˇThis is the second paragraph, second line.
 5375        "},
 5376        indoc! {"
 5377            // ˇThis is the first paragraph, first
 5378            // line. ˇThis is the first paragraph,
 5379            // second line.
 5380
 5381            // ˇThis is the second paragraph, first
 5382            // line. ˇThis is the second paragraph,
 5383            // second line.
 5384        "},
 5385        cpp_language.clone(),
 5386        &mut cx,
 5387    );
 5388
 5389    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 5390    assert_rewrap(
 5391        indoc! {"
 5392            «// A regular long long comment to be wrapped.
 5393            /// A documentation long comment to be wrapped.ˇ»
 5394          "},
 5395        indoc! {"
 5396            «// A regular long long comment to be
 5397            // wrapped.
 5398            /// A documentation long comment to be
 5399            /// wrapped.ˇ»
 5400          "},
 5401        rust_language.clone(),
 5402        &mut cx,
 5403    );
 5404
 5405    // Test that change in indentation level trigger seperate rewraps
 5406    assert_rewrap(
 5407        indoc! {"
 5408            fn foo() {
 5409                «// This is a long comment at the base indent.
 5410                    // This is a long comment at the next indent.ˇ»
 5411            }
 5412        "},
 5413        indoc! {"
 5414            fn foo() {
 5415                «// This is a long comment at the
 5416                // base indent.
 5417                    // This is a long comment at the
 5418                    // next indent.ˇ»
 5419            }
 5420        "},
 5421        rust_language.clone(),
 5422        &mut cx,
 5423    );
 5424
 5425    // Test that different comment prefix characters (e.g., '#') are handled correctly
 5426    assert_rewrap(
 5427        indoc! {"
 5428            # ˇThis is a long comment using a pound sign.
 5429        "},
 5430        indoc! {"
 5431            # ˇThis is a long comment using a pound
 5432            # sign.
 5433        "},
 5434        python_language.clone(),
 5435        &mut cx,
 5436    );
 5437
 5438    // Test rewrapping only affects comments, not code even when selected
 5439    assert_rewrap(
 5440        indoc! {"
 5441            «/// This doc comment is long and should be wrapped.
 5442            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 5443        "},
 5444        indoc! {"
 5445            «/// This doc comment is long and should
 5446            /// be wrapped.
 5447            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 5448        "},
 5449        rust_language.clone(),
 5450        &mut cx,
 5451    );
 5452
 5453    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 5454    assert_rewrap(
 5455        indoc! {"
 5456            # Header
 5457
 5458            A long long long line of markdown text to wrap.ˇ
 5459         "},
 5460        indoc! {"
 5461            # Header
 5462
 5463            A long long long line of markdown text
 5464            to wrap.ˇ
 5465         "},
 5466        markdown_language.clone(),
 5467        &mut cx,
 5468    );
 5469
 5470    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 5471    assert_rewrap(
 5472        indoc! {"
 5473            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 5474            2. This is a numbered list item that is very long and needs to be wrapped properly.
 5475            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 5476        "},
 5477        indoc! {"
 5478            «1. This is a numbered list item that is
 5479               very long and needs to be wrapped
 5480               properly.
 5481            2. This is a numbered list item that is
 5482               very long and needs to be wrapped
 5483               properly.
 5484            - This is an unordered list item that is
 5485              also very long and should not merge
 5486              with the numbered item.ˇ»
 5487        "},
 5488        markdown_language.clone(),
 5489        &mut cx,
 5490    );
 5491
 5492    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 5493    assert_rewrap(
 5494        indoc! {"
 5495            «1. This is a numbered list item that is
 5496            very long and needs to be wrapped
 5497            properly.
 5498            2. This is a numbered list item that is
 5499            very long and needs to be wrapped
 5500            properly.
 5501            - This is an unordered list item that is
 5502            also very long and should not merge with
 5503            the numbered item.ˇ»
 5504        "},
 5505        indoc! {"
 5506            «1. This is a numbered list item that is
 5507               very long and needs to be wrapped
 5508               properly.
 5509            2. This is a numbered list item that is
 5510               very long and needs to be wrapped
 5511               properly.
 5512            - This is an unordered list item that is
 5513              also very long and should not merge
 5514              with the numbered item.ˇ»
 5515        "},
 5516        markdown_language.clone(),
 5517        &mut cx,
 5518    );
 5519
 5520    // Test that rewrapping maintain indents even when they already exists.
 5521    assert_rewrap(
 5522        indoc! {"
 5523            «1. This is a numbered list
 5524               item that is very long and needs to be wrapped properly.
 5525            2. This is a numbered list
 5526               item that is very long and needs to be wrapped properly.
 5527            - This is an unordered list item that is also very long and
 5528              should not merge with the numbered item.ˇ»
 5529        "},
 5530        indoc! {"
 5531            «1. This is a numbered list item that is
 5532               very long and needs to be wrapped
 5533               properly.
 5534            2. This is a numbered list item that is
 5535               very long and needs to be wrapped
 5536               properly.
 5537            - This is an unordered list item that is
 5538              also very long and should not merge
 5539              with the numbered item.ˇ»
 5540        "},
 5541        markdown_language.clone(),
 5542        &mut cx,
 5543    );
 5544
 5545    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 5546    assert_rewrap(
 5547        indoc! {"
 5548            ˇThis is a very long line of plain text that will be wrapped.
 5549        "},
 5550        indoc! {"
 5551            ˇThis is a very long line of plain text
 5552            that will be wrapped.
 5553        "},
 5554        plaintext_language.clone(),
 5555        &mut cx,
 5556    );
 5557
 5558    // Test that non-commented code acts as a paragraph boundary within a selection
 5559    assert_rewrap(
 5560        indoc! {"
 5561               «// This is the first long comment block to be wrapped.
 5562               fn my_func(a: u32);
 5563               // This is the second long comment block to be wrapped.ˇ»
 5564           "},
 5565        indoc! {"
 5566               «// This is the first long comment block
 5567               // to be wrapped.
 5568               fn my_func(a: u32);
 5569               // This is the second long comment block
 5570               // to be wrapped.ˇ»
 5571           "},
 5572        rust_language.clone(),
 5573        &mut cx,
 5574    );
 5575
 5576    // Test rewrapping multiple selections, including ones with blank lines or tabs
 5577    assert_rewrap(
 5578        indoc! {"
 5579            «ˇThis is a very long line that will be wrapped.
 5580
 5581            This is another paragraph in the same selection.»
 5582
 5583            «\tThis is a very long indented line that will be wrapped.ˇ»
 5584         "},
 5585        indoc! {"
 5586            «ˇThis is a very long line that will be
 5587            wrapped.
 5588
 5589            This is another paragraph in the same
 5590            selection.»
 5591
 5592            «\tThis is a very long indented line
 5593            \tthat will be wrapped.ˇ»
 5594         "},
 5595        plaintext_language.clone(),
 5596        &mut cx,
 5597    );
 5598
 5599    // Test that an empty comment line acts as a paragraph boundary
 5600    assert_rewrap(
 5601        indoc! {"
 5602            // ˇThis is a long comment that will be wrapped.
 5603            //
 5604            // And this is another long comment that will also be wrapped.ˇ
 5605         "},
 5606        indoc! {"
 5607            // ˇThis is a long comment that will be
 5608            // wrapped.
 5609            //
 5610            // And this is another long comment that
 5611            // will also be wrapped.ˇ
 5612         "},
 5613        cpp_language,
 5614        &mut cx,
 5615    );
 5616
 5617    #[track_caller]
 5618    fn assert_rewrap(
 5619        unwrapped_text: &str,
 5620        wrapped_text: &str,
 5621        language: Arc<Language>,
 5622        cx: &mut EditorTestContext,
 5623    ) {
 5624        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 5625        cx.set_state(unwrapped_text);
 5626        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 5627        cx.assert_editor_state(wrapped_text);
 5628    }
 5629}
 5630
 5631#[gpui::test]
 5632async fn test_hard_wrap(cx: &mut TestAppContext) {
 5633    init_test(cx, |_| {});
 5634    let mut cx = EditorTestContext::new(cx).await;
 5635
 5636    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 5637    cx.update_editor(|editor, _, cx| {
 5638        editor.set_hard_wrap(Some(14), cx);
 5639    });
 5640
 5641    cx.set_state(indoc!(
 5642        "
 5643        one two three ˇ
 5644        "
 5645    ));
 5646    cx.simulate_input("four");
 5647    cx.run_until_parked();
 5648
 5649    cx.assert_editor_state(indoc!(
 5650        "
 5651        one two three
 5652        fourˇ
 5653        "
 5654    ));
 5655
 5656    cx.update_editor(|editor, window, cx| {
 5657        editor.newline(&Default::default(), window, cx);
 5658    });
 5659    cx.run_until_parked();
 5660    cx.assert_editor_state(indoc!(
 5661        "
 5662        one two three
 5663        four
 5664        ˇ
 5665        "
 5666    ));
 5667
 5668    cx.simulate_input("five");
 5669    cx.run_until_parked();
 5670    cx.assert_editor_state(indoc!(
 5671        "
 5672        one two three
 5673        four
 5674        fiveˇ
 5675        "
 5676    ));
 5677
 5678    cx.update_editor(|editor, window, cx| {
 5679        editor.newline(&Default::default(), window, cx);
 5680    });
 5681    cx.run_until_parked();
 5682    cx.simulate_input("# ");
 5683    cx.run_until_parked();
 5684    cx.assert_editor_state(indoc!(
 5685        "
 5686        one two three
 5687        four
 5688        five
 5689        # ˇ
 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        five
 5702        #\x20
 5703 5704        "
 5705    ));
 5706
 5707    cx.simulate_input(" 6");
 5708    cx.run_until_parked();
 5709    cx.assert_editor_state(indoc!(
 5710        "
 5711        one two three
 5712        four
 5713        five
 5714        #
 5715        # 6ˇ
 5716        "
 5717    ));
 5718}
 5719
 5720#[gpui::test]
 5721async fn test_clipboard(cx: &mut TestAppContext) {
 5722    init_test(cx, |_| {});
 5723
 5724    let mut cx = EditorTestContext::new(cx).await;
 5725
 5726    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 5727    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5728    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 5729
 5730    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 5731    cx.set_state("two ˇfour ˇsix ˇ");
 5732    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5733    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 5734
 5735    // Paste again but with only two cursors. Since the number of cursors doesn't
 5736    // match the number of slices in the clipboard, the entire clipboard text
 5737    // is pasted at each cursor.
 5738    cx.set_state("ˇtwo one✅ four three six five ˇ");
 5739    cx.update_editor(|e, window, cx| {
 5740        e.handle_input("( ", window, cx);
 5741        e.paste(&Paste, window, cx);
 5742        e.handle_input(") ", window, cx);
 5743    });
 5744    cx.assert_editor_state(
 5745        &([
 5746            "( one✅ ",
 5747            "three ",
 5748            "five ) ˇtwo one✅ four three six five ( one✅ ",
 5749            "three ",
 5750            "five ) ˇ",
 5751        ]
 5752        .join("\n")),
 5753    );
 5754
 5755    // Cut with three selections, one of which is full-line.
 5756    cx.set_state(indoc! {"
 5757        1«2ˇ»3
 5758        4ˇ567
 5759        «8ˇ»9"});
 5760    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5761    cx.assert_editor_state(indoc! {"
 5762        1ˇ3
 5763        ˇ9"});
 5764
 5765    // Paste with three selections, noticing how the copied selection that was full-line
 5766    // gets inserted before the second cursor.
 5767    cx.set_state(indoc! {"
 5768        1ˇ3
 5769 5770        «oˇ»ne"});
 5771    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5772    cx.assert_editor_state(indoc! {"
 5773        12ˇ3
 5774        4567
 5775 5776        8ˇne"});
 5777
 5778    // Copy with a single cursor only, which writes the whole line into the clipboard.
 5779    cx.set_state(indoc! {"
 5780        The quick brown
 5781        fox juˇmps over
 5782        the lazy dog"});
 5783    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5784    assert_eq!(
 5785        cx.read_from_clipboard()
 5786            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5787        Some("fox jumps over\n".to_string())
 5788    );
 5789
 5790    // Paste with three selections, noticing how the copied full-line selection is inserted
 5791    // before the empty selections but replaces the selection that is non-empty.
 5792    cx.set_state(indoc! {"
 5793        Tˇhe quick brown
 5794        «foˇ»x jumps over
 5795        tˇhe lazy dog"});
 5796    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5797    cx.assert_editor_state(indoc! {"
 5798        fox jumps over
 5799        Tˇhe quick brown
 5800        fox jumps over
 5801        ˇx jumps over
 5802        fox jumps over
 5803        tˇhe lazy dog"});
 5804}
 5805
 5806#[gpui::test]
 5807async fn test_copy_trim(cx: &mut TestAppContext) {
 5808    init_test(cx, |_| {});
 5809
 5810    let mut cx = EditorTestContext::new(cx).await;
 5811    cx.set_state(
 5812        r#"            «for selection in selections.iter() {
 5813            let mut start = selection.start;
 5814            let mut end = selection.end;
 5815            let is_entire_line = selection.is_empty();
 5816            if is_entire_line {
 5817                start = Point::new(start.row, 0);ˇ»
 5818                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5819            }
 5820        "#,
 5821    );
 5822    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5823    assert_eq!(
 5824        cx.read_from_clipboard()
 5825            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5826        Some(
 5827            "for selection in selections.iter() {
 5828            let mut start = selection.start;
 5829            let mut end = selection.end;
 5830            let is_entire_line = selection.is_empty();
 5831            if is_entire_line {
 5832                start = Point::new(start.row, 0);"
 5833                .to_string()
 5834        ),
 5835        "Regular copying preserves all indentation selected",
 5836    );
 5837    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5838    assert_eq!(
 5839        cx.read_from_clipboard()
 5840            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5841        Some(
 5842            "for selection in selections.iter() {
 5843let mut start = selection.start;
 5844let mut end = selection.end;
 5845let is_entire_line = selection.is_empty();
 5846if is_entire_line {
 5847    start = Point::new(start.row, 0);"
 5848                .to_string()
 5849        ),
 5850        "Copying with stripping should strip all leading whitespaces"
 5851    );
 5852
 5853    cx.set_state(
 5854        r#"       «     for selection in selections.iter() {
 5855            let mut start = selection.start;
 5856            let mut end = selection.end;
 5857            let is_entire_line = selection.is_empty();
 5858            if is_entire_line {
 5859                start = Point::new(start.row, 0);ˇ»
 5860                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5861            }
 5862        "#,
 5863    );
 5864    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5865    assert_eq!(
 5866        cx.read_from_clipboard()
 5867            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5868        Some(
 5869            "     for selection in selections.iter() {
 5870            let mut start = selection.start;
 5871            let mut end = selection.end;
 5872            let is_entire_line = selection.is_empty();
 5873            if is_entire_line {
 5874                start = Point::new(start.row, 0);"
 5875                .to_string()
 5876        ),
 5877        "Regular copying preserves all indentation selected",
 5878    );
 5879    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5880    assert_eq!(
 5881        cx.read_from_clipboard()
 5882            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5883        Some(
 5884            "for selection in selections.iter() {
 5885let mut start = selection.start;
 5886let mut end = selection.end;
 5887let is_entire_line = selection.is_empty();
 5888if is_entire_line {
 5889    start = Point::new(start.row, 0);"
 5890                .to_string()
 5891        ),
 5892        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 5893    );
 5894
 5895    cx.set_state(
 5896        r#"       «ˇ     for selection in selections.iter() {
 5897            let mut start = selection.start;
 5898            let mut end = selection.end;
 5899            let is_entire_line = selection.is_empty();
 5900            if is_entire_line {
 5901                start = Point::new(start.row, 0);»
 5902                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5903            }
 5904        "#,
 5905    );
 5906    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5907    assert_eq!(
 5908        cx.read_from_clipboard()
 5909            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5910        Some(
 5911            "     for selection in selections.iter() {
 5912            let mut start = selection.start;
 5913            let mut end = selection.end;
 5914            let is_entire_line = selection.is_empty();
 5915            if is_entire_line {
 5916                start = Point::new(start.row, 0);"
 5917                .to_string()
 5918        ),
 5919        "Regular copying for reverse selection works the same",
 5920    );
 5921    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5922    assert_eq!(
 5923        cx.read_from_clipboard()
 5924            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5925        Some(
 5926            "for selection in selections.iter() {
 5927let mut start = selection.start;
 5928let mut end = selection.end;
 5929let is_entire_line = selection.is_empty();
 5930if is_entire_line {
 5931    start = Point::new(start.row, 0);"
 5932                .to_string()
 5933        ),
 5934        "Copying with stripping for reverse selection works the same"
 5935    );
 5936
 5937    cx.set_state(
 5938        r#"            for selection «in selections.iter() {
 5939            let mut start = selection.start;
 5940            let mut end = selection.end;
 5941            let is_entire_line = selection.is_empty();
 5942            if is_entire_line {
 5943                start = Point::new(start.row, 0);ˇ»
 5944                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5945            }
 5946        "#,
 5947    );
 5948    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5949    assert_eq!(
 5950        cx.read_from_clipboard()
 5951            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5952        Some(
 5953            "in selections.iter() {
 5954            let mut start = selection.start;
 5955            let mut end = selection.end;
 5956            let is_entire_line = selection.is_empty();
 5957            if is_entire_line {
 5958                start = Point::new(start.row, 0);"
 5959                .to_string()
 5960        ),
 5961        "When selecting past the indent, the copying works as usual",
 5962    );
 5963    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5964    assert_eq!(
 5965        cx.read_from_clipboard()
 5966            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5967        Some(
 5968            "in selections.iter() {
 5969            let mut start = selection.start;
 5970            let mut end = selection.end;
 5971            let is_entire_line = selection.is_empty();
 5972            if is_entire_line {
 5973                start = Point::new(start.row, 0);"
 5974                .to_string()
 5975        ),
 5976        "When selecting past the indent, nothing is trimmed"
 5977    );
 5978
 5979    cx.set_state(
 5980        r#"            «for selection in selections.iter() {
 5981            let mut start = selection.start;
 5982
 5983            let mut end = selection.end;
 5984            let is_entire_line = selection.is_empty();
 5985            if is_entire_line {
 5986                start = Point::new(start.row, 0);
 5987ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5988            }
 5989        "#,
 5990    );
 5991    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5992    assert_eq!(
 5993        cx.read_from_clipboard()
 5994            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5995        Some(
 5996            "for selection in selections.iter() {
 5997let mut start = selection.start;
 5998
 5999let mut end = selection.end;
 6000let is_entire_line = selection.is_empty();
 6001if is_entire_line {
 6002    start = Point::new(start.row, 0);
 6003"
 6004            .to_string()
 6005        ),
 6006        "Copying with stripping should ignore empty lines"
 6007    );
 6008}
 6009
 6010#[gpui::test]
 6011async fn test_paste_multiline(cx: &mut TestAppContext) {
 6012    init_test(cx, |_| {});
 6013
 6014    let mut cx = EditorTestContext::new(cx).await;
 6015    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 6016
 6017    // Cut an indented block, without the leading whitespace.
 6018    cx.set_state(indoc! {"
 6019        const a: B = (
 6020            c(),
 6021            «d(
 6022                e,
 6023                f
 6024            )ˇ»
 6025        );
 6026    "});
 6027    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6028    cx.assert_editor_state(indoc! {"
 6029        const a: B = (
 6030            c(),
 6031            ˇ
 6032        );
 6033    "});
 6034
 6035    // Paste it at the same position.
 6036    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6037    cx.assert_editor_state(indoc! {"
 6038        const a: B = (
 6039            c(),
 6040            d(
 6041                e,
 6042                f
 6043 6044        );
 6045    "});
 6046
 6047    // Paste it at a line with a lower indent level.
 6048    cx.set_state(indoc! {"
 6049        ˇ
 6050        const a: B = (
 6051            c(),
 6052        );
 6053    "});
 6054    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6055    cx.assert_editor_state(indoc! {"
 6056        d(
 6057            e,
 6058            f
 6059 6060        const a: B = (
 6061            c(),
 6062        );
 6063    "});
 6064
 6065    // Cut an indented block, with the leading whitespace.
 6066    cx.set_state(indoc! {"
 6067        const a: B = (
 6068            c(),
 6069        «    d(
 6070                e,
 6071                f
 6072            )
 6073        ˇ»);
 6074    "});
 6075    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6076    cx.assert_editor_state(indoc! {"
 6077        const a: B = (
 6078            c(),
 6079        ˇ);
 6080    "});
 6081
 6082    // Paste it at the same position.
 6083    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6084    cx.assert_editor_state(indoc! {"
 6085        const a: B = (
 6086            c(),
 6087            d(
 6088                e,
 6089                f
 6090            )
 6091        ˇ);
 6092    "});
 6093
 6094    // Paste it at a line with a higher indent level.
 6095    cx.set_state(indoc! {"
 6096        const a: B = (
 6097            c(),
 6098            d(
 6099                e,
 6100 6101            )
 6102        );
 6103    "});
 6104    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6105    cx.assert_editor_state(indoc! {"
 6106        const a: B = (
 6107            c(),
 6108            d(
 6109                e,
 6110                f    d(
 6111                    e,
 6112                    f
 6113                )
 6114        ˇ
 6115            )
 6116        );
 6117    "});
 6118
 6119    // Copy an indented block, starting mid-line
 6120    cx.set_state(indoc! {"
 6121        const a: B = (
 6122            c(),
 6123            somethin«g(
 6124                e,
 6125                f
 6126            )ˇ»
 6127        );
 6128    "});
 6129    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6130
 6131    // Paste it on a line with a lower indent level
 6132    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 6133    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6134    cx.assert_editor_state(indoc! {"
 6135        const a: B = (
 6136            c(),
 6137            something(
 6138                e,
 6139                f
 6140            )
 6141        );
 6142        g(
 6143            e,
 6144            f
 6145"});
 6146}
 6147
 6148#[gpui::test]
 6149async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 6150    init_test(cx, |_| {});
 6151
 6152    cx.write_to_clipboard(ClipboardItem::new_string(
 6153        "    d(\n        e\n    );\n".into(),
 6154    ));
 6155
 6156    let mut cx = EditorTestContext::new(cx).await;
 6157    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 6158
 6159    cx.set_state(indoc! {"
 6160        fn a() {
 6161            b();
 6162            if c() {
 6163                ˇ
 6164            }
 6165        }
 6166    "});
 6167
 6168    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6169    cx.assert_editor_state(indoc! {"
 6170        fn a() {
 6171            b();
 6172            if c() {
 6173                d(
 6174                    e
 6175                );
 6176        ˇ
 6177            }
 6178        }
 6179    "});
 6180
 6181    cx.set_state(indoc! {"
 6182        fn a() {
 6183            b();
 6184            ˇ
 6185        }
 6186    "});
 6187
 6188    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6189    cx.assert_editor_state(indoc! {"
 6190        fn a() {
 6191            b();
 6192            d(
 6193                e
 6194            );
 6195        ˇ
 6196        }
 6197    "});
 6198}
 6199
 6200#[gpui::test]
 6201fn test_select_all(cx: &mut TestAppContext) {
 6202    init_test(cx, |_| {});
 6203
 6204    let editor = cx.add_window(|window, cx| {
 6205        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 6206        build_editor(buffer, window, cx)
 6207    });
 6208    _ = editor.update(cx, |editor, window, cx| {
 6209        editor.select_all(&SelectAll, window, cx);
 6210        assert_eq!(
 6211            editor.selections.display_ranges(cx),
 6212            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 6213        );
 6214    });
 6215}
 6216
 6217#[gpui::test]
 6218fn test_select_line(cx: &mut TestAppContext) {
 6219    init_test(cx, |_| {});
 6220
 6221    let editor = cx.add_window(|window, cx| {
 6222        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 6223        build_editor(buffer, window, cx)
 6224    });
 6225    _ = editor.update(cx, |editor, window, cx| {
 6226        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6227            s.select_display_ranges([
 6228                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6229                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6230                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6231                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 6232            ])
 6233        });
 6234        editor.select_line(&SelectLine, window, cx);
 6235        assert_eq!(
 6236            editor.selections.display_ranges(cx),
 6237            vec![
 6238                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 6239                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 6240            ]
 6241        );
 6242    });
 6243
 6244    _ = editor.update(cx, |editor, window, cx| {
 6245        editor.select_line(&SelectLine, window, cx);
 6246        assert_eq!(
 6247            editor.selections.display_ranges(cx),
 6248            vec![
 6249                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 6250                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 6251            ]
 6252        );
 6253    });
 6254
 6255    _ = editor.update(cx, |editor, window, cx| {
 6256        editor.select_line(&SelectLine, window, cx);
 6257        assert_eq!(
 6258            editor.selections.display_ranges(cx),
 6259            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 6260        );
 6261    });
 6262}
 6263
 6264#[gpui::test]
 6265async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 6266    init_test(cx, |_| {});
 6267    let mut cx = EditorTestContext::new(cx).await;
 6268
 6269    #[track_caller]
 6270    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 6271        cx.set_state(initial_state);
 6272        cx.update_editor(|e, window, cx| {
 6273            e.split_selection_into_lines(&SplitSelectionIntoLines, window, cx)
 6274        });
 6275        cx.assert_editor_state(expected_state);
 6276    }
 6277
 6278    // Selection starts and ends at the middle of lines, left-to-right
 6279    test(
 6280        &mut cx,
 6281        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 6282        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 6283    );
 6284    // Same thing, right-to-left
 6285    test(
 6286        &mut cx,
 6287        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 6288        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 6289    );
 6290
 6291    // Whole buffer, left-to-right, last line *doesn't* end with newline
 6292    test(
 6293        &mut cx,
 6294        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 6295        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 6296    );
 6297    // Same thing, right-to-left
 6298    test(
 6299        &mut cx,
 6300        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 6301        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 6302    );
 6303
 6304    // Whole buffer, left-to-right, last line ends with newline
 6305    test(
 6306        &mut cx,
 6307        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 6308        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 6309    );
 6310    // Same thing, right-to-left
 6311    test(
 6312        &mut cx,
 6313        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 6314        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 6315    );
 6316
 6317    // Starts at the end of a line, ends at the start of another
 6318    test(
 6319        &mut cx,
 6320        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 6321        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 6322    );
 6323}
 6324
 6325#[gpui::test]
 6326async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 6327    init_test(cx, |_| {});
 6328
 6329    let editor = cx.add_window(|window, cx| {
 6330        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 6331        build_editor(buffer, window, cx)
 6332    });
 6333
 6334    // setup
 6335    _ = editor.update(cx, |editor, window, cx| {
 6336        editor.fold_creases(
 6337            vec![
 6338                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 6339                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 6340                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 6341            ],
 6342            true,
 6343            window,
 6344            cx,
 6345        );
 6346        assert_eq!(
 6347            editor.display_text(cx),
 6348            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 6349        );
 6350    });
 6351
 6352    _ = editor.update(cx, |editor, window, cx| {
 6353        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6354            s.select_display_ranges([
 6355                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6356                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6357                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6358                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 6359            ])
 6360        });
 6361        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 6362        assert_eq!(
 6363            editor.display_text(cx),
 6364            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 6365        );
 6366    });
 6367    EditorTestContext::for_editor(editor, cx)
 6368        .await
 6369        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 6370
 6371    _ = editor.update(cx, |editor, window, cx| {
 6372        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6373            s.select_display_ranges([
 6374                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 6375            ])
 6376        });
 6377        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 6378        assert_eq!(
 6379            editor.display_text(cx),
 6380            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 6381        );
 6382        assert_eq!(
 6383            editor.selections.display_ranges(cx),
 6384            [
 6385                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 6386                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 6387                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 6388                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 6389                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 6390                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 6391                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 6392            ]
 6393        );
 6394    });
 6395    EditorTestContext::for_editor(editor, cx)
 6396        .await
 6397        .assert_editor_state(
 6398            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 6399        );
 6400}
 6401
 6402#[gpui::test]
 6403async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 6404    init_test(cx, |_| {});
 6405
 6406    let mut cx = EditorTestContext::new(cx).await;
 6407
 6408    cx.set_state(indoc!(
 6409        r#"abc
 6410           defˇghi
 6411
 6412           jk
 6413           nlmo
 6414           "#
 6415    ));
 6416
 6417    cx.update_editor(|editor, window, cx| {
 6418        editor.add_selection_above(&Default::default(), window, cx);
 6419    });
 6420
 6421    cx.assert_editor_state(indoc!(
 6422        r#"abcˇ
 6423           defˇghi
 6424
 6425           jk
 6426           nlmo
 6427           "#
 6428    ));
 6429
 6430    cx.update_editor(|editor, window, cx| {
 6431        editor.add_selection_above(&Default::default(), window, cx);
 6432    });
 6433
 6434    cx.assert_editor_state(indoc!(
 6435        r#"abcˇ
 6436            defˇghi
 6437
 6438            jk
 6439            nlmo
 6440            "#
 6441    ));
 6442
 6443    cx.update_editor(|editor, window, cx| {
 6444        editor.add_selection_below(&Default::default(), window, cx);
 6445    });
 6446
 6447    cx.assert_editor_state(indoc!(
 6448        r#"abc
 6449           defˇghi
 6450
 6451           jk
 6452           nlmo
 6453           "#
 6454    ));
 6455
 6456    cx.update_editor(|editor, window, cx| {
 6457        editor.undo_selection(&Default::default(), window, cx);
 6458    });
 6459
 6460    cx.assert_editor_state(indoc!(
 6461        r#"abcˇ
 6462           defˇghi
 6463
 6464           jk
 6465           nlmo
 6466           "#
 6467    ));
 6468
 6469    cx.update_editor(|editor, window, cx| {
 6470        editor.redo_selection(&Default::default(), window, cx);
 6471    });
 6472
 6473    cx.assert_editor_state(indoc!(
 6474        r#"abc
 6475           defˇghi
 6476
 6477           jk
 6478           nlmo
 6479           "#
 6480    ));
 6481
 6482    cx.update_editor(|editor, window, cx| {
 6483        editor.add_selection_below(&Default::default(), window, cx);
 6484    });
 6485
 6486    cx.assert_editor_state(indoc!(
 6487        r#"abc
 6488           defˇghi
 6489           ˇ
 6490           jk
 6491           nlmo
 6492           "#
 6493    ));
 6494
 6495    cx.update_editor(|editor, window, cx| {
 6496        editor.add_selection_below(&Default::default(), window, cx);
 6497    });
 6498
 6499    cx.assert_editor_state(indoc!(
 6500        r#"abc
 6501           defˇghi
 6502           ˇ
 6503           jkˇ
 6504           nlmo
 6505           "#
 6506    ));
 6507
 6508    cx.update_editor(|editor, window, cx| {
 6509        editor.add_selection_below(&Default::default(), window, cx);
 6510    });
 6511
 6512    cx.assert_editor_state(indoc!(
 6513        r#"abc
 6514           defˇghi
 6515           ˇ
 6516           jkˇ
 6517           nlmˇo
 6518           "#
 6519    ));
 6520
 6521    cx.update_editor(|editor, window, cx| {
 6522        editor.add_selection_below(&Default::default(), window, cx);
 6523    });
 6524
 6525    cx.assert_editor_state(indoc!(
 6526        r#"abc
 6527           defˇghi
 6528           ˇ
 6529           jkˇ
 6530           nlmˇo
 6531           ˇ"#
 6532    ));
 6533
 6534    // change selections
 6535    cx.set_state(indoc!(
 6536        r#"abc
 6537           def«ˇg»hi
 6538
 6539           jk
 6540           nlmo
 6541           "#
 6542    ));
 6543
 6544    cx.update_editor(|editor, window, cx| {
 6545        editor.add_selection_below(&Default::default(), window, cx);
 6546    });
 6547
 6548    cx.assert_editor_state(indoc!(
 6549        r#"abc
 6550           def«ˇg»hi
 6551
 6552           jk
 6553           nlm«ˇo»
 6554           "#
 6555    ));
 6556
 6557    cx.update_editor(|editor, window, cx| {
 6558        editor.add_selection_below(&Default::default(), window, cx);
 6559    });
 6560
 6561    cx.assert_editor_state(indoc!(
 6562        r#"abc
 6563           def«ˇg»hi
 6564
 6565           jk
 6566           nlm«ˇo»
 6567           "#
 6568    ));
 6569
 6570    cx.update_editor(|editor, window, cx| {
 6571        editor.add_selection_above(&Default::default(), window, cx);
 6572    });
 6573
 6574    cx.assert_editor_state(indoc!(
 6575        r#"abc
 6576           def«ˇg»hi
 6577
 6578           jk
 6579           nlmo
 6580           "#
 6581    ));
 6582
 6583    cx.update_editor(|editor, window, cx| {
 6584        editor.add_selection_above(&Default::default(), window, cx);
 6585    });
 6586
 6587    cx.assert_editor_state(indoc!(
 6588        r#"abc
 6589           def«ˇg»hi
 6590
 6591           jk
 6592           nlmo
 6593           "#
 6594    ));
 6595
 6596    // Change selections again
 6597    cx.set_state(indoc!(
 6598        r#"a«bc
 6599           defgˇ»hi
 6600
 6601           jk
 6602           nlmo
 6603           "#
 6604    ));
 6605
 6606    cx.update_editor(|editor, window, cx| {
 6607        editor.add_selection_below(&Default::default(), window, cx);
 6608    });
 6609
 6610    cx.assert_editor_state(indoc!(
 6611        r#"a«bcˇ»
 6612           d«efgˇ»hi
 6613
 6614           j«kˇ»
 6615           nlmo
 6616           "#
 6617    ));
 6618
 6619    cx.update_editor(|editor, window, cx| {
 6620        editor.add_selection_below(&Default::default(), window, cx);
 6621    });
 6622    cx.assert_editor_state(indoc!(
 6623        r#"a«bcˇ»
 6624           d«efgˇ»hi
 6625
 6626           j«kˇ»
 6627           n«lmoˇ»
 6628           "#
 6629    ));
 6630    cx.update_editor(|editor, window, cx| {
 6631        editor.add_selection_above(&Default::default(), window, cx);
 6632    });
 6633
 6634    cx.assert_editor_state(indoc!(
 6635        r#"a«bcˇ»
 6636           d«efgˇ»hi
 6637
 6638           j«kˇ»
 6639           nlmo
 6640           "#
 6641    ));
 6642
 6643    // Change selections again
 6644    cx.set_state(indoc!(
 6645        r#"abc
 6646           d«ˇefghi
 6647
 6648           jk
 6649           nlm»o
 6650           "#
 6651    ));
 6652
 6653    cx.update_editor(|editor, window, cx| {
 6654        editor.add_selection_above(&Default::default(), window, cx);
 6655    });
 6656
 6657    cx.assert_editor_state(indoc!(
 6658        r#"a«ˇbc»
 6659           d«ˇef»ghi
 6660
 6661           j«ˇk»
 6662           n«ˇlm»o
 6663           "#
 6664    ));
 6665
 6666    cx.update_editor(|editor, window, cx| {
 6667        editor.add_selection_below(&Default::default(), window, cx);
 6668    });
 6669
 6670    cx.assert_editor_state(indoc!(
 6671        r#"abc
 6672           d«ˇef»ghi
 6673
 6674           j«ˇk»
 6675           n«ˇlm»o
 6676           "#
 6677    ));
 6678}
 6679
 6680#[gpui::test]
 6681async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 6682    init_test(cx, |_| {});
 6683    let mut cx = EditorTestContext::new(cx).await;
 6684
 6685    cx.set_state(indoc!(
 6686        r#"line onˇe
 6687           liˇne two
 6688           line three
 6689           line four"#
 6690    ));
 6691
 6692    cx.update_editor(|editor, window, cx| {
 6693        editor.add_selection_below(&Default::default(), window, cx);
 6694    });
 6695
 6696    // test multiple cursors expand in the same direction
 6697    cx.assert_editor_state(indoc!(
 6698        r#"line onˇe
 6699           liˇne twˇo
 6700           liˇne three
 6701           line four"#
 6702    ));
 6703
 6704    cx.update_editor(|editor, window, cx| {
 6705        editor.add_selection_below(&Default::default(), window, cx);
 6706    });
 6707
 6708    cx.update_editor(|editor, window, cx| {
 6709        editor.add_selection_below(&Default::default(), window, cx);
 6710    });
 6711
 6712    // test multiple cursors expand below overflow
 6713    cx.assert_editor_state(indoc!(
 6714        r#"line onˇe
 6715           liˇne twˇo
 6716           liˇne thˇree
 6717           liˇne foˇur"#
 6718    ));
 6719
 6720    cx.update_editor(|editor, window, cx| {
 6721        editor.add_selection_above(&Default::default(), window, cx);
 6722    });
 6723
 6724    // test multiple cursors retrieves back correctly
 6725    cx.assert_editor_state(indoc!(
 6726        r#"line onˇe
 6727           liˇne twˇo
 6728           liˇne thˇree
 6729           line four"#
 6730    ));
 6731
 6732    cx.update_editor(|editor, window, cx| {
 6733        editor.add_selection_above(&Default::default(), window, cx);
 6734    });
 6735
 6736    cx.update_editor(|editor, window, cx| {
 6737        editor.add_selection_above(&Default::default(), window, cx);
 6738    });
 6739
 6740    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 6741    cx.assert_editor_state(indoc!(
 6742        r#"liˇne onˇe
 6743           liˇne two
 6744           line three
 6745           line four"#
 6746    ));
 6747
 6748    cx.update_editor(|editor, window, cx| {
 6749        editor.undo_selection(&Default::default(), window, cx);
 6750    });
 6751
 6752    // test undo
 6753    cx.assert_editor_state(indoc!(
 6754        r#"line onˇe
 6755           liˇne twˇo
 6756           line three
 6757           line four"#
 6758    ));
 6759
 6760    cx.update_editor(|editor, window, cx| {
 6761        editor.redo_selection(&Default::default(), window, cx);
 6762    });
 6763
 6764    // test redo
 6765    cx.assert_editor_state(indoc!(
 6766        r#"liˇne onˇe
 6767           liˇne two
 6768           line three
 6769           line four"#
 6770    ));
 6771
 6772    cx.set_state(indoc!(
 6773        r#"abcd
 6774           ef«ghˇ»
 6775           ijkl
 6776           «mˇ»nop"#
 6777    ));
 6778
 6779    cx.update_editor(|editor, window, cx| {
 6780        editor.add_selection_above(&Default::default(), window, cx);
 6781    });
 6782
 6783    // test multiple selections expand in the same direction
 6784    cx.assert_editor_state(indoc!(
 6785        r#"ab«cdˇ»
 6786           ef«ghˇ»
 6787           «iˇ»jkl
 6788           «mˇ»nop"#
 6789    ));
 6790
 6791    cx.update_editor(|editor, window, cx| {
 6792        editor.add_selection_above(&Default::default(), window, cx);
 6793    });
 6794
 6795    // test multiple selection upward overflow
 6796    cx.assert_editor_state(indoc!(
 6797        r#"ab«cdˇ»
 6798           «eˇ»f«ghˇ»
 6799           «iˇ»jkl
 6800           «mˇ»nop"#
 6801    ));
 6802
 6803    cx.update_editor(|editor, window, cx| {
 6804        editor.add_selection_below(&Default::default(), window, cx);
 6805    });
 6806
 6807    // test multiple selection retrieves back correctly
 6808    cx.assert_editor_state(indoc!(
 6809        r#"abcd
 6810           ef«ghˇ»
 6811           «iˇ»jkl
 6812           «mˇ»nop"#
 6813    ));
 6814
 6815    cx.update_editor(|editor, window, cx| {
 6816        editor.add_selection_below(&Default::default(), window, cx);
 6817    });
 6818
 6819    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 6820    cx.assert_editor_state(indoc!(
 6821        r#"abcd
 6822           ef«ghˇ»
 6823           ij«klˇ»
 6824           «mˇ»nop"#
 6825    ));
 6826
 6827    cx.update_editor(|editor, window, cx| {
 6828        editor.undo_selection(&Default::default(), window, cx);
 6829    });
 6830
 6831    // test undo
 6832    cx.assert_editor_state(indoc!(
 6833        r#"abcd
 6834           ef«ghˇ»
 6835           «iˇ»jkl
 6836           «mˇ»nop"#
 6837    ));
 6838
 6839    cx.update_editor(|editor, window, cx| {
 6840        editor.redo_selection(&Default::default(), window, cx);
 6841    });
 6842
 6843    // test redo
 6844    cx.assert_editor_state(indoc!(
 6845        r#"abcd
 6846           ef«ghˇ»
 6847           ij«klˇ»
 6848           «mˇ»nop"#
 6849    ));
 6850}
 6851
 6852#[gpui::test]
 6853async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 6854    init_test(cx, |_| {});
 6855    let mut cx = EditorTestContext::new(cx).await;
 6856
 6857    cx.set_state(indoc!(
 6858        r#"line onˇe
 6859           liˇne two
 6860           line three
 6861           line four"#
 6862    ));
 6863
 6864    cx.update_editor(|editor, window, cx| {
 6865        editor.add_selection_below(&Default::default(), window, cx);
 6866        editor.add_selection_below(&Default::default(), window, cx);
 6867        editor.add_selection_below(&Default::default(), window, cx);
 6868    });
 6869
 6870    // initial state with two multi cursor groups
 6871    cx.assert_editor_state(indoc!(
 6872        r#"line onˇe
 6873           liˇne twˇo
 6874           liˇne thˇree
 6875           liˇne foˇur"#
 6876    ));
 6877
 6878    // add single cursor in middle - simulate opt click
 6879    cx.update_editor(|editor, window, cx| {
 6880        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 6881        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 6882        editor.end_selection(window, cx);
 6883    });
 6884
 6885    cx.assert_editor_state(indoc!(
 6886        r#"line onˇe
 6887           liˇne twˇo
 6888           liˇneˇ thˇree
 6889           liˇne foˇur"#
 6890    ));
 6891
 6892    cx.update_editor(|editor, window, cx| {
 6893        editor.add_selection_above(&Default::default(), window, cx);
 6894    });
 6895
 6896    // test new added selection expands above and existing selection shrinks
 6897    cx.assert_editor_state(indoc!(
 6898        r#"line onˇe
 6899           liˇneˇ twˇo
 6900           liˇneˇ thˇree
 6901           line four"#
 6902    ));
 6903
 6904    cx.update_editor(|editor, window, cx| {
 6905        editor.add_selection_above(&Default::default(), window, cx);
 6906    });
 6907
 6908    // test new added selection expands above and existing selection shrinks
 6909    cx.assert_editor_state(indoc!(
 6910        r#"lineˇ onˇe
 6911           liˇneˇ twˇo
 6912           lineˇ three
 6913           line four"#
 6914    ));
 6915
 6916    // intial state with two selection groups
 6917    cx.set_state(indoc!(
 6918        r#"abcd
 6919           ef«ghˇ»
 6920           ijkl
 6921           «mˇ»nop"#
 6922    ));
 6923
 6924    cx.update_editor(|editor, window, cx| {
 6925        editor.add_selection_above(&Default::default(), window, cx);
 6926        editor.add_selection_above(&Default::default(), window, cx);
 6927    });
 6928
 6929    cx.assert_editor_state(indoc!(
 6930        r#"ab«cdˇ»
 6931           «eˇ»f«ghˇ»
 6932           «iˇ»jkl
 6933           «mˇ»nop"#
 6934    ));
 6935
 6936    // add single selection in middle - simulate opt drag
 6937    cx.update_editor(|editor, window, cx| {
 6938        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 6939        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 6940        editor.update_selection(
 6941            DisplayPoint::new(DisplayRow(2), 4),
 6942            0,
 6943            gpui::Point::<f32>::default(),
 6944            window,
 6945            cx,
 6946        );
 6947        editor.end_selection(window, cx);
 6948    });
 6949
 6950    cx.assert_editor_state(indoc!(
 6951        r#"ab«cdˇ»
 6952           «eˇ»f«ghˇ»
 6953           «iˇ»jk«lˇ»
 6954           «mˇ»nop"#
 6955    ));
 6956
 6957    cx.update_editor(|editor, window, cx| {
 6958        editor.add_selection_below(&Default::default(), window, cx);
 6959    });
 6960
 6961    // test new added selection expands below, others shrinks from above
 6962    cx.assert_editor_state(indoc!(
 6963        r#"abcd
 6964           ef«ghˇ»
 6965           «iˇ»jk«lˇ»
 6966           «mˇ»no«pˇ»"#
 6967    ));
 6968}
 6969
 6970#[gpui::test]
 6971async fn test_select_next(cx: &mut TestAppContext) {
 6972    init_test(cx, |_| {});
 6973
 6974    let mut cx = EditorTestContext::new(cx).await;
 6975    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 6976
 6977    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6978        .unwrap();
 6979    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6980
 6981    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6982        .unwrap();
 6983    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 6984
 6985    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 6986    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6987
 6988    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 6989    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 6990
 6991    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6992        .unwrap();
 6993    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6994
 6995    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6996        .unwrap();
 6997    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6998
 6999    // Test selection direction should be preserved
 7000    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 7001
 7002    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7003        .unwrap();
 7004    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 7005}
 7006
 7007#[gpui::test]
 7008async fn test_select_all_matches(cx: &mut TestAppContext) {
 7009    init_test(cx, |_| {});
 7010
 7011    let mut cx = EditorTestContext::new(cx).await;
 7012
 7013    // Test caret-only selections
 7014    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7015    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7016        .unwrap();
 7017    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7018
 7019    // Test left-to-right selections
 7020    cx.set_state("abc\n«abcˇ»\nabc");
 7021    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7022        .unwrap();
 7023    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 7024
 7025    // Test right-to-left selections
 7026    cx.set_state("abc\n«ˇabc»\nabc");
 7027    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7028        .unwrap();
 7029    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 7030
 7031    // Test selecting whitespace with caret selection
 7032    cx.set_state("abc\nˇ   abc\nabc");
 7033    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7034        .unwrap();
 7035    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 7036
 7037    // Test selecting whitespace with left-to-right selection
 7038    cx.set_state("abc\n«ˇ  »abc\nabc");
 7039    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7040        .unwrap();
 7041    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 7042
 7043    // Test no matches with right-to-left selection
 7044    cx.set_state("abc\n«  ˇ»abc\nabc");
 7045    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7046        .unwrap();
 7047    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 7048
 7049    // Test with a single word and clip_at_line_ends=true (#29823)
 7050    cx.set_state("aˇbc");
 7051    cx.update_editor(|e, window, cx| {
 7052        e.set_clip_at_line_ends(true, cx);
 7053        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 7054        e.set_clip_at_line_ends(false, cx);
 7055    });
 7056    cx.assert_editor_state("«abcˇ»");
 7057}
 7058
 7059#[gpui::test]
 7060async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 7061    init_test(cx, |_| {});
 7062
 7063    let mut cx = EditorTestContext::new(cx).await;
 7064
 7065    let large_body_1 = "\nd".repeat(200);
 7066    let large_body_2 = "\ne".repeat(200);
 7067
 7068    cx.set_state(&format!(
 7069        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 7070    ));
 7071    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 7072        let scroll_position = editor.scroll_position(cx);
 7073        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 7074        scroll_position
 7075    });
 7076
 7077    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7078        .unwrap();
 7079    cx.assert_editor_state(&format!(
 7080        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 7081    ));
 7082    let scroll_position_after_selection =
 7083        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 7084    assert_eq!(
 7085        initial_scroll_position, scroll_position_after_selection,
 7086        "Scroll position should not change after selecting all matches"
 7087    );
 7088}
 7089
 7090#[gpui::test]
 7091async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 7092    init_test(cx, |_| {});
 7093
 7094    let mut cx = EditorLspTestContext::new_rust(
 7095        lsp::ServerCapabilities {
 7096            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 7097            ..Default::default()
 7098        },
 7099        cx,
 7100    )
 7101    .await;
 7102
 7103    cx.set_state(indoc! {"
 7104        line 1
 7105        line 2
 7106        linˇe 3
 7107        line 4
 7108        line 5
 7109    "});
 7110
 7111    // Make an edit
 7112    cx.update_editor(|editor, window, cx| {
 7113        editor.handle_input("X", window, cx);
 7114    });
 7115
 7116    // Move cursor to a different position
 7117    cx.update_editor(|editor, window, cx| {
 7118        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7119            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 7120        });
 7121    });
 7122
 7123    cx.assert_editor_state(indoc! {"
 7124        line 1
 7125        line 2
 7126        linXe 3
 7127        line 4
 7128        liˇne 5
 7129    "});
 7130
 7131    cx.lsp
 7132        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 7133            Ok(Some(vec![lsp::TextEdit::new(
 7134                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 7135                "PREFIX ".to_string(),
 7136            )]))
 7137        });
 7138
 7139    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 7140        .unwrap()
 7141        .await
 7142        .unwrap();
 7143
 7144    cx.assert_editor_state(indoc! {"
 7145        PREFIX line 1
 7146        line 2
 7147        linXe 3
 7148        line 4
 7149        liˇne 5
 7150    "});
 7151
 7152    // Undo formatting
 7153    cx.update_editor(|editor, window, cx| {
 7154        editor.undo(&Default::default(), window, cx);
 7155    });
 7156
 7157    // Verify cursor moved back to position after edit
 7158    cx.assert_editor_state(indoc! {"
 7159        line 1
 7160        line 2
 7161        linXˇe 3
 7162        line 4
 7163        line 5
 7164    "});
 7165}
 7166
 7167#[gpui::test]
 7168async fn test_undo_inline_completion_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 7169    init_test(cx, |_| {});
 7170
 7171    let mut cx = EditorTestContext::new(cx).await;
 7172
 7173    let provider = cx.new(|_| FakeInlineCompletionProvider::default());
 7174    cx.update_editor(|editor, window, cx| {
 7175        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 7176    });
 7177
 7178    cx.set_state(indoc! {"
 7179        line 1
 7180        line 2
 7181        linˇe 3
 7182        line 4
 7183        line 5
 7184        line 6
 7185        line 7
 7186        line 8
 7187        line 9
 7188        line 10
 7189    "});
 7190
 7191    let snapshot = cx.buffer_snapshot();
 7192    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 7193
 7194    cx.update(|_, cx| {
 7195        provider.update(cx, |provider, _| {
 7196            provider.set_inline_completion(Some(inline_completion::InlineCompletion {
 7197                id: None,
 7198                edits: vec![(edit_position..edit_position, "X".into())],
 7199                edit_preview: None,
 7200            }))
 7201        })
 7202    });
 7203
 7204    cx.update_editor(|editor, window, cx| editor.update_visible_inline_completion(window, cx));
 7205    cx.update_editor(|editor, window, cx| {
 7206        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 7207    });
 7208
 7209    cx.assert_editor_state(indoc! {"
 7210        line 1
 7211        line 2
 7212        lineXˇ 3
 7213        line 4
 7214        line 5
 7215        line 6
 7216        line 7
 7217        line 8
 7218        line 9
 7219        line 10
 7220    "});
 7221
 7222    cx.update_editor(|editor, window, cx| {
 7223        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7224            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 7225        });
 7226    });
 7227
 7228    cx.assert_editor_state(indoc! {"
 7229        line 1
 7230        line 2
 7231        lineX 3
 7232        line 4
 7233        line 5
 7234        line 6
 7235        line 7
 7236        line 8
 7237        line 9
 7238        liˇne 10
 7239    "});
 7240
 7241    cx.update_editor(|editor, window, cx| {
 7242        editor.undo(&Default::default(), window, cx);
 7243    });
 7244
 7245    cx.assert_editor_state(indoc! {"
 7246        line 1
 7247        line 2
 7248        lineˇ 3
 7249        line 4
 7250        line 5
 7251        line 6
 7252        line 7
 7253        line 8
 7254        line 9
 7255        line 10
 7256    "});
 7257}
 7258
 7259#[gpui::test]
 7260async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 7261    init_test(cx, |_| {});
 7262
 7263    let mut cx = EditorTestContext::new(cx).await;
 7264    cx.set_state(
 7265        r#"let foo = 2;
 7266lˇet foo = 2;
 7267let fooˇ = 2;
 7268let foo = 2;
 7269let foo = ˇ2;"#,
 7270    );
 7271
 7272    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7273        .unwrap();
 7274    cx.assert_editor_state(
 7275        r#"let foo = 2;
 7276«letˇ» foo = 2;
 7277let «fooˇ» = 2;
 7278let foo = 2;
 7279let foo = «2ˇ»;"#,
 7280    );
 7281
 7282    // noop for multiple selections with different contents
 7283    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7284        .unwrap();
 7285    cx.assert_editor_state(
 7286        r#"let foo = 2;
 7287«letˇ» foo = 2;
 7288let «fooˇ» = 2;
 7289let foo = 2;
 7290let foo = «2ˇ»;"#,
 7291    );
 7292
 7293    // Test last selection direction should be preserved
 7294    cx.set_state(
 7295        r#"let foo = 2;
 7296let foo = 2;
 7297let «fooˇ» = 2;
 7298let «ˇfoo» = 2;
 7299let foo = 2;"#,
 7300    );
 7301
 7302    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7303        .unwrap();
 7304    cx.assert_editor_state(
 7305        r#"let foo = 2;
 7306let foo = 2;
 7307let «fooˇ» = 2;
 7308let «ˇfoo» = 2;
 7309let «ˇfoo» = 2;"#,
 7310    );
 7311}
 7312
 7313#[gpui::test]
 7314async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 7315    init_test(cx, |_| {});
 7316
 7317    let mut cx =
 7318        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 7319
 7320    cx.assert_editor_state(indoc! {"
 7321        ˇbbb
 7322        ccc
 7323
 7324        bbb
 7325        ccc
 7326        "});
 7327    cx.dispatch_action(SelectPrevious::default());
 7328    cx.assert_editor_state(indoc! {"
 7329                «bbbˇ»
 7330                ccc
 7331
 7332                bbb
 7333                ccc
 7334                "});
 7335    cx.dispatch_action(SelectPrevious::default());
 7336    cx.assert_editor_state(indoc! {"
 7337                «bbbˇ»
 7338                ccc
 7339
 7340                «bbbˇ»
 7341                ccc
 7342                "});
 7343}
 7344
 7345#[gpui::test]
 7346async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 7347    init_test(cx, |_| {});
 7348
 7349    let mut cx = EditorTestContext::new(cx).await;
 7350    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7351
 7352    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7353        .unwrap();
 7354    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7355
 7356    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7357        .unwrap();
 7358    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 7359
 7360    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7361    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7362
 7363    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7364    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 7365
 7366    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7367        .unwrap();
 7368    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 7369
 7370    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7371        .unwrap();
 7372    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7373}
 7374
 7375#[gpui::test]
 7376async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 7377    init_test(cx, |_| {});
 7378
 7379    let mut cx = EditorTestContext::new(cx).await;
 7380    cx.set_state("");
 7381
 7382    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7383        .unwrap();
 7384    cx.assert_editor_state("«aˇ»");
 7385    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7386        .unwrap();
 7387    cx.assert_editor_state("«aˇ»");
 7388}
 7389
 7390#[gpui::test]
 7391async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 7392    init_test(cx, |_| {});
 7393
 7394    let mut cx = EditorTestContext::new(cx).await;
 7395    cx.set_state(
 7396        r#"let foo = 2;
 7397lˇet foo = 2;
 7398let fooˇ = 2;
 7399let foo = 2;
 7400let foo = ˇ2;"#,
 7401    );
 7402
 7403    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7404        .unwrap();
 7405    cx.assert_editor_state(
 7406        r#"let foo = 2;
 7407«letˇ» foo = 2;
 7408let «fooˇ» = 2;
 7409let foo = 2;
 7410let foo = «2ˇ»;"#,
 7411    );
 7412
 7413    // noop for multiple selections with different contents
 7414    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7415        .unwrap();
 7416    cx.assert_editor_state(
 7417        r#"let foo = 2;
 7418«letˇ» foo = 2;
 7419let «fooˇ» = 2;
 7420let foo = 2;
 7421let foo = «2ˇ»;"#,
 7422    );
 7423}
 7424
 7425#[gpui::test]
 7426async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 7427    init_test(cx, |_| {});
 7428
 7429    let mut cx = EditorTestContext::new(cx).await;
 7430    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 7431
 7432    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7433        .unwrap();
 7434    // selection direction is preserved
 7435    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 7436
 7437    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7438        .unwrap();
 7439    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7440
 7441    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7442    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 7443
 7444    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7445    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7446
 7447    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7448        .unwrap();
 7449    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 7450
 7451    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7452        .unwrap();
 7453    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 7454}
 7455
 7456#[gpui::test]
 7457async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 7458    init_test(cx, |_| {});
 7459
 7460    let language = Arc::new(Language::new(
 7461        LanguageConfig::default(),
 7462        Some(tree_sitter_rust::LANGUAGE.into()),
 7463    ));
 7464
 7465    let text = r#"
 7466        use mod1::mod2::{mod3, mod4};
 7467
 7468        fn fn_1(param1: bool, param2: &str) {
 7469            let var1 = "text";
 7470        }
 7471    "#
 7472    .unindent();
 7473
 7474    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7475    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7476    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7477
 7478    editor
 7479        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7480        .await;
 7481
 7482    editor.update_in(cx, |editor, window, cx| {
 7483        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7484            s.select_display_ranges([
 7485                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 7486                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 7487                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 7488            ]);
 7489        });
 7490        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7491    });
 7492    editor.update(cx, |editor, cx| {
 7493        assert_text_with_selections(
 7494            editor,
 7495            indoc! {r#"
 7496                use mod1::mod2::{mod3, «mod4ˇ»};
 7497
 7498                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7499                    let var1 = "«ˇtext»";
 7500                }
 7501            "#},
 7502            cx,
 7503        );
 7504    });
 7505
 7506    editor.update_in(cx, |editor, window, cx| {
 7507        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7508    });
 7509    editor.update(cx, |editor, cx| {
 7510        assert_text_with_selections(
 7511            editor,
 7512            indoc! {r#"
 7513                use mod1::mod2::«{mod3, mod4}ˇ»;
 7514
 7515                «ˇfn fn_1(param1: bool, param2: &str) {
 7516                    let var1 = "text";
 7517 7518            "#},
 7519            cx,
 7520        );
 7521    });
 7522
 7523    editor.update_in(cx, |editor, window, cx| {
 7524        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7525    });
 7526    assert_eq!(
 7527        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7528        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7529    );
 7530
 7531    // Trying to expand the selected syntax node one more time has no effect.
 7532    editor.update_in(cx, |editor, window, cx| {
 7533        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7534    });
 7535    assert_eq!(
 7536        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7537        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7538    );
 7539
 7540    editor.update_in(cx, |editor, window, cx| {
 7541        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7542    });
 7543    editor.update(cx, |editor, cx| {
 7544        assert_text_with_selections(
 7545            editor,
 7546            indoc! {r#"
 7547                use mod1::mod2::«{mod3, mod4}ˇ»;
 7548
 7549                «ˇfn fn_1(param1: bool, param2: &str) {
 7550                    let var1 = "text";
 7551 7552            "#},
 7553            cx,
 7554        );
 7555    });
 7556
 7557    editor.update_in(cx, |editor, window, cx| {
 7558        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7559    });
 7560    editor.update(cx, |editor, cx| {
 7561        assert_text_with_selections(
 7562            editor,
 7563            indoc! {r#"
 7564                use mod1::mod2::{mod3, «mod4ˇ»};
 7565
 7566                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7567                    let var1 = "«ˇtext»";
 7568                }
 7569            "#},
 7570            cx,
 7571        );
 7572    });
 7573
 7574    editor.update_in(cx, |editor, window, cx| {
 7575        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7576    });
 7577    editor.update(cx, |editor, cx| {
 7578        assert_text_with_selections(
 7579            editor,
 7580            indoc! {r#"
 7581                use mod1::mod2::{mod3, mo«ˇ»d4};
 7582
 7583                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7584                    let var1 = "te«ˇ»xt";
 7585                }
 7586            "#},
 7587            cx,
 7588        );
 7589    });
 7590
 7591    // Trying to shrink the selected syntax node one more time has no effect.
 7592    editor.update_in(cx, |editor, window, cx| {
 7593        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7594    });
 7595    editor.update_in(cx, |editor, _, cx| {
 7596        assert_text_with_selections(
 7597            editor,
 7598            indoc! {r#"
 7599                use mod1::mod2::{mod3, mo«ˇ»d4};
 7600
 7601                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7602                    let var1 = "te«ˇ»xt";
 7603                }
 7604            "#},
 7605            cx,
 7606        );
 7607    });
 7608
 7609    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 7610    // a fold.
 7611    editor.update_in(cx, |editor, window, cx| {
 7612        editor.fold_creases(
 7613            vec![
 7614                Crease::simple(
 7615                    Point::new(0, 21)..Point::new(0, 24),
 7616                    FoldPlaceholder::test(),
 7617                ),
 7618                Crease::simple(
 7619                    Point::new(3, 20)..Point::new(3, 22),
 7620                    FoldPlaceholder::test(),
 7621                ),
 7622            ],
 7623            true,
 7624            window,
 7625            cx,
 7626        );
 7627        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7628    });
 7629    editor.update(cx, |editor, cx| {
 7630        assert_text_with_selections(
 7631            editor,
 7632            indoc! {r#"
 7633                use mod1::mod2::«{mod3, mod4}ˇ»;
 7634
 7635                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7636                    let var1 = "«ˇtext»";
 7637                }
 7638            "#},
 7639            cx,
 7640        );
 7641    });
 7642}
 7643
 7644#[gpui::test]
 7645async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 7646    init_test(cx, |_| {});
 7647
 7648    let language = Arc::new(Language::new(
 7649        LanguageConfig::default(),
 7650        Some(tree_sitter_rust::LANGUAGE.into()),
 7651    ));
 7652
 7653    let text = "let a = 2;";
 7654
 7655    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7656    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7657    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7658
 7659    editor
 7660        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7661        .await;
 7662
 7663    // Test case 1: Cursor at end of word
 7664    editor.update_in(cx, |editor, window, cx| {
 7665        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7666            s.select_display_ranges([
 7667                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 7668            ]);
 7669        });
 7670    });
 7671    editor.update(cx, |editor, cx| {
 7672        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 7673    });
 7674    editor.update_in(cx, |editor, window, cx| {
 7675        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7676    });
 7677    editor.update(cx, |editor, cx| {
 7678        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 7679    });
 7680    editor.update_in(cx, |editor, window, cx| {
 7681        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7682    });
 7683    editor.update(cx, |editor, cx| {
 7684        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7685    });
 7686
 7687    // Test case 2: Cursor at end of statement
 7688    editor.update_in(cx, |editor, window, cx| {
 7689        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7690            s.select_display_ranges([
 7691                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 7692            ]);
 7693        });
 7694    });
 7695    editor.update(cx, |editor, cx| {
 7696        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 7697    });
 7698    editor.update_in(cx, |editor, window, cx| {
 7699        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7700    });
 7701    editor.update(cx, |editor, cx| {
 7702        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7703    });
 7704}
 7705
 7706#[gpui::test]
 7707async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 7708    init_test(cx, |_| {});
 7709
 7710    let language = Arc::new(Language::new(
 7711        LanguageConfig::default(),
 7712        Some(tree_sitter_rust::LANGUAGE.into()),
 7713    ));
 7714
 7715    let text = r#"
 7716        use mod1::mod2::{mod3, mod4};
 7717
 7718        fn fn_1(param1: bool, param2: &str) {
 7719            let var1 = "hello world";
 7720        }
 7721    "#
 7722    .unindent();
 7723
 7724    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7725    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7726    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7727
 7728    editor
 7729        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7730        .await;
 7731
 7732    // Test 1: Cursor on a letter of a string word
 7733    editor.update_in(cx, |editor, window, cx| {
 7734        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7735            s.select_display_ranges([
 7736                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 7737            ]);
 7738        });
 7739    });
 7740    editor.update_in(cx, |editor, window, cx| {
 7741        assert_text_with_selections(
 7742            editor,
 7743            indoc! {r#"
 7744                use mod1::mod2::{mod3, mod4};
 7745
 7746                fn fn_1(param1: bool, param2: &str) {
 7747                    let var1 = "hˇello world";
 7748                }
 7749            "#},
 7750            cx,
 7751        );
 7752        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7753        assert_text_with_selections(
 7754            editor,
 7755            indoc! {r#"
 7756                use mod1::mod2::{mod3, mod4};
 7757
 7758                fn fn_1(param1: bool, param2: &str) {
 7759                    let var1 = "«ˇhello» world";
 7760                }
 7761            "#},
 7762            cx,
 7763        );
 7764    });
 7765
 7766    // Test 2: Partial selection within a word
 7767    editor.update_in(cx, |editor, window, cx| {
 7768        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7769            s.select_display_ranges([
 7770                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 7771            ]);
 7772        });
 7773    });
 7774    editor.update_in(cx, |editor, window, cx| {
 7775        assert_text_with_selections(
 7776            editor,
 7777            indoc! {r#"
 7778                use mod1::mod2::{mod3, mod4};
 7779
 7780                fn fn_1(param1: bool, param2: &str) {
 7781                    let var1 = "h«elˇ»lo world";
 7782                }
 7783            "#},
 7784            cx,
 7785        );
 7786        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7787        assert_text_with_selections(
 7788            editor,
 7789            indoc! {r#"
 7790                use mod1::mod2::{mod3, mod4};
 7791
 7792                fn fn_1(param1: bool, param2: &str) {
 7793                    let var1 = "«ˇhello» world";
 7794                }
 7795            "#},
 7796            cx,
 7797        );
 7798    });
 7799
 7800    // Test 3: Complete word already selected
 7801    editor.update_in(cx, |editor, window, cx| {
 7802        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7803            s.select_display_ranges([
 7804                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 7805            ]);
 7806        });
 7807    });
 7808    editor.update_in(cx, |editor, window, cx| {
 7809        assert_text_with_selections(
 7810            editor,
 7811            indoc! {r#"
 7812                use mod1::mod2::{mod3, mod4};
 7813
 7814                fn fn_1(param1: bool, param2: &str) {
 7815                    let var1 = "«helloˇ» world";
 7816                }
 7817            "#},
 7818            cx,
 7819        );
 7820        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7821        assert_text_with_selections(
 7822            editor,
 7823            indoc! {r#"
 7824                use mod1::mod2::{mod3, mod4};
 7825
 7826                fn fn_1(param1: bool, param2: &str) {
 7827                    let var1 = "«hello worldˇ»";
 7828                }
 7829            "#},
 7830            cx,
 7831        );
 7832    });
 7833
 7834    // Test 4: Selection spanning across words
 7835    editor.update_in(cx, |editor, window, cx| {
 7836        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7837            s.select_display_ranges([
 7838                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 7839            ]);
 7840        });
 7841    });
 7842    editor.update_in(cx, |editor, window, cx| {
 7843        assert_text_with_selections(
 7844            editor,
 7845            indoc! {r#"
 7846                use mod1::mod2::{mod3, mod4};
 7847
 7848                fn fn_1(param1: bool, param2: &str) {
 7849                    let var1 = "hel«lo woˇ»rld";
 7850                }
 7851            "#},
 7852            cx,
 7853        );
 7854        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7855        assert_text_with_selections(
 7856            editor,
 7857            indoc! {r#"
 7858                use mod1::mod2::{mod3, mod4};
 7859
 7860                fn fn_1(param1: bool, param2: &str) {
 7861                    let var1 = "«ˇhello world»";
 7862                }
 7863            "#},
 7864            cx,
 7865        );
 7866    });
 7867
 7868    // Test 5: Expansion beyond string
 7869    editor.update_in(cx, |editor, window, cx| {
 7870        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7871        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7872        assert_text_with_selections(
 7873            editor,
 7874            indoc! {r#"
 7875                use mod1::mod2::{mod3, mod4};
 7876
 7877                fn fn_1(param1: bool, param2: &str) {
 7878                    «ˇlet var1 = "hello world";»
 7879                }
 7880            "#},
 7881            cx,
 7882        );
 7883    });
 7884}
 7885
 7886#[gpui::test]
 7887async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 7888    init_test(cx, |_| {});
 7889
 7890    let base_text = r#"
 7891        impl A {
 7892            // this is an uncommitted comment
 7893
 7894            fn b() {
 7895                c();
 7896            }
 7897
 7898            // this is another uncommitted comment
 7899
 7900            fn d() {
 7901                // e
 7902                // f
 7903            }
 7904        }
 7905
 7906        fn g() {
 7907            // h
 7908        }
 7909    "#
 7910    .unindent();
 7911
 7912    let text = r#"
 7913        ˇimpl A {
 7914
 7915            fn b() {
 7916                c();
 7917            }
 7918
 7919            fn d() {
 7920                // e
 7921                // f
 7922            }
 7923        }
 7924
 7925        fn g() {
 7926            // h
 7927        }
 7928    "#
 7929    .unindent();
 7930
 7931    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 7932    cx.set_state(&text);
 7933    cx.set_head_text(&base_text);
 7934    cx.update_editor(|editor, window, cx| {
 7935        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 7936    });
 7937
 7938    cx.assert_state_with_diff(
 7939        "
 7940        ˇimpl A {
 7941      -     // this is an uncommitted comment
 7942
 7943            fn b() {
 7944                c();
 7945            }
 7946
 7947      -     // this is another uncommitted comment
 7948      -
 7949            fn d() {
 7950                // e
 7951                // f
 7952            }
 7953        }
 7954
 7955        fn g() {
 7956            // h
 7957        }
 7958    "
 7959        .unindent(),
 7960    );
 7961
 7962    let expected_display_text = "
 7963        impl A {
 7964            // this is an uncommitted comment
 7965
 7966            fn b() {
 7967 7968            }
 7969
 7970            // this is another uncommitted comment
 7971
 7972            fn d() {
 7973 7974            }
 7975        }
 7976
 7977        fn g() {
 7978 7979        }
 7980        "
 7981    .unindent();
 7982
 7983    cx.update_editor(|editor, window, cx| {
 7984        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 7985        assert_eq!(editor.display_text(cx), expected_display_text);
 7986    });
 7987}
 7988
 7989#[gpui::test]
 7990async fn test_autoindent(cx: &mut TestAppContext) {
 7991    init_test(cx, |_| {});
 7992
 7993    let language = Arc::new(
 7994        Language::new(
 7995            LanguageConfig {
 7996                brackets: BracketPairConfig {
 7997                    pairs: vec![
 7998                        BracketPair {
 7999                            start: "{".to_string(),
 8000                            end: "}".to_string(),
 8001                            close: false,
 8002                            surround: false,
 8003                            newline: true,
 8004                        },
 8005                        BracketPair {
 8006                            start: "(".to_string(),
 8007                            end: ")".to_string(),
 8008                            close: false,
 8009                            surround: false,
 8010                            newline: true,
 8011                        },
 8012                    ],
 8013                    ..Default::default()
 8014                },
 8015                ..Default::default()
 8016            },
 8017            Some(tree_sitter_rust::LANGUAGE.into()),
 8018        )
 8019        .with_indents_query(
 8020            r#"
 8021                (_ "(" ")" @end) @indent
 8022                (_ "{" "}" @end) @indent
 8023            "#,
 8024        )
 8025        .unwrap(),
 8026    );
 8027
 8028    let text = "fn a() {}";
 8029
 8030    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8031    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8032    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8033    editor
 8034        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8035        .await;
 8036
 8037    editor.update_in(cx, |editor, window, cx| {
 8038        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8039            s.select_ranges([5..5, 8..8, 9..9])
 8040        });
 8041        editor.newline(&Newline, window, cx);
 8042        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 8043        assert_eq!(
 8044            editor.selections.ranges(cx),
 8045            &[
 8046                Point::new(1, 4)..Point::new(1, 4),
 8047                Point::new(3, 4)..Point::new(3, 4),
 8048                Point::new(5, 0)..Point::new(5, 0)
 8049            ]
 8050        );
 8051    });
 8052}
 8053
 8054#[gpui::test]
 8055async fn test_autoindent_selections(cx: &mut TestAppContext) {
 8056    init_test(cx, |_| {});
 8057
 8058    {
 8059        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 8060        cx.set_state(indoc! {"
 8061            impl A {
 8062
 8063                fn b() {}
 8064
 8065            «fn c() {
 8066
 8067            }ˇ»
 8068            }
 8069        "});
 8070
 8071        cx.update_editor(|editor, window, cx| {
 8072            editor.autoindent(&Default::default(), window, cx);
 8073        });
 8074
 8075        cx.assert_editor_state(indoc! {"
 8076            impl A {
 8077
 8078                fn b() {}
 8079
 8080                «fn c() {
 8081
 8082                }ˇ»
 8083            }
 8084        "});
 8085    }
 8086
 8087    {
 8088        let mut cx = EditorTestContext::new_multibuffer(
 8089            cx,
 8090            [indoc! { "
 8091                impl A {
 8092                «
 8093                // a
 8094                fn b(){}
 8095                »
 8096                «
 8097                    }
 8098                    fn c(){}
 8099                »
 8100            "}],
 8101        );
 8102
 8103        let buffer = cx.update_editor(|editor, _, cx| {
 8104            let buffer = editor.buffer().update(cx, |buffer, _| {
 8105                buffer.all_buffers().iter().next().unwrap().clone()
 8106            });
 8107            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 8108            buffer
 8109        });
 8110
 8111        cx.run_until_parked();
 8112        cx.update_editor(|editor, window, cx| {
 8113            editor.select_all(&Default::default(), window, cx);
 8114            editor.autoindent(&Default::default(), window, cx)
 8115        });
 8116        cx.run_until_parked();
 8117
 8118        cx.update(|_, cx| {
 8119            assert_eq!(
 8120                buffer.read(cx).text(),
 8121                indoc! { "
 8122                    impl A {
 8123
 8124                        // a
 8125                        fn b(){}
 8126
 8127
 8128                    }
 8129                    fn c(){}
 8130
 8131                " }
 8132            )
 8133        });
 8134    }
 8135}
 8136
 8137#[gpui::test]
 8138async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 8139    init_test(cx, |_| {});
 8140
 8141    let mut cx = EditorTestContext::new(cx).await;
 8142
 8143    let language = Arc::new(Language::new(
 8144        LanguageConfig {
 8145            brackets: BracketPairConfig {
 8146                pairs: vec![
 8147                    BracketPair {
 8148                        start: "{".to_string(),
 8149                        end: "}".to_string(),
 8150                        close: true,
 8151                        surround: true,
 8152                        newline: true,
 8153                    },
 8154                    BracketPair {
 8155                        start: "(".to_string(),
 8156                        end: ")".to_string(),
 8157                        close: true,
 8158                        surround: true,
 8159                        newline: true,
 8160                    },
 8161                    BracketPair {
 8162                        start: "/*".to_string(),
 8163                        end: " */".to_string(),
 8164                        close: true,
 8165                        surround: true,
 8166                        newline: true,
 8167                    },
 8168                    BracketPair {
 8169                        start: "[".to_string(),
 8170                        end: "]".to_string(),
 8171                        close: false,
 8172                        surround: false,
 8173                        newline: true,
 8174                    },
 8175                    BracketPair {
 8176                        start: "\"".to_string(),
 8177                        end: "\"".to_string(),
 8178                        close: true,
 8179                        surround: true,
 8180                        newline: false,
 8181                    },
 8182                    BracketPair {
 8183                        start: "<".to_string(),
 8184                        end: ">".to_string(),
 8185                        close: false,
 8186                        surround: true,
 8187                        newline: true,
 8188                    },
 8189                ],
 8190                ..Default::default()
 8191            },
 8192            autoclose_before: "})]".to_string(),
 8193            ..Default::default()
 8194        },
 8195        Some(tree_sitter_rust::LANGUAGE.into()),
 8196    ));
 8197
 8198    cx.language_registry().add(language.clone());
 8199    cx.update_buffer(|buffer, cx| {
 8200        buffer.set_language(Some(language), cx);
 8201    });
 8202
 8203    cx.set_state(
 8204        &r#"
 8205            🏀ˇ
 8206            εˇ
 8207            ❤️ˇ
 8208        "#
 8209        .unindent(),
 8210    );
 8211
 8212    // autoclose multiple nested brackets at multiple cursors
 8213    cx.update_editor(|editor, window, cx| {
 8214        editor.handle_input("{", window, cx);
 8215        editor.handle_input("{", window, cx);
 8216        editor.handle_input("{", window, cx);
 8217    });
 8218    cx.assert_editor_state(
 8219        &"
 8220            🏀{{{ˇ}}}
 8221            ε{{{ˇ}}}
 8222            ❤️{{{ˇ}}}
 8223        "
 8224        .unindent(),
 8225    );
 8226
 8227    // insert a different closing bracket
 8228    cx.update_editor(|editor, window, cx| {
 8229        editor.handle_input(")", window, cx);
 8230    });
 8231    cx.assert_editor_state(
 8232        &"
 8233            🏀{{{)ˇ}}}
 8234            ε{{{)ˇ}}}
 8235            ❤️{{{)ˇ}}}
 8236        "
 8237        .unindent(),
 8238    );
 8239
 8240    // skip over the auto-closed brackets when typing a closing bracket
 8241    cx.update_editor(|editor, window, cx| {
 8242        editor.move_right(&MoveRight, window, cx);
 8243        editor.handle_input("}", window, cx);
 8244        editor.handle_input("}", window, cx);
 8245        editor.handle_input("}", window, cx);
 8246    });
 8247    cx.assert_editor_state(
 8248        &"
 8249            🏀{{{)}}}}ˇ
 8250            ε{{{)}}}}ˇ
 8251            ❤️{{{)}}}}ˇ
 8252        "
 8253        .unindent(),
 8254    );
 8255
 8256    // autoclose multi-character pairs
 8257    cx.set_state(
 8258        &"
 8259            ˇ
 8260            ˇ
 8261        "
 8262        .unindent(),
 8263    );
 8264    cx.update_editor(|editor, window, cx| {
 8265        editor.handle_input("/", window, cx);
 8266        editor.handle_input("*", window, cx);
 8267    });
 8268    cx.assert_editor_state(
 8269        &"
 8270            /*ˇ */
 8271            /*ˇ */
 8272        "
 8273        .unindent(),
 8274    );
 8275
 8276    // one cursor autocloses a multi-character pair, one cursor
 8277    // does not autoclose.
 8278    cx.set_state(
 8279        &"
 8280 8281            ˇ
 8282        "
 8283        .unindent(),
 8284    );
 8285    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 8286    cx.assert_editor_state(
 8287        &"
 8288            /*ˇ */
 8289 8290        "
 8291        .unindent(),
 8292    );
 8293
 8294    // Don't autoclose if the next character isn't whitespace and isn't
 8295    // listed in the language's "autoclose_before" section.
 8296    cx.set_state("ˇa b");
 8297    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8298    cx.assert_editor_state("{ˇa b");
 8299
 8300    // Don't autoclose if `close` is false for the bracket pair
 8301    cx.set_state("ˇ");
 8302    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 8303    cx.assert_editor_state("");
 8304
 8305    // Surround with brackets if text is selected
 8306    cx.set_state("«aˇ» b");
 8307    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8308    cx.assert_editor_state("{«aˇ»} b");
 8309
 8310    // Autoclose when not immediately after a word character
 8311    cx.set_state("a ˇ");
 8312    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8313    cx.assert_editor_state("a \"ˇ\"");
 8314
 8315    // Autoclose pair where the start and end characters are the same
 8316    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8317    cx.assert_editor_state("a \"\"ˇ");
 8318
 8319    // Don't autoclose when immediately after a word character
 8320    cx.set_state("");
 8321    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8322    cx.assert_editor_state("a\"ˇ");
 8323
 8324    // Do autoclose when after a non-word character
 8325    cx.set_state("");
 8326    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8327    cx.assert_editor_state("{\"ˇ\"");
 8328
 8329    // Non identical pairs autoclose regardless of preceding character
 8330    cx.set_state("");
 8331    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8332    cx.assert_editor_state("a{ˇ}");
 8333
 8334    // Don't autoclose pair if autoclose is disabled
 8335    cx.set_state("ˇ");
 8336    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 8337    cx.assert_editor_state("");
 8338
 8339    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 8340    cx.set_state("«aˇ» b");
 8341    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 8342    cx.assert_editor_state("<«aˇ»> b");
 8343}
 8344
 8345#[gpui::test]
 8346async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 8347    init_test(cx, |settings| {
 8348        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 8349    });
 8350
 8351    let mut cx = EditorTestContext::new(cx).await;
 8352
 8353    let language = Arc::new(Language::new(
 8354        LanguageConfig {
 8355            brackets: BracketPairConfig {
 8356                pairs: vec![
 8357                    BracketPair {
 8358                        start: "{".to_string(),
 8359                        end: "}".to_string(),
 8360                        close: true,
 8361                        surround: true,
 8362                        newline: true,
 8363                    },
 8364                    BracketPair {
 8365                        start: "(".to_string(),
 8366                        end: ")".to_string(),
 8367                        close: true,
 8368                        surround: true,
 8369                        newline: true,
 8370                    },
 8371                    BracketPair {
 8372                        start: "[".to_string(),
 8373                        end: "]".to_string(),
 8374                        close: false,
 8375                        surround: false,
 8376                        newline: true,
 8377                    },
 8378                ],
 8379                ..Default::default()
 8380            },
 8381            autoclose_before: "})]".to_string(),
 8382            ..Default::default()
 8383        },
 8384        Some(tree_sitter_rust::LANGUAGE.into()),
 8385    ));
 8386
 8387    cx.language_registry().add(language.clone());
 8388    cx.update_buffer(|buffer, cx| {
 8389        buffer.set_language(Some(language), cx);
 8390    });
 8391
 8392    cx.set_state(
 8393        &"
 8394            ˇ
 8395            ˇ
 8396            ˇ
 8397        "
 8398        .unindent(),
 8399    );
 8400
 8401    // ensure only matching closing brackets are skipped over
 8402    cx.update_editor(|editor, window, cx| {
 8403        editor.handle_input("}", window, cx);
 8404        editor.move_left(&MoveLeft, window, cx);
 8405        editor.handle_input(")", window, cx);
 8406        editor.move_left(&MoveLeft, window, cx);
 8407    });
 8408    cx.assert_editor_state(
 8409        &"
 8410            ˇ)}
 8411            ˇ)}
 8412            ˇ)}
 8413        "
 8414        .unindent(),
 8415    );
 8416
 8417    // skip-over closing brackets at multiple cursors
 8418    cx.update_editor(|editor, window, cx| {
 8419        editor.handle_input(")", window, cx);
 8420        editor.handle_input("}", window, cx);
 8421    });
 8422    cx.assert_editor_state(
 8423        &"
 8424            )}ˇ
 8425            )}ˇ
 8426            )}ˇ
 8427        "
 8428        .unindent(),
 8429    );
 8430
 8431    // ignore non-close brackets
 8432    cx.update_editor(|editor, window, cx| {
 8433        editor.handle_input("]", window, cx);
 8434        editor.move_left(&MoveLeft, window, cx);
 8435        editor.handle_input("]", window, cx);
 8436    });
 8437    cx.assert_editor_state(
 8438        &"
 8439            )}]ˇ]
 8440            )}]ˇ]
 8441            )}]ˇ]
 8442        "
 8443        .unindent(),
 8444    );
 8445}
 8446
 8447#[gpui::test]
 8448async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
 8449    init_test(cx, |_| {});
 8450
 8451    let mut cx = EditorTestContext::new(cx).await;
 8452
 8453    let html_language = Arc::new(
 8454        Language::new(
 8455            LanguageConfig {
 8456                name: "HTML".into(),
 8457                brackets: BracketPairConfig {
 8458                    pairs: vec![
 8459                        BracketPair {
 8460                            start: "<".into(),
 8461                            end: ">".into(),
 8462                            close: true,
 8463                            ..Default::default()
 8464                        },
 8465                        BracketPair {
 8466                            start: "{".into(),
 8467                            end: "}".into(),
 8468                            close: true,
 8469                            ..Default::default()
 8470                        },
 8471                        BracketPair {
 8472                            start: "(".into(),
 8473                            end: ")".into(),
 8474                            close: true,
 8475                            ..Default::default()
 8476                        },
 8477                    ],
 8478                    ..Default::default()
 8479                },
 8480                autoclose_before: "})]>".into(),
 8481                ..Default::default()
 8482            },
 8483            Some(tree_sitter_html::LANGUAGE.into()),
 8484        )
 8485        .with_injection_query(
 8486            r#"
 8487            (script_element
 8488                (raw_text) @injection.content
 8489                (#set! injection.language "javascript"))
 8490            "#,
 8491        )
 8492        .unwrap(),
 8493    );
 8494
 8495    let javascript_language = Arc::new(Language::new(
 8496        LanguageConfig {
 8497            name: "JavaScript".into(),
 8498            brackets: BracketPairConfig {
 8499                pairs: vec![
 8500                    BracketPair {
 8501                        start: "/*".into(),
 8502                        end: " */".into(),
 8503                        close: true,
 8504                        ..Default::default()
 8505                    },
 8506                    BracketPair {
 8507                        start: "{".into(),
 8508                        end: "}".into(),
 8509                        close: true,
 8510                        ..Default::default()
 8511                    },
 8512                    BracketPair {
 8513                        start: "(".into(),
 8514                        end: ")".into(),
 8515                        close: true,
 8516                        ..Default::default()
 8517                    },
 8518                ],
 8519                ..Default::default()
 8520            },
 8521            autoclose_before: "})]>".into(),
 8522            ..Default::default()
 8523        },
 8524        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 8525    ));
 8526
 8527    cx.language_registry().add(html_language.clone());
 8528    cx.language_registry().add(javascript_language.clone());
 8529
 8530    cx.update_buffer(|buffer, cx| {
 8531        buffer.set_language(Some(html_language), cx);
 8532    });
 8533
 8534    cx.set_state(
 8535        &r#"
 8536            <body>ˇ
 8537                <script>
 8538                    var x = 1;ˇ
 8539                </script>
 8540            </body>ˇ
 8541        "#
 8542        .unindent(),
 8543    );
 8544
 8545    // Precondition: different languages are active at different locations.
 8546    cx.update_editor(|editor, window, cx| {
 8547        let snapshot = editor.snapshot(window, cx);
 8548        let cursors = editor.selections.ranges::<usize>(cx);
 8549        let languages = cursors
 8550            .iter()
 8551            .map(|c| snapshot.language_at(c.start).unwrap().name())
 8552            .collect::<Vec<_>>();
 8553        assert_eq!(
 8554            languages,
 8555            &["HTML".into(), "JavaScript".into(), "HTML".into()]
 8556        );
 8557    });
 8558
 8559    // Angle brackets autoclose in HTML, but not JavaScript.
 8560    cx.update_editor(|editor, window, cx| {
 8561        editor.handle_input("<", window, cx);
 8562        editor.handle_input("a", window, cx);
 8563    });
 8564    cx.assert_editor_state(
 8565        &r#"
 8566            <body><aˇ>
 8567                <script>
 8568                    var x = 1;<aˇ
 8569                </script>
 8570            </body><aˇ>
 8571        "#
 8572        .unindent(),
 8573    );
 8574
 8575    // Curly braces and parens autoclose in both HTML and JavaScript.
 8576    cx.update_editor(|editor, window, cx| {
 8577        editor.handle_input(" b=", window, cx);
 8578        editor.handle_input("{", window, cx);
 8579        editor.handle_input("c", window, cx);
 8580        editor.handle_input("(", window, cx);
 8581    });
 8582    cx.assert_editor_state(
 8583        &r#"
 8584            <body><a b={c(ˇ)}>
 8585                <script>
 8586                    var x = 1;<a b={c(ˇ)}
 8587                </script>
 8588            </body><a b={c(ˇ)}>
 8589        "#
 8590        .unindent(),
 8591    );
 8592
 8593    // Brackets that were already autoclosed are skipped.
 8594    cx.update_editor(|editor, window, cx| {
 8595        editor.handle_input(")", window, cx);
 8596        editor.handle_input("d", window, cx);
 8597        editor.handle_input("}", window, cx);
 8598    });
 8599    cx.assert_editor_state(
 8600        &r#"
 8601            <body><a b={c()d}ˇ>
 8602                <script>
 8603                    var x = 1;<a b={c()d}ˇ
 8604                </script>
 8605            </body><a b={c()d}ˇ>
 8606        "#
 8607        .unindent(),
 8608    );
 8609    cx.update_editor(|editor, window, cx| {
 8610        editor.handle_input(">", window, cx);
 8611    });
 8612    cx.assert_editor_state(
 8613        &r#"
 8614            <body><a b={c()d}>ˇ
 8615                <script>
 8616                    var x = 1;<a b={c()d}>ˇ
 8617                </script>
 8618            </body><a b={c()d}>ˇ
 8619        "#
 8620        .unindent(),
 8621    );
 8622
 8623    // Reset
 8624    cx.set_state(
 8625        &r#"
 8626            <body>ˇ
 8627                <script>
 8628                    var x = 1;ˇ
 8629                </script>
 8630            </body>ˇ
 8631        "#
 8632        .unindent(),
 8633    );
 8634
 8635    cx.update_editor(|editor, window, cx| {
 8636        editor.handle_input("<", window, cx);
 8637    });
 8638    cx.assert_editor_state(
 8639        &r#"
 8640            <body><ˇ>
 8641                <script>
 8642                    var x = 1;<ˇ
 8643                </script>
 8644            </body><ˇ>
 8645        "#
 8646        .unindent(),
 8647    );
 8648
 8649    // When backspacing, the closing angle brackets are removed.
 8650    cx.update_editor(|editor, window, cx| {
 8651        editor.backspace(&Backspace, window, cx);
 8652    });
 8653    cx.assert_editor_state(
 8654        &r#"
 8655            <body>ˇ
 8656                <script>
 8657                    var x = 1;ˇ
 8658                </script>
 8659            </body>ˇ
 8660        "#
 8661        .unindent(),
 8662    );
 8663
 8664    // Block comments autoclose in JavaScript, but not HTML.
 8665    cx.update_editor(|editor, window, cx| {
 8666        editor.handle_input("/", window, cx);
 8667        editor.handle_input("*", window, cx);
 8668    });
 8669    cx.assert_editor_state(
 8670        &r#"
 8671            <body>/*ˇ
 8672                <script>
 8673                    var x = 1;/*ˇ */
 8674                </script>
 8675            </body>/*ˇ
 8676        "#
 8677        .unindent(),
 8678    );
 8679}
 8680
 8681#[gpui::test]
 8682async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
 8683    init_test(cx, |_| {});
 8684
 8685    let mut cx = EditorTestContext::new(cx).await;
 8686
 8687    let rust_language = Arc::new(
 8688        Language::new(
 8689            LanguageConfig {
 8690                name: "Rust".into(),
 8691                brackets: serde_json::from_value(json!([
 8692                    { "start": "{", "end": "}", "close": true, "newline": true },
 8693                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
 8694                ]))
 8695                .unwrap(),
 8696                autoclose_before: "})]>".into(),
 8697                ..Default::default()
 8698            },
 8699            Some(tree_sitter_rust::LANGUAGE.into()),
 8700        )
 8701        .with_override_query("(string_literal) @string")
 8702        .unwrap(),
 8703    );
 8704
 8705    cx.language_registry().add(rust_language.clone());
 8706    cx.update_buffer(|buffer, cx| {
 8707        buffer.set_language(Some(rust_language), cx);
 8708    });
 8709
 8710    cx.set_state(
 8711        &r#"
 8712            let x = ˇ
 8713        "#
 8714        .unindent(),
 8715    );
 8716
 8717    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
 8718    cx.update_editor(|editor, window, cx| {
 8719        editor.handle_input("\"", window, cx);
 8720    });
 8721    cx.assert_editor_state(
 8722        &r#"
 8723            let x = "ˇ"
 8724        "#
 8725        .unindent(),
 8726    );
 8727
 8728    // Inserting another quotation mark. The cursor moves across the existing
 8729    // automatically-inserted quotation mark.
 8730    cx.update_editor(|editor, window, cx| {
 8731        editor.handle_input("\"", window, cx);
 8732    });
 8733    cx.assert_editor_state(
 8734        &r#"
 8735            let x = ""ˇ
 8736        "#
 8737        .unindent(),
 8738    );
 8739
 8740    // Reset
 8741    cx.set_state(
 8742        &r#"
 8743            let x = ˇ
 8744        "#
 8745        .unindent(),
 8746    );
 8747
 8748    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
 8749    cx.update_editor(|editor, window, cx| {
 8750        editor.handle_input("\"", window, cx);
 8751        editor.handle_input(" ", window, cx);
 8752        editor.move_left(&Default::default(), window, cx);
 8753        editor.handle_input("\\", window, cx);
 8754        editor.handle_input("\"", window, cx);
 8755    });
 8756    cx.assert_editor_state(
 8757        &r#"
 8758            let x = "\"ˇ "
 8759        "#
 8760        .unindent(),
 8761    );
 8762
 8763    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
 8764    // mark. Nothing is inserted.
 8765    cx.update_editor(|editor, window, cx| {
 8766        editor.move_right(&Default::default(), window, cx);
 8767        editor.handle_input("\"", window, cx);
 8768    });
 8769    cx.assert_editor_state(
 8770        &r#"
 8771            let x = "\" "ˇ
 8772        "#
 8773        .unindent(),
 8774    );
 8775}
 8776
 8777#[gpui::test]
 8778async fn test_surround_with_pair(cx: &mut TestAppContext) {
 8779    init_test(cx, |_| {});
 8780
 8781    let language = Arc::new(Language::new(
 8782        LanguageConfig {
 8783            brackets: BracketPairConfig {
 8784                pairs: vec![
 8785                    BracketPair {
 8786                        start: "{".to_string(),
 8787                        end: "}".to_string(),
 8788                        close: true,
 8789                        surround: true,
 8790                        newline: true,
 8791                    },
 8792                    BracketPair {
 8793                        start: "/* ".to_string(),
 8794                        end: "*/".to_string(),
 8795                        close: true,
 8796                        surround: true,
 8797                        ..Default::default()
 8798                    },
 8799                ],
 8800                ..Default::default()
 8801            },
 8802            ..Default::default()
 8803        },
 8804        Some(tree_sitter_rust::LANGUAGE.into()),
 8805    ));
 8806
 8807    let text = r#"
 8808        a
 8809        b
 8810        c
 8811    "#
 8812    .unindent();
 8813
 8814    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8815    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8816    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8817    editor
 8818        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8819        .await;
 8820
 8821    editor.update_in(cx, |editor, window, cx| {
 8822        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8823            s.select_display_ranges([
 8824                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8825                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8826                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
 8827            ])
 8828        });
 8829
 8830        editor.handle_input("{", window, cx);
 8831        editor.handle_input("{", window, cx);
 8832        editor.handle_input("{", window, cx);
 8833        assert_eq!(
 8834            editor.text(cx),
 8835            "
 8836                {{{a}}}
 8837                {{{b}}}
 8838                {{{c}}}
 8839            "
 8840            .unindent()
 8841        );
 8842        assert_eq!(
 8843            editor.selections.display_ranges(cx),
 8844            [
 8845                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
 8846                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
 8847                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
 8848            ]
 8849        );
 8850
 8851        editor.undo(&Undo, window, cx);
 8852        editor.undo(&Undo, window, cx);
 8853        editor.undo(&Undo, window, cx);
 8854        assert_eq!(
 8855            editor.text(cx),
 8856            "
 8857                a
 8858                b
 8859                c
 8860            "
 8861            .unindent()
 8862        );
 8863        assert_eq!(
 8864            editor.selections.display_ranges(cx),
 8865            [
 8866                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8867                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8868                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 8869            ]
 8870        );
 8871
 8872        // Ensure inserting the first character of a multi-byte bracket pair
 8873        // doesn't surround the selections with the bracket.
 8874        editor.handle_input("/", window, cx);
 8875        assert_eq!(
 8876            editor.text(cx),
 8877            "
 8878                /
 8879                /
 8880                /
 8881            "
 8882            .unindent()
 8883        );
 8884        assert_eq!(
 8885            editor.selections.display_ranges(cx),
 8886            [
 8887                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 8888                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 8889                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 8890            ]
 8891        );
 8892
 8893        editor.undo(&Undo, window, cx);
 8894        assert_eq!(
 8895            editor.text(cx),
 8896            "
 8897                a
 8898                b
 8899                c
 8900            "
 8901            .unindent()
 8902        );
 8903        assert_eq!(
 8904            editor.selections.display_ranges(cx),
 8905            [
 8906                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8907                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8908                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 8909            ]
 8910        );
 8911
 8912        // Ensure inserting the last character of a multi-byte bracket pair
 8913        // doesn't surround the selections with the bracket.
 8914        editor.handle_input("*", window, cx);
 8915        assert_eq!(
 8916            editor.text(cx),
 8917            "
 8918                *
 8919                *
 8920                *
 8921            "
 8922            .unindent()
 8923        );
 8924        assert_eq!(
 8925            editor.selections.display_ranges(cx),
 8926            [
 8927                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 8928                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 8929                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 8930            ]
 8931        );
 8932    });
 8933}
 8934
 8935#[gpui::test]
 8936async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
 8937    init_test(cx, |_| {});
 8938
 8939    let language = Arc::new(Language::new(
 8940        LanguageConfig {
 8941            brackets: BracketPairConfig {
 8942                pairs: vec![BracketPair {
 8943                    start: "{".to_string(),
 8944                    end: "}".to_string(),
 8945                    close: true,
 8946                    surround: true,
 8947                    newline: true,
 8948                }],
 8949                ..Default::default()
 8950            },
 8951            autoclose_before: "}".to_string(),
 8952            ..Default::default()
 8953        },
 8954        Some(tree_sitter_rust::LANGUAGE.into()),
 8955    ));
 8956
 8957    let text = r#"
 8958        a
 8959        b
 8960        c
 8961    "#
 8962    .unindent();
 8963
 8964    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8965    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8966    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8967    editor
 8968        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8969        .await;
 8970
 8971    editor.update_in(cx, |editor, window, cx| {
 8972        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8973            s.select_ranges([
 8974                Point::new(0, 1)..Point::new(0, 1),
 8975                Point::new(1, 1)..Point::new(1, 1),
 8976                Point::new(2, 1)..Point::new(2, 1),
 8977            ])
 8978        });
 8979
 8980        editor.handle_input("{", window, cx);
 8981        editor.handle_input("{", window, cx);
 8982        editor.handle_input("_", window, cx);
 8983        assert_eq!(
 8984            editor.text(cx),
 8985            "
 8986                a{{_}}
 8987                b{{_}}
 8988                c{{_}}
 8989            "
 8990            .unindent()
 8991        );
 8992        assert_eq!(
 8993            editor.selections.ranges::<Point>(cx),
 8994            [
 8995                Point::new(0, 4)..Point::new(0, 4),
 8996                Point::new(1, 4)..Point::new(1, 4),
 8997                Point::new(2, 4)..Point::new(2, 4)
 8998            ]
 8999        );
 9000
 9001        editor.backspace(&Default::default(), window, cx);
 9002        editor.backspace(&Default::default(), window, cx);
 9003        assert_eq!(
 9004            editor.text(cx),
 9005            "
 9006                a{}
 9007                b{}
 9008                c{}
 9009            "
 9010            .unindent()
 9011        );
 9012        assert_eq!(
 9013            editor.selections.ranges::<Point>(cx),
 9014            [
 9015                Point::new(0, 2)..Point::new(0, 2),
 9016                Point::new(1, 2)..Point::new(1, 2),
 9017                Point::new(2, 2)..Point::new(2, 2)
 9018            ]
 9019        );
 9020
 9021        editor.delete_to_previous_word_start(&Default::default(), window, cx);
 9022        assert_eq!(
 9023            editor.text(cx),
 9024            "
 9025                a
 9026                b
 9027                c
 9028            "
 9029            .unindent()
 9030        );
 9031        assert_eq!(
 9032            editor.selections.ranges::<Point>(cx),
 9033            [
 9034                Point::new(0, 1)..Point::new(0, 1),
 9035                Point::new(1, 1)..Point::new(1, 1),
 9036                Point::new(2, 1)..Point::new(2, 1)
 9037            ]
 9038        );
 9039    });
 9040}
 9041
 9042#[gpui::test]
 9043async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
 9044    init_test(cx, |settings| {
 9045        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 9046    });
 9047
 9048    let mut cx = EditorTestContext::new(cx).await;
 9049
 9050    let language = Arc::new(Language::new(
 9051        LanguageConfig {
 9052            brackets: BracketPairConfig {
 9053                pairs: vec![
 9054                    BracketPair {
 9055                        start: "{".to_string(),
 9056                        end: "}".to_string(),
 9057                        close: true,
 9058                        surround: true,
 9059                        newline: true,
 9060                    },
 9061                    BracketPair {
 9062                        start: "(".to_string(),
 9063                        end: ")".to_string(),
 9064                        close: true,
 9065                        surround: true,
 9066                        newline: true,
 9067                    },
 9068                    BracketPair {
 9069                        start: "[".to_string(),
 9070                        end: "]".to_string(),
 9071                        close: false,
 9072                        surround: true,
 9073                        newline: true,
 9074                    },
 9075                ],
 9076                ..Default::default()
 9077            },
 9078            autoclose_before: "})]".to_string(),
 9079            ..Default::default()
 9080        },
 9081        Some(tree_sitter_rust::LANGUAGE.into()),
 9082    ));
 9083
 9084    cx.language_registry().add(language.clone());
 9085    cx.update_buffer(|buffer, cx| {
 9086        buffer.set_language(Some(language), cx);
 9087    });
 9088
 9089    cx.set_state(
 9090        &"
 9091            {(ˇ)}
 9092            [[ˇ]]
 9093            {(ˇ)}
 9094        "
 9095        .unindent(),
 9096    );
 9097
 9098    cx.update_editor(|editor, window, cx| {
 9099        editor.backspace(&Default::default(), window, cx);
 9100        editor.backspace(&Default::default(), window, cx);
 9101    });
 9102
 9103    cx.assert_editor_state(
 9104        &"
 9105            ˇ
 9106            ˇ]]
 9107            ˇ
 9108        "
 9109        .unindent(),
 9110    );
 9111
 9112    cx.update_editor(|editor, window, cx| {
 9113        editor.handle_input("{", window, cx);
 9114        editor.handle_input("{", window, cx);
 9115        editor.move_right(&MoveRight, window, cx);
 9116        editor.move_right(&MoveRight, window, cx);
 9117        editor.move_left(&MoveLeft, window, cx);
 9118        editor.move_left(&MoveLeft, window, cx);
 9119        editor.backspace(&Default::default(), window, cx);
 9120    });
 9121
 9122    cx.assert_editor_state(
 9123        &"
 9124            {ˇ}
 9125            {ˇ}]]
 9126            {ˇ}
 9127        "
 9128        .unindent(),
 9129    );
 9130
 9131    cx.update_editor(|editor, window, cx| {
 9132        editor.backspace(&Default::default(), window, cx);
 9133    });
 9134
 9135    cx.assert_editor_state(
 9136        &"
 9137            ˇ
 9138            ˇ]]
 9139            ˇ
 9140        "
 9141        .unindent(),
 9142    );
 9143}
 9144
 9145#[gpui::test]
 9146async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
 9147    init_test(cx, |_| {});
 9148
 9149    let language = Arc::new(Language::new(
 9150        LanguageConfig::default(),
 9151        Some(tree_sitter_rust::LANGUAGE.into()),
 9152    ));
 9153
 9154    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
 9155    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9156    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9157    editor
 9158        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9159        .await;
 9160
 9161    editor.update_in(cx, |editor, window, cx| {
 9162        editor.set_auto_replace_emoji_shortcode(true);
 9163
 9164        editor.handle_input("Hello ", window, cx);
 9165        editor.handle_input(":wave", window, cx);
 9166        assert_eq!(editor.text(cx), "Hello :wave".unindent());
 9167
 9168        editor.handle_input(":", window, cx);
 9169        assert_eq!(editor.text(cx), "Hello 👋".unindent());
 9170
 9171        editor.handle_input(" :smile", window, cx);
 9172        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
 9173
 9174        editor.handle_input(":", window, cx);
 9175        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
 9176
 9177        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
 9178        editor.handle_input(":wave", window, cx);
 9179        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
 9180
 9181        editor.handle_input(":", window, cx);
 9182        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
 9183
 9184        editor.handle_input(":1", window, cx);
 9185        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
 9186
 9187        editor.handle_input(":", window, cx);
 9188        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
 9189
 9190        // Ensure shortcode does not get replaced when it is part of a word
 9191        editor.handle_input(" Test:wave", window, cx);
 9192        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
 9193
 9194        editor.handle_input(":", window, cx);
 9195        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
 9196
 9197        editor.set_auto_replace_emoji_shortcode(false);
 9198
 9199        // Ensure shortcode does not get replaced when auto replace is off
 9200        editor.handle_input(" :wave", window, cx);
 9201        assert_eq!(
 9202            editor.text(cx),
 9203            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
 9204        );
 9205
 9206        editor.handle_input(":", window, cx);
 9207        assert_eq!(
 9208            editor.text(cx),
 9209            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
 9210        );
 9211    });
 9212}
 9213
 9214#[gpui::test]
 9215async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
 9216    init_test(cx, |_| {});
 9217
 9218    let (text, insertion_ranges) = marked_text_ranges(
 9219        indoc! {"
 9220            ˇ
 9221        "},
 9222        false,
 9223    );
 9224
 9225    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
 9226    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9227
 9228    _ = editor.update_in(cx, |editor, window, cx| {
 9229        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
 9230
 9231        editor
 9232            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9233            .unwrap();
 9234
 9235        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
 9236            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
 9237            assert_eq!(editor.text(cx), expected_text);
 9238            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
 9239        }
 9240
 9241        assert(
 9242            editor,
 9243            cx,
 9244            indoc! {"
 9245            type «» =•
 9246            "},
 9247        );
 9248
 9249        assert!(editor.context_menu_visible(), "There should be a matches");
 9250    });
 9251}
 9252
 9253#[gpui::test]
 9254async fn test_snippets(cx: &mut TestAppContext) {
 9255    init_test(cx, |_| {});
 9256
 9257    let mut cx = EditorTestContext::new(cx).await;
 9258
 9259    cx.set_state(indoc! {"
 9260        a.ˇ b
 9261        a.ˇ b
 9262        a.ˇ b
 9263    "});
 9264
 9265    cx.update_editor(|editor, window, cx| {
 9266        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
 9267        let insertion_ranges = editor
 9268            .selections
 9269            .all(cx)
 9270            .iter()
 9271            .map(|s| s.range().clone())
 9272            .collect::<Vec<_>>();
 9273        editor
 9274            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9275            .unwrap();
 9276    });
 9277
 9278    cx.assert_editor_state(indoc! {"
 9279        a.f(«oneˇ», two, «threeˇ») b
 9280        a.f(«oneˇ», two, «threeˇ») b
 9281        a.f(«oneˇ», two, «threeˇ») b
 9282    "});
 9283
 9284    // Can't move earlier than the first tab stop
 9285    cx.update_editor(|editor, window, cx| {
 9286        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 9287    });
 9288    cx.assert_editor_state(indoc! {"
 9289        a.f(«oneˇ», two, «threeˇ») b
 9290        a.f(«oneˇ», two, «threeˇ») b
 9291        a.f(«oneˇ», two, «threeˇ») b
 9292    "});
 9293
 9294    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9295    cx.assert_editor_state(indoc! {"
 9296        a.f(one, «twoˇ», three) b
 9297        a.f(one, «twoˇ», three) b
 9298        a.f(one, «twoˇ», three) b
 9299    "});
 9300
 9301    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
 9302    cx.assert_editor_state(indoc! {"
 9303        a.f(«oneˇ», two, «threeˇ») b
 9304        a.f(«oneˇ», two, «threeˇ») b
 9305        a.f(«oneˇ», two, «threeˇ») b
 9306    "});
 9307
 9308    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9309    cx.assert_editor_state(indoc! {"
 9310        a.f(one, «twoˇ», three) b
 9311        a.f(one, «twoˇ», three) b
 9312        a.f(one, «twoˇ», three) b
 9313    "});
 9314    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 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    // As soon as the last tab stop is reached, snippet state is gone
 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
 9332#[gpui::test]
 9333async fn test_snippet_indentation(cx: &mut TestAppContext) {
 9334    init_test(cx, |_| {});
 9335
 9336    let mut cx = EditorTestContext::new(cx).await;
 9337
 9338    cx.update_editor(|editor, window, cx| {
 9339        let snippet = Snippet::parse(indoc! {"
 9340            /*
 9341             * Multiline comment with leading indentation
 9342             *
 9343             * $1
 9344             */
 9345            $0"})
 9346        .unwrap();
 9347        let insertion_ranges = editor
 9348            .selections
 9349            .all(cx)
 9350            .iter()
 9351            .map(|s| s.range().clone())
 9352            .collect::<Vec<_>>();
 9353        editor
 9354            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9355            .unwrap();
 9356    });
 9357
 9358    cx.assert_editor_state(indoc! {"
 9359        /*
 9360         * Multiline comment with leading indentation
 9361         *
 9362         * ˇ
 9363         */
 9364    "});
 9365
 9366    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9367    cx.assert_editor_state(indoc! {"
 9368        /*
 9369         * Multiline comment with leading indentation
 9370         *
 9371         *•
 9372         */
 9373        ˇ"});
 9374}
 9375
 9376#[gpui::test]
 9377async fn test_document_format_during_save(cx: &mut TestAppContext) {
 9378    init_test(cx, |_| {});
 9379
 9380    let fs = FakeFs::new(cx.executor());
 9381    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9382
 9383    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
 9384
 9385    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9386    language_registry.add(rust_lang());
 9387    let mut fake_servers = language_registry.register_fake_lsp(
 9388        "Rust",
 9389        FakeLspAdapter {
 9390            capabilities: lsp::ServerCapabilities {
 9391                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9392                ..Default::default()
 9393            },
 9394            ..Default::default()
 9395        },
 9396    );
 9397
 9398    let buffer = project
 9399        .update(cx, |project, cx| {
 9400            project.open_local_buffer(path!("/file.rs"), cx)
 9401        })
 9402        .await
 9403        .unwrap();
 9404
 9405    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9406    let (editor, cx) = cx.add_window_view(|window, cx| {
 9407        build_editor_with_project(project.clone(), buffer, window, cx)
 9408    });
 9409    editor.update_in(cx, |editor, window, cx| {
 9410        editor.set_text("one\ntwo\nthree\n", window, cx)
 9411    });
 9412    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9413
 9414    cx.executor().start_waiting();
 9415    let fake_server = fake_servers.next().await.unwrap();
 9416
 9417    {
 9418        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9419            move |params, _| async move {
 9420                assert_eq!(
 9421                    params.text_document.uri,
 9422                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9423                );
 9424                assert_eq!(params.options.tab_size, 4);
 9425                Ok(Some(vec![lsp::TextEdit::new(
 9426                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9427                    ", ".to_string(),
 9428                )]))
 9429            },
 9430        );
 9431        let save = editor
 9432            .update_in(cx, |editor, window, cx| {
 9433                editor.save(
 9434                    SaveOptions {
 9435                        format: true,
 9436                        autosave: false,
 9437                    },
 9438                    project.clone(),
 9439                    window,
 9440                    cx,
 9441                )
 9442            })
 9443            .unwrap();
 9444        cx.executor().start_waiting();
 9445        save.await;
 9446
 9447        assert_eq!(
 9448            editor.update(cx, |editor, cx| editor.text(cx)),
 9449            "one, two\nthree\n"
 9450        );
 9451        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9452    }
 9453
 9454    {
 9455        editor.update_in(cx, |editor, window, cx| {
 9456            editor.set_text("one\ntwo\nthree\n", window, cx)
 9457        });
 9458        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9459
 9460        // Ensure we can still save even if formatting hangs.
 9461        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9462            move |params, _| async move {
 9463                assert_eq!(
 9464                    params.text_document.uri,
 9465                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9466                );
 9467                futures::future::pending::<()>().await;
 9468                unreachable!()
 9469            },
 9470        );
 9471        let save = editor
 9472            .update_in(cx, |editor, window, cx| {
 9473                editor.save(
 9474                    SaveOptions {
 9475                        format: true,
 9476                        autosave: false,
 9477                    },
 9478                    project.clone(),
 9479                    window,
 9480                    cx,
 9481                )
 9482            })
 9483            .unwrap();
 9484        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9485        cx.executor().start_waiting();
 9486        save.await;
 9487        assert_eq!(
 9488            editor.update(cx, |editor, cx| editor.text(cx)),
 9489            "one\ntwo\nthree\n"
 9490        );
 9491    }
 9492
 9493    // Set rust language override and assert overridden tabsize is sent to language server
 9494    update_test_language_settings(cx, |settings| {
 9495        settings.languages.0.insert(
 9496            "Rust".into(),
 9497            LanguageSettingsContent {
 9498                tab_size: NonZeroU32::new(8),
 9499                ..Default::default()
 9500            },
 9501        );
 9502    });
 9503
 9504    {
 9505        editor.update_in(cx, |editor, window, cx| {
 9506            editor.set_text("somehting_new\n", window, cx)
 9507        });
 9508        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9509        let _formatting_request_signal = fake_server
 9510            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9511                assert_eq!(
 9512                    params.text_document.uri,
 9513                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9514                );
 9515                assert_eq!(params.options.tab_size, 8);
 9516                Ok(Some(vec![]))
 9517            });
 9518        let save = editor
 9519            .update_in(cx, |editor, window, cx| {
 9520                editor.save(
 9521                    SaveOptions {
 9522                        format: true,
 9523                        autosave: false,
 9524                    },
 9525                    project.clone(),
 9526                    window,
 9527                    cx,
 9528                )
 9529            })
 9530            .unwrap();
 9531        cx.executor().start_waiting();
 9532        save.await;
 9533    }
 9534}
 9535
 9536#[gpui::test]
 9537async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
 9538    init_test(cx, |_| {});
 9539
 9540    let cols = 4;
 9541    let rows = 10;
 9542    let sample_text_1 = sample_text(rows, cols, 'a');
 9543    assert_eq!(
 9544        sample_text_1,
 9545        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
 9546    );
 9547    let sample_text_2 = sample_text(rows, cols, 'l');
 9548    assert_eq!(
 9549        sample_text_2,
 9550        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
 9551    );
 9552    let sample_text_3 = sample_text(rows, cols, 'v');
 9553    assert_eq!(
 9554        sample_text_3,
 9555        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
 9556    );
 9557
 9558    let fs = FakeFs::new(cx.executor());
 9559    fs.insert_tree(
 9560        path!("/a"),
 9561        json!({
 9562            "main.rs": sample_text_1,
 9563            "other.rs": sample_text_2,
 9564            "lib.rs": sample_text_3,
 9565        }),
 9566    )
 9567    .await;
 9568
 9569    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
 9570    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9571    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 9572
 9573    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9574    language_registry.add(rust_lang());
 9575    let mut fake_servers = language_registry.register_fake_lsp(
 9576        "Rust",
 9577        FakeLspAdapter {
 9578            capabilities: lsp::ServerCapabilities {
 9579                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9580                ..Default::default()
 9581            },
 9582            ..Default::default()
 9583        },
 9584    );
 9585
 9586    let worktree = project.update(cx, |project, cx| {
 9587        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
 9588        assert_eq!(worktrees.len(), 1);
 9589        worktrees.pop().unwrap()
 9590    });
 9591    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 9592
 9593    let buffer_1 = project
 9594        .update(cx, |project, cx| {
 9595            project.open_buffer((worktree_id, "main.rs"), cx)
 9596        })
 9597        .await
 9598        .unwrap();
 9599    let buffer_2 = project
 9600        .update(cx, |project, cx| {
 9601            project.open_buffer((worktree_id, "other.rs"), cx)
 9602        })
 9603        .await
 9604        .unwrap();
 9605    let buffer_3 = project
 9606        .update(cx, |project, cx| {
 9607            project.open_buffer((worktree_id, "lib.rs"), cx)
 9608        })
 9609        .await
 9610        .unwrap();
 9611
 9612    let multi_buffer = cx.new(|cx| {
 9613        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 9614        multi_buffer.push_excerpts(
 9615            buffer_1.clone(),
 9616            [
 9617                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9618                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9619                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9620            ],
 9621            cx,
 9622        );
 9623        multi_buffer.push_excerpts(
 9624            buffer_2.clone(),
 9625            [
 9626                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9627                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9628                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9629            ],
 9630            cx,
 9631        );
 9632        multi_buffer.push_excerpts(
 9633            buffer_3.clone(),
 9634            [
 9635                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9636                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9637                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9638            ],
 9639            cx,
 9640        );
 9641        multi_buffer
 9642    });
 9643    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
 9644        Editor::new(
 9645            EditorMode::full(),
 9646            multi_buffer,
 9647            Some(project.clone()),
 9648            window,
 9649            cx,
 9650        )
 9651    });
 9652
 9653    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 9654        editor.change_selections(
 9655            SelectionEffects::scroll(Autoscroll::Next),
 9656            window,
 9657            cx,
 9658            |s| s.select_ranges(Some(1..2)),
 9659        );
 9660        editor.insert("|one|two|three|", window, cx);
 9661    });
 9662    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 9663    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 9664        editor.change_selections(
 9665            SelectionEffects::scroll(Autoscroll::Next),
 9666            window,
 9667            cx,
 9668            |s| s.select_ranges(Some(60..70)),
 9669        );
 9670        editor.insert("|four|five|six|", window, cx);
 9671    });
 9672    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 9673
 9674    // First two buffers should be edited, but not the third one.
 9675    assert_eq!(
 9676        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 9677        "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}",
 9678    );
 9679    buffer_1.update(cx, |buffer, _| {
 9680        assert!(buffer.is_dirty());
 9681        assert_eq!(
 9682            buffer.text(),
 9683            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
 9684        )
 9685    });
 9686    buffer_2.update(cx, |buffer, _| {
 9687        assert!(buffer.is_dirty());
 9688        assert_eq!(
 9689            buffer.text(),
 9690            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
 9691        )
 9692    });
 9693    buffer_3.update(cx, |buffer, _| {
 9694        assert!(!buffer.is_dirty());
 9695        assert_eq!(buffer.text(), sample_text_3,)
 9696    });
 9697    cx.executor().run_until_parked();
 9698
 9699    cx.executor().start_waiting();
 9700    let save = multi_buffer_editor
 9701        .update_in(cx, |editor, window, cx| {
 9702            editor.save(
 9703                SaveOptions {
 9704                    format: true,
 9705                    autosave: false,
 9706                },
 9707                project.clone(),
 9708                window,
 9709                cx,
 9710            )
 9711        })
 9712        .unwrap();
 9713
 9714    let fake_server = fake_servers.next().await.unwrap();
 9715    fake_server
 9716        .server
 9717        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9718            Ok(Some(vec![lsp::TextEdit::new(
 9719                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9720                format!("[{} formatted]", params.text_document.uri),
 9721            )]))
 9722        })
 9723        .detach();
 9724    save.await;
 9725
 9726    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
 9727    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
 9728    assert_eq!(
 9729        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 9730        uri!(
 9731            "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}"
 9732        ),
 9733    );
 9734    buffer_1.update(cx, |buffer, _| {
 9735        assert!(!buffer.is_dirty());
 9736        assert_eq!(
 9737            buffer.text(),
 9738            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
 9739        )
 9740    });
 9741    buffer_2.update(cx, |buffer, _| {
 9742        assert!(!buffer.is_dirty());
 9743        assert_eq!(
 9744            buffer.text(),
 9745            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
 9746        )
 9747    });
 9748    buffer_3.update(cx, |buffer, _| {
 9749        assert!(!buffer.is_dirty());
 9750        assert_eq!(buffer.text(), sample_text_3,)
 9751    });
 9752}
 9753
 9754#[gpui::test]
 9755async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
 9756    init_test(cx, |_| {});
 9757
 9758    let fs = FakeFs::new(cx.executor());
 9759    fs.insert_tree(
 9760        path!("/dir"),
 9761        json!({
 9762            "file1.rs": "fn main() { println!(\"hello\"); }",
 9763            "file2.rs": "fn test() { println!(\"test\"); }",
 9764            "file3.rs": "fn other() { println!(\"other\"); }\n",
 9765        }),
 9766    )
 9767    .await;
 9768
 9769    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
 9770    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9771    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 9772
 9773    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9774    language_registry.add(rust_lang());
 9775
 9776    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
 9777    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 9778
 9779    // Open three buffers
 9780    let buffer_1 = project
 9781        .update(cx, |project, cx| {
 9782            project.open_buffer((worktree_id, "file1.rs"), cx)
 9783        })
 9784        .await
 9785        .unwrap();
 9786    let buffer_2 = project
 9787        .update(cx, |project, cx| {
 9788            project.open_buffer((worktree_id, "file2.rs"), cx)
 9789        })
 9790        .await
 9791        .unwrap();
 9792    let buffer_3 = project
 9793        .update(cx, |project, cx| {
 9794            project.open_buffer((worktree_id, "file3.rs"), cx)
 9795        })
 9796        .await
 9797        .unwrap();
 9798
 9799    // Create a multi-buffer with all three buffers
 9800    let multi_buffer = cx.new(|cx| {
 9801        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 9802        multi_buffer.push_excerpts(
 9803            buffer_1.clone(),
 9804            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9805            cx,
 9806        );
 9807        multi_buffer.push_excerpts(
 9808            buffer_2.clone(),
 9809            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9810            cx,
 9811        );
 9812        multi_buffer.push_excerpts(
 9813            buffer_3.clone(),
 9814            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9815            cx,
 9816        );
 9817        multi_buffer
 9818    });
 9819
 9820    let editor = cx.new_window_entity(|window, cx| {
 9821        Editor::new(
 9822            EditorMode::full(),
 9823            multi_buffer,
 9824            Some(project.clone()),
 9825            window,
 9826            cx,
 9827        )
 9828    });
 9829
 9830    // Edit only the first buffer
 9831    editor.update_in(cx, |editor, window, cx| {
 9832        editor.change_selections(
 9833            SelectionEffects::scroll(Autoscroll::Next),
 9834            window,
 9835            cx,
 9836            |s| s.select_ranges(Some(10..10)),
 9837        );
 9838        editor.insert("// edited", window, cx);
 9839    });
 9840
 9841    // Verify that only buffer 1 is dirty
 9842    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
 9843    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9844    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9845
 9846    // Get write counts after file creation (files were created with initial content)
 9847    // We expect each file to have been written once during creation
 9848    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
 9849    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
 9850    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
 9851
 9852    // Perform autosave
 9853    let save_task = editor.update_in(cx, |editor, window, cx| {
 9854        editor.save(
 9855            SaveOptions {
 9856                format: true,
 9857                autosave: true,
 9858            },
 9859            project.clone(),
 9860            window,
 9861            cx,
 9862        )
 9863    });
 9864    save_task.await.unwrap();
 9865
 9866    // Only the dirty buffer should have been saved
 9867    assert_eq!(
 9868        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
 9869        1,
 9870        "Buffer 1 was dirty, so it should have been written once during autosave"
 9871    );
 9872    assert_eq!(
 9873        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
 9874        0,
 9875        "Buffer 2 was clean, so it should not have been written during autosave"
 9876    );
 9877    assert_eq!(
 9878        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
 9879        0,
 9880        "Buffer 3 was clean, so it should not have been written during autosave"
 9881    );
 9882
 9883    // Verify buffer states after autosave
 9884    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9885    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9886    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9887
 9888    // Now perform a manual save (format = true)
 9889    let save_task = editor.update_in(cx, |editor, window, cx| {
 9890        editor.save(
 9891            SaveOptions {
 9892                format: true,
 9893                autosave: false,
 9894            },
 9895            project.clone(),
 9896            window,
 9897            cx,
 9898        )
 9899    });
 9900    save_task.await.unwrap();
 9901
 9902    // During manual save, clean buffers don't get written to disk
 9903    // They just get did_save called for language server notifications
 9904    assert_eq!(
 9905        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
 9906        1,
 9907        "Buffer 1 should only have been written once total (during autosave, not manual save)"
 9908    );
 9909    assert_eq!(
 9910        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
 9911        0,
 9912        "Buffer 2 should not have been written at all"
 9913    );
 9914    assert_eq!(
 9915        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
 9916        0,
 9917        "Buffer 3 should not have been written at all"
 9918    );
 9919}
 9920
 9921#[gpui::test]
 9922async fn test_range_format_during_save(cx: &mut TestAppContext) {
 9923    init_test(cx, |_| {});
 9924
 9925    let fs = FakeFs::new(cx.executor());
 9926    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9927
 9928    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 9929
 9930    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9931    language_registry.add(rust_lang());
 9932    let mut fake_servers = language_registry.register_fake_lsp(
 9933        "Rust",
 9934        FakeLspAdapter {
 9935            capabilities: lsp::ServerCapabilities {
 9936                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
 9937                ..Default::default()
 9938            },
 9939            ..Default::default()
 9940        },
 9941    );
 9942
 9943    let buffer = project
 9944        .update(cx, |project, cx| {
 9945            project.open_local_buffer(path!("/file.rs"), cx)
 9946        })
 9947        .await
 9948        .unwrap();
 9949
 9950    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9951    let (editor, cx) = cx.add_window_view(|window, cx| {
 9952        build_editor_with_project(project.clone(), buffer, window, cx)
 9953    });
 9954    editor.update_in(cx, |editor, window, cx| {
 9955        editor.set_text("one\ntwo\nthree\n", window, cx)
 9956    });
 9957    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9958
 9959    cx.executor().start_waiting();
 9960    let fake_server = fake_servers.next().await.unwrap();
 9961
 9962    let save = editor
 9963        .update_in(cx, |editor, window, cx| {
 9964            editor.save(
 9965                SaveOptions {
 9966                    format: true,
 9967                    autosave: false,
 9968                },
 9969                project.clone(),
 9970                window,
 9971                cx,
 9972            )
 9973        })
 9974        .unwrap();
 9975    fake_server
 9976        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
 9977            assert_eq!(
 9978                params.text_document.uri,
 9979                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9980            );
 9981            assert_eq!(params.options.tab_size, 4);
 9982            Ok(Some(vec![lsp::TextEdit::new(
 9983                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9984                ", ".to_string(),
 9985            )]))
 9986        })
 9987        .next()
 9988        .await;
 9989    cx.executor().start_waiting();
 9990    save.await;
 9991    assert_eq!(
 9992        editor.update(cx, |editor, cx| editor.text(cx)),
 9993        "one, two\nthree\n"
 9994    );
 9995    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9996
 9997    editor.update_in(cx, |editor, window, cx| {
 9998        editor.set_text("one\ntwo\nthree\n", window, cx)
 9999    });
10000    assert!(cx.read(|cx| editor.is_dirty(cx)));
10001
10002    // Ensure we can still save even if formatting hangs.
10003    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
10004        move |params, _| async move {
10005            assert_eq!(
10006                params.text_document.uri,
10007                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10008            );
10009            futures::future::pending::<()>().await;
10010            unreachable!()
10011        },
10012    );
10013    let save = editor
10014        .update_in(cx, |editor, window, cx| {
10015            editor.save(
10016                SaveOptions {
10017                    format: true,
10018                    autosave: false,
10019                },
10020                project.clone(),
10021                window,
10022                cx,
10023            )
10024        })
10025        .unwrap();
10026    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10027    cx.executor().start_waiting();
10028    save.await;
10029    assert_eq!(
10030        editor.update(cx, |editor, cx| editor.text(cx)),
10031        "one\ntwo\nthree\n"
10032    );
10033    assert!(!cx.read(|cx| editor.is_dirty(cx)));
10034
10035    // For non-dirty buffer, no formatting request should be sent
10036    let save = editor
10037        .update_in(cx, |editor, window, cx| {
10038            editor.save(
10039                SaveOptions {
10040                    format: false,
10041                    autosave: false,
10042                },
10043                project.clone(),
10044                window,
10045                cx,
10046            )
10047        })
10048        .unwrap();
10049    let _pending_format_request = fake_server
10050        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
10051            panic!("Should not be invoked");
10052        })
10053        .next();
10054    cx.executor().start_waiting();
10055    save.await;
10056
10057    // Set Rust language override and assert overridden tabsize is sent to language server
10058    update_test_language_settings(cx, |settings| {
10059        settings.languages.0.insert(
10060            "Rust".into(),
10061            LanguageSettingsContent {
10062                tab_size: NonZeroU32::new(8),
10063                ..Default::default()
10064            },
10065        );
10066    });
10067
10068    editor.update_in(cx, |editor, window, cx| {
10069        editor.set_text("somehting_new\n", window, cx)
10070    });
10071    assert!(cx.read(|cx| editor.is_dirty(cx)));
10072    let save = editor
10073        .update_in(cx, |editor, window, cx| {
10074            editor.save(
10075                SaveOptions {
10076                    format: true,
10077                    autosave: false,
10078                },
10079                project.clone(),
10080                window,
10081                cx,
10082            )
10083        })
10084        .unwrap();
10085    fake_server
10086        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
10087            assert_eq!(
10088                params.text_document.uri,
10089                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10090            );
10091            assert_eq!(params.options.tab_size, 8);
10092            Ok(Some(Vec::new()))
10093        })
10094        .next()
10095        .await;
10096    save.await;
10097}
10098
10099#[gpui::test]
10100async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
10101    init_test(cx, |settings| {
10102        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
10103            Formatter::LanguageServer { name: None },
10104        )))
10105    });
10106
10107    let fs = FakeFs::new(cx.executor());
10108    fs.insert_file(path!("/file.rs"), Default::default()).await;
10109
10110    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10111
10112    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10113    language_registry.add(Arc::new(Language::new(
10114        LanguageConfig {
10115            name: "Rust".into(),
10116            matcher: LanguageMatcher {
10117                path_suffixes: vec!["rs".to_string()],
10118                ..Default::default()
10119            },
10120            ..LanguageConfig::default()
10121        },
10122        Some(tree_sitter_rust::LANGUAGE.into()),
10123    )));
10124    update_test_language_settings(cx, |settings| {
10125        // Enable Prettier formatting for the same buffer, and ensure
10126        // LSP is called instead of Prettier.
10127        settings.defaults.prettier = Some(PrettierSettings {
10128            allowed: true,
10129            ..PrettierSettings::default()
10130        });
10131    });
10132    let mut fake_servers = language_registry.register_fake_lsp(
10133        "Rust",
10134        FakeLspAdapter {
10135            capabilities: lsp::ServerCapabilities {
10136                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10137                ..Default::default()
10138            },
10139            ..Default::default()
10140        },
10141    );
10142
10143    let buffer = project
10144        .update(cx, |project, cx| {
10145            project.open_local_buffer(path!("/file.rs"), cx)
10146        })
10147        .await
10148        .unwrap();
10149
10150    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10151    let (editor, cx) = cx.add_window_view(|window, cx| {
10152        build_editor_with_project(project.clone(), buffer, window, cx)
10153    });
10154    editor.update_in(cx, |editor, window, cx| {
10155        editor.set_text("one\ntwo\nthree\n", window, cx)
10156    });
10157
10158    cx.executor().start_waiting();
10159    let fake_server = fake_servers.next().await.unwrap();
10160
10161    let format = editor
10162        .update_in(cx, |editor, window, cx| {
10163            editor.perform_format(
10164                project.clone(),
10165                FormatTrigger::Manual,
10166                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10167                window,
10168                cx,
10169            )
10170        })
10171        .unwrap();
10172    fake_server
10173        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
10174            assert_eq!(
10175                params.text_document.uri,
10176                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10177            );
10178            assert_eq!(params.options.tab_size, 4);
10179            Ok(Some(vec![lsp::TextEdit::new(
10180                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10181                ", ".to_string(),
10182            )]))
10183        })
10184        .next()
10185        .await;
10186    cx.executor().start_waiting();
10187    format.await;
10188    assert_eq!(
10189        editor.update(cx, |editor, cx| editor.text(cx)),
10190        "one, two\nthree\n"
10191    );
10192
10193    editor.update_in(cx, |editor, window, cx| {
10194        editor.set_text("one\ntwo\nthree\n", window, cx)
10195    });
10196    // Ensure we don't lock if formatting hangs.
10197    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10198        move |params, _| async move {
10199            assert_eq!(
10200                params.text_document.uri,
10201                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10202            );
10203            futures::future::pending::<()>().await;
10204            unreachable!()
10205        },
10206    );
10207    let format = editor
10208        .update_in(cx, |editor, window, cx| {
10209            editor.perform_format(
10210                project,
10211                FormatTrigger::Manual,
10212                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10213                window,
10214                cx,
10215            )
10216        })
10217        .unwrap();
10218    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10219    cx.executor().start_waiting();
10220    format.await;
10221    assert_eq!(
10222        editor.update(cx, |editor, cx| editor.text(cx)),
10223        "one\ntwo\nthree\n"
10224    );
10225}
10226
10227#[gpui::test]
10228async fn test_multiple_formatters(cx: &mut TestAppContext) {
10229    init_test(cx, |settings| {
10230        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
10231        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
10232            Formatter::LanguageServer { name: None },
10233            Formatter::CodeActions(
10234                [
10235                    ("code-action-1".into(), true),
10236                    ("code-action-2".into(), true),
10237                ]
10238                .into_iter()
10239                .collect(),
10240            ),
10241        ])))
10242    });
10243
10244    let fs = FakeFs::new(cx.executor());
10245    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
10246        .await;
10247
10248    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10249    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10250    language_registry.add(rust_lang());
10251
10252    let mut fake_servers = language_registry.register_fake_lsp(
10253        "Rust",
10254        FakeLspAdapter {
10255            capabilities: lsp::ServerCapabilities {
10256                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10257                execute_command_provider: Some(lsp::ExecuteCommandOptions {
10258                    commands: vec!["the-command-for-code-action-1".into()],
10259                    ..Default::default()
10260                }),
10261                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10262                ..Default::default()
10263            },
10264            ..Default::default()
10265        },
10266    );
10267
10268    let buffer = project
10269        .update(cx, |project, cx| {
10270            project.open_local_buffer(path!("/file.rs"), cx)
10271        })
10272        .await
10273        .unwrap();
10274
10275    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10276    let (editor, cx) = cx.add_window_view(|window, cx| {
10277        build_editor_with_project(project.clone(), buffer, window, cx)
10278    });
10279
10280    cx.executor().start_waiting();
10281
10282    let fake_server = fake_servers.next().await.unwrap();
10283    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10284        move |_params, _| async move {
10285            Ok(Some(vec![lsp::TextEdit::new(
10286                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10287                "applied-formatting\n".to_string(),
10288            )]))
10289        },
10290    );
10291    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
10292        move |params, _| async move {
10293            assert_eq!(
10294                params.context.only,
10295                Some(vec!["code-action-1".into(), "code-action-2".into()])
10296            );
10297            let uri = lsp::Url::from_file_path(path!("/file.rs")).unwrap();
10298            Ok(Some(vec![
10299                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10300                    kind: Some("code-action-1".into()),
10301                    edit: Some(lsp::WorkspaceEdit::new(
10302                        [(
10303                            uri.clone(),
10304                            vec![lsp::TextEdit::new(
10305                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10306                                "applied-code-action-1-edit\n".to_string(),
10307                            )],
10308                        )]
10309                        .into_iter()
10310                        .collect(),
10311                    )),
10312                    command: Some(lsp::Command {
10313                        command: "the-command-for-code-action-1".into(),
10314                        ..Default::default()
10315                    }),
10316                    ..Default::default()
10317                }),
10318                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10319                    kind: Some("code-action-2".into()),
10320                    edit: Some(lsp::WorkspaceEdit::new(
10321                        [(
10322                            uri.clone(),
10323                            vec![lsp::TextEdit::new(
10324                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10325                                "applied-code-action-2-edit\n".to_string(),
10326                            )],
10327                        )]
10328                        .into_iter()
10329                        .collect(),
10330                    )),
10331                    ..Default::default()
10332                }),
10333            ]))
10334        },
10335    );
10336
10337    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
10338        move |params, _| async move { Ok(params) }
10339    });
10340
10341    let command_lock = Arc::new(futures::lock::Mutex::new(()));
10342    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
10343        let fake = fake_server.clone();
10344        let lock = command_lock.clone();
10345        move |params, _| {
10346            assert_eq!(params.command, "the-command-for-code-action-1");
10347            let fake = fake.clone();
10348            let lock = lock.clone();
10349            async move {
10350                lock.lock().await;
10351                fake.server
10352                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
10353                        label: None,
10354                        edit: lsp::WorkspaceEdit {
10355                            changes: Some(
10356                                [(
10357                                    lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
10358                                    vec![lsp::TextEdit {
10359                                        range: lsp::Range::new(
10360                                            lsp::Position::new(0, 0),
10361                                            lsp::Position::new(0, 0),
10362                                        ),
10363                                        new_text: "applied-code-action-1-command\n".into(),
10364                                    }],
10365                                )]
10366                                .into_iter()
10367                                .collect(),
10368                            ),
10369                            ..Default::default()
10370                        },
10371                    })
10372                    .await
10373                    .into_response()
10374                    .unwrap();
10375                Ok(Some(json!(null)))
10376            }
10377        }
10378    });
10379
10380    cx.executor().start_waiting();
10381    editor
10382        .update_in(cx, |editor, window, cx| {
10383            editor.perform_format(
10384                project.clone(),
10385                FormatTrigger::Manual,
10386                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10387                window,
10388                cx,
10389            )
10390        })
10391        .unwrap()
10392        .await;
10393    editor.update(cx, |editor, cx| {
10394        assert_eq!(
10395            editor.text(cx),
10396            r#"
10397                applied-code-action-2-edit
10398                applied-code-action-1-command
10399                applied-code-action-1-edit
10400                applied-formatting
10401                one
10402                two
10403                three
10404            "#
10405            .unindent()
10406        );
10407    });
10408
10409    editor.update_in(cx, |editor, window, cx| {
10410        editor.undo(&Default::default(), window, cx);
10411        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10412    });
10413
10414    // Perform a manual edit while waiting for an LSP command
10415    // that's being run as part of a formatting code action.
10416    let lock_guard = command_lock.lock().await;
10417    let format = editor
10418        .update_in(cx, |editor, window, cx| {
10419            editor.perform_format(
10420                project.clone(),
10421                FormatTrigger::Manual,
10422                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10423                window,
10424                cx,
10425            )
10426        })
10427        .unwrap();
10428    cx.run_until_parked();
10429    editor.update(cx, |editor, cx| {
10430        assert_eq!(
10431            editor.text(cx),
10432            r#"
10433                applied-code-action-1-edit
10434                applied-formatting
10435                one
10436                two
10437                three
10438            "#
10439            .unindent()
10440        );
10441
10442        editor.buffer.update(cx, |buffer, cx| {
10443            let ix = buffer.len(cx);
10444            buffer.edit([(ix..ix, "edited\n")], None, cx);
10445        });
10446    });
10447
10448    // Allow the LSP command to proceed. Because the buffer was edited,
10449    // the second code action will not be run.
10450    drop(lock_guard);
10451    format.await;
10452    editor.update_in(cx, |editor, window, cx| {
10453        assert_eq!(
10454            editor.text(cx),
10455            r#"
10456                applied-code-action-1-command
10457                applied-code-action-1-edit
10458                applied-formatting
10459                one
10460                two
10461                three
10462                edited
10463            "#
10464            .unindent()
10465        );
10466
10467        // The manual edit is undone first, because it is the last thing the user did
10468        // (even though the command completed afterwards).
10469        editor.undo(&Default::default(), window, cx);
10470        assert_eq!(
10471            editor.text(cx),
10472            r#"
10473                applied-code-action-1-command
10474                applied-code-action-1-edit
10475                applied-formatting
10476                one
10477                two
10478                three
10479            "#
10480            .unindent()
10481        );
10482
10483        // All the formatting (including the command, which completed after the manual edit)
10484        // is undone together.
10485        editor.undo(&Default::default(), window, cx);
10486        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10487    });
10488}
10489
10490#[gpui::test]
10491async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
10492    init_test(cx, |settings| {
10493        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
10494            Formatter::LanguageServer { name: None },
10495        ])))
10496    });
10497
10498    let fs = FakeFs::new(cx.executor());
10499    fs.insert_file(path!("/file.ts"), Default::default()).await;
10500
10501    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10502
10503    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10504    language_registry.add(Arc::new(Language::new(
10505        LanguageConfig {
10506            name: "TypeScript".into(),
10507            matcher: LanguageMatcher {
10508                path_suffixes: vec!["ts".to_string()],
10509                ..Default::default()
10510            },
10511            ..LanguageConfig::default()
10512        },
10513        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
10514    )));
10515    update_test_language_settings(cx, |settings| {
10516        settings.defaults.prettier = Some(PrettierSettings {
10517            allowed: true,
10518            ..PrettierSettings::default()
10519        });
10520    });
10521    let mut fake_servers = language_registry.register_fake_lsp(
10522        "TypeScript",
10523        FakeLspAdapter {
10524            capabilities: lsp::ServerCapabilities {
10525                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10526                ..Default::default()
10527            },
10528            ..Default::default()
10529        },
10530    );
10531
10532    let buffer = project
10533        .update(cx, |project, cx| {
10534            project.open_local_buffer(path!("/file.ts"), cx)
10535        })
10536        .await
10537        .unwrap();
10538
10539    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10540    let (editor, cx) = cx.add_window_view(|window, cx| {
10541        build_editor_with_project(project.clone(), buffer, window, cx)
10542    });
10543    editor.update_in(cx, |editor, window, cx| {
10544        editor.set_text(
10545            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
10546            window,
10547            cx,
10548        )
10549    });
10550
10551    cx.executor().start_waiting();
10552    let fake_server = fake_servers.next().await.unwrap();
10553
10554    let format = editor
10555        .update_in(cx, |editor, window, cx| {
10556            editor.perform_code_action_kind(
10557                project.clone(),
10558                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
10559                window,
10560                cx,
10561            )
10562        })
10563        .unwrap();
10564    fake_server
10565        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
10566            assert_eq!(
10567                params.text_document.uri,
10568                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
10569            );
10570            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
10571                lsp::CodeAction {
10572                    title: "Organize Imports".to_string(),
10573                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
10574                    edit: Some(lsp::WorkspaceEdit {
10575                        changes: Some(
10576                            [(
10577                                params.text_document.uri.clone(),
10578                                vec![lsp::TextEdit::new(
10579                                    lsp::Range::new(
10580                                        lsp::Position::new(1, 0),
10581                                        lsp::Position::new(2, 0),
10582                                    ),
10583                                    "".to_string(),
10584                                )],
10585                            )]
10586                            .into_iter()
10587                            .collect(),
10588                        ),
10589                        ..Default::default()
10590                    }),
10591                    ..Default::default()
10592                },
10593            )]))
10594        })
10595        .next()
10596        .await;
10597    cx.executor().start_waiting();
10598    format.await;
10599    assert_eq!(
10600        editor.update(cx, |editor, cx| editor.text(cx)),
10601        "import { a } from 'module';\n\nconst x = a;\n"
10602    );
10603
10604    editor.update_in(cx, |editor, window, cx| {
10605        editor.set_text(
10606            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
10607            window,
10608            cx,
10609        )
10610    });
10611    // Ensure we don't lock if code action hangs.
10612    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
10613        move |params, _| async move {
10614            assert_eq!(
10615                params.text_document.uri,
10616                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
10617            );
10618            futures::future::pending::<()>().await;
10619            unreachable!()
10620        },
10621    );
10622    let format = editor
10623        .update_in(cx, |editor, window, cx| {
10624            editor.perform_code_action_kind(
10625                project,
10626                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
10627                window,
10628                cx,
10629            )
10630        })
10631        .unwrap();
10632    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
10633    cx.executor().start_waiting();
10634    format.await;
10635    assert_eq!(
10636        editor.update(cx, |editor, cx| editor.text(cx)),
10637        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
10638    );
10639}
10640
10641#[gpui::test]
10642async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
10643    init_test(cx, |_| {});
10644
10645    let mut cx = EditorLspTestContext::new_rust(
10646        lsp::ServerCapabilities {
10647            document_formatting_provider: Some(lsp::OneOf::Left(true)),
10648            ..Default::default()
10649        },
10650        cx,
10651    )
10652    .await;
10653
10654    cx.set_state(indoc! {"
10655        one.twoˇ
10656    "});
10657
10658    // The format request takes a long time. When it completes, it inserts
10659    // a newline and an indent before the `.`
10660    cx.lsp
10661        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
10662            let executor = cx.background_executor().clone();
10663            async move {
10664                executor.timer(Duration::from_millis(100)).await;
10665                Ok(Some(vec![lsp::TextEdit {
10666                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
10667                    new_text: "\n    ".into(),
10668                }]))
10669            }
10670        });
10671
10672    // Submit a format request.
10673    let format_1 = cx
10674        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10675        .unwrap();
10676    cx.executor().run_until_parked();
10677
10678    // Submit a second format request.
10679    let format_2 = cx
10680        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10681        .unwrap();
10682    cx.executor().run_until_parked();
10683
10684    // Wait for both format requests to complete
10685    cx.executor().advance_clock(Duration::from_millis(200));
10686    cx.executor().start_waiting();
10687    format_1.await.unwrap();
10688    cx.executor().start_waiting();
10689    format_2.await.unwrap();
10690
10691    // The formatting edits only happens once.
10692    cx.assert_editor_state(indoc! {"
10693        one
10694            .twoˇ
10695    "});
10696}
10697
10698#[gpui::test]
10699async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
10700    init_test(cx, |settings| {
10701        settings.defaults.formatter = Some(SelectedFormatter::Auto)
10702    });
10703
10704    let mut cx = EditorLspTestContext::new_rust(
10705        lsp::ServerCapabilities {
10706            document_formatting_provider: Some(lsp::OneOf::Left(true)),
10707            ..Default::default()
10708        },
10709        cx,
10710    )
10711    .await;
10712
10713    // Set up a buffer white some trailing whitespace and no trailing newline.
10714    cx.set_state(
10715        &[
10716            "one ",   //
10717            "twoˇ",   //
10718            "three ", //
10719            "four",   //
10720        ]
10721        .join("\n"),
10722    );
10723
10724    // Submit a format request.
10725    let format = cx
10726        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10727        .unwrap();
10728
10729    // Record which buffer changes have been sent to the language server
10730    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
10731    cx.lsp
10732        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
10733            let buffer_changes = buffer_changes.clone();
10734            move |params, _| {
10735                buffer_changes.lock().extend(
10736                    params
10737                        .content_changes
10738                        .into_iter()
10739                        .map(|e| (e.range.unwrap(), e.text)),
10740                );
10741            }
10742        });
10743
10744    // Handle formatting requests to the language server.
10745    cx.lsp
10746        .set_request_handler::<lsp::request::Formatting, _, _>({
10747            let buffer_changes = buffer_changes.clone();
10748            move |_, _| {
10749                // When formatting is requested, trailing whitespace has already been stripped,
10750                // and the trailing newline has already been added.
10751                assert_eq!(
10752                    &buffer_changes.lock()[1..],
10753                    &[
10754                        (
10755                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
10756                            "".into()
10757                        ),
10758                        (
10759                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
10760                            "".into()
10761                        ),
10762                        (
10763                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
10764                            "\n".into()
10765                        ),
10766                    ]
10767                );
10768
10769                // Insert blank lines between each line of the buffer.
10770                async move {
10771                    Ok(Some(vec![
10772                        lsp::TextEdit {
10773                            range: lsp::Range::new(
10774                                lsp::Position::new(1, 0),
10775                                lsp::Position::new(1, 0),
10776                            ),
10777                            new_text: "\n".into(),
10778                        },
10779                        lsp::TextEdit {
10780                            range: lsp::Range::new(
10781                                lsp::Position::new(2, 0),
10782                                lsp::Position::new(2, 0),
10783                            ),
10784                            new_text: "\n".into(),
10785                        },
10786                    ]))
10787                }
10788            }
10789        });
10790
10791    // After formatting the buffer, the trailing whitespace is stripped,
10792    // a newline is appended, and the edits provided by the language server
10793    // have been applied.
10794    format.await.unwrap();
10795    cx.assert_editor_state(
10796        &[
10797            "one",   //
10798            "",      //
10799            "twoˇ",  //
10800            "",      //
10801            "three", //
10802            "four",  //
10803            "",      //
10804        ]
10805        .join("\n"),
10806    );
10807
10808    // Undoing the formatting undoes the trailing whitespace removal, the
10809    // trailing newline, and the LSP edits.
10810    cx.update_buffer(|buffer, cx| buffer.undo(cx));
10811    cx.assert_editor_state(
10812        &[
10813            "one ",   //
10814            "twoˇ",   //
10815            "three ", //
10816            "four",   //
10817        ]
10818        .join("\n"),
10819    );
10820}
10821
10822#[gpui::test]
10823async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
10824    cx: &mut TestAppContext,
10825) {
10826    init_test(cx, |_| {});
10827
10828    cx.update(|cx| {
10829        cx.update_global::<SettingsStore, _>(|settings, cx| {
10830            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10831                settings.auto_signature_help = Some(true);
10832            });
10833        });
10834    });
10835
10836    let mut cx = EditorLspTestContext::new_rust(
10837        lsp::ServerCapabilities {
10838            signature_help_provider: Some(lsp::SignatureHelpOptions {
10839                ..Default::default()
10840            }),
10841            ..Default::default()
10842        },
10843        cx,
10844    )
10845    .await;
10846
10847    let language = Language::new(
10848        LanguageConfig {
10849            name: "Rust".into(),
10850            brackets: BracketPairConfig {
10851                pairs: vec![
10852                    BracketPair {
10853                        start: "{".to_string(),
10854                        end: "}".to_string(),
10855                        close: true,
10856                        surround: true,
10857                        newline: true,
10858                    },
10859                    BracketPair {
10860                        start: "(".to_string(),
10861                        end: ")".to_string(),
10862                        close: true,
10863                        surround: true,
10864                        newline: true,
10865                    },
10866                    BracketPair {
10867                        start: "/*".to_string(),
10868                        end: " */".to_string(),
10869                        close: true,
10870                        surround: true,
10871                        newline: true,
10872                    },
10873                    BracketPair {
10874                        start: "[".to_string(),
10875                        end: "]".to_string(),
10876                        close: false,
10877                        surround: false,
10878                        newline: true,
10879                    },
10880                    BracketPair {
10881                        start: "\"".to_string(),
10882                        end: "\"".to_string(),
10883                        close: true,
10884                        surround: true,
10885                        newline: false,
10886                    },
10887                    BracketPair {
10888                        start: "<".to_string(),
10889                        end: ">".to_string(),
10890                        close: false,
10891                        surround: true,
10892                        newline: true,
10893                    },
10894                ],
10895                ..Default::default()
10896            },
10897            autoclose_before: "})]".to_string(),
10898            ..Default::default()
10899        },
10900        Some(tree_sitter_rust::LANGUAGE.into()),
10901    );
10902    let language = Arc::new(language);
10903
10904    cx.language_registry().add(language.clone());
10905    cx.update_buffer(|buffer, cx| {
10906        buffer.set_language(Some(language), cx);
10907    });
10908
10909    cx.set_state(
10910        &r#"
10911            fn main() {
10912                sampleˇ
10913            }
10914        "#
10915        .unindent(),
10916    );
10917
10918    cx.update_editor(|editor, window, cx| {
10919        editor.handle_input("(", window, cx);
10920    });
10921    cx.assert_editor_state(
10922        &"
10923            fn main() {
10924                sample(ˇ)
10925            }
10926        "
10927        .unindent(),
10928    );
10929
10930    let mocked_response = lsp::SignatureHelp {
10931        signatures: vec![lsp::SignatureInformation {
10932            label: "fn sample(param1: u8, param2: u8)".to_string(),
10933            documentation: None,
10934            parameters: Some(vec![
10935                lsp::ParameterInformation {
10936                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10937                    documentation: None,
10938                },
10939                lsp::ParameterInformation {
10940                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10941                    documentation: None,
10942                },
10943            ]),
10944            active_parameter: None,
10945        }],
10946        active_signature: Some(0),
10947        active_parameter: Some(0),
10948    };
10949    handle_signature_help_request(&mut cx, mocked_response).await;
10950
10951    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10952        .await;
10953
10954    cx.editor(|editor, _, _| {
10955        let signature_help_state = editor.signature_help_state.popover().cloned();
10956        let signature = signature_help_state.unwrap();
10957        assert_eq!(
10958            signature.signatures[signature.current_signature].label,
10959            "fn sample(param1: u8, param2: u8)"
10960        );
10961    });
10962}
10963
10964#[gpui::test]
10965async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
10966    init_test(cx, |_| {});
10967
10968    cx.update(|cx| {
10969        cx.update_global::<SettingsStore, _>(|settings, cx| {
10970            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10971                settings.auto_signature_help = Some(false);
10972                settings.show_signature_help_after_edits = Some(false);
10973            });
10974        });
10975    });
10976
10977    let mut cx = EditorLspTestContext::new_rust(
10978        lsp::ServerCapabilities {
10979            signature_help_provider: Some(lsp::SignatureHelpOptions {
10980                ..Default::default()
10981            }),
10982            ..Default::default()
10983        },
10984        cx,
10985    )
10986    .await;
10987
10988    let language = Language::new(
10989        LanguageConfig {
10990            name: "Rust".into(),
10991            brackets: BracketPairConfig {
10992                pairs: vec![
10993                    BracketPair {
10994                        start: "{".to_string(),
10995                        end: "}".to_string(),
10996                        close: true,
10997                        surround: true,
10998                        newline: true,
10999                    },
11000                    BracketPair {
11001                        start: "(".to_string(),
11002                        end: ")".to_string(),
11003                        close: true,
11004                        surround: true,
11005                        newline: true,
11006                    },
11007                    BracketPair {
11008                        start: "/*".to_string(),
11009                        end: " */".to_string(),
11010                        close: true,
11011                        surround: true,
11012                        newline: true,
11013                    },
11014                    BracketPair {
11015                        start: "[".to_string(),
11016                        end: "]".to_string(),
11017                        close: false,
11018                        surround: false,
11019                        newline: true,
11020                    },
11021                    BracketPair {
11022                        start: "\"".to_string(),
11023                        end: "\"".to_string(),
11024                        close: true,
11025                        surround: true,
11026                        newline: false,
11027                    },
11028                    BracketPair {
11029                        start: "<".to_string(),
11030                        end: ">".to_string(),
11031                        close: false,
11032                        surround: true,
11033                        newline: true,
11034                    },
11035                ],
11036                ..Default::default()
11037            },
11038            autoclose_before: "})]".to_string(),
11039            ..Default::default()
11040        },
11041        Some(tree_sitter_rust::LANGUAGE.into()),
11042    );
11043    let language = Arc::new(language);
11044
11045    cx.language_registry().add(language.clone());
11046    cx.update_buffer(|buffer, cx| {
11047        buffer.set_language(Some(language), cx);
11048    });
11049
11050    // Ensure that signature_help is not called when no signature help is enabled.
11051    cx.set_state(
11052        &r#"
11053            fn main() {
11054                sampleˇ
11055            }
11056        "#
11057        .unindent(),
11058    );
11059    cx.update_editor(|editor, window, cx| {
11060        editor.handle_input("(", window, cx);
11061    });
11062    cx.assert_editor_state(
11063        &"
11064            fn main() {
11065                sample(ˇ)
11066            }
11067        "
11068        .unindent(),
11069    );
11070    cx.editor(|editor, _, _| {
11071        assert!(editor.signature_help_state.task().is_none());
11072    });
11073
11074    let mocked_response = lsp::SignatureHelp {
11075        signatures: vec![lsp::SignatureInformation {
11076            label: "fn sample(param1: u8, param2: u8)".to_string(),
11077            documentation: None,
11078            parameters: Some(vec![
11079                lsp::ParameterInformation {
11080                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11081                    documentation: None,
11082                },
11083                lsp::ParameterInformation {
11084                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11085                    documentation: None,
11086                },
11087            ]),
11088            active_parameter: None,
11089        }],
11090        active_signature: Some(0),
11091        active_parameter: Some(0),
11092    };
11093
11094    // Ensure that signature_help is called when enabled afte edits
11095    cx.update(|_, cx| {
11096        cx.update_global::<SettingsStore, _>(|settings, cx| {
11097            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11098                settings.auto_signature_help = Some(false);
11099                settings.show_signature_help_after_edits = Some(true);
11100            });
11101        });
11102    });
11103    cx.set_state(
11104        &r#"
11105            fn main() {
11106                sampleˇ
11107            }
11108        "#
11109        .unindent(),
11110    );
11111    cx.update_editor(|editor, window, cx| {
11112        editor.handle_input("(", window, cx);
11113    });
11114    cx.assert_editor_state(
11115        &"
11116            fn main() {
11117                sample(ˇ)
11118            }
11119        "
11120        .unindent(),
11121    );
11122    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11123    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11124        .await;
11125    cx.update_editor(|editor, _, _| {
11126        let signature_help_state = editor.signature_help_state.popover().cloned();
11127        assert!(signature_help_state.is_some());
11128        let signature = signature_help_state.unwrap();
11129        assert_eq!(
11130            signature.signatures[signature.current_signature].label,
11131            "fn sample(param1: u8, param2: u8)"
11132        );
11133        editor.signature_help_state = SignatureHelpState::default();
11134    });
11135
11136    // Ensure that signature_help is called when auto signature help override is enabled
11137    cx.update(|_, cx| {
11138        cx.update_global::<SettingsStore, _>(|settings, cx| {
11139            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11140                settings.auto_signature_help = Some(true);
11141                settings.show_signature_help_after_edits = Some(false);
11142            });
11143        });
11144    });
11145    cx.set_state(
11146        &r#"
11147            fn main() {
11148                sampleˇ
11149            }
11150        "#
11151        .unindent(),
11152    );
11153    cx.update_editor(|editor, window, cx| {
11154        editor.handle_input("(", window, cx);
11155    });
11156    cx.assert_editor_state(
11157        &"
11158            fn main() {
11159                sample(ˇ)
11160            }
11161        "
11162        .unindent(),
11163    );
11164    handle_signature_help_request(&mut cx, mocked_response).await;
11165    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11166        .await;
11167    cx.editor(|editor, _, _| {
11168        let signature_help_state = editor.signature_help_state.popover().cloned();
11169        assert!(signature_help_state.is_some());
11170        let signature = signature_help_state.unwrap();
11171        assert_eq!(
11172            signature.signatures[signature.current_signature].label,
11173            "fn sample(param1: u8, param2: u8)"
11174        );
11175    });
11176}
11177
11178#[gpui::test]
11179async fn test_signature_help(cx: &mut TestAppContext) {
11180    init_test(cx, |_| {});
11181    cx.update(|cx| {
11182        cx.update_global::<SettingsStore, _>(|settings, cx| {
11183            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11184                settings.auto_signature_help = Some(true);
11185            });
11186        });
11187    });
11188
11189    let mut cx = EditorLspTestContext::new_rust(
11190        lsp::ServerCapabilities {
11191            signature_help_provider: Some(lsp::SignatureHelpOptions {
11192                ..Default::default()
11193            }),
11194            ..Default::default()
11195        },
11196        cx,
11197    )
11198    .await;
11199
11200    // A test that directly calls `show_signature_help`
11201    cx.update_editor(|editor, window, cx| {
11202        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11203    });
11204
11205    let mocked_response = lsp::SignatureHelp {
11206        signatures: vec![lsp::SignatureInformation {
11207            label: "fn sample(param1: u8, param2: u8)".to_string(),
11208            documentation: None,
11209            parameters: Some(vec![
11210                lsp::ParameterInformation {
11211                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11212                    documentation: None,
11213                },
11214                lsp::ParameterInformation {
11215                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11216                    documentation: None,
11217                },
11218            ]),
11219            active_parameter: None,
11220        }],
11221        active_signature: Some(0),
11222        active_parameter: Some(0),
11223    };
11224    handle_signature_help_request(&mut cx, mocked_response).await;
11225
11226    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11227        .await;
11228
11229    cx.editor(|editor, _, _| {
11230        let signature_help_state = editor.signature_help_state.popover().cloned();
11231        assert!(signature_help_state.is_some());
11232        let signature = signature_help_state.unwrap();
11233        assert_eq!(
11234            signature.signatures[signature.current_signature].label,
11235            "fn sample(param1: u8, param2: u8)"
11236        );
11237    });
11238
11239    // When exiting outside from inside the brackets, `signature_help` is closed.
11240    cx.set_state(indoc! {"
11241        fn main() {
11242            sample(ˇ);
11243        }
11244
11245        fn sample(param1: u8, param2: u8) {}
11246    "});
11247
11248    cx.update_editor(|editor, window, cx| {
11249        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11250            s.select_ranges([0..0])
11251        });
11252    });
11253
11254    let mocked_response = lsp::SignatureHelp {
11255        signatures: Vec::new(),
11256        active_signature: None,
11257        active_parameter: None,
11258    };
11259    handle_signature_help_request(&mut cx, mocked_response).await;
11260
11261    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11262        .await;
11263
11264    cx.editor(|editor, _, _| {
11265        assert!(!editor.signature_help_state.is_shown());
11266    });
11267
11268    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
11269    cx.set_state(indoc! {"
11270        fn main() {
11271            sample(ˇ);
11272        }
11273
11274        fn sample(param1: u8, param2: u8) {}
11275    "});
11276
11277    let mocked_response = lsp::SignatureHelp {
11278        signatures: vec![lsp::SignatureInformation {
11279            label: "fn sample(param1: u8, param2: u8)".to_string(),
11280            documentation: None,
11281            parameters: Some(vec![
11282                lsp::ParameterInformation {
11283                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11284                    documentation: None,
11285                },
11286                lsp::ParameterInformation {
11287                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11288                    documentation: None,
11289                },
11290            ]),
11291            active_parameter: None,
11292        }],
11293        active_signature: Some(0),
11294        active_parameter: Some(0),
11295    };
11296    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11297    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11298        .await;
11299    cx.editor(|editor, _, _| {
11300        assert!(editor.signature_help_state.is_shown());
11301    });
11302
11303    // Restore the popover with more parameter input
11304    cx.set_state(indoc! {"
11305        fn main() {
11306            sample(param1, param2ˇ);
11307        }
11308
11309        fn sample(param1: u8, param2: u8) {}
11310    "});
11311
11312    let mocked_response = lsp::SignatureHelp {
11313        signatures: vec![lsp::SignatureInformation {
11314            label: "fn sample(param1: u8, param2: u8)".to_string(),
11315            documentation: None,
11316            parameters: Some(vec![
11317                lsp::ParameterInformation {
11318                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11319                    documentation: None,
11320                },
11321                lsp::ParameterInformation {
11322                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11323                    documentation: None,
11324                },
11325            ]),
11326            active_parameter: None,
11327        }],
11328        active_signature: Some(0),
11329        active_parameter: Some(1),
11330    };
11331    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11332    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11333        .await;
11334
11335    // When selecting a range, the popover is gone.
11336    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
11337    cx.update_editor(|editor, window, cx| {
11338        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11339            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11340        })
11341    });
11342    cx.assert_editor_state(indoc! {"
11343        fn main() {
11344            sample(param1, «ˇparam2»);
11345        }
11346
11347        fn sample(param1: u8, param2: u8) {}
11348    "});
11349    cx.editor(|editor, _, _| {
11350        assert!(!editor.signature_help_state.is_shown());
11351    });
11352
11353    // When unselecting again, the popover is back if within the brackets.
11354    cx.update_editor(|editor, window, cx| {
11355        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11356            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11357        })
11358    });
11359    cx.assert_editor_state(indoc! {"
11360        fn main() {
11361            sample(param1, ˇparam2);
11362        }
11363
11364        fn sample(param1: u8, param2: u8) {}
11365    "});
11366    handle_signature_help_request(&mut cx, mocked_response).await;
11367    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11368        .await;
11369    cx.editor(|editor, _, _| {
11370        assert!(editor.signature_help_state.is_shown());
11371    });
11372
11373    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
11374    cx.update_editor(|editor, window, cx| {
11375        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11376            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
11377            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11378        })
11379    });
11380    cx.assert_editor_state(indoc! {"
11381        fn main() {
11382            sample(param1, ˇparam2);
11383        }
11384
11385        fn sample(param1: u8, param2: u8) {}
11386    "});
11387
11388    let mocked_response = lsp::SignatureHelp {
11389        signatures: vec![lsp::SignatureInformation {
11390            label: "fn sample(param1: u8, param2: u8)".to_string(),
11391            documentation: None,
11392            parameters: Some(vec![
11393                lsp::ParameterInformation {
11394                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11395                    documentation: None,
11396                },
11397                lsp::ParameterInformation {
11398                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11399                    documentation: None,
11400                },
11401            ]),
11402            active_parameter: None,
11403        }],
11404        active_signature: Some(0),
11405        active_parameter: Some(1),
11406    };
11407    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11408    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11409        .await;
11410    cx.update_editor(|editor, _, cx| {
11411        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
11412    });
11413    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11414        .await;
11415    cx.update_editor(|editor, window, cx| {
11416        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11417            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11418        })
11419    });
11420    cx.assert_editor_state(indoc! {"
11421        fn main() {
11422            sample(param1, «ˇparam2»);
11423        }
11424
11425        fn sample(param1: u8, param2: u8) {}
11426    "});
11427    cx.update_editor(|editor, window, cx| {
11428        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11429            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11430        })
11431    });
11432    cx.assert_editor_state(indoc! {"
11433        fn main() {
11434            sample(param1, ˇparam2);
11435        }
11436
11437        fn sample(param1: u8, param2: u8) {}
11438    "});
11439    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
11440        .await;
11441}
11442
11443#[gpui::test]
11444async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
11445    init_test(cx, |_| {});
11446
11447    let mut cx = EditorLspTestContext::new_rust(
11448        lsp::ServerCapabilities {
11449            signature_help_provider: Some(lsp::SignatureHelpOptions {
11450                ..Default::default()
11451            }),
11452            ..Default::default()
11453        },
11454        cx,
11455    )
11456    .await;
11457
11458    cx.set_state(indoc! {"
11459        fn main() {
11460            overloadedˇ
11461        }
11462    "});
11463
11464    cx.update_editor(|editor, window, cx| {
11465        editor.handle_input("(", window, cx);
11466        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11467    });
11468
11469    // Mock response with 3 signatures
11470    let mocked_response = lsp::SignatureHelp {
11471        signatures: vec![
11472            lsp::SignatureInformation {
11473                label: "fn overloaded(x: i32)".to_string(),
11474                documentation: None,
11475                parameters: Some(vec![lsp::ParameterInformation {
11476                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11477                    documentation: None,
11478                }]),
11479                active_parameter: None,
11480            },
11481            lsp::SignatureInformation {
11482                label: "fn overloaded(x: i32, y: i32)".to_string(),
11483                documentation: None,
11484                parameters: Some(vec![
11485                    lsp::ParameterInformation {
11486                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11487                        documentation: None,
11488                    },
11489                    lsp::ParameterInformation {
11490                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
11491                        documentation: None,
11492                    },
11493                ]),
11494                active_parameter: None,
11495            },
11496            lsp::SignatureInformation {
11497                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
11498                documentation: None,
11499                parameters: Some(vec![
11500                    lsp::ParameterInformation {
11501                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11502                        documentation: None,
11503                    },
11504                    lsp::ParameterInformation {
11505                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
11506                        documentation: None,
11507                    },
11508                    lsp::ParameterInformation {
11509                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
11510                        documentation: None,
11511                    },
11512                ]),
11513                active_parameter: None,
11514            },
11515        ],
11516        active_signature: Some(1),
11517        active_parameter: Some(0),
11518    };
11519    handle_signature_help_request(&mut cx, mocked_response).await;
11520
11521    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11522        .await;
11523
11524    // Verify we have multiple signatures and the right one is selected
11525    cx.editor(|editor, _, _| {
11526        let popover = editor.signature_help_state.popover().cloned().unwrap();
11527        assert_eq!(popover.signatures.len(), 3);
11528        // active_signature was 1, so that should be the current
11529        assert_eq!(popover.current_signature, 1);
11530        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
11531        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
11532        assert_eq!(
11533            popover.signatures[2].label,
11534            "fn overloaded(x: i32, y: i32, z: i32)"
11535        );
11536    });
11537
11538    // Test navigation functionality
11539    cx.update_editor(|editor, window, cx| {
11540        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
11541    });
11542
11543    cx.editor(|editor, _, _| {
11544        let popover = editor.signature_help_state.popover().cloned().unwrap();
11545        assert_eq!(popover.current_signature, 2);
11546    });
11547
11548    // Test wrap around
11549    cx.update_editor(|editor, window, cx| {
11550        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
11551    });
11552
11553    cx.editor(|editor, _, _| {
11554        let popover = editor.signature_help_state.popover().cloned().unwrap();
11555        assert_eq!(popover.current_signature, 0);
11556    });
11557
11558    // Test previous navigation
11559    cx.update_editor(|editor, window, cx| {
11560        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
11561    });
11562
11563    cx.editor(|editor, _, _| {
11564        let popover = editor.signature_help_state.popover().cloned().unwrap();
11565        assert_eq!(popover.current_signature, 2);
11566    });
11567}
11568
11569#[gpui::test]
11570async fn test_completion_mode(cx: &mut TestAppContext) {
11571    init_test(cx, |_| {});
11572    let mut cx = EditorLspTestContext::new_rust(
11573        lsp::ServerCapabilities {
11574            completion_provider: Some(lsp::CompletionOptions {
11575                resolve_provider: Some(true),
11576                ..Default::default()
11577            }),
11578            ..Default::default()
11579        },
11580        cx,
11581    )
11582    .await;
11583
11584    struct Run {
11585        run_description: &'static str,
11586        initial_state: String,
11587        buffer_marked_text: String,
11588        completion_label: &'static str,
11589        completion_text: &'static str,
11590        expected_with_insert_mode: String,
11591        expected_with_replace_mode: String,
11592        expected_with_replace_subsequence_mode: String,
11593        expected_with_replace_suffix_mode: String,
11594    }
11595
11596    let runs = [
11597        Run {
11598            run_description: "Start of word matches completion text",
11599            initial_state: "before ediˇ after".into(),
11600            buffer_marked_text: "before <edi|> after".into(),
11601            completion_label: "editor",
11602            completion_text: "editor",
11603            expected_with_insert_mode: "before editorˇ after".into(),
11604            expected_with_replace_mode: "before editorˇ after".into(),
11605            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11606            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11607        },
11608        Run {
11609            run_description: "Accept same text at the middle of the word",
11610            initial_state: "before ediˇtor after".into(),
11611            buffer_marked_text: "before <edi|tor> after".into(),
11612            completion_label: "editor",
11613            completion_text: "editor",
11614            expected_with_insert_mode: "before editorˇtor after".into(),
11615            expected_with_replace_mode: "before editorˇ after".into(),
11616            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11617            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11618        },
11619        Run {
11620            run_description: "End of word matches completion text -- cursor at end",
11621            initial_state: "before torˇ after".into(),
11622            buffer_marked_text: "before <tor|> after".into(),
11623            completion_label: "editor",
11624            completion_text: "editor",
11625            expected_with_insert_mode: "before editorˇ after".into(),
11626            expected_with_replace_mode: "before editorˇ after".into(),
11627            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11628            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11629        },
11630        Run {
11631            run_description: "End of word matches completion text -- cursor at start",
11632            initial_state: "before ˇtor after".into(),
11633            buffer_marked_text: "before <|tor> after".into(),
11634            completion_label: "editor",
11635            completion_text: "editor",
11636            expected_with_insert_mode: "before editorˇtor after".into(),
11637            expected_with_replace_mode: "before editorˇ after".into(),
11638            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11639            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11640        },
11641        Run {
11642            run_description: "Prepend text containing whitespace",
11643            initial_state: "pˇfield: bool".into(),
11644            buffer_marked_text: "<p|field>: bool".into(),
11645            completion_label: "pub ",
11646            completion_text: "pub ",
11647            expected_with_insert_mode: "pub ˇfield: bool".into(),
11648            expected_with_replace_mode: "pub ˇ: bool".into(),
11649            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
11650            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
11651        },
11652        Run {
11653            run_description: "Add element to start of list",
11654            initial_state: "[element_ˇelement_2]".into(),
11655            buffer_marked_text: "[<element_|element_2>]".into(),
11656            completion_label: "element_1",
11657            completion_text: "element_1",
11658            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
11659            expected_with_replace_mode: "[element_1ˇ]".into(),
11660            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
11661            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
11662        },
11663        Run {
11664            run_description: "Add element to start of list -- first and second elements are equal",
11665            initial_state: "[elˇelement]".into(),
11666            buffer_marked_text: "[<el|element>]".into(),
11667            completion_label: "element",
11668            completion_text: "element",
11669            expected_with_insert_mode: "[elementˇelement]".into(),
11670            expected_with_replace_mode: "[elementˇ]".into(),
11671            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
11672            expected_with_replace_suffix_mode: "[elementˇ]".into(),
11673        },
11674        Run {
11675            run_description: "Ends with matching suffix",
11676            initial_state: "SubˇError".into(),
11677            buffer_marked_text: "<Sub|Error>".into(),
11678            completion_label: "SubscriptionError",
11679            completion_text: "SubscriptionError",
11680            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
11681            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11682            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11683            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
11684        },
11685        Run {
11686            run_description: "Suffix is a subsequence -- contiguous",
11687            initial_state: "SubˇErr".into(),
11688            buffer_marked_text: "<Sub|Err>".into(),
11689            completion_label: "SubscriptionError",
11690            completion_text: "SubscriptionError",
11691            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
11692            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11693            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11694            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
11695        },
11696        Run {
11697            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
11698            initial_state: "Suˇscrirr".into(),
11699            buffer_marked_text: "<Su|scrirr>".into(),
11700            completion_label: "SubscriptionError",
11701            completion_text: "SubscriptionError",
11702            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
11703            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11704            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11705            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
11706        },
11707        Run {
11708            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
11709            initial_state: "foo(indˇix)".into(),
11710            buffer_marked_text: "foo(<ind|ix>)".into(),
11711            completion_label: "node_index",
11712            completion_text: "node_index",
11713            expected_with_insert_mode: "foo(node_indexˇix)".into(),
11714            expected_with_replace_mode: "foo(node_indexˇ)".into(),
11715            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
11716            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
11717        },
11718        Run {
11719            run_description: "Replace range ends before cursor - should extend to cursor",
11720            initial_state: "before editˇo after".into(),
11721            buffer_marked_text: "before <{ed}>it|o after".into(),
11722            completion_label: "editor",
11723            completion_text: "editor",
11724            expected_with_insert_mode: "before editorˇo after".into(),
11725            expected_with_replace_mode: "before editorˇo after".into(),
11726            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
11727            expected_with_replace_suffix_mode: "before editorˇo after".into(),
11728        },
11729        Run {
11730            run_description: "Uses label for suffix matching",
11731            initial_state: "before ediˇtor after".into(),
11732            buffer_marked_text: "before <edi|tor> after".into(),
11733            completion_label: "editor",
11734            completion_text: "editor()",
11735            expected_with_insert_mode: "before editor()ˇtor after".into(),
11736            expected_with_replace_mode: "before editor()ˇ after".into(),
11737            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
11738            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
11739        },
11740        Run {
11741            run_description: "Case insensitive subsequence and suffix matching",
11742            initial_state: "before EDiˇtoR after".into(),
11743            buffer_marked_text: "before <EDi|toR> after".into(),
11744            completion_label: "editor",
11745            completion_text: "editor",
11746            expected_with_insert_mode: "before editorˇtoR after".into(),
11747            expected_with_replace_mode: "before editorˇ after".into(),
11748            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11749            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11750        },
11751    ];
11752
11753    for run in runs {
11754        let run_variations = [
11755            (LspInsertMode::Insert, run.expected_with_insert_mode),
11756            (LspInsertMode::Replace, run.expected_with_replace_mode),
11757            (
11758                LspInsertMode::ReplaceSubsequence,
11759                run.expected_with_replace_subsequence_mode,
11760            ),
11761            (
11762                LspInsertMode::ReplaceSuffix,
11763                run.expected_with_replace_suffix_mode,
11764            ),
11765        ];
11766
11767        for (lsp_insert_mode, expected_text) in run_variations {
11768            eprintln!(
11769                "run = {:?}, mode = {lsp_insert_mode:.?}",
11770                run.run_description,
11771            );
11772
11773            update_test_language_settings(&mut cx, |settings| {
11774                settings.defaults.completions = Some(CompletionSettings {
11775                    lsp_insert_mode,
11776                    words: WordsCompletionMode::Disabled,
11777                    lsp: true,
11778                    lsp_fetch_timeout_ms: 0,
11779                });
11780            });
11781
11782            cx.set_state(&run.initial_state);
11783            cx.update_editor(|editor, window, cx| {
11784                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11785            });
11786
11787            let counter = Arc::new(AtomicUsize::new(0));
11788            handle_completion_request_with_insert_and_replace(
11789                &mut cx,
11790                &run.buffer_marked_text,
11791                vec![(run.completion_label, run.completion_text)],
11792                counter.clone(),
11793            )
11794            .await;
11795            cx.condition(|editor, _| editor.context_menu_visible())
11796                .await;
11797            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11798
11799            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11800                editor
11801                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
11802                    .unwrap()
11803            });
11804            cx.assert_editor_state(&expected_text);
11805            handle_resolve_completion_request(&mut cx, None).await;
11806            apply_additional_edits.await.unwrap();
11807        }
11808    }
11809}
11810
11811#[gpui::test]
11812async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
11813    init_test(cx, |_| {});
11814    let mut cx = EditorLspTestContext::new_rust(
11815        lsp::ServerCapabilities {
11816            completion_provider: Some(lsp::CompletionOptions {
11817                resolve_provider: Some(true),
11818                ..Default::default()
11819            }),
11820            ..Default::default()
11821        },
11822        cx,
11823    )
11824    .await;
11825
11826    let initial_state = "SubˇError";
11827    let buffer_marked_text = "<Sub|Error>";
11828    let completion_text = "SubscriptionError";
11829    let expected_with_insert_mode = "SubscriptionErrorˇError";
11830    let expected_with_replace_mode = "SubscriptionErrorˇ";
11831
11832    update_test_language_settings(&mut cx, |settings| {
11833        settings.defaults.completions = Some(CompletionSettings {
11834            words: WordsCompletionMode::Disabled,
11835            // set the opposite here to ensure that the action is overriding the default behavior
11836            lsp_insert_mode: LspInsertMode::Insert,
11837            lsp: true,
11838            lsp_fetch_timeout_ms: 0,
11839        });
11840    });
11841
11842    cx.set_state(initial_state);
11843    cx.update_editor(|editor, window, cx| {
11844        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11845    });
11846
11847    let counter = Arc::new(AtomicUsize::new(0));
11848    handle_completion_request_with_insert_and_replace(
11849        &mut cx,
11850        &buffer_marked_text,
11851        vec![(completion_text, completion_text)],
11852        counter.clone(),
11853    )
11854    .await;
11855    cx.condition(|editor, _| editor.context_menu_visible())
11856        .await;
11857    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11858
11859    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11860        editor
11861            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11862            .unwrap()
11863    });
11864    cx.assert_editor_state(&expected_with_replace_mode);
11865    handle_resolve_completion_request(&mut cx, None).await;
11866    apply_additional_edits.await.unwrap();
11867
11868    update_test_language_settings(&mut cx, |settings| {
11869        settings.defaults.completions = Some(CompletionSettings {
11870            words: WordsCompletionMode::Disabled,
11871            // set the opposite here to ensure that the action is overriding the default behavior
11872            lsp_insert_mode: LspInsertMode::Replace,
11873            lsp: true,
11874            lsp_fetch_timeout_ms: 0,
11875        });
11876    });
11877
11878    cx.set_state(initial_state);
11879    cx.update_editor(|editor, window, cx| {
11880        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11881    });
11882    handle_completion_request_with_insert_and_replace(
11883        &mut cx,
11884        &buffer_marked_text,
11885        vec![(completion_text, completion_text)],
11886        counter.clone(),
11887    )
11888    .await;
11889    cx.condition(|editor, _| editor.context_menu_visible())
11890        .await;
11891    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
11892
11893    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11894        editor
11895            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
11896            .unwrap()
11897    });
11898    cx.assert_editor_state(&expected_with_insert_mode);
11899    handle_resolve_completion_request(&mut cx, None).await;
11900    apply_additional_edits.await.unwrap();
11901}
11902
11903#[gpui::test]
11904async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
11905    init_test(cx, |_| {});
11906    let mut cx = EditorLspTestContext::new_rust(
11907        lsp::ServerCapabilities {
11908            completion_provider: Some(lsp::CompletionOptions {
11909                resolve_provider: Some(true),
11910                ..Default::default()
11911            }),
11912            ..Default::default()
11913        },
11914        cx,
11915    )
11916    .await;
11917
11918    // scenario: surrounding text matches completion text
11919    let completion_text = "to_offset";
11920    let initial_state = indoc! {"
11921        1. buf.to_offˇsuffix
11922        2. buf.to_offˇsuf
11923        3. buf.to_offˇfix
11924        4. buf.to_offˇ
11925        5. into_offˇensive
11926        6. ˇsuffix
11927        7. let ˇ //
11928        8. aaˇzz
11929        9. buf.to_off«zzzzzˇ»suffix
11930        10. buf.«ˇzzzzz»suffix
11931        11. to_off«ˇzzzzz»
11932
11933        buf.to_offˇsuffix  // newest cursor
11934    "};
11935    let completion_marked_buffer = indoc! {"
11936        1. buf.to_offsuffix
11937        2. buf.to_offsuf
11938        3. buf.to_offfix
11939        4. buf.to_off
11940        5. into_offensive
11941        6. suffix
11942        7. let  //
11943        8. aazz
11944        9. buf.to_offzzzzzsuffix
11945        10. buf.zzzzzsuffix
11946        11. to_offzzzzz
11947
11948        buf.<to_off|suffix>  // newest cursor
11949    "};
11950    let expected = indoc! {"
11951        1. buf.to_offsetˇ
11952        2. buf.to_offsetˇsuf
11953        3. buf.to_offsetˇfix
11954        4. buf.to_offsetˇ
11955        5. into_offsetˇensive
11956        6. to_offsetˇsuffix
11957        7. let to_offsetˇ //
11958        8. aato_offsetˇzz
11959        9. buf.to_offsetˇ
11960        10. buf.to_offsetˇsuffix
11961        11. to_offsetˇ
11962
11963        buf.to_offsetˇ  // newest cursor
11964    "};
11965    cx.set_state(initial_state);
11966    cx.update_editor(|editor, window, cx| {
11967        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11968    });
11969    handle_completion_request_with_insert_and_replace(
11970        &mut cx,
11971        completion_marked_buffer,
11972        vec![(completion_text, completion_text)],
11973        Arc::new(AtomicUsize::new(0)),
11974    )
11975    .await;
11976    cx.condition(|editor, _| editor.context_menu_visible())
11977        .await;
11978    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11979        editor
11980            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11981            .unwrap()
11982    });
11983    cx.assert_editor_state(expected);
11984    handle_resolve_completion_request(&mut cx, None).await;
11985    apply_additional_edits.await.unwrap();
11986
11987    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
11988    let completion_text = "foo_and_bar";
11989    let initial_state = indoc! {"
11990        1. ooanbˇ
11991        2. zooanbˇ
11992        3. ooanbˇz
11993        4. zooanbˇz
11994        5. ooanˇ
11995        6. oanbˇ
11996
11997        ooanbˇ
11998    "};
11999    let completion_marked_buffer = indoc! {"
12000        1. ooanb
12001        2. zooanb
12002        3. ooanbz
12003        4. zooanbz
12004        5. ooan
12005        6. oanb
12006
12007        <ooanb|>
12008    "};
12009    let expected = indoc! {"
12010        1. foo_and_barˇ
12011        2. zfoo_and_barˇ
12012        3. foo_and_barˇz
12013        4. zfoo_and_barˇz
12014        5. ooanfoo_and_barˇ
12015        6. oanbfoo_and_barˇ
12016
12017        foo_and_barˇ
12018    "};
12019    cx.set_state(initial_state);
12020    cx.update_editor(|editor, window, cx| {
12021        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12022    });
12023    handle_completion_request_with_insert_and_replace(
12024        &mut cx,
12025        completion_marked_buffer,
12026        vec![(completion_text, completion_text)],
12027        Arc::new(AtomicUsize::new(0)),
12028    )
12029    .await;
12030    cx.condition(|editor, _| editor.context_menu_visible())
12031        .await;
12032    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12033        editor
12034            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12035            .unwrap()
12036    });
12037    cx.assert_editor_state(expected);
12038    handle_resolve_completion_request(&mut cx, None).await;
12039    apply_additional_edits.await.unwrap();
12040
12041    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
12042    // (expects the same as if it was inserted at the end)
12043    let completion_text = "foo_and_bar";
12044    let initial_state = indoc! {"
12045        1. ooˇanb
12046        2. zooˇanb
12047        3. ooˇanbz
12048        4. zooˇanbz
12049
12050        ooˇanb
12051    "};
12052    let completion_marked_buffer = indoc! {"
12053        1. ooanb
12054        2. zooanb
12055        3. ooanbz
12056        4. zooanbz
12057
12058        <oo|anb>
12059    "};
12060    let expected = indoc! {"
12061        1. foo_and_barˇ
12062        2. zfoo_and_barˇ
12063        3. foo_and_barˇz
12064        4. zfoo_and_barˇz
12065
12066        foo_and_barˇ
12067    "};
12068    cx.set_state(initial_state);
12069    cx.update_editor(|editor, window, cx| {
12070        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12071    });
12072    handle_completion_request_with_insert_and_replace(
12073        &mut cx,
12074        completion_marked_buffer,
12075        vec![(completion_text, completion_text)],
12076        Arc::new(AtomicUsize::new(0)),
12077    )
12078    .await;
12079    cx.condition(|editor, _| editor.context_menu_visible())
12080        .await;
12081    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12082        editor
12083            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12084            .unwrap()
12085    });
12086    cx.assert_editor_state(expected);
12087    handle_resolve_completion_request(&mut cx, None).await;
12088    apply_additional_edits.await.unwrap();
12089}
12090
12091// This used to crash
12092#[gpui::test]
12093async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
12094    init_test(cx, |_| {});
12095
12096    let buffer_text = indoc! {"
12097        fn main() {
12098            10.satu;
12099
12100            //
12101            // separate cursors so they open in different excerpts (manually reproducible)
12102            //
12103
12104            10.satu20;
12105        }
12106    "};
12107    let multibuffer_text_with_selections = indoc! {"
12108        fn main() {
12109            10.satuˇ;
12110
12111            //
12112
12113            //
12114
12115            10.satuˇ20;
12116        }
12117    "};
12118    let expected_multibuffer = indoc! {"
12119        fn main() {
12120            10.saturating_sub()ˇ;
12121
12122            //
12123
12124            //
12125
12126            10.saturating_sub()ˇ;
12127        }
12128    "};
12129
12130    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
12131    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
12132
12133    let fs = FakeFs::new(cx.executor());
12134    fs.insert_tree(
12135        path!("/a"),
12136        json!({
12137            "main.rs": buffer_text,
12138        }),
12139    )
12140    .await;
12141
12142    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12143    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12144    language_registry.add(rust_lang());
12145    let mut fake_servers = language_registry.register_fake_lsp(
12146        "Rust",
12147        FakeLspAdapter {
12148            capabilities: lsp::ServerCapabilities {
12149                completion_provider: Some(lsp::CompletionOptions {
12150                    resolve_provider: None,
12151                    ..lsp::CompletionOptions::default()
12152                }),
12153                ..lsp::ServerCapabilities::default()
12154            },
12155            ..FakeLspAdapter::default()
12156        },
12157    );
12158    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12159    let cx = &mut VisualTestContext::from_window(*workspace, cx);
12160    let buffer = project
12161        .update(cx, |project, cx| {
12162            project.open_local_buffer(path!("/a/main.rs"), cx)
12163        })
12164        .await
12165        .unwrap();
12166
12167    let multi_buffer = cx.new(|cx| {
12168        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
12169        multi_buffer.push_excerpts(
12170            buffer.clone(),
12171            [ExcerptRange::new(0..first_excerpt_end)],
12172            cx,
12173        );
12174        multi_buffer.push_excerpts(
12175            buffer.clone(),
12176            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
12177            cx,
12178        );
12179        multi_buffer
12180    });
12181
12182    let editor = workspace
12183        .update(cx, |_, window, cx| {
12184            cx.new(|cx| {
12185                Editor::new(
12186                    EditorMode::Full {
12187                        scale_ui_elements_with_buffer_font_size: false,
12188                        show_active_line_background: false,
12189                        sized_by_content: false,
12190                    },
12191                    multi_buffer.clone(),
12192                    Some(project.clone()),
12193                    window,
12194                    cx,
12195                )
12196            })
12197        })
12198        .unwrap();
12199
12200    let pane = workspace
12201        .update(cx, |workspace, _, _| workspace.active_pane().clone())
12202        .unwrap();
12203    pane.update_in(cx, |pane, window, cx| {
12204        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
12205    });
12206
12207    let fake_server = fake_servers.next().await.unwrap();
12208
12209    editor.update_in(cx, |editor, window, cx| {
12210        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12211            s.select_ranges([
12212                Point::new(1, 11)..Point::new(1, 11),
12213                Point::new(7, 11)..Point::new(7, 11),
12214            ])
12215        });
12216
12217        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
12218    });
12219
12220    editor.update_in(cx, |editor, window, cx| {
12221        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12222    });
12223
12224    fake_server
12225        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
12226            let completion_item = lsp::CompletionItem {
12227                label: "saturating_sub()".into(),
12228                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
12229                    lsp::InsertReplaceEdit {
12230                        new_text: "saturating_sub()".to_owned(),
12231                        insert: lsp::Range::new(
12232                            lsp::Position::new(7, 7),
12233                            lsp::Position::new(7, 11),
12234                        ),
12235                        replace: lsp::Range::new(
12236                            lsp::Position::new(7, 7),
12237                            lsp::Position::new(7, 13),
12238                        ),
12239                    },
12240                )),
12241                ..lsp::CompletionItem::default()
12242            };
12243
12244            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
12245        })
12246        .next()
12247        .await
12248        .unwrap();
12249
12250    cx.condition(&editor, |editor, _| editor.context_menu_visible())
12251        .await;
12252
12253    editor
12254        .update_in(cx, |editor, window, cx| {
12255            editor
12256                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12257                .unwrap()
12258        })
12259        .await
12260        .unwrap();
12261
12262    editor.update(cx, |editor, cx| {
12263        assert_text_with_selections(editor, expected_multibuffer, cx);
12264    })
12265}
12266
12267#[gpui::test]
12268async fn test_completion(cx: &mut TestAppContext) {
12269    init_test(cx, |_| {});
12270
12271    let mut cx = EditorLspTestContext::new_rust(
12272        lsp::ServerCapabilities {
12273            completion_provider: Some(lsp::CompletionOptions {
12274                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12275                resolve_provider: Some(true),
12276                ..Default::default()
12277            }),
12278            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12279            ..Default::default()
12280        },
12281        cx,
12282    )
12283    .await;
12284    let counter = Arc::new(AtomicUsize::new(0));
12285
12286    cx.set_state(indoc! {"
12287        oneˇ
12288        two
12289        three
12290    "});
12291    cx.simulate_keystroke(".");
12292    handle_completion_request(
12293        indoc! {"
12294            one.|<>
12295            two
12296            three
12297        "},
12298        vec!["first_completion", "second_completion"],
12299        true,
12300        counter.clone(),
12301        &mut cx,
12302    )
12303    .await;
12304    cx.condition(|editor, _| editor.context_menu_visible())
12305        .await;
12306    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12307
12308    let _handler = handle_signature_help_request(
12309        &mut cx,
12310        lsp::SignatureHelp {
12311            signatures: vec![lsp::SignatureInformation {
12312                label: "test signature".to_string(),
12313                documentation: None,
12314                parameters: Some(vec![lsp::ParameterInformation {
12315                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
12316                    documentation: None,
12317                }]),
12318                active_parameter: None,
12319            }],
12320            active_signature: None,
12321            active_parameter: None,
12322        },
12323    );
12324    cx.update_editor(|editor, window, cx| {
12325        assert!(
12326            !editor.signature_help_state.is_shown(),
12327            "No signature help was called for"
12328        );
12329        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12330    });
12331    cx.run_until_parked();
12332    cx.update_editor(|editor, _, _| {
12333        assert!(
12334            !editor.signature_help_state.is_shown(),
12335            "No signature help should be shown when completions menu is open"
12336        );
12337    });
12338
12339    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12340        editor.context_menu_next(&Default::default(), window, cx);
12341        editor
12342            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12343            .unwrap()
12344    });
12345    cx.assert_editor_state(indoc! {"
12346        one.second_completionˇ
12347        two
12348        three
12349    "});
12350
12351    handle_resolve_completion_request(
12352        &mut cx,
12353        Some(vec![
12354            (
12355                //This overlaps with the primary completion edit which is
12356                //misbehavior from the LSP spec, test that we filter it out
12357                indoc! {"
12358                    one.second_ˇcompletion
12359                    two
12360                    threeˇ
12361                "},
12362                "overlapping additional edit",
12363            ),
12364            (
12365                indoc! {"
12366                    one.second_completion
12367                    two
12368                    threeˇ
12369                "},
12370                "\nadditional edit",
12371            ),
12372        ]),
12373    )
12374    .await;
12375    apply_additional_edits.await.unwrap();
12376    cx.assert_editor_state(indoc! {"
12377        one.second_completionˇ
12378        two
12379        three
12380        additional edit
12381    "});
12382
12383    cx.set_state(indoc! {"
12384        one.second_completion
12385        twoˇ
12386        threeˇ
12387        additional edit
12388    "});
12389    cx.simulate_keystroke(" ");
12390    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12391    cx.simulate_keystroke("s");
12392    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12393
12394    cx.assert_editor_state(indoc! {"
12395        one.second_completion
12396        two sˇ
12397        three sˇ
12398        additional edit
12399    "});
12400    handle_completion_request(
12401        indoc! {"
12402            one.second_completion
12403            two s
12404            three <s|>
12405            additional edit
12406        "},
12407        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
12408        true,
12409        counter.clone(),
12410        &mut cx,
12411    )
12412    .await;
12413    cx.condition(|editor, _| editor.context_menu_visible())
12414        .await;
12415    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12416
12417    cx.simulate_keystroke("i");
12418
12419    handle_completion_request(
12420        indoc! {"
12421            one.second_completion
12422            two si
12423            three <si|>
12424            additional edit
12425        "},
12426        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
12427        true,
12428        counter.clone(),
12429        &mut cx,
12430    )
12431    .await;
12432    cx.condition(|editor, _| editor.context_menu_visible())
12433        .await;
12434    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12435
12436    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12437        editor
12438            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12439            .unwrap()
12440    });
12441    cx.assert_editor_state(indoc! {"
12442        one.second_completion
12443        two sixth_completionˇ
12444        three sixth_completionˇ
12445        additional edit
12446    "});
12447
12448    apply_additional_edits.await.unwrap();
12449
12450    update_test_language_settings(&mut cx, |settings| {
12451        settings.defaults.show_completions_on_input = Some(false);
12452    });
12453    cx.set_state("editorˇ");
12454    cx.simulate_keystroke(".");
12455    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12456    cx.simulate_keystrokes("c l o");
12457    cx.assert_editor_state("editor.cloˇ");
12458    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12459    cx.update_editor(|editor, window, cx| {
12460        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12461    });
12462    handle_completion_request(
12463        "editor.<clo|>",
12464        vec!["close", "clobber"],
12465        true,
12466        counter.clone(),
12467        &mut cx,
12468    )
12469    .await;
12470    cx.condition(|editor, _| editor.context_menu_visible())
12471        .await;
12472    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
12473
12474    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12475        editor
12476            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12477            .unwrap()
12478    });
12479    cx.assert_editor_state("editor.clobberˇ");
12480    handle_resolve_completion_request(&mut cx, None).await;
12481    apply_additional_edits.await.unwrap();
12482}
12483
12484#[gpui::test]
12485async fn test_completion_reuse(cx: &mut TestAppContext) {
12486    init_test(cx, |_| {});
12487
12488    let mut cx = EditorLspTestContext::new_rust(
12489        lsp::ServerCapabilities {
12490            completion_provider: Some(lsp::CompletionOptions {
12491                trigger_characters: Some(vec![".".to_string()]),
12492                ..Default::default()
12493            }),
12494            ..Default::default()
12495        },
12496        cx,
12497    )
12498    .await;
12499
12500    let counter = Arc::new(AtomicUsize::new(0));
12501    cx.set_state("objˇ");
12502    cx.simulate_keystroke(".");
12503
12504    // Initial completion request returns complete results
12505    let is_incomplete = false;
12506    handle_completion_request(
12507        "obj.|<>",
12508        vec!["a", "ab", "abc"],
12509        is_incomplete,
12510        counter.clone(),
12511        &mut cx,
12512    )
12513    .await;
12514    cx.run_until_parked();
12515    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12516    cx.assert_editor_state("obj.ˇ");
12517    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12518
12519    // Type "a" - filters existing completions
12520    cx.simulate_keystroke("a");
12521    cx.run_until_parked();
12522    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12523    cx.assert_editor_state("obj.aˇ");
12524    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12525
12526    // Type "b" - filters existing completions
12527    cx.simulate_keystroke("b");
12528    cx.run_until_parked();
12529    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12530    cx.assert_editor_state("obj.abˇ");
12531    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12532
12533    // Type "c" - filters existing completions
12534    cx.simulate_keystroke("c");
12535    cx.run_until_parked();
12536    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12537    cx.assert_editor_state("obj.abcˇ");
12538    check_displayed_completions(vec!["abc"], &mut cx);
12539
12540    // Backspace to delete "c" - filters existing completions
12541    cx.update_editor(|editor, window, cx| {
12542        editor.backspace(&Backspace, window, cx);
12543    });
12544    cx.run_until_parked();
12545    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12546    cx.assert_editor_state("obj.abˇ");
12547    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12548
12549    // Moving cursor to the left dismisses menu.
12550    cx.update_editor(|editor, window, cx| {
12551        editor.move_left(&MoveLeft, window, cx);
12552    });
12553    cx.run_until_parked();
12554    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12555    cx.assert_editor_state("obj.aˇb");
12556    cx.update_editor(|editor, _, _| {
12557        assert_eq!(editor.context_menu_visible(), false);
12558    });
12559
12560    // Type "b" - new request
12561    cx.simulate_keystroke("b");
12562    let is_incomplete = false;
12563    handle_completion_request(
12564        "obj.<ab|>a",
12565        vec!["ab", "abc"],
12566        is_incomplete,
12567        counter.clone(),
12568        &mut cx,
12569    )
12570    .await;
12571    cx.run_until_parked();
12572    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12573    cx.assert_editor_state("obj.abˇb");
12574    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12575
12576    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
12577    cx.update_editor(|editor, window, cx| {
12578        editor.backspace(&Backspace, window, cx);
12579    });
12580    let is_incomplete = false;
12581    handle_completion_request(
12582        "obj.<a|>b",
12583        vec!["a", "ab", "abc"],
12584        is_incomplete,
12585        counter.clone(),
12586        &mut cx,
12587    )
12588    .await;
12589    cx.run_until_parked();
12590    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12591    cx.assert_editor_state("obj.aˇb");
12592    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12593
12594    // Backspace to delete "a" - dismisses menu.
12595    cx.update_editor(|editor, window, cx| {
12596        editor.backspace(&Backspace, window, cx);
12597    });
12598    cx.run_until_parked();
12599    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12600    cx.assert_editor_state("obj.ˇb");
12601    cx.update_editor(|editor, _, _| {
12602        assert_eq!(editor.context_menu_visible(), false);
12603    });
12604}
12605
12606#[gpui::test]
12607async fn test_word_completion(cx: &mut TestAppContext) {
12608    let lsp_fetch_timeout_ms = 10;
12609    init_test(cx, |language_settings| {
12610        language_settings.defaults.completions = Some(CompletionSettings {
12611            words: WordsCompletionMode::Fallback,
12612            lsp: true,
12613            lsp_fetch_timeout_ms: 10,
12614            lsp_insert_mode: LspInsertMode::Insert,
12615        });
12616    });
12617
12618    let mut cx = EditorLspTestContext::new_rust(
12619        lsp::ServerCapabilities {
12620            completion_provider: Some(lsp::CompletionOptions {
12621                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12622                ..lsp::CompletionOptions::default()
12623            }),
12624            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12625            ..lsp::ServerCapabilities::default()
12626        },
12627        cx,
12628    )
12629    .await;
12630
12631    let throttle_completions = Arc::new(AtomicBool::new(false));
12632
12633    let lsp_throttle_completions = throttle_completions.clone();
12634    let _completion_requests_handler =
12635        cx.lsp
12636            .server
12637            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
12638                let lsp_throttle_completions = lsp_throttle_completions.clone();
12639                let cx = cx.clone();
12640                async move {
12641                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
12642                        cx.background_executor()
12643                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
12644                            .await;
12645                    }
12646                    Ok(Some(lsp::CompletionResponse::Array(vec![
12647                        lsp::CompletionItem {
12648                            label: "first".into(),
12649                            ..lsp::CompletionItem::default()
12650                        },
12651                        lsp::CompletionItem {
12652                            label: "last".into(),
12653                            ..lsp::CompletionItem::default()
12654                        },
12655                    ])))
12656                }
12657            });
12658
12659    cx.set_state(indoc! {"
12660        oneˇ
12661        two
12662        three
12663    "});
12664    cx.simulate_keystroke(".");
12665    cx.executor().run_until_parked();
12666    cx.condition(|editor, _| editor.context_menu_visible())
12667        .await;
12668    cx.update_editor(|editor, window, cx| {
12669        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12670        {
12671            assert_eq!(
12672                completion_menu_entries(&menu),
12673                &["first", "last"],
12674                "When LSP server is fast to reply, no fallback word completions are used"
12675            );
12676        } else {
12677            panic!("expected completion menu to be open");
12678        }
12679        editor.cancel(&Cancel, window, cx);
12680    });
12681    cx.executor().run_until_parked();
12682    cx.condition(|editor, _| !editor.context_menu_visible())
12683        .await;
12684
12685    throttle_completions.store(true, atomic::Ordering::Release);
12686    cx.simulate_keystroke(".");
12687    cx.executor()
12688        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
12689    cx.executor().run_until_parked();
12690    cx.condition(|editor, _| editor.context_menu_visible())
12691        .await;
12692    cx.update_editor(|editor, _, _| {
12693        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12694        {
12695            assert_eq!(completion_menu_entries(&menu), &["one", "three", "two"],
12696                "When LSP server is slow, document words can be shown instead, if configured accordingly");
12697        } else {
12698            panic!("expected completion menu to be open");
12699        }
12700    });
12701}
12702
12703#[gpui::test]
12704async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
12705    init_test(cx, |language_settings| {
12706        language_settings.defaults.completions = Some(CompletionSettings {
12707            words: WordsCompletionMode::Enabled,
12708            lsp: true,
12709            lsp_fetch_timeout_ms: 0,
12710            lsp_insert_mode: LspInsertMode::Insert,
12711        });
12712    });
12713
12714    let mut cx = EditorLspTestContext::new_rust(
12715        lsp::ServerCapabilities {
12716            completion_provider: Some(lsp::CompletionOptions {
12717                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12718                ..lsp::CompletionOptions::default()
12719            }),
12720            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12721            ..lsp::ServerCapabilities::default()
12722        },
12723        cx,
12724    )
12725    .await;
12726
12727    let _completion_requests_handler =
12728        cx.lsp
12729            .server
12730            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12731                Ok(Some(lsp::CompletionResponse::Array(vec![
12732                    lsp::CompletionItem {
12733                        label: "first".into(),
12734                        ..lsp::CompletionItem::default()
12735                    },
12736                    lsp::CompletionItem {
12737                        label: "last".into(),
12738                        ..lsp::CompletionItem::default()
12739                    },
12740                ])))
12741            });
12742
12743    cx.set_state(indoc! {"ˇ
12744        first
12745        last
12746        second
12747    "});
12748    cx.simulate_keystroke(".");
12749    cx.executor().run_until_parked();
12750    cx.condition(|editor, _| editor.context_menu_visible())
12751        .await;
12752    cx.update_editor(|editor, _, _| {
12753        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12754        {
12755            assert_eq!(
12756                completion_menu_entries(&menu),
12757                &["first", "last", "second"],
12758                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
12759            );
12760        } else {
12761            panic!("expected completion menu to be open");
12762        }
12763    });
12764}
12765
12766#[gpui::test]
12767async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
12768    init_test(cx, |language_settings| {
12769        language_settings.defaults.completions = Some(CompletionSettings {
12770            words: WordsCompletionMode::Disabled,
12771            lsp: true,
12772            lsp_fetch_timeout_ms: 0,
12773            lsp_insert_mode: LspInsertMode::Insert,
12774        });
12775    });
12776
12777    let mut cx = EditorLspTestContext::new_rust(
12778        lsp::ServerCapabilities {
12779            completion_provider: Some(lsp::CompletionOptions {
12780                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12781                ..lsp::CompletionOptions::default()
12782            }),
12783            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12784            ..lsp::ServerCapabilities::default()
12785        },
12786        cx,
12787    )
12788    .await;
12789
12790    let _completion_requests_handler =
12791        cx.lsp
12792            .server
12793            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12794                panic!("LSP completions should not be queried when dealing with word completions")
12795            });
12796
12797    cx.set_state(indoc! {"ˇ
12798        first
12799        last
12800        second
12801    "});
12802    cx.update_editor(|editor, window, cx| {
12803        editor.show_word_completions(&ShowWordCompletions, window, cx);
12804    });
12805    cx.executor().run_until_parked();
12806    cx.condition(|editor, _| editor.context_menu_visible())
12807        .await;
12808    cx.update_editor(|editor, _, _| {
12809        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12810        {
12811            assert_eq!(
12812                completion_menu_entries(&menu),
12813                &["first", "last", "second"],
12814                "`ShowWordCompletions` action should show word completions"
12815            );
12816        } else {
12817            panic!("expected completion menu to be open");
12818        }
12819    });
12820
12821    cx.simulate_keystroke("l");
12822    cx.executor().run_until_parked();
12823    cx.condition(|editor, _| editor.context_menu_visible())
12824        .await;
12825    cx.update_editor(|editor, _, _| {
12826        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12827        {
12828            assert_eq!(
12829                completion_menu_entries(&menu),
12830                &["last"],
12831                "After showing word completions, further editing should filter them and not query the LSP"
12832            );
12833        } else {
12834            panic!("expected completion menu to be open");
12835        }
12836    });
12837}
12838
12839#[gpui::test]
12840async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
12841    init_test(cx, |language_settings| {
12842        language_settings.defaults.completions = Some(CompletionSettings {
12843            words: WordsCompletionMode::Fallback,
12844            lsp: false,
12845            lsp_fetch_timeout_ms: 0,
12846            lsp_insert_mode: LspInsertMode::Insert,
12847        });
12848    });
12849
12850    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12851
12852    cx.set_state(indoc! {"ˇ
12853        0_usize
12854        let
12855        33
12856        4.5f32
12857    "});
12858    cx.update_editor(|editor, window, cx| {
12859        editor.show_completions(&ShowCompletions::default(), window, cx);
12860    });
12861    cx.executor().run_until_parked();
12862    cx.condition(|editor, _| editor.context_menu_visible())
12863        .await;
12864    cx.update_editor(|editor, window, cx| {
12865        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12866        {
12867            assert_eq!(
12868                completion_menu_entries(&menu),
12869                &["let"],
12870                "With no digits in the completion query, no digits should be in the word completions"
12871            );
12872        } else {
12873            panic!("expected completion menu to be open");
12874        }
12875        editor.cancel(&Cancel, window, cx);
12876    });
12877
12878    cx.set_state(indoc! {"12879        0_usize
12880        let
12881        3
12882        33.35f32
12883    "});
12884    cx.update_editor(|editor, window, cx| {
12885        editor.show_completions(&ShowCompletions::default(), window, cx);
12886    });
12887    cx.executor().run_until_parked();
12888    cx.condition(|editor, _| editor.context_menu_visible())
12889        .await;
12890    cx.update_editor(|editor, _, _| {
12891        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12892        {
12893            assert_eq!(completion_menu_entries(&menu), &["33", "35f32"], "The digit is in the completion query, \
12894                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
12895        } else {
12896            panic!("expected completion menu to be open");
12897        }
12898    });
12899}
12900
12901fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
12902    let position = || lsp::Position {
12903        line: params.text_document_position.position.line,
12904        character: params.text_document_position.position.character,
12905    };
12906    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12907        range: lsp::Range {
12908            start: position(),
12909            end: position(),
12910        },
12911        new_text: text.to_string(),
12912    }))
12913}
12914
12915#[gpui::test]
12916async fn test_multiline_completion(cx: &mut TestAppContext) {
12917    init_test(cx, |_| {});
12918
12919    let fs = FakeFs::new(cx.executor());
12920    fs.insert_tree(
12921        path!("/a"),
12922        json!({
12923            "main.ts": "a",
12924        }),
12925    )
12926    .await;
12927
12928    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12929    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12930    let typescript_language = Arc::new(Language::new(
12931        LanguageConfig {
12932            name: "TypeScript".into(),
12933            matcher: LanguageMatcher {
12934                path_suffixes: vec!["ts".to_string()],
12935                ..LanguageMatcher::default()
12936            },
12937            line_comments: vec!["// ".into()],
12938            ..LanguageConfig::default()
12939        },
12940        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12941    ));
12942    language_registry.add(typescript_language.clone());
12943    let mut fake_servers = language_registry.register_fake_lsp(
12944        "TypeScript",
12945        FakeLspAdapter {
12946            capabilities: lsp::ServerCapabilities {
12947                completion_provider: Some(lsp::CompletionOptions {
12948                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12949                    ..lsp::CompletionOptions::default()
12950                }),
12951                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12952                ..lsp::ServerCapabilities::default()
12953            },
12954            // Emulate vtsls label generation
12955            label_for_completion: Some(Box::new(|item, _| {
12956                let text = if let Some(description) = item
12957                    .label_details
12958                    .as_ref()
12959                    .and_then(|label_details| label_details.description.as_ref())
12960                {
12961                    format!("{} {}", item.label, description)
12962                } else if let Some(detail) = &item.detail {
12963                    format!("{} {}", item.label, detail)
12964                } else {
12965                    item.label.clone()
12966                };
12967                let len = text.len();
12968                Some(language::CodeLabel {
12969                    text,
12970                    runs: Vec::new(),
12971                    filter_range: 0..len,
12972                })
12973            })),
12974            ..FakeLspAdapter::default()
12975        },
12976    );
12977    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12978    let cx = &mut VisualTestContext::from_window(*workspace, cx);
12979    let worktree_id = workspace
12980        .update(cx, |workspace, _window, cx| {
12981            workspace.project().update(cx, |project, cx| {
12982                project.worktrees(cx).next().unwrap().read(cx).id()
12983            })
12984        })
12985        .unwrap();
12986    let _buffer = project
12987        .update(cx, |project, cx| {
12988            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
12989        })
12990        .await
12991        .unwrap();
12992    let editor = workspace
12993        .update(cx, |workspace, window, cx| {
12994            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
12995        })
12996        .unwrap()
12997        .await
12998        .unwrap()
12999        .downcast::<Editor>()
13000        .unwrap();
13001    let fake_server = fake_servers.next().await.unwrap();
13002
13003    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
13004    let multiline_label_2 = "a\nb\nc\n";
13005    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
13006    let multiline_description = "d\ne\nf\n";
13007    let multiline_detail_2 = "g\nh\ni\n";
13008
13009    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
13010        move |params, _| async move {
13011            Ok(Some(lsp::CompletionResponse::Array(vec![
13012                lsp::CompletionItem {
13013                    label: multiline_label.to_string(),
13014                    text_edit: gen_text_edit(&params, "new_text_1"),
13015                    ..lsp::CompletionItem::default()
13016                },
13017                lsp::CompletionItem {
13018                    label: "single line label 1".to_string(),
13019                    detail: Some(multiline_detail.to_string()),
13020                    text_edit: gen_text_edit(&params, "new_text_2"),
13021                    ..lsp::CompletionItem::default()
13022                },
13023                lsp::CompletionItem {
13024                    label: "single line label 2".to_string(),
13025                    label_details: Some(lsp::CompletionItemLabelDetails {
13026                        description: Some(multiline_description.to_string()),
13027                        detail: None,
13028                    }),
13029                    text_edit: gen_text_edit(&params, "new_text_2"),
13030                    ..lsp::CompletionItem::default()
13031                },
13032                lsp::CompletionItem {
13033                    label: multiline_label_2.to_string(),
13034                    detail: Some(multiline_detail_2.to_string()),
13035                    text_edit: gen_text_edit(&params, "new_text_3"),
13036                    ..lsp::CompletionItem::default()
13037                },
13038                lsp::CompletionItem {
13039                    label: "Label with many     spaces and \t but without newlines".to_string(),
13040                    detail: Some(
13041                        "Details with many     spaces and \t but without newlines".to_string(),
13042                    ),
13043                    text_edit: gen_text_edit(&params, "new_text_4"),
13044                    ..lsp::CompletionItem::default()
13045                },
13046            ])))
13047        },
13048    );
13049
13050    editor.update_in(cx, |editor, window, cx| {
13051        cx.focus_self(window);
13052        editor.move_to_end(&MoveToEnd, window, cx);
13053        editor.handle_input(".", window, cx);
13054    });
13055    cx.run_until_parked();
13056    completion_handle.next().await.unwrap();
13057
13058    editor.update(cx, |editor, _| {
13059        assert!(editor.context_menu_visible());
13060        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13061        {
13062            let completion_labels = menu
13063                .completions
13064                .borrow()
13065                .iter()
13066                .map(|c| c.label.text.clone())
13067                .collect::<Vec<_>>();
13068            assert_eq!(
13069                completion_labels,
13070                &[
13071                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
13072                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
13073                    "single line label 2 d e f ",
13074                    "a b c g h i ",
13075                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
13076                ],
13077                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
13078            );
13079
13080            for completion in menu
13081                .completions
13082                .borrow()
13083                .iter() {
13084                    assert_eq!(
13085                        completion.label.filter_range,
13086                        0..completion.label.text.len(),
13087                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
13088                    );
13089                }
13090        } else {
13091            panic!("expected completion menu to be open");
13092        }
13093    });
13094}
13095
13096#[gpui::test]
13097async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
13098    init_test(cx, |_| {});
13099    let mut cx = EditorLspTestContext::new_rust(
13100        lsp::ServerCapabilities {
13101            completion_provider: Some(lsp::CompletionOptions {
13102                trigger_characters: Some(vec![".".to_string()]),
13103                ..Default::default()
13104            }),
13105            ..Default::default()
13106        },
13107        cx,
13108    )
13109    .await;
13110    cx.lsp
13111        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13112            Ok(Some(lsp::CompletionResponse::Array(vec![
13113                lsp::CompletionItem {
13114                    label: "first".into(),
13115                    ..Default::default()
13116                },
13117                lsp::CompletionItem {
13118                    label: "last".into(),
13119                    ..Default::default()
13120                },
13121            ])))
13122        });
13123    cx.set_state("variableˇ");
13124    cx.simulate_keystroke(".");
13125    cx.executor().run_until_parked();
13126
13127    cx.update_editor(|editor, _, _| {
13128        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13129        {
13130            assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
13131        } else {
13132            panic!("expected completion menu to be open");
13133        }
13134    });
13135
13136    cx.update_editor(|editor, window, cx| {
13137        editor.move_page_down(&MovePageDown::default(), window, cx);
13138        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13139        {
13140            assert!(
13141                menu.selected_item == 1,
13142                "expected PageDown to select the last item from the context menu"
13143            );
13144        } else {
13145            panic!("expected completion menu to stay open after PageDown");
13146        }
13147    });
13148
13149    cx.update_editor(|editor, window, cx| {
13150        editor.move_page_up(&MovePageUp::default(), window, cx);
13151        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13152        {
13153            assert!(
13154                menu.selected_item == 0,
13155                "expected PageUp to select the first item from the context menu"
13156            );
13157        } else {
13158            panic!("expected completion menu to stay open after PageUp");
13159        }
13160    });
13161}
13162
13163#[gpui::test]
13164async fn test_as_is_completions(cx: &mut TestAppContext) {
13165    init_test(cx, |_| {});
13166    let mut cx = EditorLspTestContext::new_rust(
13167        lsp::ServerCapabilities {
13168            completion_provider: Some(lsp::CompletionOptions {
13169                ..Default::default()
13170            }),
13171            ..Default::default()
13172        },
13173        cx,
13174    )
13175    .await;
13176    cx.lsp
13177        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13178            Ok(Some(lsp::CompletionResponse::Array(vec![
13179                lsp::CompletionItem {
13180                    label: "unsafe".into(),
13181                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13182                        range: lsp::Range {
13183                            start: lsp::Position {
13184                                line: 1,
13185                                character: 2,
13186                            },
13187                            end: lsp::Position {
13188                                line: 1,
13189                                character: 3,
13190                            },
13191                        },
13192                        new_text: "unsafe".to_string(),
13193                    })),
13194                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
13195                    ..Default::default()
13196                },
13197            ])))
13198        });
13199    cx.set_state("fn a() {}\n");
13200    cx.executor().run_until_parked();
13201    cx.update_editor(|editor, window, cx| {
13202        editor.show_completions(
13203            &ShowCompletions {
13204                trigger: Some("\n".into()),
13205            },
13206            window,
13207            cx,
13208        );
13209    });
13210    cx.executor().run_until_parked();
13211
13212    cx.update_editor(|editor, window, cx| {
13213        editor.confirm_completion(&Default::default(), window, cx)
13214    });
13215    cx.executor().run_until_parked();
13216    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
13217}
13218
13219#[gpui::test]
13220async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
13221    init_test(cx, |_| {});
13222
13223    let mut cx = EditorLspTestContext::new_rust(
13224        lsp::ServerCapabilities {
13225            completion_provider: Some(lsp::CompletionOptions {
13226                trigger_characters: Some(vec![".".to_string()]),
13227                resolve_provider: Some(true),
13228                ..Default::default()
13229            }),
13230            ..Default::default()
13231        },
13232        cx,
13233    )
13234    .await;
13235
13236    cx.set_state("fn main() { let a = 2ˇ; }");
13237    cx.simulate_keystroke(".");
13238    let completion_item = lsp::CompletionItem {
13239        label: "Some".into(),
13240        kind: Some(lsp::CompletionItemKind::SNIPPET),
13241        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
13242        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
13243            kind: lsp::MarkupKind::Markdown,
13244            value: "```rust\nSome(2)\n```".to_string(),
13245        })),
13246        deprecated: Some(false),
13247        sort_text: Some("Some".to_string()),
13248        filter_text: Some("Some".to_string()),
13249        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
13250        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13251            range: lsp::Range {
13252                start: lsp::Position {
13253                    line: 0,
13254                    character: 22,
13255                },
13256                end: lsp::Position {
13257                    line: 0,
13258                    character: 22,
13259                },
13260            },
13261            new_text: "Some(2)".to_string(),
13262        })),
13263        additional_text_edits: Some(vec![lsp::TextEdit {
13264            range: lsp::Range {
13265                start: lsp::Position {
13266                    line: 0,
13267                    character: 20,
13268                },
13269                end: lsp::Position {
13270                    line: 0,
13271                    character: 22,
13272                },
13273            },
13274            new_text: "".to_string(),
13275        }]),
13276        ..Default::default()
13277    };
13278
13279    let closure_completion_item = completion_item.clone();
13280    let counter = Arc::new(AtomicUsize::new(0));
13281    let counter_clone = counter.clone();
13282    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
13283        let task_completion_item = closure_completion_item.clone();
13284        counter_clone.fetch_add(1, atomic::Ordering::Release);
13285        async move {
13286            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
13287                is_incomplete: true,
13288                item_defaults: None,
13289                items: vec![task_completion_item],
13290            })))
13291        }
13292    });
13293
13294    cx.condition(|editor, _| editor.context_menu_visible())
13295        .await;
13296    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
13297    assert!(request.next().await.is_some());
13298    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13299
13300    cx.simulate_keystrokes("S o m");
13301    cx.condition(|editor, _| editor.context_menu_visible())
13302        .await;
13303    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
13304    assert!(request.next().await.is_some());
13305    assert!(request.next().await.is_some());
13306    assert!(request.next().await.is_some());
13307    request.close();
13308    assert!(request.next().await.is_none());
13309    assert_eq!(
13310        counter.load(atomic::Ordering::Acquire),
13311        4,
13312        "With the completions menu open, only one LSP request should happen per input"
13313    );
13314}
13315
13316#[gpui::test]
13317async fn test_toggle_comment(cx: &mut TestAppContext) {
13318    init_test(cx, |_| {});
13319    let mut cx = EditorTestContext::new(cx).await;
13320    let language = Arc::new(Language::new(
13321        LanguageConfig {
13322            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
13323            ..Default::default()
13324        },
13325        Some(tree_sitter_rust::LANGUAGE.into()),
13326    ));
13327    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
13328
13329    // If multiple selections intersect a line, the line is only toggled once.
13330    cx.set_state(indoc! {"
13331        fn a() {
13332            «//b();
13333            ˇ»// «c();
13334            //ˇ»  d();
13335        }
13336    "});
13337
13338    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13339
13340    cx.assert_editor_state(indoc! {"
13341        fn a() {
13342            «b();
13343            c();
13344            ˇ» d();
13345        }
13346    "});
13347
13348    // The comment prefix is inserted at the same column for every line in a
13349    // selection.
13350    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13351
13352    cx.assert_editor_state(indoc! {"
13353        fn a() {
13354            // «b();
13355            // c();
13356            ˇ»//  d();
13357        }
13358    "});
13359
13360    // If a selection ends at the beginning of a line, that line is not toggled.
13361    cx.set_selections_state(indoc! {"
13362        fn a() {
13363            // b();
13364            «// c();
13365        ˇ»    //  d();
13366        }
13367    "});
13368
13369    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13370
13371    cx.assert_editor_state(indoc! {"
13372        fn a() {
13373            // b();
13374            «c();
13375        ˇ»    //  d();
13376        }
13377    "});
13378
13379    // If a selection span a single line and is empty, the line is toggled.
13380    cx.set_state(indoc! {"
13381        fn a() {
13382            a();
13383            b();
13384        ˇ
13385        }
13386    "});
13387
13388    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13389
13390    cx.assert_editor_state(indoc! {"
13391        fn a() {
13392            a();
13393            b();
13394        //•ˇ
13395        }
13396    "});
13397
13398    // If a selection span multiple lines, empty lines are not toggled.
13399    cx.set_state(indoc! {"
13400        fn a() {
13401            «a();
13402
13403            c();ˇ»
13404        }
13405    "});
13406
13407    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13408
13409    cx.assert_editor_state(indoc! {"
13410        fn a() {
13411            // «a();
13412
13413            // c();ˇ»
13414        }
13415    "});
13416
13417    // If a selection includes multiple comment prefixes, all lines are uncommented.
13418    cx.set_state(indoc! {"
13419        fn a() {
13420            «// a();
13421            /// b();
13422            //! c();ˇ»
13423        }
13424    "});
13425
13426    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13427
13428    cx.assert_editor_state(indoc! {"
13429        fn a() {
13430            «a();
13431            b();
13432            c();ˇ»
13433        }
13434    "});
13435}
13436
13437#[gpui::test]
13438async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
13439    init_test(cx, |_| {});
13440    let mut cx = EditorTestContext::new(cx).await;
13441    let language = Arc::new(Language::new(
13442        LanguageConfig {
13443            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
13444            ..Default::default()
13445        },
13446        Some(tree_sitter_rust::LANGUAGE.into()),
13447    ));
13448    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
13449
13450    let toggle_comments = &ToggleComments {
13451        advance_downwards: false,
13452        ignore_indent: true,
13453    };
13454
13455    // If multiple selections intersect a line, the line is only toggled once.
13456    cx.set_state(indoc! {"
13457        fn a() {
13458        //    «b();
13459        //    c();
13460        //    ˇ» d();
13461        }
13462    "});
13463
13464    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13465
13466    cx.assert_editor_state(indoc! {"
13467        fn a() {
13468            «b();
13469            c();
13470            ˇ» d();
13471        }
13472    "});
13473
13474    // The comment prefix is inserted at the beginning of each line
13475    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13476
13477    cx.assert_editor_state(indoc! {"
13478        fn a() {
13479        //    «b();
13480        //    c();
13481        //    ˇ» d();
13482        }
13483    "});
13484
13485    // If a selection ends at the beginning of a line, that line is not toggled.
13486    cx.set_selections_state(indoc! {"
13487        fn a() {
13488        //    b();
13489        //    «c();
13490        ˇ»//     d();
13491        }
13492    "});
13493
13494    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13495
13496    cx.assert_editor_state(indoc! {"
13497        fn a() {
13498        //    b();
13499            «c();
13500        ˇ»//     d();
13501        }
13502    "});
13503
13504    // If a selection span a single line and is empty, the line is toggled.
13505    cx.set_state(indoc! {"
13506        fn a() {
13507            a();
13508            b();
13509        ˇ
13510        }
13511    "});
13512
13513    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13514
13515    cx.assert_editor_state(indoc! {"
13516        fn a() {
13517            a();
13518            b();
13519        //ˇ
13520        }
13521    "});
13522
13523    // If a selection span multiple lines, empty lines are not toggled.
13524    cx.set_state(indoc! {"
13525        fn a() {
13526            «a();
13527
13528            c();ˇ»
13529        }
13530    "});
13531
13532    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13533
13534    cx.assert_editor_state(indoc! {"
13535        fn a() {
13536        //    «a();
13537
13538        //    c();ˇ»
13539        }
13540    "});
13541
13542    // If a selection includes multiple comment prefixes, all lines are uncommented.
13543    cx.set_state(indoc! {"
13544        fn a() {
13545        //    «a();
13546        ///    b();
13547        //!    c();ˇ»
13548        }
13549    "});
13550
13551    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13552
13553    cx.assert_editor_state(indoc! {"
13554        fn a() {
13555            «a();
13556            b();
13557            c();ˇ»
13558        }
13559    "});
13560}
13561
13562#[gpui::test]
13563async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
13564    init_test(cx, |_| {});
13565
13566    let language = Arc::new(Language::new(
13567        LanguageConfig {
13568            line_comments: vec!["// ".into()],
13569            ..Default::default()
13570        },
13571        Some(tree_sitter_rust::LANGUAGE.into()),
13572    ));
13573
13574    let mut cx = EditorTestContext::new(cx).await;
13575
13576    cx.language_registry().add(language.clone());
13577    cx.update_buffer(|buffer, cx| {
13578        buffer.set_language(Some(language), cx);
13579    });
13580
13581    let toggle_comments = &ToggleComments {
13582        advance_downwards: true,
13583        ignore_indent: false,
13584    };
13585
13586    // Single cursor on one line -> advance
13587    // Cursor moves horizontally 3 characters as well on non-blank line
13588    cx.set_state(indoc!(
13589        "fn a() {
13590             ˇdog();
13591             cat();
13592        }"
13593    ));
13594    cx.update_editor(|editor, window, cx| {
13595        editor.toggle_comments(toggle_comments, window, cx);
13596    });
13597    cx.assert_editor_state(indoc!(
13598        "fn a() {
13599             // dog();
13600             catˇ();
13601        }"
13602    ));
13603
13604    // Single selection on one line -> don't advance
13605    cx.set_state(indoc!(
13606        "fn a() {
13607             «dog()ˇ»;
13608             cat();
13609        }"
13610    ));
13611    cx.update_editor(|editor, window, cx| {
13612        editor.toggle_comments(toggle_comments, window, cx);
13613    });
13614    cx.assert_editor_state(indoc!(
13615        "fn a() {
13616             // «dog()ˇ»;
13617             cat();
13618        }"
13619    ));
13620
13621    // Multiple cursors on one line -> advance
13622    cx.set_state(indoc!(
13623        "fn a() {
13624             ˇdˇog();
13625             cat();
13626        }"
13627    ));
13628    cx.update_editor(|editor, window, cx| {
13629        editor.toggle_comments(toggle_comments, window, cx);
13630    });
13631    cx.assert_editor_state(indoc!(
13632        "fn a() {
13633             // dog();
13634             catˇ(ˇ);
13635        }"
13636    ));
13637
13638    // Multiple cursors on one line, with selection -> don't advance
13639    cx.set_state(indoc!(
13640        "fn a() {
13641             ˇdˇog«()ˇ»;
13642             cat();
13643        }"
13644    ));
13645    cx.update_editor(|editor, window, cx| {
13646        editor.toggle_comments(toggle_comments, window, cx);
13647    });
13648    cx.assert_editor_state(indoc!(
13649        "fn a() {
13650             // ˇdˇog«()ˇ»;
13651             cat();
13652        }"
13653    ));
13654
13655    // Single cursor on one line -> advance
13656    // Cursor moves to column 0 on blank line
13657    cx.set_state(indoc!(
13658        "fn a() {
13659             ˇdog();
13660
13661             cat();
13662        }"
13663    ));
13664    cx.update_editor(|editor, window, cx| {
13665        editor.toggle_comments(toggle_comments, window, cx);
13666    });
13667    cx.assert_editor_state(indoc!(
13668        "fn a() {
13669             // dog();
13670        ˇ
13671             cat();
13672        }"
13673    ));
13674
13675    // Single cursor on one line -> advance
13676    // Cursor starts and ends at column 0
13677    cx.set_state(indoc!(
13678        "fn a() {
13679         ˇ    dog();
13680             cat();
13681        }"
13682    ));
13683    cx.update_editor(|editor, window, cx| {
13684        editor.toggle_comments(toggle_comments, window, cx);
13685    });
13686    cx.assert_editor_state(indoc!(
13687        "fn a() {
13688             // dog();
13689         ˇ    cat();
13690        }"
13691    ));
13692}
13693
13694#[gpui::test]
13695async fn test_toggle_block_comment(cx: &mut TestAppContext) {
13696    init_test(cx, |_| {});
13697
13698    let mut cx = EditorTestContext::new(cx).await;
13699
13700    let html_language = Arc::new(
13701        Language::new(
13702            LanguageConfig {
13703                name: "HTML".into(),
13704                block_comment: Some(("<!-- ".into(), " -->".into())),
13705                ..Default::default()
13706            },
13707            Some(tree_sitter_html::LANGUAGE.into()),
13708        )
13709        .with_injection_query(
13710            r#"
13711            (script_element
13712                (raw_text) @injection.content
13713                (#set! injection.language "javascript"))
13714            "#,
13715        )
13716        .unwrap(),
13717    );
13718
13719    let javascript_language = Arc::new(Language::new(
13720        LanguageConfig {
13721            name: "JavaScript".into(),
13722            line_comments: vec!["// ".into()],
13723            ..Default::default()
13724        },
13725        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
13726    ));
13727
13728    cx.language_registry().add(html_language.clone());
13729    cx.language_registry().add(javascript_language.clone());
13730    cx.update_buffer(|buffer, cx| {
13731        buffer.set_language(Some(html_language), cx);
13732    });
13733
13734    // Toggle comments for empty selections
13735    cx.set_state(
13736        &r#"
13737            <p>A</p>ˇ
13738            <p>B</p>ˇ
13739            <p>C</p>ˇ
13740        "#
13741        .unindent(),
13742    );
13743    cx.update_editor(|editor, window, cx| {
13744        editor.toggle_comments(&ToggleComments::default(), window, cx)
13745    });
13746    cx.assert_editor_state(
13747        &r#"
13748            <!-- <p>A</p>ˇ -->
13749            <!-- <p>B</p>ˇ -->
13750            <!-- <p>C</p>ˇ -->
13751        "#
13752        .unindent(),
13753    );
13754    cx.update_editor(|editor, window, cx| {
13755        editor.toggle_comments(&ToggleComments::default(), window, cx)
13756    });
13757    cx.assert_editor_state(
13758        &r#"
13759            <p>A</p>ˇ
13760            <p>B</p>ˇ
13761            <p>C</p>ˇ
13762        "#
13763        .unindent(),
13764    );
13765
13766    // Toggle comments for mixture of empty and non-empty selections, where
13767    // multiple selections occupy a given line.
13768    cx.set_state(
13769        &r#"
13770            <p>A«</p>
13771            <p>ˇ»B</p>ˇ
13772            <p>C«</p>
13773            <p>ˇ»D</p>ˇ
13774        "#
13775        .unindent(),
13776    );
13777
13778    cx.update_editor(|editor, window, cx| {
13779        editor.toggle_comments(&ToggleComments::default(), window, cx)
13780    });
13781    cx.assert_editor_state(
13782        &r#"
13783            <!-- <p>A«</p>
13784            <p>ˇ»B</p>ˇ -->
13785            <!-- <p>C«</p>
13786            <p>ˇ»D</p>ˇ -->
13787        "#
13788        .unindent(),
13789    );
13790    cx.update_editor(|editor, window, cx| {
13791        editor.toggle_comments(&ToggleComments::default(), window, cx)
13792    });
13793    cx.assert_editor_state(
13794        &r#"
13795            <p>A«</p>
13796            <p>ˇ»B</p>ˇ
13797            <p>C«</p>
13798            <p>ˇ»D</p>ˇ
13799        "#
13800        .unindent(),
13801    );
13802
13803    // Toggle comments when different languages are active for different
13804    // selections.
13805    cx.set_state(
13806        &r#"
13807            ˇ<script>
13808                ˇvar x = new Y();
13809            ˇ</script>
13810        "#
13811        .unindent(),
13812    );
13813    cx.executor().run_until_parked();
13814    cx.update_editor(|editor, window, cx| {
13815        editor.toggle_comments(&ToggleComments::default(), window, cx)
13816    });
13817    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
13818    // Uncommenting and commenting from this position brings in even more wrong artifacts.
13819    cx.assert_editor_state(
13820        &r#"
13821            <!-- ˇ<script> -->
13822                // ˇvar x = new Y();
13823            <!-- ˇ</script> -->
13824        "#
13825        .unindent(),
13826    );
13827}
13828
13829#[gpui::test]
13830fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
13831    init_test(cx, |_| {});
13832
13833    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
13834    let multibuffer = cx.new(|cx| {
13835        let mut multibuffer = MultiBuffer::new(ReadWrite);
13836        multibuffer.push_excerpts(
13837            buffer.clone(),
13838            [
13839                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
13840                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
13841            ],
13842            cx,
13843        );
13844        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
13845        multibuffer
13846    });
13847
13848    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
13849    editor.update_in(cx, |editor, window, cx| {
13850        assert_eq!(editor.text(cx), "aaaa\nbbbb");
13851        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13852            s.select_ranges([
13853                Point::new(0, 0)..Point::new(0, 0),
13854                Point::new(1, 0)..Point::new(1, 0),
13855            ])
13856        });
13857
13858        editor.handle_input("X", window, cx);
13859        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
13860        assert_eq!(
13861            editor.selections.ranges(cx),
13862            [
13863                Point::new(0, 1)..Point::new(0, 1),
13864                Point::new(1, 1)..Point::new(1, 1),
13865            ]
13866        );
13867
13868        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
13869        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13870            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
13871        });
13872        editor.backspace(&Default::default(), window, cx);
13873        assert_eq!(editor.text(cx), "Xa\nbbb");
13874        assert_eq!(
13875            editor.selections.ranges(cx),
13876            [Point::new(1, 0)..Point::new(1, 0)]
13877        );
13878
13879        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13880            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
13881        });
13882        editor.backspace(&Default::default(), window, cx);
13883        assert_eq!(editor.text(cx), "X\nbb");
13884        assert_eq!(
13885            editor.selections.ranges(cx),
13886            [Point::new(0, 1)..Point::new(0, 1)]
13887        );
13888    });
13889}
13890
13891#[gpui::test]
13892fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
13893    init_test(cx, |_| {});
13894
13895    let markers = vec![('[', ']').into(), ('(', ')').into()];
13896    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
13897        indoc! {"
13898            [aaaa
13899            (bbbb]
13900            cccc)",
13901        },
13902        markers.clone(),
13903    );
13904    let excerpt_ranges = markers.into_iter().map(|marker| {
13905        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
13906        ExcerptRange::new(context.clone())
13907    });
13908    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
13909    let multibuffer = cx.new(|cx| {
13910        let mut multibuffer = MultiBuffer::new(ReadWrite);
13911        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
13912        multibuffer
13913    });
13914
13915    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
13916    editor.update_in(cx, |editor, window, cx| {
13917        let (expected_text, selection_ranges) = marked_text_ranges(
13918            indoc! {"
13919                aaaa
13920                bˇbbb
13921                bˇbbˇb
13922                cccc"
13923            },
13924            true,
13925        );
13926        assert_eq!(editor.text(cx), expected_text);
13927        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13928            s.select_ranges(selection_ranges)
13929        });
13930
13931        editor.handle_input("X", window, cx);
13932
13933        let (expected_text, expected_selections) = marked_text_ranges(
13934            indoc! {"
13935                aaaa
13936                bXˇbbXb
13937                bXˇbbXˇb
13938                cccc"
13939            },
13940            false,
13941        );
13942        assert_eq!(editor.text(cx), expected_text);
13943        assert_eq!(editor.selections.ranges(cx), expected_selections);
13944
13945        editor.newline(&Newline, window, cx);
13946        let (expected_text, expected_selections) = marked_text_ranges(
13947            indoc! {"
13948                aaaa
13949                bX
13950                ˇbbX
13951                b
13952                bX
13953                ˇbbX
13954                ˇb
13955                cccc"
13956            },
13957            false,
13958        );
13959        assert_eq!(editor.text(cx), expected_text);
13960        assert_eq!(editor.selections.ranges(cx), expected_selections);
13961    });
13962}
13963
13964#[gpui::test]
13965fn test_refresh_selections(cx: &mut TestAppContext) {
13966    init_test(cx, |_| {});
13967
13968    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
13969    let mut excerpt1_id = None;
13970    let multibuffer = cx.new(|cx| {
13971        let mut multibuffer = MultiBuffer::new(ReadWrite);
13972        excerpt1_id = multibuffer
13973            .push_excerpts(
13974                buffer.clone(),
13975                [
13976                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
13977                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
13978                ],
13979                cx,
13980            )
13981            .into_iter()
13982            .next();
13983        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
13984        multibuffer
13985    });
13986
13987    let editor = cx.add_window(|window, cx| {
13988        let mut editor = build_editor(multibuffer.clone(), window, cx);
13989        let snapshot = editor.snapshot(window, cx);
13990        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13991            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
13992        });
13993        editor.begin_selection(
13994            Point::new(2, 1).to_display_point(&snapshot),
13995            true,
13996            1,
13997            window,
13998            cx,
13999        );
14000        assert_eq!(
14001            editor.selections.ranges(cx),
14002            [
14003                Point::new(1, 3)..Point::new(1, 3),
14004                Point::new(2, 1)..Point::new(2, 1),
14005            ]
14006        );
14007        editor
14008    });
14009
14010    // Refreshing selections is a no-op when excerpts haven't changed.
14011    _ = editor.update(cx, |editor, window, cx| {
14012        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14013        assert_eq!(
14014            editor.selections.ranges(cx),
14015            [
14016                Point::new(1, 3)..Point::new(1, 3),
14017                Point::new(2, 1)..Point::new(2, 1),
14018            ]
14019        );
14020    });
14021
14022    multibuffer.update(cx, |multibuffer, cx| {
14023        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
14024    });
14025    _ = editor.update(cx, |editor, window, cx| {
14026        // Removing an excerpt causes the first selection to become degenerate.
14027        assert_eq!(
14028            editor.selections.ranges(cx),
14029            [
14030                Point::new(0, 0)..Point::new(0, 0),
14031                Point::new(0, 1)..Point::new(0, 1)
14032            ]
14033        );
14034
14035        // Refreshing selections will relocate the first selection to the original buffer
14036        // location.
14037        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14038        assert_eq!(
14039            editor.selections.ranges(cx),
14040            [
14041                Point::new(0, 1)..Point::new(0, 1),
14042                Point::new(0, 3)..Point::new(0, 3)
14043            ]
14044        );
14045        assert!(editor.selections.pending_anchor().is_some());
14046    });
14047}
14048
14049#[gpui::test]
14050fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
14051    init_test(cx, |_| {});
14052
14053    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
14054    let mut excerpt1_id = None;
14055    let multibuffer = cx.new(|cx| {
14056        let mut multibuffer = MultiBuffer::new(ReadWrite);
14057        excerpt1_id = multibuffer
14058            .push_excerpts(
14059                buffer.clone(),
14060                [
14061                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
14062                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
14063                ],
14064                cx,
14065            )
14066            .into_iter()
14067            .next();
14068        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
14069        multibuffer
14070    });
14071
14072    let editor = cx.add_window(|window, cx| {
14073        let mut editor = build_editor(multibuffer.clone(), window, cx);
14074        let snapshot = editor.snapshot(window, cx);
14075        editor.begin_selection(
14076            Point::new(1, 3).to_display_point(&snapshot),
14077            false,
14078            1,
14079            window,
14080            cx,
14081        );
14082        assert_eq!(
14083            editor.selections.ranges(cx),
14084            [Point::new(1, 3)..Point::new(1, 3)]
14085        );
14086        editor
14087    });
14088
14089    multibuffer.update(cx, |multibuffer, cx| {
14090        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
14091    });
14092    _ = editor.update(cx, |editor, window, cx| {
14093        assert_eq!(
14094            editor.selections.ranges(cx),
14095            [Point::new(0, 0)..Point::new(0, 0)]
14096        );
14097
14098        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
14099        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14100        assert_eq!(
14101            editor.selections.ranges(cx),
14102            [Point::new(0, 3)..Point::new(0, 3)]
14103        );
14104        assert!(editor.selections.pending_anchor().is_some());
14105    });
14106}
14107
14108#[gpui::test]
14109async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
14110    init_test(cx, |_| {});
14111
14112    let language = Arc::new(
14113        Language::new(
14114            LanguageConfig {
14115                brackets: BracketPairConfig {
14116                    pairs: vec![
14117                        BracketPair {
14118                            start: "{".to_string(),
14119                            end: "}".to_string(),
14120                            close: true,
14121                            surround: true,
14122                            newline: true,
14123                        },
14124                        BracketPair {
14125                            start: "/* ".to_string(),
14126                            end: " */".to_string(),
14127                            close: true,
14128                            surround: true,
14129                            newline: true,
14130                        },
14131                    ],
14132                    ..Default::default()
14133                },
14134                ..Default::default()
14135            },
14136            Some(tree_sitter_rust::LANGUAGE.into()),
14137        )
14138        .with_indents_query("")
14139        .unwrap(),
14140    );
14141
14142    let text = concat!(
14143        "{   }\n",     //
14144        "  x\n",       //
14145        "  /*   */\n", //
14146        "x\n",         //
14147        "{{} }\n",     //
14148    );
14149
14150    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
14151    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
14152    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
14153    editor
14154        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
14155        .await;
14156
14157    editor.update_in(cx, |editor, window, cx| {
14158        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14159            s.select_display_ranges([
14160                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
14161                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
14162                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
14163            ])
14164        });
14165        editor.newline(&Newline, window, cx);
14166
14167        assert_eq!(
14168            editor.buffer().read(cx).read(cx).text(),
14169            concat!(
14170                "{ \n",    // Suppress rustfmt
14171                "\n",      //
14172                "}\n",     //
14173                "  x\n",   //
14174                "  /* \n", //
14175                "  \n",    //
14176                "  */\n",  //
14177                "x\n",     //
14178                "{{} \n",  //
14179                "}\n",     //
14180            )
14181        );
14182    });
14183}
14184
14185#[gpui::test]
14186fn test_highlighted_ranges(cx: &mut TestAppContext) {
14187    init_test(cx, |_| {});
14188
14189    let editor = cx.add_window(|window, cx| {
14190        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
14191        build_editor(buffer.clone(), window, cx)
14192    });
14193
14194    _ = editor.update(cx, |editor, window, cx| {
14195        struct Type1;
14196        struct Type2;
14197
14198        let buffer = editor.buffer.read(cx).snapshot(cx);
14199
14200        let anchor_range =
14201            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
14202
14203        editor.highlight_background::<Type1>(
14204            &[
14205                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
14206                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
14207                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
14208                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
14209            ],
14210            |_| Hsla::red(),
14211            cx,
14212        );
14213        editor.highlight_background::<Type2>(
14214            &[
14215                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
14216                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
14217                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
14218                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
14219            ],
14220            |_| Hsla::green(),
14221            cx,
14222        );
14223
14224        let snapshot = editor.snapshot(window, cx);
14225        let mut highlighted_ranges = editor.background_highlights_in_range(
14226            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
14227            &snapshot,
14228            cx.theme(),
14229        );
14230        // Enforce a consistent ordering based on color without relying on the ordering of the
14231        // highlight's `TypeId` which is non-executor.
14232        highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
14233        assert_eq!(
14234            highlighted_ranges,
14235            &[
14236                (
14237                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
14238                    Hsla::red(),
14239                ),
14240                (
14241                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
14242                    Hsla::red(),
14243                ),
14244                (
14245                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
14246                    Hsla::green(),
14247                ),
14248                (
14249                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
14250                    Hsla::green(),
14251                ),
14252            ]
14253        );
14254        assert_eq!(
14255            editor.background_highlights_in_range(
14256                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
14257                &snapshot,
14258                cx.theme(),
14259            ),
14260            &[(
14261                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
14262                Hsla::red(),
14263            )]
14264        );
14265    });
14266}
14267
14268#[gpui::test]
14269async fn test_following(cx: &mut TestAppContext) {
14270    init_test(cx, |_| {});
14271
14272    let fs = FakeFs::new(cx.executor());
14273    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
14274
14275    let buffer = project.update(cx, |project, cx| {
14276        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
14277        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
14278    });
14279    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
14280    let follower = cx.update(|cx| {
14281        cx.open_window(
14282            WindowOptions {
14283                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
14284                    gpui::Point::new(px(0.), px(0.)),
14285                    gpui::Point::new(px(10.), px(80.)),
14286                ))),
14287                ..Default::default()
14288            },
14289            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
14290        )
14291        .unwrap()
14292    });
14293
14294    let is_still_following = Rc::new(RefCell::new(true));
14295    let follower_edit_event_count = Rc::new(RefCell::new(0));
14296    let pending_update = Rc::new(RefCell::new(None));
14297    let leader_entity = leader.root(cx).unwrap();
14298    let follower_entity = follower.root(cx).unwrap();
14299    _ = follower.update(cx, {
14300        let update = pending_update.clone();
14301        let is_still_following = is_still_following.clone();
14302        let follower_edit_event_count = follower_edit_event_count.clone();
14303        |_, window, cx| {
14304            cx.subscribe_in(
14305                &leader_entity,
14306                window,
14307                move |_, leader, event, window, cx| {
14308                    leader.read(cx).add_event_to_update_proto(
14309                        event,
14310                        &mut update.borrow_mut(),
14311                        window,
14312                        cx,
14313                    );
14314                },
14315            )
14316            .detach();
14317
14318            cx.subscribe_in(
14319                &follower_entity,
14320                window,
14321                move |_, _, event: &EditorEvent, _window, _cx| {
14322                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
14323                        *is_still_following.borrow_mut() = false;
14324                    }
14325
14326                    if let EditorEvent::BufferEdited = event {
14327                        *follower_edit_event_count.borrow_mut() += 1;
14328                    }
14329                },
14330            )
14331            .detach();
14332        }
14333    });
14334
14335    // Update the selections only
14336    _ = leader.update(cx, |leader, window, cx| {
14337        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14338            s.select_ranges([1..1])
14339        });
14340    });
14341    follower
14342        .update(cx, |follower, window, cx| {
14343            follower.apply_update_proto(
14344                &project,
14345                pending_update.borrow_mut().take().unwrap(),
14346                window,
14347                cx,
14348            )
14349        })
14350        .unwrap()
14351        .await
14352        .unwrap();
14353    _ = follower.update(cx, |follower, _, cx| {
14354        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
14355    });
14356    assert!(*is_still_following.borrow());
14357    assert_eq!(*follower_edit_event_count.borrow(), 0);
14358
14359    // Update the scroll position only
14360    _ = leader.update(cx, |leader, window, cx| {
14361        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
14362    });
14363    follower
14364        .update(cx, |follower, window, cx| {
14365            follower.apply_update_proto(
14366                &project,
14367                pending_update.borrow_mut().take().unwrap(),
14368                window,
14369                cx,
14370            )
14371        })
14372        .unwrap()
14373        .await
14374        .unwrap();
14375    assert_eq!(
14376        follower
14377            .update(cx, |follower, _, cx| follower.scroll_position(cx))
14378            .unwrap(),
14379        gpui::Point::new(1.5, 3.5)
14380    );
14381    assert!(*is_still_following.borrow());
14382    assert_eq!(*follower_edit_event_count.borrow(), 0);
14383
14384    // Update the selections and scroll position. The follower's scroll position is updated
14385    // via autoscroll, not via the leader's exact scroll position.
14386    _ = leader.update(cx, |leader, window, cx| {
14387        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14388            s.select_ranges([0..0])
14389        });
14390        leader.request_autoscroll(Autoscroll::newest(), cx);
14391        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
14392    });
14393    follower
14394        .update(cx, |follower, window, cx| {
14395            follower.apply_update_proto(
14396                &project,
14397                pending_update.borrow_mut().take().unwrap(),
14398                window,
14399                cx,
14400            )
14401        })
14402        .unwrap()
14403        .await
14404        .unwrap();
14405    _ = follower.update(cx, |follower, _, cx| {
14406        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
14407        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
14408    });
14409    assert!(*is_still_following.borrow());
14410
14411    // Creating a pending selection that precedes another selection
14412    _ = leader.update(cx, |leader, window, cx| {
14413        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14414            s.select_ranges([1..1])
14415        });
14416        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
14417    });
14418    follower
14419        .update(cx, |follower, window, cx| {
14420            follower.apply_update_proto(
14421                &project,
14422                pending_update.borrow_mut().take().unwrap(),
14423                window,
14424                cx,
14425            )
14426        })
14427        .unwrap()
14428        .await
14429        .unwrap();
14430    _ = follower.update(cx, |follower, _, cx| {
14431        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
14432    });
14433    assert!(*is_still_following.borrow());
14434
14435    // Extend the pending selection so that it surrounds another selection
14436    _ = leader.update(cx, |leader, window, cx| {
14437        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
14438    });
14439    follower
14440        .update(cx, |follower, window, cx| {
14441            follower.apply_update_proto(
14442                &project,
14443                pending_update.borrow_mut().take().unwrap(),
14444                window,
14445                cx,
14446            )
14447        })
14448        .unwrap()
14449        .await
14450        .unwrap();
14451    _ = follower.update(cx, |follower, _, cx| {
14452        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
14453    });
14454
14455    // Scrolling locally breaks the follow
14456    _ = follower.update(cx, |follower, window, cx| {
14457        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
14458        follower.set_scroll_anchor(
14459            ScrollAnchor {
14460                anchor: top_anchor,
14461                offset: gpui::Point::new(0.0, 0.5),
14462            },
14463            window,
14464            cx,
14465        );
14466    });
14467    assert!(!(*is_still_following.borrow()));
14468}
14469
14470#[gpui::test]
14471async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
14472    init_test(cx, |_| {});
14473
14474    let fs = FakeFs::new(cx.executor());
14475    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
14476    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14477    let pane = workspace
14478        .update(cx, |workspace, _, _| workspace.active_pane().clone())
14479        .unwrap();
14480
14481    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14482
14483    let leader = pane.update_in(cx, |_, window, cx| {
14484        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
14485        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
14486    });
14487
14488    // Start following the editor when it has no excerpts.
14489    let mut state_message =
14490        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
14491    let workspace_entity = workspace.root(cx).unwrap();
14492    let follower_1 = cx
14493        .update_window(*workspace.deref(), |_, window, cx| {
14494            Editor::from_state_proto(
14495                workspace_entity,
14496                ViewId {
14497                    creator: CollaboratorId::PeerId(PeerId::default()),
14498                    id: 0,
14499                },
14500                &mut state_message,
14501                window,
14502                cx,
14503            )
14504        })
14505        .unwrap()
14506        .unwrap()
14507        .await
14508        .unwrap();
14509
14510    let update_message = Rc::new(RefCell::new(None));
14511    follower_1.update_in(cx, {
14512        let update = update_message.clone();
14513        |_, window, cx| {
14514            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
14515                leader.read(cx).add_event_to_update_proto(
14516                    event,
14517                    &mut update.borrow_mut(),
14518                    window,
14519                    cx,
14520                );
14521            })
14522            .detach();
14523        }
14524    });
14525
14526    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
14527        (
14528            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
14529            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
14530        )
14531    });
14532
14533    // Insert some excerpts.
14534    leader.update(cx, |leader, cx| {
14535        leader.buffer.update(cx, |multibuffer, cx| {
14536            multibuffer.set_excerpts_for_path(
14537                PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
14538                buffer_1.clone(),
14539                vec![
14540                    Point::row_range(0..3),
14541                    Point::row_range(1..6),
14542                    Point::row_range(12..15),
14543                ],
14544                0,
14545                cx,
14546            );
14547            multibuffer.set_excerpts_for_path(
14548                PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
14549                buffer_2.clone(),
14550                vec![Point::row_range(0..6), Point::row_range(8..12)],
14551                0,
14552                cx,
14553            );
14554        });
14555    });
14556
14557    // Apply the update of adding the excerpts.
14558    follower_1
14559        .update_in(cx, |follower, window, cx| {
14560            follower.apply_update_proto(
14561                &project,
14562                update_message.borrow().clone().unwrap(),
14563                window,
14564                cx,
14565            )
14566        })
14567        .await
14568        .unwrap();
14569    assert_eq!(
14570        follower_1.update(cx, |editor, cx| editor.text(cx)),
14571        leader.update(cx, |editor, cx| editor.text(cx))
14572    );
14573    update_message.borrow_mut().take();
14574
14575    // Start following separately after it already has excerpts.
14576    let mut state_message =
14577        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
14578    let workspace_entity = workspace.root(cx).unwrap();
14579    let follower_2 = cx
14580        .update_window(*workspace.deref(), |_, window, cx| {
14581            Editor::from_state_proto(
14582                workspace_entity,
14583                ViewId {
14584                    creator: CollaboratorId::PeerId(PeerId::default()),
14585                    id: 0,
14586                },
14587                &mut state_message,
14588                window,
14589                cx,
14590            )
14591        })
14592        .unwrap()
14593        .unwrap()
14594        .await
14595        .unwrap();
14596    assert_eq!(
14597        follower_2.update(cx, |editor, cx| editor.text(cx)),
14598        leader.update(cx, |editor, cx| editor.text(cx))
14599    );
14600
14601    // Remove some excerpts.
14602    leader.update(cx, |leader, cx| {
14603        leader.buffer.update(cx, |multibuffer, cx| {
14604            let excerpt_ids = multibuffer.excerpt_ids();
14605            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
14606            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
14607        });
14608    });
14609
14610    // Apply the update of removing the excerpts.
14611    follower_1
14612        .update_in(cx, |follower, window, cx| {
14613            follower.apply_update_proto(
14614                &project,
14615                update_message.borrow().clone().unwrap(),
14616                window,
14617                cx,
14618            )
14619        })
14620        .await
14621        .unwrap();
14622    follower_2
14623        .update_in(cx, |follower, window, cx| {
14624            follower.apply_update_proto(
14625                &project,
14626                update_message.borrow().clone().unwrap(),
14627                window,
14628                cx,
14629            )
14630        })
14631        .await
14632        .unwrap();
14633    update_message.borrow_mut().take();
14634    assert_eq!(
14635        follower_1.update(cx, |editor, cx| editor.text(cx)),
14636        leader.update(cx, |editor, cx| editor.text(cx))
14637    );
14638}
14639
14640#[gpui::test]
14641async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14642    init_test(cx, |_| {});
14643
14644    let mut cx = EditorTestContext::new(cx).await;
14645    let lsp_store =
14646        cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
14647
14648    cx.set_state(indoc! {"
14649        ˇfn func(abc def: i32) -> u32 {
14650        }
14651    "});
14652
14653    cx.update(|_, cx| {
14654        lsp_store.update(cx, |lsp_store, cx| {
14655            lsp_store
14656                .update_diagnostics(
14657                    LanguageServerId(0),
14658                    lsp::PublishDiagnosticsParams {
14659                        uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
14660                        version: None,
14661                        diagnostics: vec![
14662                            lsp::Diagnostic {
14663                                range: lsp::Range::new(
14664                                    lsp::Position::new(0, 11),
14665                                    lsp::Position::new(0, 12),
14666                                ),
14667                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14668                                ..Default::default()
14669                            },
14670                            lsp::Diagnostic {
14671                                range: lsp::Range::new(
14672                                    lsp::Position::new(0, 12),
14673                                    lsp::Position::new(0, 15),
14674                                ),
14675                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14676                                ..Default::default()
14677                            },
14678                            lsp::Diagnostic {
14679                                range: lsp::Range::new(
14680                                    lsp::Position::new(0, 25),
14681                                    lsp::Position::new(0, 28),
14682                                ),
14683                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14684                                ..Default::default()
14685                            },
14686                        ],
14687                    },
14688                    None,
14689                    DiagnosticSourceKind::Pushed,
14690                    &[],
14691                    cx,
14692                )
14693                .unwrap()
14694        });
14695    });
14696
14697    executor.run_until_parked();
14698
14699    cx.update_editor(|editor, window, cx| {
14700        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
14701    });
14702
14703    cx.assert_editor_state(indoc! {"
14704        fn func(abc def: i32) -> ˇu32 {
14705        }
14706    "});
14707
14708    cx.update_editor(|editor, window, cx| {
14709        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
14710    });
14711
14712    cx.assert_editor_state(indoc! {"
14713        fn func(abc ˇdef: i32) -> u32 {
14714        }
14715    "});
14716
14717    cx.update_editor(|editor, window, cx| {
14718        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
14719    });
14720
14721    cx.assert_editor_state(indoc! {"
14722        fn func(abcˇ def: i32) -> u32 {
14723        }
14724    "});
14725
14726    cx.update_editor(|editor, window, cx| {
14727        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
14728    });
14729
14730    cx.assert_editor_state(indoc! {"
14731        fn func(abc def: i32) -> ˇu32 {
14732        }
14733    "});
14734}
14735
14736#[gpui::test]
14737async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14738    init_test(cx, |_| {});
14739
14740    let mut cx = EditorTestContext::new(cx).await;
14741
14742    let diff_base = r#"
14743        use some::mod;
14744
14745        const A: u32 = 42;
14746
14747        fn main() {
14748            println!("hello");
14749
14750            println!("world");
14751        }
14752        "#
14753    .unindent();
14754
14755    // Edits are modified, removed, modified, added
14756    cx.set_state(
14757        &r#"
14758        use some::modified;
14759
14760        ˇ
14761        fn main() {
14762            println!("hello there");
14763
14764            println!("around the");
14765            println!("world");
14766        }
14767        "#
14768        .unindent(),
14769    );
14770
14771    cx.set_head_text(&diff_base);
14772    executor.run_until_parked();
14773
14774    cx.update_editor(|editor, window, cx| {
14775        //Wrap around the bottom of the buffer
14776        for _ in 0..3 {
14777            editor.go_to_next_hunk(&GoToHunk, window, cx);
14778        }
14779    });
14780
14781    cx.assert_editor_state(
14782        &r#"
14783        ˇuse some::modified;
14784
14785
14786        fn main() {
14787            println!("hello there");
14788
14789            println!("around the");
14790            println!("world");
14791        }
14792        "#
14793        .unindent(),
14794    );
14795
14796    cx.update_editor(|editor, window, cx| {
14797        //Wrap around the top of the buffer
14798        for _ in 0..2 {
14799            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14800        }
14801    });
14802
14803    cx.assert_editor_state(
14804        &r#"
14805        use some::modified;
14806
14807
14808        fn main() {
14809        ˇ    println!("hello there");
14810
14811            println!("around the");
14812            println!("world");
14813        }
14814        "#
14815        .unindent(),
14816    );
14817
14818    cx.update_editor(|editor, window, cx| {
14819        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14820    });
14821
14822    cx.assert_editor_state(
14823        &r#"
14824        use some::modified;
14825
14826        ˇ
14827        fn main() {
14828            println!("hello there");
14829
14830            println!("around the");
14831            println!("world");
14832        }
14833        "#
14834        .unindent(),
14835    );
14836
14837    cx.update_editor(|editor, window, cx| {
14838        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14839    });
14840
14841    cx.assert_editor_state(
14842        &r#"
14843        ˇuse some::modified;
14844
14845
14846        fn main() {
14847            println!("hello there");
14848
14849            println!("around the");
14850            println!("world");
14851        }
14852        "#
14853        .unindent(),
14854    );
14855
14856    cx.update_editor(|editor, window, cx| {
14857        for _ in 0..2 {
14858            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14859        }
14860    });
14861
14862    cx.assert_editor_state(
14863        &r#"
14864        use some::modified;
14865
14866
14867        fn main() {
14868        ˇ    println!("hello there");
14869
14870            println!("around the");
14871            println!("world");
14872        }
14873        "#
14874        .unindent(),
14875    );
14876
14877    cx.update_editor(|editor, window, cx| {
14878        editor.fold(&Fold, window, cx);
14879    });
14880
14881    cx.update_editor(|editor, window, cx| {
14882        editor.go_to_next_hunk(&GoToHunk, window, cx);
14883    });
14884
14885    cx.assert_editor_state(
14886        &r#"
14887        ˇuse some::modified;
14888
14889
14890        fn main() {
14891            println!("hello there");
14892
14893            println!("around the");
14894            println!("world");
14895        }
14896        "#
14897        .unindent(),
14898    );
14899}
14900
14901#[test]
14902fn test_split_words() {
14903    fn split(text: &str) -> Vec<&str> {
14904        split_words(text).collect()
14905    }
14906
14907    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
14908    assert_eq!(split("hello_world"), &["hello_", "world"]);
14909    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
14910    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
14911    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
14912    assert_eq!(split("helloworld"), &["helloworld"]);
14913
14914    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
14915}
14916
14917#[gpui::test]
14918async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
14919    init_test(cx, |_| {});
14920
14921    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
14922    let mut assert = |before, after| {
14923        let _state_context = cx.set_state(before);
14924        cx.run_until_parked();
14925        cx.update_editor(|editor, window, cx| {
14926            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
14927        });
14928        cx.run_until_parked();
14929        cx.assert_editor_state(after);
14930    };
14931
14932    // Outside bracket jumps to outside of matching bracket
14933    assert("console.logˇ(var);", "console.log(var)ˇ;");
14934    assert("console.log(var)ˇ;", "console.logˇ(var);");
14935
14936    // Inside bracket jumps to inside of matching bracket
14937    assert("console.log(ˇvar);", "console.log(varˇ);");
14938    assert("console.log(varˇ);", "console.log(ˇvar);");
14939
14940    // When outside a bracket and inside, favor jumping to the inside bracket
14941    assert(
14942        "console.log('foo', [1, 2, 3]ˇ);",
14943        "console.log(ˇ'foo', [1, 2, 3]);",
14944    );
14945    assert(
14946        "console.log(ˇ'foo', [1, 2, 3]);",
14947        "console.log('foo', [1, 2, 3]ˇ);",
14948    );
14949
14950    // Bias forward if two options are equally likely
14951    assert(
14952        "let result = curried_fun()ˇ();",
14953        "let result = curried_fun()()ˇ;",
14954    );
14955
14956    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
14957    assert(
14958        indoc! {"
14959            function test() {
14960                console.log('test')ˇ
14961            }"},
14962        indoc! {"
14963            function test() {
14964                console.logˇ('test')
14965            }"},
14966    );
14967}
14968
14969#[gpui::test]
14970async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
14971    init_test(cx, |_| {});
14972
14973    let fs = FakeFs::new(cx.executor());
14974    fs.insert_tree(
14975        path!("/a"),
14976        json!({
14977            "main.rs": "fn main() { let a = 5; }",
14978            "other.rs": "// Test file",
14979        }),
14980    )
14981    .await;
14982    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14983
14984    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14985    language_registry.add(Arc::new(Language::new(
14986        LanguageConfig {
14987            name: "Rust".into(),
14988            matcher: LanguageMatcher {
14989                path_suffixes: vec!["rs".to_string()],
14990                ..Default::default()
14991            },
14992            brackets: BracketPairConfig {
14993                pairs: vec![BracketPair {
14994                    start: "{".to_string(),
14995                    end: "}".to_string(),
14996                    close: true,
14997                    surround: true,
14998                    newline: true,
14999                }],
15000                disabled_scopes_by_bracket_ix: Vec::new(),
15001            },
15002            ..Default::default()
15003        },
15004        Some(tree_sitter_rust::LANGUAGE.into()),
15005    )));
15006    let mut fake_servers = language_registry.register_fake_lsp(
15007        "Rust",
15008        FakeLspAdapter {
15009            capabilities: lsp::ServerCapabilities {
15010                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
15011                    first_trigger_character: "{".to_string(),
15012                    more_trigger_character: None,
15013                }),
15014                ..Default::default()
15015            },
15016            ..Default::default()
15017        },
15018    );
15019
15020    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15021
15022    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15023
15024    let worktree_id = workspace
15025        .update(cx, |workspace, _, cx| {
15026            workspace.project().update(cx, |project, cx| {
15027                project.worktrees(cx).next().unwrap().read(cx).id()
15028            })
15029        })
15030        .unwrap();
15031
15032    let buffer = project
15033        .update(cx, |project, cx| {
15034            project.open_local_buffer(path!("/a/main.rs"), cx)
15035        })
15036        .await
15037        .unwrap();
15038    let editor_handle = workspace
15039        .update(cx, |workspace, window, cx| {
15040            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
15041        })
15042        .unwrap()
15043        .await
15044        .unwrap()
15045        .downcast::<Editor>()
15046        .unwrap();
15047
15048    cx.executor().start_waiting();
15049    let fake_server = fake_servers.next().await.unwrap();
15050
15051    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
15052        |params, _| async move {
15053            assert_eq!(
15054                params.text_document_position.text_document.uri,
15055                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
15056            );
15057            assert_eq!(
15058                params.text_document_position.position,
15059                lsp::Position::new(0, 21),
15060            );
15061
15062            Ok(Some(vec![lsp::TextEdit {
15063                new_text: "]".to_string(),
15064                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15065            }]))
15066        },
15067    );
15068
15069    editor_handle.update_in(cx, |editor, window, cx| {
15070        window.focus(&editor.focus_handle(cx));
15071        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15072            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
15073        });
15074        editor.handle_input("{", window, cx);
15075    });
15076
15077    cx.executor().run_until_parked();
15078
15079    buffer.update(cx, |buffer, _| {
15080        assert_eq!(
15081            buffer.text(),
15082            "fn main() { let a = {5}; }",
15083            "No extra braces from on type formatting should appear in the buffer"
15084        )
15085    });
15086}
15087
15088#[gpui::test(iterations = 20, seeds(31))]
15089async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
15090    init_test(cx, |_| {});
15091
15092    let mut cx = EditorLspTestContext::new_rust(
15093        lsp::ServerCapabilities {
15094            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
15095                first_trigger_character: ".".to_string(),
15096                more_trigger_character: None,
15097            }),
15098            ..Default::default()
15099        },
15100        cx,
15101    )
15102    .await;
15103
15104    cx.update_buffer(|buffer, _| {
15105        // This causes autoindent to be async.
15106        buffer.set_sync_parse_timeout(Duration::ZERO)
15107    });
15108
15109    cx.set_state("fn c() {\n    d()ˇ\n}\n");
15110    cx.simulate_keystroke("\n");
15111    cx.run_until_parked();
15112
15113    let buffer_cloned =
15114        cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap().clone());
15115    let mut request =
15116        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
15117            let buffer_cloned = buffer_cloned.clone();
15118            async move {
15119                buffer_cloned.update(&mut cx, |buffer, _| {
15120                    assert_eq!(
15121                        buffer.text(),
15122                        "fn c() {\n    d()\n        .\n}\n",
15123                        "OnTypeFormatting should triggered after autoindent applied"
15124                    )
15125                })?;
15126
15127                Ok(Some(vec![]))
15128            }
15129        });
15130
15131    cx.simulate_keystroke(".");
15132    cx.run_until_parked();
15133
15134    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
15135    assert!(request.next().await.is_some());
15136    request.close();
15137    assert!(request.next().await.is_none());
15138}
15139
15140#[gpui::test]
15141async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
15142    init_test(cx, |_| {});
15143
15144    let fs = FakeFs::new(cx.executor());
15145    fs.insert_tree(
15146        path!("/a"),
15147        json!({
15148            "main.rs": "fn main() { let a = 5; }",
15149            "other.rs": "// Test file",
15150        }),
15151    )
15152    .await;
15153
15154    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15155
15156    let server_restarts = Arc::new(AtomicUsize::new(0));
15157    let closure_restarts = Arc::clone(&server_restarts);
15158    let language_server_name = "test language server";
15159    let language_name: LanguageName = "Rust".into();
15160
15161    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15162    language_registry.add(Arc::new(Language::new(
15163        LanguageConfig {
15164            name: language_name.clone(),
15165            matcher: LanguageMatcher {
15166                path_suffixes: vec!["rs".to_string()],
15167                ..Default::default()
15168            },
15169            ..Default::default()
15170        },
15171        Some(tree_sitter_rust::LANGUAGE.into()),
15172    )));
15173    let mut fake_servers = language_registry.register_fake_lsp(
15174        "Rust",
15175        FakeLspAdapter {
15176            name: language_server_name,
15177            initialization_options: Some(json!({
15178                "testOptionValue": true
15179            })),
15180            initializer: Some(Box::new(move |fake_server| {
15181                let task_restarts = Arc::clone(&closure_restarts);
15182                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
15183                    task_restarts.fetch_add(1, atomic::Ordering::Release);
15184                    futures::future::ready(Ok(()))
15185                });
15186            })),
15187            ..Default::default()
15188        },
15189    );
15190
15191    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15192    let _buffer = project
15193        .update(cx, |project, cx| {
15194            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
15195        })
15196        .await
15197        .unwrap();
15198    let _fake_server = fake_servers.next().await.unwrap();
15199    update_test_language_settings(cx, |language_settings| {
15200        language_settings.languages.0.insert(
15201            language_name.clone(),
15202            LanguageSettingsContent {
15203                tab_size: NonZeroU32::new(8),
15204                ..Default::default()
15205            },
15206        );
15207    });
15208    cx.executor().run_until_parked();
15209    assert_eq!(
15210        server_restarts.load(atomic::Ordering::Acquire),
15211        0,
15212        "Should not restart LSP server on an unrelated change"
15213    );
15214
15215    update_test_project_settings(cx, |project_settings| {
15216        project_settings.lsp.insert(
15217            "Some other server name".into(),
15218            LspSettings {
15219                binary: None,
15220                settings: None,
15221                initialization_options: Some(json!({
15222                    "some other init value": false
15223                })),
15224                enable_lsp_tasks: false,
15225            },
15226        );
15227    });
15228    cx.executor().run_until_parked();
15229    assert_eq!(
15230        server_restarts.load(atomic::Ordering::Acquire),
15231        0,
15232        "Should not restart LSP server on an unrelated LSP settings change"
15233    );
15234
15235    update_test_project_settings(cx, |project_settings| {
15236        project_settings.lsp.insert(
15237            language_server_name.into(),
15238            LspSettings {
15239                binary: None,
15240                settings: None,
15241                initialization_options: Some(json!({
15242                    "anotherInitValue": false
15243                })),
15244                enable_lsp_tasks: false,
15245            },
15246        );
15247    });
15248    cx.executor().run_until_parked();
15249    assert_eq!(
15250        server_restarts.load(atomic::Ordering::Acquire),
15251        1,
15252        "Should restart LSP server on a related LSP settings change"
15253    );
15254
15255    update_test_project_settings(cx, |project_settings| {
15256        project_settings.lsp.insert(
15257            language_server_name.into(),
15258            LspSettings {
15259                binary: None,
15260                settings: None,
15261                initialization_options: Some(json!({
15262                    "anotherInitValue": false
15263                })),
15264                enable_lsp_tasks: false,
15265            },
15266        );
15267    });
15268    cx.executor().run_until_parked();
15269    assert_eq!(
15270        server_restarts.load(atomic::Ordering::Acquire),
15271        1,
15272        "Should not restart LSP server on a related LSP settings change that is the same"
15273    );
15274
15275    update_test_project_settings(cx, |project_settings| {
15276        project_settings.lsp.insert(
15277            language_server_name.into(),
15278            LspSettings {
15279                binary: None,
15280                settings: None,
15281                initialization_options: None,
15282                enable_lsp_tasks: false,
15283            },
15284        );
15285    });
15286    cx.executor().run_until_parked();
15287    assert_eq!(
15288        server_restarts.load(atomic::Ordering::Acquire),
15289        2,
15290        "Should restart LSP server on another related LSP settings change"
15291    );
15292}
15293
15294#[gpui::test]
15295async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
15296    init_test(cx, |_| {});
15297
15298    let mut cx = EditorLspTestContext::new_rust(
15299        lsp::ServerCapabilities {
15300            completion_provider: Some(lsp::CompletionOptions {
15301                trigger_characters: Some(vec![".".to_string()]),
15302                resolve_provider: Some(true),
15303                ..Default::default()
15304            }),
15305            ..Default::default()
15306        },
15307        cx,
15308    )
15309    .await;
15310
15311    cx.set_state("fn main() { let a = 2ˇ; }");
15312    cx.simulate_keystroke(".");
15313    let completion_item = lsp::CompletionItem {
15314        label: "some".into(),
15315        kind: Some(lsp::CompletionItemKind::SNIPPET),
15316        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15317        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15318            kind: lsp::MarkupKind::Markdown,
15319            value: "```rust\nSome(2)\n```".to_string(),
15320        })),
15321        deprecated: Some(false),
15322        sort_text: Some("fffffff2".to_string()),
15323        filter_text: Some("some".to_string()),
15324        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15325        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15326            range: lsp::Range {
15327                start: lsp::Position {
15328                    line: 0,
15329                    character: 22,
15330                },
15331                end: lsp::Position {
15332                    line: 0,
15333                    character: 22,
15334                },
15335            },
15336            new_text: "Some(2)".to_string(),
15337        })),
15338        additional_text_edits: Some(vec![lsp::TextEdit {
15339            range: lsp::Range {
15340                start: lsp::Position {
15341                    line: 0,
15342                    character: 20,
15343                },
15344                end: lsp::Position {
15345                    line: 0,
15346                    character: 22,
15347                },
15348            },
15349            new_text: "".to_string(),
15350        }]),
15351        ..Default::default()
15352    };
15353
15354    let closure_completion_item = completion_item.clone();
15355    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15356        let task_completion_item = closure_completion_item.clone();
15357        async move {
15358            Ok(Some(lsp::CompletionResponse::Array(vec![
15359                task_completion_item,
15360            ])))
15361        }
15362    });
15363
15364    request.next().await;
15365
15366    cx.condition(|editor, _| editor.context_menu_visible())
15367        .await;
15368    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15369        editor
15370            .confirm_completion(&ConfirmCompletion::default(), window, cx)
15371            .unwrap()
15372    });
15373    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
15374
15375    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
15376        let task_completion_item = completion_item.clone();
15377        async move { Ok(task_completion_item) }
15378    })
15379    .next()
15380    .await
15381    .unwrap();
15382    apply_additional_edits.await.unwrap();
15383    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
15384}
15385
15386#[gpui::test]
15387async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
15388    init_test(cx, |_| {});
15389
15390    let mut cx = EditorLspTestContext::new_rust(
15391        lsp::ServerCapabilities {
15392            completion_provider: Some(lsp::CompletionOptions {
15393                trigger_characters: Some(vec![".".to_string()]),
15394                resolve_provider: Some(true),
15395                ..Default::default()
15396            }),
15397            ..Default::default()
15398        },
15399        cx,
15400    )
15401    .await;
15402
15403    cx.set_state("fn main() { let a = 2ˇ; }");
15404    cx.simulate_keystroke(".");
15405
15406    let item1 = lsp::CompletionItem {
15407        label: "method id()".to_string(),
15408        filter_text: Some("id".to_string()),
15409        detail: None,
15410        documentation: None,
15411        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15412            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15413            new_text: ".id".to_string(),
15414        })),
15415        ..lsp::CompletionItem::default()
15416    };
15417
15418    let item2 = lsp::CompletionItem {
15419        label: "other".to_string(),
15420        filter_text: Some("other".to_string()),
15421        detail: None,
15422        documentation: None,
15423        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15424            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15425            new_text: ".other".to_string(),
15426        })),
15427        ..lsp::CompletionItem::default()
15428    };
15429
15430    let item1 = item1.clone();
15431    cx.set_request_handler::<lsp::request::Completion, _, _>({
15432        let item1 = item1.clone();
15433        move |_, _, _| {
15434            let item1 = item1.clone();
15435            let item2 = item2.clone();
15436            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
15437        }
15438    })
15439    .next()
15440    .await;
15441
15442    cx.condition(|editor, _| editor.context_menu_visible())
15443        .await;
15444    cx.update_editor(|editor, _, _| {
15445        let context_menu = editor.context_menu.borrow_mut();
15446        let context_menu = context_menu
15447            .as_ref()
15448            .expect("Should have the context menu deployed");
15449        match context_menu {
15450            CodeContextMenu::Completions(completions_menu) => {
15451                let completions = completions_menu.completions.borrow_mut();
15452                assert_eq!(
15453                    completions
15454                        .iter()
15455                        .map(|completion| &completion.label.text)
15456                        .collect::<Vec<_>>(),
15457                    vec!["method id()", "other"]
15458                )
15459            }
15460            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15461        }
15462    });
15463
15464    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
15465        let item1 = item1.clone();
15466        move |_, item_to_resolve, _| {
15467            let item1 = item1.clone();
15468            async move {
15469                if item1 == item_to_resolve {
15470                    Ok(lsp::CompletionItem {
15471                        label: "method id()".to_string(),
15472                        filter_text: Some("id".to_string()),
15473                        detail: Some("Now resolved!".to_string()),
15474                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
15475                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15476                            range: lsp::Range::new(
15477                                lsp::Position::new(0, 22),
15478                                lsp::Position::new(0, 22),
15479                            ),
15480                            new_text: ".id".to_string(),
15481                        })),
15482                        ..lsp::CompletionItem::default()
15483                    })
15484                } else {
15485                    Ok(item_to_resolve)
15486                }
15487            }
15488        }
15489    })
15490    .next()
15491    .await
15492    .unwrap();
15493    cx.run_until_parked();
15494
15495    cx.update_editor(|editor, window, cx| {
15496        editor.context_menu_next(&Default::default(), window, cx);
15497    });
15498
15499    cx.update_editor(|editor, _, _| {
15500        let context_menu = editor.context_menu.borrow_mut();
15501        let context_menu = context_menu
15502            .as_ref()
15503            .expect("Should have the context menu deployed");
15504        match context_menu {
15505            CodeContextMenu::Completions(completions_menu) => {
15506                let completions = completions_menu.completions.borrow_mut();
15507                assert_eq!(
15508                    completions
15509                        .iter()
15510                        .map(|completion| &completion.label.text)
15511                        .collect::<Vec<_>>(),
15512                    vec!["method id() Now resolved!", "other"],
15513                    "Should update first completion label, but not second as the filter text did not match."
15514                );
15515            }
15516            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15517        }
15518    });
15519}
15520
15521#[gpui::test]
15522async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
15523    init_test(cx, |_| {});
15524    let mut cx = EditorLspTestContext::new_rust(
15525        lsp::ServerCapabilities {
15526            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
15527            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
15528            completion_provider: Some(lsp::CompletionOptions {
15529                resolve_provider: Some(true),
15530                ..Default::default()
15531            }),
15532            ..Default::default()
15533        },
15534        cx,
15535    )
15536    .await;
15537    cx.set_state(indoc! {"
15538        struct TestStruct {
15539            field: i32
15540        }
15541
15542        fn mainˇ() {
15543            let unused_var = 42;
15544            let test_struct = TestStruct { field: 42 };
15545        }
15546    "});
15547    let symbol_range = cx.lsp_range(indoc! {"
15548        struct TestStruct {
15549            field: i32
15550        }
15551
15552        «fn main»() {
15553            let unused_var = 42;
15554            let test_struct = TestStruct { field: 42 };
15555        }
15556    "});
15557    let mut hover_requests =
15558        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
15559            Ok(Some(lsp::Hover {
15560                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
15561                    kind: lsp::MarkupKind::Markdown,
15562                    value: "Function documentation".to_string(),
15563                }),
15564                range: Some(symbol_range),
15565            }))
15566        });
15567
15568    // Case 1: Test that code action menu hide hover popover
15569    cx.dispatch_action(Hover);
15570    hover_requests.next().await;
15571    cx.condition(|editor, _| editor.hover_state.visible()).await;
15572    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
15573        move |_, _, _| async move {
15574            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
15575                lsp::CodeAction {
15576                    title: "Remove unused variable".to_string(),
15577                    kind: Some(CodeActionKind::QUICKFIX),
15578                    edit: Some(lsp::WorkspaceEdit {
15579                        changes: Some(
15580                            [(
15581                                lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
15582                                vec![lsp::TextEdit {
15583                                    range: lsp::Range::new(
15584                                        lsp::Position::new(5, 4),
15585                                        lsp::Position::new(5, 27),
15586                                    ),
15587                                    new_text: "".to_string(),
15588                                }],
15589                            )]
15590                            .into_iter()
15591                            .collect(),
15592                        ),
15593                        ..Default::default()
15594                    }),
15595                    ..Default::default()
15596                },
15597            )]))
15598        },
15599    );
15600    cx.update_editor(|editor, window, cx| {
15601        editor.toggle_code_actions(
15602            &ToggleCodeActions {
15603                deployed_from: None,
15604                quick_launch: false,
15605            },
15606            window,
15607            cx,
15608        );
15609    });
15610    code_action_requests.next().await;
15611    cx.run_until_parked();
15612    cx.condition(|editor, _| editor.context_menu_visible())
15613        .await;
15614    cx.update_editor(|editor, _, _| {
15615        assert!(
15616            !editor.hover_state.visible(),
15617            "Hover popover should be hidden when code action menu is shown"
15618        );
15619        // Hide code actions
15620        editor.context_menu.take();
15621    });
15622
15623    // Case 2: Test that code completions hide hover popover
15624    cx.dispatch_action(Hover);
15625    hover_requests.next().await;
15626    cx.condition(|editor, _| editor.hover_state.visible()).await;
15627    let counter = Arc::new(AtomicUsize::new(0));
15628    let mut completion_requests =
15629        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15630            let counter = counter.clone();
15631            async move {
15632                counter.fetch_add(1, atomic::Ordering::Release);
15633                Ok(Some(lsp::CompletionResponse::Array(vec![
15634                    lsp::CompletionItem {
15635                        label: "main".into(),
15636                        kind: Some(lsp::CompletionItemKind::FUNCTION),
15637                        detail: Some("() -> ()".to_string()),
15638                        ..Default::default()
15639                    },
15640                    lsp::CompletionItem {
15641                        label: "TestStruct".into(),
15642                        kind: Some(lsp::CompletionItemKind::STRUCT),
15643                        detail: Some("struct TestStruct".to_string()),
15644                        ..Default::default()
15645                    },
15646                ])))
15647            }
15648        });
15649    cx.update_editor(|editor, window, cx| {
15650        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15651    });
15652    completion_requests.next().await;
15653    cx.condition(|editor, _| editor.context_menu_visible())
15654        .await;
15655    cx.update_editor(|editor, _, _| {
15656        assert!(
15657            !editor.hover_state.visible(),
15658            "Hover popover should be hidden when completion menu is shown"
15659        );
15660    });
15661}
15662
15663#[gpui::test]
15664async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
15665    init_test(cx, |_| {});
15666
15667    let mut cx = EditorLspTestContext::new_rust(
15668        lsp::ServerCapabilities {
15669            completion_provider: Some(lsp::CompletionOptions {
15670                trigger_characters: Some(vec![".".to_string()]),
15671                resolve_provider: Some(true),
15672                ..Default::default()
15673            }),
15674            ..Default::default()
15675        },
15676        cx,
15677    )
15678    .await;
15679
15680    cx.set_state("fn main() { let a = 2ˇ; }");
15681    cx.simulate_keystroke(".");
15682
15683    let unresolved_item_1 = lsp::CompletionItem {
15684        label: "id".to_string(),
15685        filter_text: Some("id".to_string()),
15686        detail: None,
15687        documentation: None,
15688        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15689            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15690            new_text: ".id".to_string(),
15691        })),
15692        ..lsp::CompletionItem::default()
15693    };
15694    let resolved_item_1 = lsp::CompletionItem {
15695        additional_text_edits: Some(vec![lsp::TextEdit {
15696            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
15697            new_text: "!!".to_string(),
15698        }]),
15699        ..unresolved_item_1.clone()
15700    };
15701    let unresolved_item_2 = lsp::CompletionItem {
15702        label: "other".to_string(),
15703        filter_text: Some("other".to_string()),
15704        detail: None,
15705        documentation: None,
15706        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15707            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15708            new_text: ".other".to_string(),
15709        })),
15710        ..lsp::CompletionItem::default()
15711    };
15712    let resolved_item_2 = lsp::CompletionItem {
15713        additional_text_edits: Some(vec![lsp::TextEdit {
15714            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
15715            new_text: "??".to_string(),
15716        }]),
15717        ..unresolved_item_2.clone()
15718    };
15719
15720    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
15721    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
15722    cx.lsp
15723        .server
15724        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
15725            let unresolved_item_1 = unresolved_item_1.clone();
15726            let resolved_item_1 = resolved_item_1.clone();
15727            let unresolved_item_2 = unresolved_item_2.clone();
15728            let resolved_item_2 = resolved_item_2.clone();
15729            let resolve_requests_1 = resolve_requests_1.clone();
15730            let resolve_requests_2 = resolve_requests_2.clone();
15731            move |unresolved_request, _| {
15732                let unresolved_item_1 = unresolved_item_1.clone();
15733                let resolved_item_1 = resolved_item_1.clone();
15734                let unresolved_item_2 = unresolved_item_2.clone();
15735                let resolved_item_2 = resolved_item_2.clone();
15736                let resolve_requests_1 = resolve_requests_1.clone();
15737                let resolve_requests_2 = resolve_requests_2.clone();
15738                async move {
15739                    if unresolved_request == unresolved_item_1 {
15740                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
15741                        Ok(resolved_item_1.clone())
15742                    } else if unresolved_request == unresolved_item_2 {
15743                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
15744                        Ok(resolved_item_2.clone())
15745                    } else {
15746                        panic!("Unexpected completion item {unresolved_request:?}")
15747                    }
15748                }
15749            }
15750        })
15751        .detach();
15752
15753    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15754        let unresolved_item_1 = unresolved_item_1.clone();
15755        let unresolved_item_2 = unresolved_item_2.clone();
15756        async move {
15757            Ok(Some(lsp::CompletionResponse::Array(vec![
15758                unresolved_item_1,
15759                unresolved_item_2,
15760            ])))
15761        }
15762    })
15763    .next()
15764    .await;
15765
15766    cx.condition(|editor, _| editor.context_menu_visible())
15767        .await;
15768    cx.update_editor(|editor, _, _| {
15769        let context_menu = editor.context_menu.borrow_mut();
15770        let context_menu = context_menu
15771            .as_ref()
15772            .expect("Should have the context menu deployed");
15773        match context_menu {
15774            CodeContextMenu::Completions(completions_menu) => {
15775                let completions = completions_menu.completions.borrow_mut();
15776                assert_eq!(
15777                    completions
15778                        .iter()
15779                        .map(|completion| &completion.label.text)
15780                        .collect::<Vec<_>>(),
15781                    vec!["id", "other"]
15782                )
15783            }
15784            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15785        }
15786    });
15787    cx.run_until_parked();
15788
15789    cx.update_editor(|editor, window, cx| {
15790        editor.context_menu_next(&ContextMenuNext, window, cx);
15791    });
15792    cx.run_until_parked();
15793    cx.update_editor(|editor, window, cx| {
15794        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
15795    });
15796    cx.run_until_parked();
15797    cx.update_editor(|editor, window, cx| {
15798        editor.context_menu_next(&ContextMenuNext, window, cx);
15799    });
15800    cx.run_until_parked();
15801    cx.update_editor(|editor, window, cx| {
15802        editor
15803            .compose_completion(&ComposeCompletion::default(), window, cx)
15804            .expect("No task returned")
15805    })
15806    .await
15807    .expect("Completion failed");
15808    cx.run_until_parked();
15809
15810    cx.update_editor(|editor, _, cx| {
15811        assert_eq!(
15812            resolve_requests_1.load(atomic::Ordering::Acquire),
15813            1,
15814            "Should always resolve once despite multiple selections"
15815        );
15816        assert_eq!(
15817            resolve_requests_2.load(atomic::Ordering::Acquire),
15818            1,
15819            "Should always resolve once after multiple selections and applying the completion"
15820        );
15821        assert_eq!(
15822            editor.text(cx),
15823            "fn main() { let a = ??.other; }",
15824            "Should use resolved data when applying the completion"
15825        );
15826    });
15827}
15828
15829#[gpui::test]
15830async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
15831    init_test(cx, |_| {});
15832
15833    let item_0 = lsp::CompletionItem {
15834        label: "abs".into(),
15835        insert_text: Some("abs".into()),
15836        data: Some(json!({ "very": "special"})),
15837        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
15838        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
15839            lsp::InsertReplaceEdit {
15840                new_text: "abs".to_string(),
15841                insert: lsp::Range::default(),
15842                replace: lsp::Range::default(),
15843            },
15844        )),
15845        ..lsp::CompletionItem::default()
15846    };
15847    let items = iter::once(item_0.clone())
15848        .chain((11..51).map(|i| lsp::CompletionItem {
15849            label: format!("item_{}", i),
15850            insert_text: Some(format!("item_{}", i)),
15851            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15852            ..lsp::CompletionItem::default()
15853        }))
15854        .collect::<Vec<_>>();
15855
15856    let default_commit_characters = vec!["?".to_string()];
15857    let default_data = json!({ "default": "data"});
15858    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
15859    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
15860    let default_edit_range = lsp::Range {
15861        start: lsp::Position {
15862            line: 0,
15863            character: 5,
15864        },
15865        end: lsp::Position {
15866            line: 0,
15867            character: 5,
15868        },
15869    };
15870
15871    let mut cx = EditorLspTestContext::new_rust(
15872        lsp::ServerCapabilities {
15873            completion_provider: Some(lsp::CompletionOptions {
15874                trigger_characters: Some(vec![".".to_string()]),
15875                resolve_provider: Some(true),
15876                ..Default::default()
15877            }),
15878            ..Default::default()
15879        },
15880        cx,
15881    )
15882    .await;
15883
15884    cx.set_state("fn main() { let a = 2ˇ; }");
15885    cx.simulate_keystroke(".");
15886
15887    let completion_data = default_data.clone();
15888    let completion_characters = default_commit_characters.clone();
15889    let completion_items = items.clone();
15890    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15891        let default_data = completion_data.clone();
15892        let default_commit_characters = completion_characters.clone();
15893        let items = completion_items.clone();
15894        async move {
15895            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15896                items,
15897                item_defaults: Some(lsp::CompletionListItemDefaults {
15898                    data: Some(default_data.clone()),
15899                    commit_characters: Some(default_commit_characters.clone()),
15900                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
15901                        default_edit_range,
15902                    )),
15903                    insert_text_format: Some(default_insert_text_format),
15904                    insert_text_mode: Some(default_insert_text_mode),
15905                }),
15906                ..lsp::CompletionList::default()
15907            })))
15908        }
15909    })
15910    .next()
15911    .await;
15912
15913    let resolved_items = Arc::new(Mutex::new(Vec::new()));
15914    cx.lsp
15915        .server
15916        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
15917            let closure_resolved_items = resolved_items.clone();
15918            move |item_to_resolve, _| {
15919                let closure_resolved_items = closure_resolved_items.clone();
15920                async move {
15921                    closure_resolved_items.lock().push(item_to_resolve.clone());
15922                    Ok(item_to_resolve)
15923                }
15924            }
15925        })
15926        .detach();
15927
15928    cx.condition(|editor, _| editor.context_menu_visible())
15929        .await;
15930    cx.run_until_parked();
15931    cx.update_editor(|editor, _, _| {
15932        let menu = editor.context_menu.borrow_mut();
15933        match menu.as_ref().expect("should have the completions menu") {
15934            CodeContextMenu::Completions(completions_menu) => {
15935                assert_eq!(
15936                    completions_menu
15937                        .entries
15938                        .borrow()
15939                        .iter()
15940                        .map(|mat| mat.string.clone())
15941                        .collect::<Vec<String>>(),
15942                    items
15943                        .iter()
15944                        .map(|completion| completion.label.clone())
15945                        .collect::<Vec<String>>()
15946                );
15947            }
15948            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
15949        }
15950    });
15951    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
15952    // with 4 from the end.
15953    assert_eq!(
15954        *resolved_items.lock(),
15955        [&items[0..16], &items[items.len() - 4..items.len()]]
15956            .concat()
15957            .iter()
15958            .cloned()
15959            .map(|mut item| {
15960                if item.data.is_none() {
15961                    item.data = Some(default_data.clone());
15962                }
15963                item
15964            })
15965            .collect::<Vec<lsp::CompletionItem>>(),
15966        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
15967    );
15968    resolved_items.lock().clear();
15969
15970    cx.update_editor(|editor, window, cx| {
15971        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
15972    });
15973    cx.run_until_parked();
15974    // Completions that have already been resolved are skipped.
15975    assert_eq!(
15976        *resolved_items.lock(),
15977        items[items.len() - 17..items.len() - 4]
15978            .iter()
15979            .cloned()
15980            .map(|mut item| {
15981                if item.data.is_none() {
15982                    item.data = Some(default_data.clone());
15983                }
15984                item
15985            })
15986            .collect::<Vec<lsp::CompletionItem>>()
15987    );
15988    resolved_items.lock().clear();
15989}
15990
15991#[gpui::test]
15992async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
15993    init_test(cx, |_| {});
15994
15995    let mut cx = EditorLspTestContext::new(
15996        Language::new(
15997            LanguageConfig {
15998                matcher: LanguageMatcher {
15999                    path_suffixes: vec!["jsx".into()],
16000                    ..Default::default()
16001                },
16002                overrides: [(
16003                    "element".into(),
16004                    LanguageConfigOverride {
16005                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
16006                        ..Default::default()
16007                    },
16008                )]
16009                .into_iter()
16010                .collect(),
16011                ..Default::default()
16012            },
16013            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
16014        )
16015        .with_override_query("(jsx_self_closing_element) @element")
16016        .unwrap(),
16017        lsp::ServerCapabilities {
16018            completion_provider: Some(lsp::CompletionOptions {
16019                trigger_characters: Some(vec![":".to_string()]),
16020                ..Default::default()
16021            }),
16022            ..Default::default()
16023        },
16024        cx,
16025    )
16026    .await;
16027
16028    cx.lsp
16029        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16030            Ok(Some(lsp::CompletionResponse::Array(vec![
16031                lsp::CompletionItem {
16032                    label: "bg-blue".into(),
16033                    ..Default::default()
16034                },
16035                lsp::CompletionItem {
16036                    label: "bg-red".into(),
16037                    ..Default::default()
16038                },
16039                lsp::CompletionItem {
16040                    label: "bg-yellow".into(),
16041                    ..Default::default()
16042                },
16043            ])))
16044        });
16045
16046    cx.set_state(r#"<p class="bgˇ" />"#);
16047
16048    // Trigger completion when typing a dash, because the dash is an extra
16049    // word character in the 'element' scope, which contains the cursor.
16050    cx.simulate_keystroke("-");
16051    cx.executor().run_until_parked();
16052    cx.update_editor(|editor, _, _| {
16053        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16054        {
16055            assert_eq!(
16056                completion_menu_entries(&menu),
16057                &["bg-blue", "bg-red", "bg-yellow"]
16058            );
16059        } else {
16060            panic!("expected completion menu to be open");
16061        }
16062    });
16063
16064    cx.simulate_keystroke("l");
16065    cx.executor().run_until_parked();
16066    cx.update_editor(|editor, _, _| {
16067        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16068        {
16069            assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
16070        } else {
16071            panic!("expected completion menu to be open");
16072        }
16073    });
16074
16075    // When filtering completions, consider the character after the '-' to
16076    // be the start of a subword.
16077    cx.set_state(r#"<p class="yelˇ" />"#);
16078    cx.simulate_keystroke("l");
16079    cx.executor().run_until_parked();
16080    cx.update_editor(|editor, _, _| {
16081        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16082        {
16083            assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
16084        } else {
16085            panic!("expected completion menu to be open");
16086        }
16087    });
16088}
16089
16090fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
16091    let entries = menu.entries.borrow();
16092    entries.iter().map(|mat| mat.string.clone()).collect()
16093}
16094
16095#[gpui::test]
16096async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
16097    init_test(cx, |settings| {
16098        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
16099            Formatter::Prettier,
16100        )))
16101    });
16102
16103    let fs = FakeFs::new(cx.executor());
16104    fs.insert_file(path!("/file.ts"), Default::default()).await;
16105
16106    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
16107    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16108
16109    language_registry.add(Arc::new(Language::new(
16110        LanguageConfig {
16111            name: "TypeScript".into(),
16112            matcher: LanguageMatcher {
16113                path_suffixes: vec!["ts".to_string()],
16114                ..Default::default()
16115            },
16116            ..Default::default()
16117        },
16118        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
16119    )));
16120    update_test_language_settings(cx, |settings| {
16121        settings.defaults.prettier = Some(PrettierSettings {
16122            allowed: true,
16123            ..PrettierSettings::default()
16124        });
16125    });
16126
16127    let test_plugin = "test_plugin";
16128    let _ = language_registry.register_fake_lsp(
16129        "TypeScript",
16130        FakeLspAdapter {
16131            prettier_plugins: vec![test_plugin],
16132            ..Default::default()
16133        },
16134    );
16135
16136    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
16137    let buffer = project
16138        .update(cx, |project, cx| {
16139            project.open_local_buffer(path!("/file.ts"), cx)
16140        })
16141        .await
16142        .unwrap();
16143
16144    let buffer_text = "one\ntwo\nthree\n";
16145    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16146    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16147    editor.update_in(cx, |editor, window, cx| {
16148        editor.set_text(buffer_text, window, cx)
16149    });
16150
16151    editor
16152        .update_in(cx, |editor, window, cx| {
16153            editor.perform_format(
16154                project.clone(),
16155                FormatTrigger::Manual,
16156                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
16157                window,
16158                cx,
16159            )
16160        })
16161        .unwrap()
16162        .await;
16163    assert_eq!(
16164        editor.update(cx, |editor, cx| editor.text(cx)),
16165        buffer_text.to_string() + prettier_format_suffix,
16166        "Test prettier formatting was not applied to the original buffer text",
16167    );
16168
16169    update_test_language_settings(cx, |settings| {
16170        settings.defaults.formatter = Some(SelectedFormatter::Auto)
16171    });
16172    let format = editor.update_in(cx, |editor, window, cx| {
16173        editor.perform_format(
16174            project.clone(),
16175            FormatTrigger::Manual,
16176            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
16177            window,
16178            cx,
16179        )
16180    });
16181    format.await.unwrap();
16182    assert_eq!(
16183        editor.update(cx, |editor, cx| editor.text(cx)),
16184        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
16185        "Autoformatting (via test prettier) was not applied to the original buffer text",
16186    );
16187}
16188
16189#[gpui::test]
16190async fn test_addition_reverts(cx: &mut TestAppContext) {
16191    init_test(cx, |_| {});
16192    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16193    let base_text = indoc! {r#"
16194        struct Row;
16195        struct Row1;
16196        struct Row2;
16197
16198        struct Row4;
16199        struct Row5;
16200        struct Row6;
16201
16202        struct Row8;
16203        struct Row9;
16204        struct Row10;"#};
16205
16206    // When addition hunks are not adjacent to carets, no hunk revert is performed
16207    assert_hunk_revert(
16208        indoc! {r#"struct Row;
16209                   struct Row1;
16210                   struct Row1.1;
16211                   struct Row1.2;
16212                   struct Row2;ˇ
16213
16214                   struct Row4;
16215                   struct Row5;
16216                   struct Row6;
16217
16218                   struct Row8;
16219                   ˇstruct Row9;
16220                   struct Row9.1;
16221                   struct Row9.2;
16222                   struct Row9.3;
16223                   struct Row10;"#},
16224        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
16225        indoc! {r#"struct Row;
16226                   struct Row1;
16227                   struct Row1.1;
16228                   struct Row1.2;
16229                   struct Row2;ˇ
16230
16231                   struct Row4;
16232                   struct Row5;
16233                   struct Row6;
16234
16235                   struct Row8;
16236                   ˇstruct Row9;
16237                   struct Row9.1;
16238                   struct Row9.2;
16239                   struct Row9.3;
16240                   struct Row10;"#},
16241        base_text,
16242        &mut cx,
16243    );
16244    // Same for selections
16245    assert_hunk_revert(
16246        indoc! {r#"struct Row;
16247                   struct Row1;
16248                   struct Row2;
16249                   struct Row2.1;
16250                   struct Row2.2;
16251                   «ˇ
16252                   struct Row4;
16253                   struct» Row5;
16254                   «struct Row6;
16255                   ˇ»
16256                   struct Row9.1;
16257                   struct Row9.2;
16258                   struct Row9.3;
16259                   struct Row8;
16260                   struct Row9;
16261                   struct Row10;"#},
16262        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
16263        indoc! {r#"struct Row;
16264                   struct Row1;
16265                   struct Row2;
16266                   struct Row2.1;
16267                   struct Row2.2;
16268                   «ˇ
16269                   struct Row4;
16270                   struct» Row5;
16271                   «struct Row6;
16272                   ˇ»
16273                   struct Row9.1;
16274                   struct Row9.2;
16275                   struct Row9.3;
16276                   struct Row8;
16277                   struct Row9;
16278                   struct Row10;"#},
16279        base_text,
16280        &mut cx,
16281    );
16282
16283    // When carets and selections intersect the addition hunks, those are reverted.
16284    // Adjacent carets got merged.
16285    assert_hunk_revert(
16286        indoc! {r#"struct Row;
16287                   ˇ// something on the top
16288                   struct Row1;
16289                   struct Row2;
16290                   struct Roˇw3.1;
16291                   struct Row2.2;
16292                   struct Row2.3;ˇ
16293
16294                   struct Row4;
16295                   struct ˇRow5.1;
16296                   struct Row5.2;
16297                   struct «Rowˇ»5.3;
16298                   struct Row5;
16299                   struct Row6;
16300                   ˇ
16301                   struct Row9.1;
16302                   struct «Rowˇ»9.2;
16303                   struct «ˇRow»9.3;
16304                   struct Row8;
16305                   struct Row9;
16306                   «ˇ// something on bottom»
16307                   struct Row10;"#},
16308        vec![
16309            DiffHunkStatusKind::Added,
16310            DiffHunkStatusKind::Added,
16311            DiffHunkStatusKind::Added,
16312            DiffHunkStatusKind::Added,
16313            DiffHunkStatusKind::Added,
16314        ],
16315        indoc! {r#"struct Row;
16316                   ˇstruct Row1;
16317                   struct Row2;
16318                   ˇ
16319                   struct Row4;
16320                   ˇstruct Row5;
16321                   struct Row6;
16322                   ˇ
16323                   ˇstruct Row8;
16324                   struct Row9;
16325                   ˇstruct Row10;"#},
16326        base_text,
16327        &mut cx,
16328    );
16329}
16330
16331#[gpui::test]
16332async fn test_modification_reverts(cx: &mut TestAppContext) {
16333    init_test(cx, |_| {});
16334    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16335    let base_text = indoc! {r#"
16336        struct Row;
16337        struct Row1;
16338        struct Row2;
16339
16340        struct Row4;
16341        struct Row5;
16342        struct Row6;
16343
16344        struct Row8;
16345        struct Row9;
16346        struct Row10;"#};
16347
16348    // Modification hunks behave the same as the addition ones.
16349    assert_hunk_revert(
16350        indoc! {r#"struct Row;
16351                   struct Row1;
16352                   struct Row33;
16353                   ˇ
16354                   struct Row4;
16355                   struct Row5;
16356                   struct Row6;
16357                   ˇ
16358                   struct Row99;
16359                   struct Row9;
16360                   struct Row10;"#},
16361        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
16362        indoc! {r#"struct Row;
16363                   struct Row1;
16364                   struct Row33;
16365                   ˇ
16366                   struct Row4;
16367                   struct Row5;
16368                   struct Row6;
16369                   ˇ
16370                   struct Row99;
16371                   struct Row9;
16372                   struct Row10;"#},
16373        base_text,
16374        &mut cx,
16375    );
16376    assert_hunk_revert(
16377        indoc! {r#"struct Row;
16378                   struct Row1;
16379                   struct Row33;
16380                   «ˇ
16381                   struct Row4;
16382                   struct» Row5;
16383                   «struct Row6;
16384                   ˇ»
16385                   struct Row99;
16386                   struct Row9;
16387                   struct Row10;"#},
16388        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
16389        indoc! {r#"struct Row;
16390                   struct Row1;
16391                   struct Row33;
16392                   «ˇ
16393                   struct Row4;
16394                   struct» Row5;
16395                   «struct Row6;
16396                   ˇ»
16397                   struct Row99;
16398                   struct Row9;
16399                   struct Row10;"#},
16400        base_text,
16401        &mut cx,
16402    );
16403
16404    assert_hunk_revert(
16405        indoc! {r#"ˇstruct Row1.1;
16406                   struct Row1;
16407                   «ˇstr»uct Row22;
16408
16409                   struct ˇRow44;
16410                   struct Row5;
16411                   struct «Rˇ»ow66;ˇ
16412
16413                   «struˇ»ct Row88;
16414                   struct Row9;
16415                   struct Row1011;ˇ"#},
16416        vec![
16417            DiffHunkStatusKind::Modified,
16418            DiffHunkStatusKind::Modified,
16419            DiffHunkStatusKind::Modified,
16420            DiffHunkStatusKind::Modified,
16421            DiffHunkStatusKind::Modified,
16422            DiffHunkStatusKind::Modified,
16423        ],
16424        indoc! {r#"struct Row;
16425                   ˇstruct Row1;
16426                   struct Row2;
16427                   ˇ
16428                   struct Row4;
16429                   ˇstruct Row5;
16430                   struct Row6;
16431                   ˇ
16432                   struct Row8;
16433                   ˇstruct Row9;
16434                   struct Row10;ˇ"#},
16435        base_text,
16436        &mut cx,
16437    );
16438}
16439
16440#[gpui::test]
16441async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
16442    init_test(cx, |_| {});
16443    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16444    let base_text = indoc! {r#"
16445        one
16446
16447        two
16448        three
16449        "#};
16450
16451    cx.set_head_text(base_text);
16452    cx.set_state("\nˇ\n");
16453    cx.executor().run_until_parked();
16454    cx.update_editor(|editor, _window, cx| {
16455        editor.expand_selected_diff_hunks(cx);
16456    });
16457    cx.executor().run_until_parked();
16458    cx.update_editor(|editor, window, cx| {
16459        editor.backspace(&Default::default(), window, cx);
16460    });
16461    cx.run_until_parked();
16462    cx.assert_state_with_diff(
16463        indoc! {r#"
16464
16465        - two
16466        - threeˇ
16467        +
16468        "#}
16469        .to_string(),
16470    );
16471}
16472
16473#[gpui::test]
16474async fn test_deletion_reverts(cx: &mut TestAppContext) {
16475    init_test(cx, |_| {});
16476    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16477    let base_text = indoc! {r#"struct Row;
16478struct Row1;
16479struct Row2;
16480
16481struct Row4;
16482struct Row5;
16483struct Row6;
16484
16485struct Row8;
16486struct Row9;
16487struct Row10;"#};
16488
16489    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
16490    assert_hunk_revert(
16491        indoc! {r#"struct Row;
16492                   struct Row2;
16493
16494                   ˇstruct Row4;
16495                   struct Row5;
16496                   struct Row6;
16497                   ˇ
16498                   struct Row8;
16499                   struct Row10;"#},
16500        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16501        indoc! {r#"struct Row;
16502                   struct Row2;
16503
16504                   ˇstruct Row4;
16505                   struct Row5;
16506                   struct Row6;
16507                   ˇ
16508                   struct Row8;
16509                   struct Row10;"#},
16510        base_text,
16511        &mut cx,
16512    );
16513    assert_hunk_revert(
16514        indoc! {r#"struct Row;
16515                   struct Row2;
16516
16517                   «ˇstruct Row4;
16518                   struct» Row5;
16519                   «struct Row6;
16520                   ˇ»
16521                   struct Row8;
16522                   struct Row10;"#},
16523        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16524        indoc! {r#"struct Row;
16525                   struct Row2;
16526
16527                   «ˇstruct Row4;
16528                   struct» Row5;
16529                   «struct Row6;
16530                   ˇ»
16531                   struct Row8;
16532                   struct Row10;"#},
16533        base_text,
16534        &mut cx,
16535    );
16536
16537    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
16538    assert_hunk_revert(
16539        indoc! {r#"struct Row;
16540                   ˇstruct Row2;
16541
16542                   struct Row4;
16543                   struct Row5;
16544                   struct Row6;
16545
16546                   struct Row8;ˇ
16547                   struct Row10;"#},
16548        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16549        indoc! {r#"struct Row;
16550                   struct Row1;
16551                   ˇstruct Row2;
16552
16553                   struct Row4;
16554                   struct Row5;
16555                   struct Row6;
16556
16557                   struct Row8;ˇ
16558                   struct Row9;
16559                   struct Row10;"#},
16560        base_text,
16561        &mut cx,
16562    );
16563    assert_hunk_revert(
16564        indoc! {r#"struct Row;
16565                   struct Row2«ˇ;
16566                   struct Row4;
16567                   struct» Row5;
16568                   «struct Row6;
16569
16570                   struct Row8;ˇ»
16571                   struct Row10;"#},
16572        vec![
16573            DiffHunkStatusKind::Deleted,
16574            DiffHunkStatusKind::Deleted,
16575            DiffHunkStatusKind::Deleted,
16576        ],
16577        indoc! {r#"struct Row;
16578                   struct Row1;
16579                   struct Row2«ˇ;
16580
16581                   struct Row4;
16582                   struct» Row5;
16583                   «struct Row6;
16584
16585                   struct Row8;ˇ»
16586                   struct Row9;
16587                   struct Row10;"#},
16588        base_text,
16589        &mut cx,
16590    );
16591}
16592
16593#[gpui::test]
16594async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
16595    init_test(cx, |_| {});
16596
16597    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
16598    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
16599    let base_text_3 =
16600        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
16601
16602    let text_1 = edit_first_char_of_every_line(base_text_1);
16603    let text_2 = edit_first_char_of_every_line(base_text_2);
16604    let text_3 = edit_first_char_of_every_line(base_text_3);
16605
16606    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
16607    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
16608    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
16609
16610    let multibuffer = cx.new(|cx| {
16611        let mut multibuffer = MultiBuffer::new(ReadWrite);
16612        multibuffer.push_excerpts(
16613            buffer_1.clone(),
16614            [
16615                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16616                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16617                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16618            ],
16619            cx,
16620        );
16621        multibuffer.push_excerpts(
16622            buffer_2.clone(),
16623            [
16624                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16625                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16626                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16627            ],
16628            cx,
16629        );
16630        multibuffer.push_excerpts(
16631            buffer_3.clone(),
16632            [
16633                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16634                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16635                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16636            ],
16637            cx,
16638        );
16639        multibuffer
16640    });
16641
16642    let fs = FakeFs::new(cx.executor());
16643    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
16644    let (editor, cx) = cx
16645        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
16646    editor.update_in(cx, |editor, _window, cx| {
16647        for (buffer, diff_base) in [
16648            (buffer_1.clone(), base_text_1),
16649            (buffer_2.clone(), base_text_2),
16650            (buffer_3.clone(), base_text_3),
16651        ] {
16652            let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
16653            editor
16654                .buffer
16655                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
16656        }
16657    });
16658    cx.executor().run_until_parked();
16659
16660    editor.update_in(cx, |editor, window, cx| {
16661        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}");
16662        editor.select_all(&SelectAll, window, cx);
16663        editor.git_restore(&Default::default(), window, cx);
16664    });
16665    cx.executor().run_until_parked();
16666
16667    // When all ranges are selected, all buffer hunks are reverted.
16668    editor.update(cx, |editor, cx| {
16669        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");
16670    });
16671    buffer_1.update(cx, |buffer, _| {
16672        assert_eq!(buffer.text(), base_text_1);
16673    });
16674    buffer_2.update(cx, |buffer, _| {
16675        assert_eq!(buffer.text(), base_text_2);
16676    });
16677    buffer_3.update(cx, |buffer, _| {
16678        assert_eq!(buffer.text(), base_text_3);
16679    });
16680
16681    editor.update_in(cx, |editor, window, cx| {
16682        editor.undo(&Default::default(), window, cx);
16683    });
16684
16685    editor.update_in(cx, |editor, window, cx| {
16686        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16687            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
16688        });
16689        editor.git_restore(&Default::default(), window, cx);
16690    });
16691
16692    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
16693    // but not affect buffer_2 and its related excerpts.
16694    editor.update(cx, |editor, cx| {
16695        assert_eq!(
16696            editor.text(cx),
16697            "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}"
16698        );
16699    });
16700    buffer_1.update(cx, |buffer, _| {
16701        assert_eq!(buffer.text(), base_text_1);
16702    });
16703    buffer_2.update(cx, |buffer, _| {
16704        assert_eq!(
16705            buffer.text(),
16706            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
16707        );
16708    });
16709    buffer_3.update(cx, |buffer, _| {
16710        assert_eq!(
16711            buffer.text(),
16712            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
16713        );
16714    });
16715
16716    fn edit_first_char_of_every_line(text: &str) -> String {
16717        text.split('\n')
16718            .map(|line| format!("X{}", &line[1..]))
16719            .collect::<Vec<_>>()
16720            .join("\n")
16721    }
16722}
16723
16724#[gpui::test]
16725async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
16726    init_test(cx, |_| {});
16727
16728    let cols = 4;
16729    let rows = 10;
16730    let sample_text_1 = sample_text(rows, cols, 'a');
16731    assert_eq!(
16732        sample_text_1,
16733        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
16734    );
16735    let sample_text_2 = sample_text(rows, cols, 'l');
16736    assert_eq!(
16737        sample_text_2,
16738        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
16739    );
16740    let sample_text_3 = sample_text(rows, cols, 'v');
16741    assert_eq!(
16742        sample_text_3,
16743        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
16744    );
16745
16746    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
16747    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
16748    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
16749
16750    let multi_buffer = cx.new(|cx| {
16751        let mut multibuffer = MultiBuffer::new(ReadWrite);
16752        multibuffer.push_excerpts(
16753            buffer_1.clone(),
16754            [
16755                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16756                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16757                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16758            ],
16759            cx,
16760        );
16761        multibuffer.push_excerpts(
16762            buffer_2.clone(),
16763            [
16764                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16765                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16766                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16767            ],
16768            cx,
16769        );
16770        multibuffer.push_excerpts(
16771            buffer_3.clone(),
16772            [
16773                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16774                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16775                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16776            ],
16777            cx,
16778        );
16779        multibuffer
16780    });
16781
16782    let fs = FakeFs::new(cx.executor());
16783    fs.insert_tree(
16784        "/a",
16785        json!({
16786            "main.rs": sample_text_1,
16787            "other.rs": sample_text_2,
16788            "lib.rs": sample_text_3,
16789        }),
16790    )
16791    .await;
16792    let project = Project::test(fs, ["/a".as_ref()], cx).await;
16793    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16794    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16795    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16796        Editor::new(
16797            EditorMode::full(),
16798            multi_buffer,
16799            Some(project.clone()),
16800            window,
16801            cx,
16802        )
16803    });
16804    let multibuffer_item_id = workspace
16805        .update(cx, |workspace, window, cx| {
16806            assert!(
16807                workspace.active_item(cx).is_none(),
16808                "active item should be None before the first item is added"
16809            );
16810            workspace.add_item_to_active_pane(
16811                Box::new(multi_buffer_editor.clone()),
16812                None,
16813                true,
16814                window,
16815                cx,
16816            );
16817            let active_item = workspace
16818                .active_item(cx)
16819                .expect("should have an active item after adding the multi buffer");
16820            assert!(
16821                !active_item.is_singleton(cx),
16822                "A multi buffer was expected to active after adding"
16823            );
16824            active_item.item_id()
16825        })
16826        .unwrap();
16827    cx.executor().run_until_parked();
16828
16829    multi_buffer_editor.update_in(cx, |editor, window, cx| {
16830        editor.change_selections(
16831            SelectionEffects::scroll(Autoscroll::Next),
16832            window,
16833            cx,
16834            |s| s.select_ranges(Some(1..2)),
16835        );
16836        editor.open_excerpts(&OpenExcerpts, window, cx);
16837    });
16838    cx.executor().run_until_parked();
16839    let first_item_id = workspace
16840        .update(cx, |workspace, window, cx| {
16841            let active_item = workspace
16842                .active_item(cx)
16843                .expect("should have an active item after navigating into the 1st buffer");
16844            let first_item_id = active_item.item_id();
16845            assert_ne!(
16846                first_item_id, multibuffer_item_id,
16847                "Should navigate into the 1st buffer and activate it"
16848            );
16849            assert!(
16850                active_item.is_singleton(cx),
16851                "New active item should be a singleton buffer"
16852            );
16853            assert_eq!(
16854                active_item
16855                    .act_as::<Editor>(cx)
16856                    .expect("should have navigated into an editor for the 1st buffer")
16857                    .read(cx)
16858                    .text(cx),
16859                sample_text_1
16860            );
16861
16862            workspace
16863                .go_back(workspace.active_pane().downgrade(), window, cx)
16864                .detach_and_log_err(cx);
16865
16866            first_item_id
16867        })
16868        .unwrap();
16869    cx.executor().run_until_parked();
16870    workspace
16871        .update(cx, |workspace, _, cx| {
16872            let active_item = workspace
16873                .active_item(cx)
16874                .expect("should have an active item after navigating back");
16875            assert_eq!(
16876                active_item.item_id(),
16877                multibuffer_item_id,
16878                "Should navigate back to the multi buffer"
16879            );
16880            assert!(!active_item.is_singleton(cx));
16881        })
16882        .unwrap();
16883
16884    multi_buffer_editor.update_in(cx, |editor, window, cx| {
16885        editor.change_selections(
16886            SelectionEffects::scroll(Autoscroll::Next),
16887            window,
16888            cx,
16889            |s| s.select_ranges(Some(39..40)),
16890        );
16891        editor.open_excerpts(&OpenExcerpts, window, cx);
16892    });
16893    cx.executor().run_until_parked();
16894    let second_item_id = workspace
16895        .update(cx, |workspace, window, cx| {
16896            let active_item = workspace
16897                .active_item(cx)
16898                .expect("should have an active item after navigating into the 2nd buffer");
16899            let second_item_id = active_item.item_id();
16900            assert_ne!(
16901                second_item_id, multibuffer_item_id,
16902                "Should navigate away from the multibuffer"
16903            );
16904            assert_ne!(
16905                second_item_id, first_item_id,
16906                "Should navigate into the 2nd buffer and activate it"
16907            );
16908            assert!(
16909                active_item.is_singleton(cx),
16910                "New active item should be a singleton buffer"
16911            );
16912            assert_eq!(
16913                active_item
16914                    .act_as::<Editor>(cx)
16915                    .expect("should have navigated into an editor")
16916                    .read(cx)
16917                    .text(cx),
16918                sample_text_2
16919            );
16920
16921            workspace
16922                .go_back(workspace.active_pane().downgrade(), window, cx)
16923                .detach_and_log_err(cx);
16924
16925            second_item_id
16926        })
16927        .unwrap();
16928    cx.executor().run_until_parked();
16929    workspace
16930        .update(cx, |workspace, _, cx| {
16931            let active_item = workspace
16932                .active_item(cx)
16933                .expect("should have an active item after navigating back from the 2nd buffer");
16934            assert_eq!(
16935                active_item.item_id(),
16936                multibuffer_item_id,
16937                "Should navigate back from the 2nd buffer to the multi buffer"
16938            );
16939            assert!(!active_item.is_singleton(cx));
16940        })
16941        .unwrap();
16942
16943    multi_buffer_editor.update_in(cx, |editor, window, cx| {
16944        editor.change_selections(
16945            SelectionEffects::scroll(Autoscroll::Next),
16946            window,
16947            cx,
16948            |s| s.select_ranges(Some(70..70)),
16949        );
16950        editor.open_excerpts(&OpenExcerpts, window, cx);
16951    });
16952    cx.executor().run_until_parked();
16953    workspace
16954        .update(cx, |workspace, window, cx| {
16955            let active_item = workspace
16956                .active_item(cx)
16957                .expect("should have an active item after navigating into the 3rd buffer");
16958            let third_item_id = active_item.item_id();
16959            assert_ne!(
16960                third_item_id, multibuffer_item_id,
16961                "Should navigate into the 3rd buffer and activate it"
16962            );
16963            assert_ne!(third_item_id, first_item_id);
16964            assert_ne!(third_item_id, second_item_id);
16965            assert!(
16966                active_item.is_singleton(cx),
16967                "New active item should be a singleton buffer"
16968            );
16969            assert_eq!(
16970                active_item
16971                    .act_as::<Editor>(cx)
16972                    .expect("should have navigated into an editor")
16973                    .read(cx)
16974                    .text(cx),
16975                sample_text_3
16976            );
16977
16978            workspace
16979                .go_back(workspace.active_pane().downgrade(), window, cx)
16980                .detach_and_log_err(cx);
16981        })
16982        .unwrap();
16983    cx.executor().run_until_parked();
16984    workspace
16985        .update(cx, |workspace, _, cx| {
16986            let active_item = workspace
16987                .active_item(cx)
16988                .expect("should have an active item after navigating back from the 3rd buffer");
16989            assert_eq!(
16990                active_item.item_id(),
16991                multibuffer_item_id,
16992                "Should navigate back from the 3rd buffer to the multi buffer"
16993            );
16994            assert!(!active_item.is_singleton(cx));
16995        })
16996        .unwrap();
16997}
16998
16999#[gpui::test]
17000async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17001    init_test(cx, |_| {});
17002
17003    let mut cx = EditorTestContext::new(cx).await;
17004
17005    let diff_base = r#"
17006        use some::mod;
17007
17008        const A: u32 = 42;
17009
17010        fn main() {
17011            println!("hello");
17012
17013            println!("world");
17014        }
17015        "#
17016    .unindent();
17017
17018    cx.set_state(
17019        &r#"
17020        use some::modified;
17021
17022        ˇ
17023        fn main() {
17024            println!("hello there");
17025
17026            println!("around the");
17027            println!("world");
17028        }
17029        "#
17030        .unindent(),
17031    );
17032
17033    cx.set_head_text(&diff_base);
17034    executor.run_until_parked();
17035
17036    cx.update_editor(|editor, window, cx| {
17037        editor.go_to_next_hunk(&GoToHunk, window, cx);
17038        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17039    });
17040    executor.run_until_parked();
17041    cx.assert_state_with_diff(
17042        r#"
17043          use some::modified;
17044
17045
17046          fn main() {
17047        -     println!("hello");
17048        + ˇ    println!("hello there");
17049
17050              println!("around the");
17051              println!("world");
17052          }
17053        "#
17054        .unindent(),
17055    );
17056
17057    cx.update_editor(|editor, window, cx| {
17058        for _ in 0..2 {
17059            editor.go_to_next_hunk(&GoToHunk, window, cx);
17060            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17061        }
17062    });
17063    executor.run_until_parked();
17064    cx.assert_state_with_diff(
17065        r#"
17066        - use some::mod;
17067        + ˇuse some::modified;
17068
17069
17070          fn main() {
17071        -     println!("hello");
17072        +     println!("hello there");
17073
17074        +     println!("around the");
17075              println!("world");
17076          }
17077        "#
17078        .unindent(),
17079    );
17080
17081    cx.update_editor(|editor, window, cx| {
17082        editor.go_to_next_hunk(&GoToHunk, window, cx);
17083        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17084    });
17085    executor.run_until_parked();
17086    cx.assert_state_with_diff(
17087        r#"
17088        - use some::mod;
17089        + use some::modified;
17090
17091        - const A: u32 = 42;
17092          ˇ
17093          fn main() {
17094        -     println!("hello");
17095        +     println!("hello there");
17096
17097        +     println!("around the");
17098              println!("world");
17099          }
17100        "#
17101        .unindent(),
17102    );
17103
17104    cx.update_editor(|editor, window, cx| {
17105        editor.cancel(&Cancel, window, cx);
17106    });
17107
17108    cx.assert_state_with_diff(
17109        r#"
17110          use some::modified;
17111
17112          ˇ
17113          fn main() {
17114              println!("hello there");
17115
17116              println!("around the");
17117              println!("world");
17118          }
17119        "#
17120        .unindent(),
17121    );
17122}
17123
17124#[gpui::test]
17125async fn test_diff_base_change_with_expanded_diff_hunks(
17126    executor: BackgroundExecutor,
17127    cx: &mut TestAppContext,
17128) {
17129    init_test(cx, |_| {});
17130
17131    let mut cx = EditorTestContext::new(cx).await;
17132
17133    let diff_base = r#"
17134        use some::mod1;
17135        use some::mod2;
17136
17137        const A: u32 = 42;
17138        const B: u32 = 42;
17139        const C: u32 = 42;
17140
17141        fn main() {
17142            println!("hello");
17143
17144            println!("world");
17145        }
17146        "#
17147    .unindent();
17148
17149    cx.set_state(
17150        &r#"
17151        use some::mod2;
17152
17153        const A: u32 = 42;
17154        const C: u32 = 42;
17155
17156        fn main(ˇ) {
17157            //println!("hello");
17158
17159            println!("world");
17160            //
17161            //
17162        }
17163        "#
17164        .unindent(),
17165    );
17166
17167    cx.set_head_text(&diff_base);
17168    executor.run_until_parked();
17169
17170    cx.update_editor(|editor, window, cx| {
17171        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17172    });
17173    executor.run_until_parked();
17174    cx.assert_state_with_diff(
17175        r#"
17176        - use some::mod1;
17177          use some::mod2;
17178
17179          const A: u32 = 42;
17180        - const B: u32 = 42;
17181          const C: u32 = 42;
17182
17183          fn main(ˇ) {
17184        -     println!("hello");
17185        +     //println!("hello");
17186
17187              println!("world");
17188        +     //
17189        +     //
17190          }
17191        "#
17192        .unindent(),
17193    );
17194
17195    cx.set_head_text("new diff base!");
17196    executor.run_until_parked();
17197    cx.assert_state_with_diff(
17198        r#"
17199        - new diff base!
17200        + use some::mod2;
17201        +
17202        + const A: u32 = 42;
17203        + const C: u32 = 42;
17204        +
17205        + fn main(ˇ) {
17206        +     //println!("hello");
17207        +
17208        +     println!("world");
17209        +     //
17210        +     //
17211        + }
17212        "#
17213        .unindent(),
17214    );
17215}
17216
17217#[gpui::test]
17218async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
17219    init_test(cx, |_| {});
17220
17221    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
17222    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
17223    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
17224    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
17225    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
17226    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
17227
17228    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
17229    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
17230    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
17231
17232    let multi_buffer = cx.new(|cx| {
17233        let mut multibuffer = MultiBuffer::new(ReadWrite);
17234        multibuffer.push_excerpts(
17235            buffer_1.clone(),
17236            [
17237                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17238                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17239                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17240            ],
17241            cx,
17242        );
17243        multibuffer.push_excerpts(
17244            buffer_2.clone(),
17245            [
17246                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17247                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17248                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17249            ],
17250            cx,
17251        );
17252        multibuffer.push_excerpts(
17253            buffer_3.clone(),
17254            [
17255                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17256                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17257                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17258            ],
17259            cx,
17260        );
17261        multibuffer
17262    });
17263
17264    let editor =
17265        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
17266    editor
17267        .update(cx, |editor, _window, cx| {
17268            for (buffer, diff_base) in [
17269                (buffer_1.clone(), file_1_old),
17270                (buffer_2.clone(), file_2_old),
17271                (buffer_3.clone(), file_3_old),
17272            ] {
17273                let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
17274                editor
17275                    .buffer
17276                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
17277            }
17278        })
17279        .unwrap();
17280
17281    let mut cx = EditorTestContext::for_editor(editor, cx).await;
17282    cx.run_until_parked();
17283
17284    cx.assert_editor_state(
17285        &"
17286            ˇaaa
17287            ccc
17288            ddd
17289
17290            ggg
17291            hhh
17292
17293
17294            lll
17295            mmm
17296            NNN
17297
17298            qqq
17299            rrr
17300
17301            uuu
17302            111
17303            222
17304            333
17305
17306            666
17307            777
17308
17309            000
17310            !!!"
17311        .unindent(),
17312    );
17313
17314    cx.update_editor(|editor, window, cx| {
17315        editor.select_all(&SelectAll, window, cx);
17316        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17317    });
17318    cx.executor().run_until_parked();
17319
17320    cx.assert_state_with_diff(
17321        "
17322            «aaa
17323          - bbb
17324            ccc
17325            ddd
17326
17327            ggg
17328            hhh
17329
17330
17331            lll
17332            mmm
17333          - nnn
17334          + NNN
17335
17336            qqq
17337            rrr
17338
17339            uuu
17340            111
17341            222
17342            333
17343
17344          + 666
17345            777
17346
17347            000
17348            !!!ˇ»"
17349            .unindent(),
17350    );
17351}
17352
17353#[gpui::test]
17354async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
17355    init_test(cx, |_| {});
17356
17357    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
17358    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
17359
17360    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
17361    let multi_buffer = cx.new(|cx| {
17362        let mut multibuffer = MultiBuffer::new(ReadWrite);
17363        multibuffer.push_excerpts(
17364            buffer.clone(),
17365            [
17366                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
17367                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
17368                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
17369            ],
17370            cx,
17371        );
17372        multibuffer
17373    });
17374
17375    let editor =
17376        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
17377    editor
17378        .update(cx, |editor, _window, cx| {
17379            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
17380            editor
17381                .buffer
17382                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
17383        })
17384        .unwrap();
17385
17386    let mut cx = EditorTestContext::for_editor(editor, cx).await;
17387    cx.run_until_parked();
17388
17389    cx.update_editor(|editor, window, cx| {
17390        editor.expand_all_diff_hunks(&Default::default(), window, cx)
17391    });
17392    cx.executor().run_until_parked();
17393
17394    // When the start of a hunk coincides with the start of its excerpt,
17395    // the hunk is expanded. When the start of a a hunk is earlier than
17396    // the start of its excerpt, the hunk is not expanded.
17397    cx.assert_state_with_diff(
17398        "
17399            ˇaaa
17400          - bbb
17401          + BBB
17402
17403          - ddd
17404          - eee
17405          + DDD
17406          + EEE
17407            fff
17408
17409            iii
17410        "
17411        .unindent(),
17412    );
17413}
17414
17415#[gpui::test]
17416async fn test_edits_around_expanded_insertion_hunks(
17417    executor: BackgroundExecutor,
17418    cx: &mut TestAppContext,
17419) {
17420    init_test(cx, |_| {});
17421
17422    let mut cx = EditorTestContext::new(cx).await;
17423
17424    let diff_base = r#"
17425        use some::mod1;
17426        use some::mod2;
17427
17428        const A: u32 = 42;
17429
17430        fn main() {
17431            println!("hello");
17432
17433            println!("world");
17434        }
17435        "#
17436    .unindent();
17437    executor.run_until_parked();
17438    cx.set_state(
17439        &r#"
17440        use some::mod1;
17441        use some::mod2;
17442
17443        const A: u32 = 42;
17444        const B: u32 = 42;
17445        const C: u32 = 42;
17446        ˇ
17447
17448        fn main() {
17449            println!("hello");
17450
17451            println!("world");
17452        }
17453        "#
17454        .unindent(),
17455    );
17456
17457    cx.set_head_text(&diff_base);
17458    executor.run_until_parked();
17459
17460    cx.update_editor(|editor, window, cx| {
17461        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17462    });
17463    executor.run_until_parked();
17464
17465    cx.assert_state_with_diff(
17466        r#"
17467        use some::mod1;
17468        use some::mod2;
17469
17470        const A: u32 = 42;
17471      + const B: u32 = 42;
17472      + const C: u32 = 42;
17473      + ˇ
17474
17475        fn main() {
17476            println!("hello");
17477
17478            println!("world");
17479        }
17480      "#
17481        .unindent(),
17482    );
17483
17484    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
17485    executor.run_until_parked();
17486
17487    cx.assert_state_with_diff(
17488        r#"
17489        use some::mod1;
17490        use some::mod2;
17491
17492        const A: u32 = 42;
17493      + const B: u32 = 42;
17494      + const C: u32 = 42;
17495      + const D: u32 = 42;
17496      + ˇ
17497
17498        fn main() {
17499            println!("hello");
17500
17501            println!("world");
17502        }
17503      "#
17504        .unindent(),
17505    );
17506
17507    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
17508    executor.run_until_parked();
17509
17510    cx.assert_state_with_diff(
17511        r#"
17512        use some::mod1;
17513        use some::mod2;
17514
17515        const A: u32 = 42;
17516      + const B: u32 = 42;
17517      + const C: u32 = 42;
17518      + const D: u32 = 42;
17519      + const E: u32 = 42;
17520      + ˇ
17521
17522        fn main() {
17523            println!("hello");
17524
17525            println!("world");
17526        }
17527      "#
17528        .unindent(),
17529    );
17530
17531    cx.update_editor(|editor, window, cx| {
17532        editor.delete_line(&DeleteLine, window, cx);
17533    });
17534    executor.run_until_parked();
17535
17536    cx.assert_state_with_diff(
17537        r#"
17538        use some::mod1;
17539        use some::mod2;
17540
17541        const A: u32 = 42;
17542      + const B: u32 = 42;
17543      + const C: u32 = 42;
17544      + const D: u32 = 42;
17545      + const E: u32 = 42;
17546        ˇ
17547        fn main() {
17548            println!("hello");
17549
17550            println!("world");
17551        }
17552      "#
17553        .unindent(),
17554    );
17555
17556    cx.update_editor(|editor, window, cx| {
17557        editor.move_up(&MoveUp, window, cx);
17558        editor.delete_line(&DeleteLine, window, cx);
17559        editor.move_up(&MoveUp, window, cx);
17560        editor.delete_line(&DeleteLine, window, cx);
17561        editor.move_up(&MoveUp, window, cx);
17562        editor.delete_line(&DeleteLine, window, cx);
17563    });
17564    executor.run_until_parked();
17565    cx.assert_state_with_diff(
17566        r#"
17567        use some::mod1;
17568        use some::mod2;
17569
17570        const A: u32 = 42;
17571      + const B: u32 = 42;
17572        ˇ
17573        fn main() {
17574            println!("hello");
17575
17576            println!("world");
17577        }
17578      "#
17579        .unindent(),
17580    );
17581
17582    cx.update_editor(|editor, window, cx| {
17583        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
17584        editor.delete_line(&DeleteLine, window, cx);
17585    });
17586    executor.run_until_parked();
17587    cx.assert_state_with_diff(
17588        r#"
17589        ˇ
17590        fn main() {
17591            println!("hello");
17592
17593            println!("world");
17594        }
17595      "#
17596        .unindent(),
17597    );
17598}
17599
17600#[gpui::test]
17601async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
17602    init_test(cx, |_| {});
17603
17604    let mut cx = EditorTestContext::new(cx).await;
17605    cx.set_head_text(indoc! { "
17606        one
17607        two
17608        three
17609        four
17610        five
17611        "
17612    });
17613    cx.set_state(indoc! { "
17614        one
17615        ˇthree
17616        five
17617    "});
17618    cx.run_until_parked();
17619    cx.update_editor(|editor, window, cx| {
17620        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17621    });
17622    cx.assert_state_with_diff(
17623        indoc! { "
17624        one
17625      - two
17626        ˇthree
17627      - four
17628        five
17629    "}
17630        .to_string(),
17631    );
17632    cx.update_editor(|editor, window, cx| {
17633        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17634    });
17635
17636    cx.assert_state_with_diff(
17637        indoc! { "
17638        one
17639        ˇthree
17640        five
17641    "}
17642        .to_string(),
17643    );
17644
17645    cx.set_state(indoc! { "
17646        one
17647        ˇTWO
17648        three
17649        four
17650        five
17651    "});
17652    cx.run_until_parked();
17653    cx.update_editor(|editor, window, cx| {
17654        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17655    });
17656
17657    cx.assert_state_with_diff(
17658        indoc! { "
17659            one
17660          - two
17661          + ˇTWO
17662            three
17663            four
17664            five
17665        "}
17666        .to_string(),
17667    );
17668    cx.update_editor(|editor, window, cx| {
17669        editor.move_up(&Default::default(), window, cx);
17670        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17671    });
17672    cx.assert_state_with_diff(
17673        indoc! { "
17674            one
17675            ˇTWO
17676            three
17677            four
17678            five
17679        "}
17680        .to_string(),
17681    );
17682}
17683
17684#[gpui::test]
17685async fn test_edits_around_expanded_deletion_hunks(
17686    executor: BackgroundExecutor,
17687    cx: &mut TestAppContext,
17688) {
17689    init_test(cx, |_| {});
17690
17691    let mut cx = EditorTestContext::new(cx).await;
17692
17693    let diff_base = r#"
17694        use some::mod1;
17695        use some::mod2;
17696
17697        const A: u32 = 42;
17698        const B: u32 = 42;
17699        const C: u32 = 42;
17700
17701
17702        fn main() {
17703            println!("hello");
17704
17705            println!("world");
17706        }
17707    "#
17708    .unindent();
17709    executor.run_until_parked();
17710    cx.set_state(
17711        &r#"
17712        use some::mod1;
17713        use some::mod2;
17714
17715        ˇconst B: u32 = 42;
17716        const C: u32 = 42;
17717
17718
17719        fn main() {
17720            println!("hello");
17721
17722            println!("world");
17723        }
17724        "#
17725        .unindent(),
17726    );
17727
17728    cx.set_head_text(&diff_base);
17729    executor.run_until_parked();
17730
17731    cx.update_editor(|editor, window, cx| {
17732        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17733    });
17734    executor.run_until_parked();
17735
17736    cx.assert_state_with_diff(
17737        r#"
17738        use some::mod1;
17739        use some::mod2;
17740
17741      - const A: u32 = 42;
17742        ˇconst B: u32 = 42;
17743        const C: u32 = 42;
17744
17745
17746        fn main() {
17747            println!("hello");
17748
17749            println!("world");
17750        }
17751      "#
17752        .unindent(),
17753    );
17754
17755    cx.update_editor(|editor, window, cx| {
17756        editor.delete_line(&DeleteLine, window, cx);
17757    });
17758    executor.run_until_parked();
17759    cx.assert_state_with_diff(
17760        r#"
17761        use some::mod1;
17762        use some::mod2;
17763
17764      - const A: u32 = 42;
17765      - const B: u32 = 42;
17766        ˇconst C: u32 = 42;
17767
17768
17769        fn main() {
17770            println!("hello");
17771
17772            println!("world");
17773        }
17774      "#
17775        .unindent(),
17776    );
17777
17778    cx.update_editor(|editor, window, cx| {
17779        editor.delete_line(&DeleteLine, window, cx);
17780    });
17781    executor.run_until_parked();
17782    cx.assert_state_with_diff(
17783        r#"
17784        use some::mod1;
17785        use some::mod2;
17786
17787      - const A: u32 = 42;
17788      - const B: u32 = 42;
17789      - const C: u32 = 42;
17790        ˇ
17791
17792        fn main() {
17793            println!("hello");
17794
17795            println!("world");
17796        }
17797      "#
17798        .unindent(),
17799    );
17800
17801    cx.update_editor(|editor, window, cx| {
17802        editor.handle_input("replacement", window, cx);
17803    });
17804    executor.run_until_parked();
17805    cx.assert_state_with_diff(
17806        r#"
17807        use some::mod1;
17808        use some::mod2;
17809
17810      - const A: u32 = 42;
17811      - const B: u32 = 42;
17812      - const C: u32 = 42;
17813      -
17814      + replacementˇ
17815
17816        fn main() {
17817            println!("hello");
17818
17819            println!("world");
17820        }
17821      "#
17822        .unindent(),
17823    );
17824}
17825
17826#[gpui::test]
17827async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17828    init_test(cx, |_| {});
17829
17830    let mut cx = EditorTestContext::new(cx).await;
17831
17832    let base_text = r#"
17833        one
17834        two
17835        three
17836        four
17837        five
17838    "#
17839    .unindent();
17840    executor.run_until_parked();
17841    cx.set_state(
17842        &r#"
17843        one
17844        two
17845        fˇour
17846        five
17847        "#
17848        .unindent(),
17849    );
17850
17851    cx.set_head_text(&base_text);
17852    executor.run_until_parked();
17853
17854    cx.update_editor(|editor, window, cx| {
17855        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17856    });
17857    executor.run_until_parked();
17858
17859    cx.assert_state_with_diff(
17860        r#"
17861          one
17862          two
17863        - three
17864          fˇour
17865          five
17866        "#
17867        .unindent(),
17868    );
17869
17870    cx.update_editor(|editor, window, cx| {
17871        editor.backspace(&Backspace, window, cx);
17872        editor.backspace(&Backspace, window, cx);
17873    });
17874    executor.run_until_parked();
17875    cx.assert_state_with_diff(
17876        r#"
17877          one
17878          two
17879        - threeˇ
17880        - four
17881        + our
17882          five
17883        "#
17884        .unindent(),
17885    );
17886}
17887
17888#[gpui::test]
17889async fn test_edit_after_expanded_modification_hunk(
17890    executor: BackgroundExecutor,
17891    cx: &mut TestAppContext,
17892) {
17893    init_test(cx, |_| {});
17894
17895    let mut cx = EditorTestContext::new(cx).await;
17896
17897    let diff_base = r#"
17898        use some::mod1;
17899        use some::mod2;
17900
17901        const A: u32 = 42;
17902        const B: u32 = 42;
17903        const C: u32 = 42;
17904        const D: u32 = 42;
17905
17906
17907        fn main() {
17908            println!("hello");
17909
17910            println!("world");
17911        }"#
17912    .unindent();
17913
17914    cx.set_state(
17915        &r#"
17916        use some::mod1;
17917        use some::mod2;
17918
17919        const A: u32 = 42;
17920        const B: u32 = 42;
17921        const C: u32 = 43ˇ
17922        const D: u32 = 42;
17923
17924
17925        fn main() {
17926            println!("hello");
17927
17928            println!("world");
17929        }"#
17930        .unindent(),
17931    );
17932
17933    cx.set_head_text(&diff_base);
17934    executor.run_until_parked();
17935    cx.update_editor(|editor, window, cx| {
17936        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17937    });
17938    executor.run_until_parked();
17939
17940    cx.assert_state_with_diff(
17941        r#"
17942        use some::mod1;
17943        use some::mod2;
17944
17945        const A: u32 = 42;
17946        const B: u32 = 42;
17947      - const C: u32 = 42;
17948      + const C: u32 = 43ˇ
17949        const D: u32 = 42;
17950
17951
17952        fn main() {
17953            println!("hello");
17954
17955            println!("world");
17956        }"#
17957        .unindent(),
17958    );
17959
17960    cx.update_editor(|editor, window, cx| {
17961        editor.handle_input("\nnew_line\n", window, cx);
17962    });
17963    executor.run_until_parked();
17964
17965    cx.assert_state_with_diff(
17966        r#"
17967        use some::mod1;
17968        use some::mod2;
17969
17970        const A: u32 = 42;
17971        const B: u32 = 42;
17972      - const C: u32 = 42;
17973      + const C: u32 = 43
17974      + new_line
17975      + ˇ
17976        const D: u32 = 42;
17977
17978
17979        fn main() {
17980            println!("hello");
17981
17982            println!("world");
17983        }"#
17984        .unindent(),
17985    );
17986}
17987
17988#[gpui::test]
17989async fn test_stage_and_unstage_added_file_hunk(
17990    executor: BackgroundExecutor,
17991    cx: &mut TestAppContext,
17992) {
17993    init_test(cx, |_| {});
17994
17995    let mut cx = EditorTestContext::new(cx).await;
17996    cx.update_editor(|editor, _, cx| {
17997        editor.set_expand_all_diff_hunks(cx);
17998    });
17999
18000    let working_copy = r#"
18001            ˇfn main() {
18002                println!("hello, world!");
18003            }
18004        "#
18005    .unindent();
18006
18007    cx.set_state(&working_copy);
18008    executor.run_until_parked();
18009
18010    cx.assert_state_with_diff(
18011        r#"
18012            + ˇfn main() {
18013            +     println!("hello, world!");
18014            + }
18015        "#
18016        .unindent(),
18017    );
18018    cx.assert_index_text(None);
18019
18020    cx.update_editor(|editor, window, cx| {
18021        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18022    });
18023    executor.run_until_parked();
18024    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
18025    cx.assert_state_with_diff(
18026        r#"
18027            + ˇfn main() {
18028            +     println!("hello, world!");
18029            + }
18030        "#
18031        .unindent(),
18032    );
18033
18034    cx.update_editor(|editor, window, cx| {
18035        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18036    });
18037    executor.run_until_parked();
18038    cx.assert_index_text(None);
18039}
18040
18041async fn setup_indent_guides_editor(
18042    text: &str,
18043    cx: &mut TestAppContext,
18044) -> (BufferId, EditorTestContext) {
18045    init_test(cx, |_| {});
18046
18047    let mut cx = EditorTestContext::new(cx).await;
18048
18049    let buffer_id = cx.update_editor(|editor, window, cx| {
18050        editor.set_text(text, window, cx);
18051        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
18052
18053        buffer_ids[0]
18054    });
18055
18056    (buffer_id, cx)
18057}
18058
18059fn assert_indent_guides(
18060    range: Range<u32>,
18061    expected: Vec<IndentGuide>,
18062    active_indices: Option<Vec<usize>>,
18063    cx: &mut EditorTestContext,
18064) {
18065    let indent_guides = cx.update_editor(|editor, window, cx| {
18066        let snapshot = editor.snapshot(window, cx).display_snapshot;
18067        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
18068            editor,
18069            MultiBufferRow(range.start)..MultiBufferRow(range.end),
18070            true,
18071            &snapshot,
18072            cx,
18073        );
18074
18075        indent_guides.sort_by(|a, b| {
18076            a.depth.cmp(&b.depth).then(
18077                a.start_row
18078                    .cmp(&b.start_row)
18079                    .then(a.end_row.cmp(&b.end_row)),
18080            )
18081        });
18082        indent_guides
18083    });
18084
18085    if let Some(expected) = active_indices {
18086        let active_indices = cx.update_editor(|editor, window, cx| {
18087            let snapshot = editor.snapshot(window, cx).display_snapshot;
18088            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
18089        });
18090
18091        assert_eq!(
18092            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
18093            expected,
18094            "Active indent guide indices do not match"
18095        );
18096    }
18097
18098    assert_eq!(indent_guides, expected, "Indent guides do not match");
18099}
18100
18101fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
18102    IndentGuide {
18103        buffer_id,
18104        start_row: MultiBufferRow(start_row),
18105        end_row: MultiBufferRow(end_row),
18106        depth,
18107        tab_size: 4,
18108        settings: IndentGuideSettings {
18109            enabled: true,
18110            line_width: 1,
18111            active_line_width: 1,
18112            ..Default::default()
18113        },
18114    }
18115}
18116
18117#[gpui::test]
18118async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
18119    let (buffer_id, mut cx) = setup_indent_guides_editor(
18120        &"
18121        fn main() {
18122            let a = 1;
18123        }"
18124        .unindent(),
18125        cx,
18126    )
18127    .await;
18128
18129    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
18130}
18131
18132#[gpui::test]
18133async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
18134    let (buffer_id, mut cx) = setup_indent_guides_editor(
18135        &"
18136        fn main() {
18137            let a = 1;
18138            let b = 2;
18139        }"
18140        .unindent(),
18141        cx,
18142    )
18143    .await;
18144
18145    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
18146}
18147
18148#[gpui::test]
18149async fn test_indent_guide_nested(cx: &mut TestAppContext) {
18150    let (buffer_id, mut cx) = setup_indent_guides_editor(
18151        &"
18152        fn main() {
18153            let a = 1;
18154            if a == 3 {
18155                let b = 2;
18156            } else {
18157                let c = 3;
18158            }
18159        }"
18160        .unindent(),
18161        cx,
18162    )
18163    .await;
18164
18165    assert_indent_guides(
18166        0..8,
18167        vec![
18168            indent_guide(buffer_id, 1, 6, 0),
18169            indent_guide(buffer_id, 3, 3, 1),
18170            indent_guide(buffer_id, 5, 5, 1),
18171        ],
18172        None,
18173        &mut cx,
18174    );
18175}
18176
18177#[gpui::test]
18178async fn test_indent_guide_tab(cx: &mut TestAppContext) {
18179    let (buffer_id, mut cx) = setup_indent_guides_editor(
18180        &"
18181        fn main() {
18182            let a = 1;
18183                let b = 2;
18184            let c = 3;
18185        }"
18186        .unindent(),
18187        cx,
18188    )
18189    .await;
18190
18191    assert_indent_guides(
18192        0..5,
18193        vec![
18194            indent_guide(buffer_id, 1, 3, 0),
18195            indent_guide(buffer_id, 2, 2, 1),
18196        ],
18197        None,
18198        &mut cx,
18199    );
18200}
18201
18202#[gpui::test]
18203async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
18204    let (buffer_id, mut cx) = setup_indent_guides_editor(
18205        &"
18206        fn main() {
18207            let a = 1;
18208
18209            let c = 3;
18210        }"
18211        .unindent(),
18212        cx,
18213    )
18214    .await;
18215
18216    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
18217}
18218
18219#[gpui::test]
18220async fn test_indent_guide_complex(cx: &mut TestAppContext) {
18221    let (buffer_id, mut cx) = setup_indent_guides_editor(
18222        &"
18223        fn main() {
18224            let a = 1;
18225
18226            let c = 3;
18227
18228            if a == 3 {
18229                let b = 2;
18230            } else {
18231                let c = 3;
18232            }
18233        }"
18234        .unindent(),
18235        cx,
18236    )
18237    .await;
18238
18239    assert_indent_guides(
18240        0..11,
18241        vec![
18242            indent_guide(buffer_id, 1, 9, 0),
18243            indent_guide(buffer_id, 6, 6, 1),
18244            indent_guide(buffer_id, 8, 8, 1),
18245        ],
18246        None,
18247        &mut cx,
18248    );
18249}
18250
18251#[gpui::test]
18252async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
18253    let (buffer_id, mut cx) = setup_indent_guides_editor(
18254        &"
18255        fn main() {
18256            let a = 1;
18257
18258            let c = 3;
18259
18260            if a == 3 {
18261                let b = 2;
18262            } else {
18263                let c = 3;
18264            }
18265        }"
18266        .unindent(),
18267        cx,
18268    )
18269    .await;
18270
18271    assert_indent_guides(
18272        1..11,
18273        vec![
18274            indent_guide(buffer_id, 1, 9, 0),
18275            indent_guide(buffer_id, 6, 6, 1),
18276            indent_guide(buffer_id, 8, 8, 1),
18277        ],
18278        None,
18279        &mut cx,
18280    );
18281}
18282
18283#[gpui::test]
18284async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
18285    let (buffer_id, mut cx) = setup_indent_guides_editor(
18286        &"
18287        fn main() {
18288            let a = 1;
18289
18290            let c = 3;
18291
18292            if a == 3 {
18293                let b = 2;
18294            } else {
18295                let c = 3;
18296            }
18297        }"
18298        .unindent(),
18299        cx,
18300    )
18301    .await;
18302
18303    assert_indent_guides(
18304        1..10,
18305        vec![
18306            indent_guide(buffer_id, 1, 9, 0),
18307            indent_guide(buffer_id, 6, 6, 1),
18308            indent_guide(buffer_id, 8, 8, 1),
18309        ],
18310        None,
18311        &mut cx,
18312    );
18313}
18314
18315#[gpui::test]
18316async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
18317    let (buffer_id, mut cx) = setup_indent_guides_editor(
18318        &"
18319        fn main() {
18320            if a {
18321                b(
18322                    c,
18323                    d,
18324                )
18325            } else {
18326                e(
18327                    f
18328                )
18329            }
18330        }"
18331        .unindent(),
18332        cx,
18333    )
18334    .await;
18335
18336    assert_indent_guides(
18337        0..11,
18338        vec![
18339            indent_guide(buffer_id, 1, 10, 0),
18340            indent_guide(buffer_id, 2, 5, 1),
18341            indent_guide(buffer_id, 7, 9, 1),
18342            indent_guide(buffer_id, 3, 4, 2),
18343            indent_guide(buffer_id, 8, 8, 2),
18344        ],
18345        None,
18346        &mut cx,
18347    );
18348
18349    cx.update_editor(|editor, window, cx| {
18350        editor.fold_at(MultiBufferRow(2), window, cx);
18351        assert_eq!(
18352            editor.display_text(cx),
18353            "
18354            fn main() {
18355                if a {
18356                    b(⋯
18357                    )
18358                } else {
18359                    e(
18360                        f
18361                    )
18362                }
18363            }"
18364            .unindent()
18365        );
18366    });
18367
18368    assert_indent_guides(
18369        0..11,
18370        vec![
18371            indent_guide(buffer_id, 1, 10, 0),
18372            indent_guide(buffer_id, 2, 5, 1),
18373            indent_guide(buffer_id, 7, 9, 1),
18374            indent_guide(buffer_id, 8, 8, 2),
18375        ],
18376        None,
18377        &mut cx,
18378    );
18379}
18380
18381#[gpui::test]
18382async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
18383    let (buffer_id, mut cx) = setup_indent_guides_editor(
18384        &"
18385        block1
18386            block2
18387                block3
18388                    block4
18389            block2
18390        block1
18391        block1"
18392            .unindent(),
18393        cx,
18394    )
18395    .await;
18396
18397    assert_indent_guides(
18398        1..10,
18399        vec![
18400            indent_guide(buffer_id, 1, 4, 0),
18401            indent_guide(buffer_id, 2, 3, 1),
18402            indent_guide(buffer_id, 3, 3, 2),
18403        ],
18404        None,
18405        &mut cx,
18406    );
18407}
18408
18409#[gpui::test]
18410async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
18411    let (buffer_id, mut cx) = setup_indent_guides_editor(
18412        &"
18413        block1
18414            block2
18415                block3
18416
18417        block1
18418        block1"
18419            .unindent(),
18420        cx,
18421    )
18422    .await;
18423
18424    assert_indent_guides(
18425        0..6,
18426        vec![
18427            indent_guide(buffer_id, 1, 2, 0),
18428            indent_guide(buffer_id, 2, 2, 1),
18429        ],
18430        None,
18431        &mut cx,
18432    );
18433}
18434
18435#[gpui::test]
18436async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
18437    let (buffer_id, mut cx) = setup_indent_guides_editor(
18438        &"
18439        function component() {
18440        \treturn (
18441        \t\t\t
18442        \t\t<div>
18443        \t\t\t<abc></abc>
18444        \t\t</div>
18445        \t)
18446        }"
18447        .unindent(),
18448        cx,
18449    )
18450    .await;
18451
18452    assert_indent_guides(
18453        0..8,
18454        vec![
18455            indent_guide(buffer_id, 1, 6, 0),
18456            indent_guide(buffer_id, 2, 5, 1),
18457            indent_guide(buffer_id, 4, 4, 2),
18458        ],
18459        None,
18460        &mut cx,
18461    );
18462}
18463
18464#[gpui::test]
18465async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
18466    let (buffer_id, mut cx) = setup_indent_guides_editor(
18467        &"
18468        function component() {
18469        \treturn (
18470        \t
18471        \t\t<div>
18472        \t\t\t<abc></abc>
18473        \t\t</div>
18474        \t)
18475        }"
18476        .unindent(),
18477        cx,
18478    )
18479    .await;
18480
18481    assert_indent_guides(
18482        0..8,
18483        vec![
18484            indent_guide(buffer_id, 1, 6, 0),
18485            indent_guide(buffer_id, 2, 5, 1),
18486            indent_guide(buffer_id, 4, 4, 2),
18487        ],
18488        None,
18489        &mut cx,
18490    );
18491}
18492
18493#[gpui::test]
18494async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
18495    let (buffer_id, mut cx) = setup_indent_guides_editor(
18496        &"
18497        block1
18498
18499
18500
18501            block2
18502        "
18503        .unindent(),
18504        cx,
18505    )
18506    .await;
18507
18508    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
18509}
18510
18511#[gpui::test]
18512async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
18513    let (buffer_id, mut cx) = setup_indent_guides_editor(
18514        &"
18515        def a:
18516        \tb = 3
18517        \tif True:
18518        \t\tc = 4
18519        \t\td = 5
18520        \tprint(b)
18521        "
18522        .unindent(),
18523        cx,
18524    )
18525    .await;
18526
18527    assert_indent_guides(
18528        0..6,
18529        vec![
18530            indent_guide(buffer_id, 1, 5, 0),
18531            indent_guide(buffer_id, 3, 4, 1),
18532        ],
18533        None,
18534        &mut cx,
18535    );
18536}
18537
18538#[gpui::test]
18539async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
18540    let (buffer_id, mut cx) = setup_indent_guides_editor(
18541        &"
18542    fn main() {
18543        let a = 1;
18544    }"
18545        .unindent(),
18546        cx,
18547    )
18548    .await;
18549
18550    cx.update_editor(|editor, window, cx| {
18551        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18552            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18553        });
18554    });
18555
18556    assert_indent_guides(
18557        0..3,
18558        vec![indent_guide(buffer_id, 1, 1, 0)],
18559        Some(vec![0]),
18560        &mut cx,
18561    );
18562}
18563
18564#[gpui::test]
18565async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
18566    let (buffer_id, mut cx) = setup_indent_guides_editor(
18567        &"
18568    fn main() {
18569        if 1 == 2 {
18570            let a = 1;
18571        }
18572    }"
18573        .unindent(),
18574        cx,
18575    )
18576    .await;
18577
18578    cx.update_editor(|editor, window, cx| {
18579        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18580            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18581        });
18582    });
18583
18584    assert_indent_guides(
18585        0..4,
18586        vec![
18587            indent_guide(buffer_id, 1, 3, 0),
18588            indent_guide(buffer_id, 2, 2, 1),
18589        ],
18590        Some(vec![1]),
18591        &mut cx,
18592    );
18593
18594    cx.update_editor(|editor, window, cx| {
18595        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18596            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
18597        });
18598    });
18599
18600    assert_indent_guides(
18601        0..4,
18602        vec![
18603            indent_guide(buffer_id, 1, 3, 0),
18604            indent_guide(buffer_id, 2, 2, 1),
18605        ],
18606        Some(vec![1]),
18607        &mut cx,
18608    );
18609
18610    cx.update_editor(|editor, window, cx| {
18611        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18612            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
18613        });
18614    });
18615
18616    assert_indent_guides(
18617        0..4,
18618        vec![
18619            indent_guide(buffer_id, 1, 3, 0),
18620            indent_guide(buffer_id, 2, 2, 1),
18621        ],
18622        Some(vec![0]),
18623        &mut cx,
18624    );
18625}
18626
18627#[gpui::test]
18628async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
18629    let (buffer_id, mut cx) = setup_indent_guides_editor(
18630        &"
18631    fn main() {
18632        let a = 1;
18633
18634        let b = 2;
18635    }"
18636        .unindent(),
18637        cx,
18638    )
18639    .await;
18640
18641    cx.update_editor(|editor, window, cx| {
18642        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18643            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
18644        });
18645    });
18646
18647    assert_indent_guides(
18648        0..5,
18649        vec![indent_guide(buffer_id, 1, 3, 0)],
18650        Some(vec![0]),
18651        &mut cx,
18652    );
18653}
18654
18655#[gpui::test]
18656async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
18657    let (buffer_id, mut cx) = setup_indent_guides_editor(
18658        &"
18659    def m:
18660        a = 1
18661        pass"
18662            .unindent(),
18663        cx,
18664    )
18665    .await;
18666
18667    cx.update_editor(|editor, window, cx| {
18668        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18669            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18670        });
18671    });
18672
18673    assert_indent_guides(
18674        0..3,
18675        vec![indent_guide(buffer_id, 1, 2, 0)],
18676        Some(vec![0]),
18677        &mut cx,
18678    );
18679}
18680
18681#[gpui::test]
18682async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
18683    init_test(cx, |_| {});
18684    let mut cx = EditorTestContext::new(cx).await;
18685    let text = indoc! {
18686        "
18687        impl A {
18688            fn b() {
18689                0;
18690                3;
18691                5;
18692                6;
18693                7;
18694            }
18695        }
18696        "
18697    };
18698    let base_text = indoc! {
18699        "
18700        impl A {
18701            fn b() {
18702                0;
18703                1;
18704                2;
18705                3;
18706                4;
18707            }
18708            fn c() {
18709                5;
18710                6;
18711                7;
18712            }
18713        }
18714        "
18715    };
18716
18717    cx.update_editor(|editor, window, cx| {
18718        editor.set_text(text, window, cx);
18719
18720        editor.buffer().update(cx, |multibuffer, cx| {
18721            let buffer = multibuffer.as_singleton().unwrap();
18722            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
18723
18724            multibuffer.set_all_diff_hunks_expanded(cx);
18725            multibuffer.add_diff(diff, cx);
18726
18727            buffer.read(cx).remote_id()
18728        })
18729    });
18730    cx.run_until_parked();
18731
18732    cx.assert_state_with_diff(
18733        indoc! { "
18734          impl A {
18735              fn b() {
18736                  0;
18737        -         1;
18738        -         2;
18739                  3;
18740        -         4;
18741        -     }
18742        -     fn c() {
18743                  5;
18744                  6;
18745                  7;
18746              }
18747          }
18748          ˇ"
18749        }
18750        .to_string(),
18751    );
18752
18753    let mut actual_guides = cx.update_editor(|editor, window, cx| {
18754        editor
18755            .snapshot(window, cx)
18756            .buffer_snapshot
18757            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
18758            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
18759            .collect::<Vec<_>>()
18760    });
18761    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
18762    assert_eq!(
18763        actual_guides,
18764        vec![
18765            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
18766            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
18767            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
18768        ]
18769    );
18770}
18771
18772#[gpui::test]
18773async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18774    init_test(cx, |_| {});
18775    let mut cx = EditorTestContext::new(cx).await;
18776
18777    let diff_base = r#"
18778        a
18779        b
18780        c
18781        "#
18782    .unindent();
18783
18784    cx.set_state(
18785        &r#"
18786        ˇA
18787        b
18788        C
18789        "#
18790        .unindent(),
18791    );
18792    cx.set_head_text(&diff_base);
18793    cx.update_editor(|editor, window, cx| {
18794        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18795    });
18796    executor.run_until_parked();
18797
18798    let both_hunks_expanded = r#"
18799        - a
18800        + ˇA
18801          b
18802        - c
18803        + C
18804        "#
18805    .unindent();
18806
18807    cx.assert_state_with_diff(both_hunks_expanded.clone());
18808
18809    let hunk_ranges = cx.update_editor(|editor, window, cx| {
18810        let snapshot = editor.snapshot(window, cx);
18811        let hunks = editor
18812            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18813            .collect::<Vec<_>>();
18814        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
18815        let buffer_id = hunks[0].buffer_id;
18816        hunks
18817            .into_iter()
18818            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
18819            .collect::<Vec<_>>()
18820    });
18821    assert_eq!(hunk_ranges.len(), 2);
18822
18823    cx.update_editor(|editor, _, cx| {
18824        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18825    });
18826    executor.run_until_parked();
18827
18828    let second_hunk_expanded = r#"
18829          ˇA
18830          b
18831        - c
18832        + C
18833        "#
18834    .unindent();
18835
18836    cx.assert_state_with_diff(second_hunk_expanded);
18837
18838    cx.update_editor(|editor, _, cx| {
18839        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18840    });
18841    executor.run_until_parked();
18842
18843    cx.assert_state_with_diff(both_hunks_expanded.clone());
18844
18845    cx.update_editor(|editor, _, cx| {
18846        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
18847    });
18848    executor.run_until_parked();
18849
18850    let first_hunk_expanded = r#"
18851        - a
18852        + ˇA
18853          b
18854          C
18855        "#
18856    .unindent();
18857
18858    cx.assert_state_with_diff(first_hunk_expanded);
18859
18860    cx.update_editor(|editor, _, cx| {
18861        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
18862    });
18863    executor.run_until_parked();
18864
18865    cx.assert_state_with_diff(both_hunks_expanded);
18866
18867    cx.set_state(
18868        &r#"
18869        ˇA
18870        b
18871        "#
18872        .unindent(),
18873    );
18874    cx.run_until_parked();
18875
18876    // TODO this cursor position seems bad
18877    cx.assert_state_with_diff(
18878        r#"
18879        - ˇa
18880        + A
18881          b
18882        "#
18883        .unindent(),
18884    );
18885
18886    cx.update_editor(|editor, window, cx| {
18887        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18888    });
18889
18890    cx.assert_state_with_diff(
18891        r#"
18892            - ˇa
18893            + A
18894              b
18895            - c
18896            "#
18897        .unindent(),
18898    );
18899
18900    let hunk_ranges = cx.update_editor(|editor, window, cx| {
18901        let snapshot = editor.snapshot(window, cx);
18902        let hunks = editor
18903            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18904            .collect::<Vec<_>>();
18905        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
18906        let buffer_id = hunks[0].buffer_id;
18907        hunks
18908            .into_iter()
18909            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
18910            .collect::<Vec<_>>()
18911    });
18912    assert_eq!(hunk_ranges.len(), 2);
18913
18914    cx.update_editor(|editor, _, cx| {
18915        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
18916    });
18917    executor.run_until_parked();
18918
18919    cx.assert_state_with_diff(
18920        r#"
18921        - ˇa
18922        + A
18923          b
18924        "#
18925        .unindent(),
18926    );
18927}
18928
18929#[gpui::test]
18930async fn test_toggle_deletion_hunk_at_start_of_file(
18931    executor: BackgroundExecutor,
18932    cx: &mut TestAppContext,
18933) {
18934    init_test(cx, |_| {});
18935    let mut cx = EditorTestContext::new(cx).await;
18936
18937    let diff_base = r#"
18938        a
18939        b
18940        c
18941        "#
18942    .unindent();
18943
18944    cx.set_state(
18945        &r#"
18946        ˇb
18947        c
18948        "#
18949        .unindent(),
18950    );
18951    cx.set_head_text(&diff_base);
18952    cx.update_editor(|editor, window, cx| {
18953        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18954    });
18955    executor.run_until_parked();
18956
18957    let hunk_expanded = r#"
18958        - a
18959          ˇb
18960          c
18961        "#
18962    .unindent();
18963
18964    cx.assert_state_with_diff(hunk_expanded.clone());
18965
18966    let hunk_ranges = cx.update_editor(|editor, window, cx| {
18967        let snapshot = editor.snapshot(window, cx);
18968        let hunks = editor
18969            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18970            .collect::<Vec<_>>();
18971        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
18972        let buffer_id = hunks[0].buffer_id;
18973        hunks
18974            .into_iter()
18975            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
18976            .collect::<Vec<_>>()
18977    });
18978    assert_eq!(hunk_ranges.len(), 1);
18979
18980    cx.update_editor(|editor, _, cx| {
18981        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18982    });
18983    executor.run_until_parked();
18984
18985    let hunk_collapsed = r#"
18986          ˇb
18987          c
18988        "#
18989    .unindent();
18990
18991    cx.assert_state_with_diff(hunk_collapsed);
18992
18993    cx.update_editor(|editor, _, cx| {
18994        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18995    });
18996    executor.run_until_parked();
18997
18998    cx.assert_state_with_diff(hunk_expanded.clone());
18999}
19000
19001#[gpui::test]
19002async fn test_display_diff_hunks(cx: &mut TestAppContext) {
19003    init_test(cx, |_| {});
19004
19005    let fs = FakeFs::new(cx.executor());
19006    fs.insert_tree(
19007        path!("/test"),
19008        json!({
19009            ".git": {},
19010            "file-1": "ONE\n",
19011            "file-2": "TWO\n",
19012            "file-3": "THREE\n",
19013        }),
19014    )
19015    .await;
19016
19017    fs.set_head_for_repo(
19018        path!("/test/.git").as_ref(),
19019        &[
19020            ("file-1".into(), "one\n".into()),
19021            ("file-2".into(), "two\n".into()),
19022            ("file-3".into(), "three\n".into()),
19023        ],
19024        "deadbeef",
19025    );
19026
19027    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
19028    let mut buffers = vec![];
19029    for i in 1..=3 {
19030        let buffer = project
19031            .update(cx, |project, cx| {
19032                let path = format!(path!("/test/file-{}"), i);
19033                project.open_local_buffer(path, cx)
19034            })
19035            .await
19036            .unwrap();
19037        buffers.push(buffer);
19038    }
19039
19040    let multibuffer = cx.new(|cx| {
19041        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
19042        multibuffer.set_all_diff_hunks_expanded(cx);
19043        for buffer in &buffers {
19044            let snapshot = buffer.read(cx).snapshot();
19045            multibuffer.set_excerpts_for_path(
19046                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
19047                buffer.clone(),
19048                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
19049                DEFAULT_MULTIBUFFER_CONTEXT,
19050                cx,
19051            );
19052        }
19053        multibuffer
19054    });
19055
19056    let editor = cx.add_window(|window, cx| {
19057        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
19058    });
19059    cx.run_until_parked();
19060
19061    let snapshot = editor
19062        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19063        .unwrap();
19064    let hunks = snapshot
19065        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
19066        .map(|hunk| match hunk {
19067            DisplayDiffHunk::Unfolded {
19068                display_row_range, ..
19069            } => display_row_range,
19070            DisplayDiffHunk::Folded { .. } => unreachable!(),
19071        })
19072        .collect::<Vec<_>>();
19073    assert_eq!(
19074        hunks,
19075        [
19076            DisplayRow(2)..DisplayRow(4),
19077            DisplayRow(7)..DisplayRow(9),
19078            DisplayRow(12)..DisplayRow(14),
19079        ]
19080    );
19081}
19082
19083#[gpui::test]
19084async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
19085    init_test(cx, |_| {});
19086
19087    let mut cx = EditorTestContext::new(cx).await;
19088    cx.set_head_text(indoc! { "
19089        one
19090        two
19091        three
19092        four
19093        five
19094        "
19095    });
19096    cx.set_index_text(indoc! { "
19097        one
19098        two
19099        three
19100        four
19101        five
19102        "
19103    });
19104    cx.set_state(indoc! {"
19105        one
19106        TWO
19107        ˇTHREE
19108        FOUR
19109        five
19110    "});
19111    cx.run_until_parked();
19112    cx.update_editor(|editor, window, cx| {
19113        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19114    });
19115    cx.run_until_parked();
19116    cx.assert_index_text(Some(indoc! {"
19117        one
19118        TWO
19119        THREE
19120        FOUR
19121        five
19122    "}));
19123    cx.set_state(indoc! { "
19124        one
19125        TWO
19126        ˇTHREE-HUNDRED
19127        FOUR
19128        five
19129    "});
19130    cx.run_until_parked();
19131    cx.update_editor(|editor, window, cx| {
19132        let snapshot = editor.snapshot(window, cx);
19133        let hunks = editor
19134            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19135            .collect::<Vec<_>>();
19136        assert_eq!(hunks.len(), 1);
19137        assert_eq!(
19138            hunks[0].status(),
19139            DiffHunkStatus {
19140                kind: DiffHunkStatusKind::Modified,
19141                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
19142            }
19143        );
19144
19145        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19146    });
19147    cx.run_until_parked();
19148    cx.assert_index_text(Some(indoc! {"
19149        one
19150        TWO
19151        THREE-HUNDRED
19152        FOUR
19153        five
19154    "}));
19155}
19156
19157#[gpui::test]
19158fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
19159    init_test(cx, |_| {});
19160
19161    let editor = cx.add_window(|window, cx| {
19162        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
19163        build_editor(buffer, window, cx)
19164    });
19165
19166    let render_args = Arc::new(Mutex::new(None));
19167    let snapshot = editor
19168        .update(cx, |editor, window, cx| {
19169            let snapshot = editor.buffer().read(cx).snapshot(cx);
19170            let range =
19171                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
19172
19173            struct RenderArgs {
19174                row: MultiBufferRow,
19175                folded: bool,
19176                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
19177            }
19178
19179            let crease = Crease::inline(
19180                range,
19181                FoldPlaceholder::test(),
19182                {
19183                    let toggle_callback = render_args.clone();
19184                    move |row, folded, callback, _window, _cx| {
19185                        *toggle_callback.lock() = Some(RenderArgs {
19186                            row,
19187                            folded,
19188                            callback,
19189                        });
19190                        div()
19191                    }
19192                },
19193                |_row, _folded, _window, _cx| div(),
19194            );
19195
19196            editor.insert_creases(Some(crease), cx);
19197            let snapshot = editor.snapshot(window, cx);
19198            let _div = snapshot.render_crease_toggle(
19199                MultiBufferRow(1),
19200                false,
19201                cx.entity().clone(),
19202                window,
19203                cx,
19204            );
19205            snapshot
19206        })
19207        .unwrap();
19208
19209    let render_args = render_args.lock().take().unwrap();
19210    assert_eq!(render_args.row, MultiBufferRow(1));
19211    assert!(!render_args.folded);
19212    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
19213
19214    cx.update_window(*editor, |_, window, cx| {
19215        (render_args.callback)(true, window, cx)
19216    })
19217    .unwrap();
19218    let snapshot = editor
19219        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19220        .unwrap();
19221    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
19222
19223    cx.update_window(*editor, |_, window, cx| {
19224        (render_args.callback)(false, window, cx)
19225    })
19226    .unwrap();
19227    let snapshot = editor
19228        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19229        .unwrap();
19230    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
19231}
19232
19233#[gpui::test]
19234async fn test_input_text(cx: &mut TestAppContext) {
19235    init_test(cx, |_| {});
19236    let mut cx = EditorTestContext::new(cx).await;
19237
19238    cx.set_state(
19239        &r#"ˇone
19240        two
19241
19242        three
19243        fourˇ
19244        five
19245
19246        siˇx"#
19247            .unindent(),
19248    );
19249
19250    cx.dispatch_action(HandleInput(String::new()));
19251    cx.assert_editor_state(
19252        &r#"ˇone
19253        two
19254
19255        three
19256        fourˇ
19257        five
19258
19259        siˇx"#
19260            .unindent(),
19261    );
19262
19263    cx.dispatch_action(HandleInput("AAAA".to_string()));
19264    cx.assert_editor_state(
19265        &r#"AAAAˇone
19266        two
19267
19268        three
19269        fourAAAAˇ
19270        five
19271
19272        siAAAAˇx"#
19273            .unindent(),
19274    );
19275}
19276
19277#[gpui::test]
19278async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
19279    init_test(cx, |_| {});
19280
19281    let mut cx = EditorTestContext::new(cx).await;
19282    cx.set_state(
19283        r#"let foo = 1;
19284let foo = 2;
19285let foo = 3;
19286let fooˇ = 4;
19287let foo = 5;
19288let foo = 6;
19289let foo = 7;
19290let foo = 8;
19291let foo = 9;
19292let foo = 10;
19293let foo = 11;
19294let foo = 12;
19295let foo = 13;
19296let foo = 14;
19297let foo = 15;"#,
19298    );
19299
19300    cx.update_editor(|e, window, cx| {
19301        assert_eq!(
19302            e.next_scroll_position,
19303            NextScrollCursorCenterTopBottom::Center,
19304            "Default next scroll direction is center",
19305        );
19306
19307        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19308        assert_eq!(
19309            e.next_scroll_position,
19310            NextScrollCursorCenterTopBottom::Top,
19311            "After center, next scroll direction should be top",
19312        );
19313
19314        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19315        assert_eq!(
19316            e.next_scroll_position,
19317            NextScrollCursorCenterTopBottom::Bottom,
19318            "After top, next scroll direction should be bottom",
19319        );
19320
19321        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19322        assert_eq!(
19323            e.next_scroll_position,
19324            NextScrollCursorCenterTopBottom::Center,
19325            "After bottom, scrolling should start over",
19326        );
19327
19328        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19329        assert_eq!(
19330            e.next_scroll_position,
19331            NextScrollCursorCenterTopBottom::Top,
19332            "Scrolling continues if retriggered fast enough"
19333        );
19334    });
19335
19336    cx.executor()
19337        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
19338    cx.executor().run_until_parked();
19339    cx.update_editor(|e, _, _| {
19340        assert_eq!(
19341            e.next_scroll_position,
19342            NextScrollCursorCenterTopBottom::Center,
19343            "If scrolling is not triggered fast enough, it should reset"
19344        );
19345    });
19346}
19347
19348#[gpui::test]
19349async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
19350    init_test(cx, |_| {});
19351    let mut cx = EditorLspTestContext::new_rust(
19352        lsp::ServerCapabilities {
19353            definition_provider: Some(lsp::OneOf::Left(true)),
19354            references_provider: Some(lsp::OneOf::Left(true)),
19355            ..lsp::ServerCapabilities::default()
19356        },
19357        cx,
19358    )
19359    .await;
19360
19361    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
19362        let go_to_definition = cx
19363            .lsp
19364            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
19365                move |params, _| async move {
19366                    if empty_go_to_definition {
19367                        Ok(None)
19368                    } else {
19369                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
19370                            uri: params.text_document_position_params.text_document.uri,
19371                            range: lsp::Range::new(
19372                                lsp::Position::new(4, 3),
19373                                lsp::Position::new(4, 6),
19374                            ),
19375                        })))
19376                    }
19377                },
19378            );
19379        let references = cx
19380            .lsp
19381            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
19382                Ok(Some(vec![lsp::Location {
19383                    uri: params.text_document_position.text_document.uri,
19384                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
19385                }]))
19386            });
19387        (go_to_definition, references)
19388    };
19389
19390    cx.set_state(
19391        &r#"fn one() {
19392            let mut a = ˇtwo();
19393        }
19394
19395        fn two() {}"#
19396            .unindent(),
19397    );
19398    set_up_lsp_handlers(false, &mut cx);
19399    let navigated = cx
19400        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19401        .await
19402        .expect("Failed to navigate to definition");
19403    assert_eq!(
19404        navigated,
19405        Navigated::Yes,
19406        "Should have navigated to definition from the GetDefinition response"
19407    );
19408    cx.assert_editor_state(
19409        &r#"fn one() {
19410            let mut a = two();
19411        }
19412
19413        fn «twoˇ»() {}"#
19414            .unindent(),
19415    );
19416
19417    let editors = cx.update_workspace(|workspace, _, cx| {
19418        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19419    });
19420    cx.update_editor(|_, _, test_editor_cx| {
19421        assert_eq!(
19422            editors.len(),
19423            1,
19424            "Initially, only one, test, editor should be open in the workspace"
19425        );
19426        assert_eq!(
19427            test_editor_cx.entity(),
19428            editors.last().expect("Asserted len is 1").clone()
19429        );
19430    });
19431
19432    set_up_lsp_handlers(true, &mut cx);
19433    let navigated = cx
19434        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19435        .await
19436        .expect("Failed to navigate to lookup references");
19437    assert_eq!(
19438        navigated,
19439        Navigated::Yes,
19440        "Should have navigated to references as a fallback after empty GoToDefinition response"
19441    );
19442    // We should not change the selections in the existing file,
19443    // if opening another milti buffer with the references
19444    cx.assert_editor_state(
19445        &r#"fn one() {
19446            let mut a = two();
19447        }
19448
19449        fn «twoˇ»() {}"#
19450            .unindent(),
19451    );
19452    let editors = cx.update_workspace(|workspace, _, cx| {
19453        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19454    });
19455    cx.update_editor(|_, _, test_editor_cx| {
19456        assert_eq!(
19457            editors.len(),
19458            2,
19459            "After falling back to references search, we open a new editor with the results"
19460        );
19461        let references_fallback_text = editors
19462            .into_iter()
19463            .find(|new_editor| *new_editor != test_editor_cx.entity())
19464            .expect("Should have one non-test editor now")
19465            .read(test_editor_cx)
19466            .text(test_editor_cx);
19467        assert_eq!(
19468            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
19469            "Should use the range from the references response and not the GoToDefinition one"
19470        );
19471    });
19472}
19473
19474#[gpui::test]
19475async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
19476    init_test(cx, |_| {});
19477    cx.update(|cx| {
19478        let mut editor_settings = EditorSettings::get_global(cx).clone();
19479        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
19480        EditorSettings::override_global(editor_settings, cx);
19481    });
19482    let mut cx = EditorLspTestContext::new_rust(
19483        lsp::ServerCapabilities {
19484            definition_provider: Some(lsp::OneOf::Left(true)),
19485            references_provider: Some(lsp::OneOf::Left(true)),
19486            ..lsp::ServerCapabilities::default()
19487        },
19488        cx,
19489    )
19490    .await;
19491    let original_state = r#"fn one() {
19492        let mut a = ˇtwo();
19493    }
19494
19495    fn two() {}"#
19496        .unindent();
19497    cx.set_state(&original_state);
19498
19499    let mut go_to_definition = cx
19500        .lsp
19501        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
19502            move |_, _| async move { Ok(None) },
19503        );
19504    let _references = cx
19505        .lsp
19506        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
19507            panic!("Should not call for references with no go to definition fallback")
19508        });
19509
19510    let navigated = cx
19511        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19512        .await
19513        .expect("Failed to navigate to lookup references");
19514    go_to_definition
19515        .next()
19516        .await
19517        .expect("Should have called the go_to_definition handler");
19518
19519    assert_eq!(
19520        navigated,
19521        Navigated::No,
19522        "Should have navigated to references as a fallback after empty GoToDefinition response"
19523    );
19524    cx.assert_editor_state(&original_state);
19525    let editors = cx.update_workspace(|workspace, _, cx| {
19526        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19527    });
19528    cx.update_editor(|_, _, _| {
19529        assert_eq!(
19530            editors.len(),
19531            1,
19532            "After unsuccessful fallback, no other editor should have been opened"
19533        );
19534    });
19535}
19536
19537#[gpui::test]
19538async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
19539    init_test(cx, |_| {});
19540
19541    let language = Arc::new(Language::new(
19542        LanguageConfig::default(),
19543        Some(tree_sitter_rust::LANGUAGE.into()),
19544    ));
19545
19546    let text = r#"
19547        #[cfg(test)]
19548        mod tests() {
19549            #[test]
19550            fn runnable_1() {
19551                let a = 1;
19552            }
19553
19554            #[test]
19555            fn runnable_2() {
19556                let a = 1;
19557                let b = 2;
19558            }
19559        }
19560    "#
19561    .unindent();
19562
19563    let fs = FakeFs::new(cx.executor());
19564    fs.insert_file("/file.rs", Default::default()).await;
19565
19566    let project = Project::test(fs, ["/a".as_ref()], cx).await;
19567    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19568    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19569    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
19570    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
19571
19572    let editor = cx.new_window_entity(|window, cx| {
19573        Editor::new(
19574            EditorMode::full(),
19575            multi_buffer,
19576            Some(project.clone()),
19577            window,
19578            cx,
19579        )
19580    });
19581
19582    editor.update_in(cx, |editor, window, cx| {
19583        let snapshot = editor.buffer().read(cx).snapshot(cx);
19584        editor.tasks.insert(
19585            (buffer.read(cx).remote_id(), 3),
19586            RunnableTasks {
19587                templates: vec![],
19588                offset: snapshot.anchor_before(43),
19589                column: 0,
19590                extra_variables: HashMap::default(),
19591                context_range: BufferOffset(43)..BufferOffset(85),
19592            },
19593        );
19594        editor.tasks.insert(
19595            (buffer.read(cx).remote_id(), 8),
19596            RunnableTasks {
19597                templates: vec![],
19598                offset: snapshot.anchor_before(86),
19599                column: 0,
19600                extra_variables: HashMap::default(),
19601                context_range: BufferOffset(86)..BufferOffset(191),
19602            },
19603        );
19604
19605        // Test finding task when cursor is inside function body
19606        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19607            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
19608        });
19609        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
19610        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
19611
19612        // Test finding task when cursor is on function name
19613        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19614            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
19615        });
19616        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
19617        assert_eq!(row, 8, "Should find task when cursor is on function name");
19618    });
19619}
19620
19621#[gpui::test]
19622async fn test_folding_buffers(cx: &mut TestAppContext) {
19623    init_test(cx, |_| {});
19624
19625    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
19626    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
19627    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
19628
19629    let fs = FakeFs::new(cx.executor());
19630    fs.insert_tree(
19631        path!("/a"),
19632        json!({
19633            "first.rs": sample_text_1,
19634            "second.rs": sample_text_2,
19635            "third.rs": sample_text_3,
19636        }),
19637    )
19638    .await;
19639    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19640    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19641    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19642    let worktree = project.update(cx, |project, cx| {
19643        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19644        assert_eq!(worktrees.len(), 1);
19645        worktrees.pop().unwrap()
19646    });
19647    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19648
19649    let buffer_1 = project
19650        .update(cx, |project, cx| {
19651            project.open_buffer((worktree_id, "first.rs"), cx)
19652        })
19653        .await
19654        .unwrap();
19655    let buffer_2 = project
19656        .update(cx, |project, cx| {
19657            project.open_buffer((worktree_id, "second.rs"), cx)
19658        })
19659        .await
19660        .unwrap();
19661    let buffer_3 = project
19662        .update(cx, |project, cx| {
19663            project.open_buffer((worktree_id, "third.rs"), cx)
19664        })
19665        .await
19666        .unwrap();
19667
19668    let multi_buffer = cx.new(|cx| {
19669        let mut multi_buffer = MultiBuffer::new(ReadWrite);
19670        multi_buffer.push_excerpts(
19671            buffer_1.clone(),
19672            [
19673                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19674                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19675                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19676            ],
19677            cx,
19678        );
19679        multi_buffer.push_excerpts(
19680            buffer_2.clone(),
19681            [
19682                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19683                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19684                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19685            ],
19686            cx,
19687        );
19688        multi_buffer.push_excerpts(
19689            buffer_3.clone(),
19690            [
19691                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19692                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19693                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19694            ],
19695            cx,
19696        );
19697        multi_buffer
19698    });
19699    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19700        Editor::new(
19701            EditorMode::full(),
19702            multi_buffer.clone(),
19703            Some(project.clone()),
19704            window,
19705            cx,
19706        )
19707    });
19708
19709    assert_eq!(
19710        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19711        "\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",
19712    );
19713
19714    multi_buffer_editor.update(cx, |editor, cx| {
19715        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
19716    });
19717    assert_eq!(
19718        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19719        "\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",
19720        "After folding the first buffer, its text should not be displayed"
19721    );
19722
19723    multi_buffer_editor.update(cx, |editor, cx| {
19724        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
19725    });
19726    assert_eq!(
19727        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19728        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
19729        "After folding the second buffer, its text should not be displayed"
19730    );
19731
19732    multi_buffer_editor.update(cx, |editor, cx| {
19733        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
19734    });
19735    assert_eq!(
19736        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19737        "\n\n\n\n\n",
19738        "After folding the third buffer, its text should not be displayed"
19739    );
19740
19741    // Emulate selection inside the fold logic, that should work
19742    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19743        editor
19744            .snapshot(window, cx)
19745            .next_line_boundary(Point::new(0, 4));
19746    });
19747
19748    multi_buffer_editor.update(cx, |editor, cx| {
19749        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
19750    });
19751    assert_eq!(
19752        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19753        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
19754        "After unfolding the second buffer, its text should be displayed"
19755    );
19756
19757    // Typing inside of buffer 1 causes that buffer to be unfolded.
19758    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19759        assert_eq!(
19760            multi_buffer
19761                .read(cx)
19762                .snapshot(cx)
19763                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
19764                .collect::<String>(),
19765            "bbbb"
19766        );
19767        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
19768            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
19769        });
19770        editor.handle_input("B", window, cx);
19771    });
19772
19773    assert_eq!(
19774        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19775        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
19776        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
19777    );
19778
19779    multi_buffer_editor.update(cx, |editor, cx| {
19780        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
19781    });
19782    assert_eq!(
19783        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19784        "\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",
19785        "After unfolding the all buffers, all original text should be displayed"
19786    );
19787}
19788
19789#[gpui::test]
19790async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
19791    init_test(cx, |_| {});
19792
19793    let sample_text_1 = "1111\n2222\n3333".to_string();
19794    let sample_text_2 = "4444\n5555\n6666".to_string();
19795    let sample_text_3 = "7777\n8888\n9999".to_string();
19796
19797    let fs = FakeFs::new(cx.executor());
19798    fs.insert_tree(
19799        path!("/a"),
19800        json!({
19801            "first.rs": sample_text_1,
19802            "second.rs": sample_text_2,
19803            "third.rs": sample_text_3,
19804        }),
19805    )
19806    .await;
19807    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19808    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19809    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19810    let worktree = project.update(cx, |project, cx| {
19811        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19812        assert_eq!(worktrees.len(), 1);
19813        worktrees.pop().unwrap()
19814    });
19815    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19816
19817    let buffer_1 = project
19818        .update(cx, |project, cx| {
19819            project.open_buffer((worktree_id, "first.rs"), cx)
19820        })
19821        .await
19822        .unwrap();
19823    let buffer_2 = project
19824        .update(cx, |project, cx| {
19825            project.open_buffer((worktree_id, "second.rs"), cx)
19826        })
19827        .await
19828        .unwrap();
19829    let buffer_3 = project
19830        .update(cx, |project, cx| {
19831            project.open_buffer((worktree_id, "third.rs"), cx)
19832        })
19833        .await
19834        .unwrap();
19835
19836    let multi_buffer = cx.new(|cx| {
19837        let mut multi_buffer = MultiBuffer::new(ReadWrite);
19838        multi_buffer.push_excerpts(
19839            buffer_1.clone(),
19840            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19841            cx,
19842        );
19843        multi_buffer.push_excerpts(
19844            buffer_2.clone(),
19845            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19846            cx,
19847        );
19848        multi_buffer.push_excerpts(
19849            buffer_3.clone(),
19850            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19851            cx,
19852        );
19853        multi_buffer
19854    });
19855
19856    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19857        Editor::new(
19858            EditorMode::full(),
19859            multi_buffer,
19860            Some(project.clone()),
19861            window,
19862            cx,
19863        )
19864    });
19865
19866    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
19867    assert_eq!(
19868        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19869        full_text,
19870    );
19871
19872    multi_buffer_editor.update(cx, |editor, cx| {
19873        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
19874    });
19875    assert_eq!(
19876        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19877        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
19878        "After folding the first buffer, its text should not be displayed"
19879    );
19880
19881    multi_buffer_editor.update(cx, |editor, cx| {
19882        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
19883    });
19884
19885    assert_eq!(
19886        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19887        "\n\n\n\n\n\n7777\n8888\n9999",
19888        "After folding the second buffer, its text should not be displayed"
19889    );
19890
19891    multi_buffer_editor.update(cx, |editor, cx| {
19892        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
19893    });
19894    assert_eq!(
19895        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19896        "\n\n\n\n\n",
19897        "After folding the third buffer, its text should not be displayed"
19898    );
19899
19900    multi_buffer_editor.update(cx, |editor, cx| {
19901        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
19902    });
19903    assert_eq!(
19904        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19905        "\n\n\n\n4444\n5555\n6666\n\n",
19906        "After unfolding the second buffer, its text should be displayed"
19907    );
19908
19909    multi_buffer_editor.update(cx, |editor, cx| {
19910        editor.unfold_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\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
19915        "After unfolding the first buffer, its text should be displayed"
19916    );
19917
19918    multi_buffer_editor.update(cx, |editor, cx| {
19919        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
19920    });
19921    assert_eq!(
19922        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19923        full_text,
19924        "After unfolding all buffers, all original text should be displayed"
19925    );
19926}
19927
19928#[gpui::test]
19929async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
19930    init_test(cx, |_| {});
19931
19932    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
19933
19934    let fs = FakeFs::new(cx.executor());
19935    fs.insert_tree(
19936        path!("/a"),
19937        json!({
19938            "main.rs": sample_text,
19939        }),
19940    )
19941    .await;
19942    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19943    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19944    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19945    let worktree = project.update(cx, |project, cx| {
19946        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19947        assert_eq!(worktrees.len(), 1);
19948        worktrees.pop().unwrap()
19949    });
19950    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19951
19952    let buffer_1 = project
19953        .update(cx, |project, cx| {
19954            project.open_buffer((worktree_id, "main.rs"), cx)
19955        })
19956        .await
19957        .unwrap();
19958
19959    let multi_buffer = cx.new(|cx| {
19960        let mut multi_buffer = MultiBuffer::new(ReadWrite);
19961        multi_buffer.push_excerpts(
19962            buffer_1.clone(),
19963            [ExcerptRange::new(
19964                Point::new(0, 0)
19965                    ..Point::new(
19966                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
19967                        0,
19968                    ),
19969            )],
19970            cx,
19971        );
19972        multi_buffer
19973    });
19974    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19975        Editor::new(
19976            EditorMode::full(),
19977            multi_buffer,
19978            Some(project.clone()),
19979            window,
19980            cx,
19981        )
19982    });
19983
19984    let selection_range = Point::new(1, 0)..Point::new(2, 0);
19985    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19986        enum TestHighlight {}
19987        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
19988        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
19989        editor.highlight_text::<TestHighlight>(
19990            vec![highlight_range.clone()],
19991            HighlightStyle::color(Hsla::green()),
19992            cx,
19993        );
19994        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19995            s.select_ranges(Some(highlight_range))
19996        });
19997    });
19998
19999    let full_text = format!("\n\n{sample_text}");
20000    assert_eq!(
20001        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20002        full_text,
20003    );
20004}
20005
20006#[gpui::test]
20007async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
20008    init_test(cx, |_| {});
20009    cx.update(|cx| {
20010        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
20011            "keymaps/default-linux.json",
20012            cx,
20013        )
20014        .unwrap();
20015        cx.bind_keys(default_key_bindings);
20016    });
20017
20018    let (editor, cx) = cx.add_window_view(|window, cx| {
20019        let multi_buffer = MultiBuffer::build_multi(
20020            [
20021                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
20022                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
20023                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
20024                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
20025            ],
20026            cx,
20027        );
20028        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
20029
20030        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
20031        // fold all but the second buffer, so that we test navigating between two
20032        // adjacent folded buffers, as well as folded buffers at the start and
20033        // end the multibuffer
20034        editor.fold_buffer(buffer_ids[0], cx);
20035        editor.fold_buffer(buffer_ids[2], cx);
20036        editor.fold_buffer(buffer_ids[3], cx);
20037
20038        editor
20039    });
20040    cx.simulate_resize(size(px(1000.), px(1000.)));
20041
20042    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
20043    cx.assert_excerpts_with_selections(indoc! {"
20044        [EXCERPT]
20045        ˇ[FOLDED]
20046        [EXCERPT]
20047        a1
20048        b1
20049        [EXCERPT]
20050        [FOLDED]
20051        [EXCERPT]
20052        [FOLDED]
20053        "
20054    });
20055    cx.simulate_keystroke("down");
20056    cx.assert_excerpts_with_selections(indoc! {"
20057        [EXCERPT]
20058        [FOLDED]
20059        [EXCERPT]
20060        ˇa1
20061        b1
20062        [EXCERPT]
20063        [FOLDED]
20064        [EXCERPT]
20065        [FOLDED]
20066        "
20067    });
20068    cx.simulate_keystroke("down");
20069    cx.assert_excerpts_with_selections(indoc! {"
20070        [EXCERPT]
20071        [FOLDED]
20072        [EXCERPT]
20073        a1
20074        ˇb1
20075        [EXCERPT]
20076        [FOLDED]
20077        [EXCERPT]
20078        [FOLDED]
20079        "
20080    });
20081    cx.simulate_keystroke("down");
20082    cx.assert_excerpts_with_selections(indoc! {"
20083        [EXCERPT]
20084        [FOLDED]
20085        [EXCERPT]
20086        a1
20087        b1
20088        ˇ[EXCERPT]
20089        [FOLDED]
20090        [EXCERPT]
20091        [FOLDED]
20092        "
20093    });
20094    cx.simulate_keystroke("down");
20095    cx.assert_excerpts_with_selections(indoc! {"
20096        [EXCERPT]
20097        [FOLDED]
20098        [EXCERPT]
20099        a1
20100        b1
20101        [EXCERPT]
20102        ˇ[FOLDED]
20103        [EXCERPT]
20104        [FOLDED]
20105        "
20106    });
20107    for _ in 0..5 {
20108        cx.simulate_keystroke("down");
20109        cx.assert_excerpts_with_selections(indoc! {"
20110            [EXCERPT]
20111            [FOLDED]
20112            [EXCERPT]
20113            a1
20114            b1
20115            [EXCERPT]
20116            [FOLDED]
20117            [EXCERPT]
20118            ˇ[FOLDED]
20119            "
20120        });
20121    }
20122
20123    cx.simulate_keystroke("up");
20124    cx.assert_excerpts_with_selections(indoc! {"
20125        [EXCERPT]
20126        [FOLDED]
20127        [EXCERPT]
20128        a1
20129        b1
20130        [EXCERPT]
20131        ˇ[FOLDED]
20132        [EXCERPT]
20133        [FOLDED]
20134        "
20135    });
20136    cx.simulate_keystroke("up");
20137    cx.assert_excerpts_with_selections(indoc! {"
20138        [EXCERPT]
20139        [FOLDED]
20140        [EXCERPT]
20141        a1
20142        b1
20143        ˇ[EXCERPT]
20144        [FOLDED]
20145        [EXCERPT]
20146        [FOLDED]
20147        "
20148    });
20149    cx.simulate_keystroke("up");
20150    cx.assert_excerpts_with_selections(indoc! {"
20151        [EXCERPT]
20152        [FOLDED]
20153        [EXCERPT]
20154        a1
20155        ˇb1
20156        [EXCERPT]
20157        [FOLDED]
20158        [EXCERPT]
20159        [FOLDED]
20160        "
20161    });
20162    cx.simulate_keystroke("up");
20163    cx.assert_excerpts_with_selections(indoc! {"
20164        [EXCERPT]
20165        [FOLDED]
20166        [EXCERPT]
20167        ˇa1
20168        b1
20169        [EXCERPT]
20170        [FOLDED]
20171        [EXCERPT]
20172        [FOLDED]
20173        "
20174    });
20175    for _ in 0..5 {
20176        cx.simulate_keystroke("up");
20177        cx.assert_excerpts_with_selections(indoc! {"
20178            [EXCERPT]
20179            ˇ[FOLDED]
20180            [EXCERPT]
20181            a1
20182            b1
20183            [EXCERPT]
20184            [FOLDED]
20185            [EXCERPT]
20186            [FOLDED]
20187            "
20188        });
20189    }
20190}
20191
20192#[gpui::test]
20193async fn test_inline_completion_text(cx: &mut TestAppContext) {
20194    init_test(cx, |_| {});
20195
20196    // Simple insertion
20197    assert_highlighted_edits(
20198        "Hello, world!",
20199        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
20200        true,
20201        cx,
20202        |highlighted_edits, cx| {
20203            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
20204            assert_eq!(highlighted_edits.highlights.len(), 1);
20205            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
20206            assert_eq!(
20207                highlighted_edits.highlights[0].1.background_color,
20208                Some(cx.theme().status().created_background)
20209            );
20210        },
20211    )
20212    .await;
20213
20214    // Replacement
20215    assert_highlighted_edits(
20216        "This is a test.",
20217        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
20218        false,
20219        cx,
20220        |highlighted_edits, cx| {
20221            assert_eq!(highlighted_edits.text, "That is a test.");
20222            assert_eq!(highlighted_edits.highlights.len(), 1);
20223            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
20224            assert_eq!(
20225                highlighted_edits.highlights[0].1.background_color,
20226                Some(cx.theme().status().created_background)
20227            );
20228        },
20229    )
20230    .await;
20231
20232    // Multiple edits
20233    assert_highlighted_edits(
20234        "Hello, world!",
20235        vec![
20236            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
20237            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
20238        ],
20239        false,
20240        cx,
20241        |highlighted_edits, cx| {
20242            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
20243            assert_eq!(highlighted_edits.highlights.len(), 2);
20244            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
20245            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
20246            assert_eq!(
20247                highlighted_edits.highlights[0].1.background_color,
20248                Some(cx.theme().status().created_background)
20249            );
20250            assert_eq!(
20251                highlighted_edits.highlights[1].1.background_color,
20252                Some(cx.theme().status().created_background)
20253            );
20254        },
20255    )
20256    .await;
20257
20258    // Multiple lines with edits
20259    assert_highlighted_edits(
20260        "First line\nSecond line\nThird line\nFourth line",
20261        vec![
20262            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
20263            (
20264                Point::new(2, 0)..Point::new(2, 10),
20265                "New third line".to_string(),
20266            ),
20267            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
20268        ],
20269        false,
20270        cx,
20271        |highlighted_edits, cx| {
20272            assert_eq!(
20273                highlighted_edits.text,
20274                "Second modified\nNew third line\nFourth updated line"
20275            );
20276            assert_eq!(highlighted_edits.highlights.len(), 3);
20277            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
20278            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
20279            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
20280            for highlight in &highlighted_edits.highlights {
20281                assert_eq!(
20282                    highlight.1.background_color,
20283                    Some(cx.theme().status().created_background)
20284                );
20285            }
20286        },
20287    )
20288    .await;
20289}
20290
20291#[gpui::test]
20292async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
20293    init_test(cx, |_| {});
20294
20295    // Deletion
20296    assert_highlighted_edits(
20297        "Hello, world!",
20298        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
20299        true,
20300        cx,
20301        |highlighted_edits, cx| {
20302            assert_eq!(highlighted_edits.text, "Hello, world!");
20303            assert_eq!(highlighted_edits.highlights.len(), 1);
20304            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
20305            assert_eq!(
20306                highlighted_edits.highlights[0].1.background_color,
20307                Some(cx.theme().status().deleted_background)
20308            );
20309        },
20310    )
20311    .await;
20312
20313    // Insertion
20314    assert_highlighted_edits(
20315        "Hello, world!",
20316        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
20317        true,
20318        cx,
20319        |highlighted_edits, cx| {
20320            assert_eq!(highlighted_edits.highlights.len(), 1);
20321            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
20322            assert_eq!(
20323                highlighted_edits.highlights[0].1.background_color,
20324                Some(cx.theme().status().created_background)
20325            );
20326        },
20327    )
20328    .await;
20329}
20330
20331async fn assert_highlighted_edits(
20332    text: &str,
20333    edits: Vec<(Range<Point>, String)>,
20334    include_deletions: bool,
20335    cx: &mut TestAppContext,
20336    assertion_fn: impl Fn(HighlightedText, &App),
20337) {
20338    let window = cx.add_window(|window, cx| {
20339        let buffer = MultiBuffer::build_simple(text, cx);
20340        Editor::new(EditorMode::full(), buffer, None, window, cx)
20341    });
20342    let cx = &mut VisualTestContext::from_window(*window, cx);
20343
20344    let (buffer, snapshot) = window
20345        .update(cx, |editor, _window, cx| {
20346            (
20347                editor.buffer().clone(),
20348                editor.buffer().read(cx).snapshot(cx),
20349            )
20350        })
20351        .unwrap();
20352
20353    let edits = edits
20354        .into_iter()
20355        .map(|(range, edit)| {
20356            (
20357                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
20358                edit,
20359            )
20360        })
20361        .collect::<Vec<_>>();
20362
20363    let text_anchor_edits = edits
20364        .clone()
20365        .into_iter()
20366        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
20367        .collect::<Vec<_>>();
20368
20369    let edit_preview = window
20370        .update(cx, |_, _window, cx| {
20371            buffer
20372                .read(cx)
20373                .as_singleton()
20374                .unwrap()
20375                .read(cx)
20376                .preview_edits(text_anchor_edits.into(), cx)
20377        })
20378        .unwrap()
20379        .await;
20380
20381    cx.update(|_window, cx| {
20382        let highlighted_edits = inline_completion_edit_text(
20383            &snapshot.as_singleton().unwrap().2,
20384            &edits,
20385            &edit_preview,
20386            include_deletions,
20387            cx,
20388        );
20389        assertion_fn(highlighted_edits, cx)
20390    });
20391}
20392
20393#[track_caller]
20394fn assert_breakpoint(
20395    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
20396    path: &Arc<Path>,
20397    expected: Vec<(u32, Breakpoint)>,
20398) {
20399    if expected.len() == 0usize {
20400        assert!(!breakpoints.contains_key(path), "{}", path.display());
20401    } else {
20402        let mut breakpoint = breakpoints
20403            .get(path)
20404            .unwrap()
20405            .into_iter()
20406            .map(|breakpoint| {
20407                (
20408                    breakpoint.row,
20409                    Breakpoint {
20410                        message: breakpoint.message.clone(),
20411                        state: breakpoint.state,
20412                        condition: breakpoint.condition.clone(),
20413                        hit_condition: breakpoint.hit_condition.clone(),
20414                    },
20415                )
20416            })
20417            .collect::<Vec<_>>();
20418
20419        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
20420
20421        assert_eq!(expected, breakpoint);
20422    }
20423}
20424
20425fn add_log_breakpoint_at_cursor(
20426    editor: &mut Editor,
20427    log_message: &str,
20428    window: &mut Window,
20429    cx: &mut Context<Editor>,
20430) {
20431    let (anchor, bp) = editor
20432        .breakpoints_at_cursors(window, cx)
20433        .first()
20434        .and_then(|(anchor, bp)| {
20435            if let Some(bp) = bp {
20436                Some((*anchor, bp.clone()))
20437            } else {
20438                None
20439            }
20440        })
20441        .unwrap_or_else(|| {
20442            let cursor_position: Point = editor.selections.newest(cx).head();
20443
20444            let breakpoint_position = editor
20445                .snapshot(window, cx)
20446                .display_snapshot
20447                .buffer_snapshot
20448                .anchor_before(Point::new(cursor_position.row, 0));
20449
20450            (breakpoint_position, Breakpoint::new_log(&log_message))
20451        });
20452
20453    editor.edit_breakpoint_at_anchor(
20454        anchor,
20455        bp,
20456        BreakpointEditAction::EditLogMessage(log_message.into()),
20457        cx,
20458    );
20459}
20460
20461#[gpui::test]
20462async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
20463    init_test(cx, |_| {});
20464
20465    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20466    let fs = FakeFs::new(cx.executor());
20467    fs.insert_tree(
20468        path!("/a"),
20469        json!({
20470            "main.rs": sample_text,
20471        }),
20472    )
20473    .await;
20474    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20475    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20476    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20477
20478    let fs = FakeFs::new(cx.executor());
20479    fs.insert_tree(
20480        path!("/a"),
20481        json!({
20482            "main.rs": sample_text,
20483        }),
20484    )
20485    .await;
20486    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20487    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20488    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20489    let worktree_id = workspace
20490        .update(cx, |workspace, _window, cx| {
20491            workspace.project().update(cx, |project, cx| {
20492                project.worktrees(cx).next().unwrap().read(cx).id()
20493            })
20494        })
20495        .unwrap();
20496
20497    let buffer = project
20498        .update(cx, |project, cx| {
20499            project.open_buffer((worktree_id, "main.rs"), cx)
20500        })
20501        .await
20502        .unwrap();
20503
20504    let (editor, cx) = cx.add_window_view(|window, cx| {
20505        Editor::new(
20506            EditorMode::full(),
20507            MultiBuffer::build_from_buffer(buffer, cx),
20508            Some(project.clone()),
20509            window,
20510            cx,
20511        )
20512    });
20513
20514    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20515    let abs_path = project.read_with(cx, |project, cx| {
20516        project
20517            .absolute_path(&project_path, cx)
20518            .map(|path_buf| Arc::from(path_buf.to_owned()))
20519            .unwrap()
20520    });
20521
20522    // assert we can add breakpoint on the first line
20523    editor.update_in(cx, |editor, window, cx| {
20524        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20525        editor.move_to_end(&MoveToEnd, window, cx);
20526        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20527    });
20528
20529    let breakpoints = editor.update(cx, |editor, cx| {
20530        editor
20531            .breakpoint_store()
20532            .as_ref()
20533            .unwrap()
20534            .read(cx)
20535            .all_source_breakpoints(cx)
20536            .clone()
20537    });
20538
20539    assert_eq!(1, breakpoints.len());
20540    assert_breakpoint(
20541        &breakpoints,
20542        &abs_path,
20543        vec![
20544            (0, Breakpoint::new_standard()),
20545            (3, Breakpoint::new_standard()),
20546        ],
20547    );
20548
20549    editor.update_in(cx, |editor, window, cx| {
20550        editor.move_to_beginning(&MoveToBeginning, window, cx);
20551        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20552    });
20553
20554    let breakpoints = editor.update(cx, |editor, cx| {
20555        editor
20556            .breakpoint_store()
20557            .as_ref()
20558            .unwrap()
20559            .read(cx)
20560            .all_source_breakpoints(cx)
20561            .clone()
20562    });
20563
20564    assert_eq!(1, breakpoints.len());
20565    assert_breakpoint(
20566        &breakpoints,
20567        &abs_path,
20568        vec![(3, Breakpoint::new_standard())],
20569    );
20570
20571    editor.update_in(cx, |editor, window, cx| {
20572        editor.move_to_end(&MoveToEnd, window, cx);
20573        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20574    });
20575
20576    let breakpoints = editor.update(cx, |editor, cx| {
20577        editor
20578            .breakpoint_store()
20579            .as_ref()
20580            .unwrap()
20581            .read(cx)
20582            .all_source_breakpoints(cx)
20583            .clone()
20584    });
20585
20586    assert_eq!(0, breakpoints.len());
20587    assert_breakpoint(&breakpoints, &abs_path, vec![]);
20588}
20589
20590#[gpui::test]
20591async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
20592    init_test(cx, |_| {});
20593
20594    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20595
20596    let fs = FakeFs::new(cx.executor());
20597    fs.insert_tree(
20598        path!("/a"),
20599        json!({
20600            "main.rs": sample_text,
20601        }),
20602    )
20603    .await;
20604    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20605    let (workspace, cx) =
20606        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
20607
20608    let worktree_id = workspace.update(cx, |workspace, cx| {
20609        workspace.project().update(cx, |project, cx| {
20610            project.worktrees(cx).next().unwrap().read(cx).id()
20611        })
20612    });
20613
20614    let buffer = project
20615        .update(cx, |project, cx| {
20616            project.open_buffer((worktree_id, "main.rs"), cx)
20617        })
20618        .await
20619        .unwrap();
20620
20621    let (editor, cx) = cx.add_window_view(|window, cx| {
20622        Editor::new(
20623            EditorMode::full(),
20624            MultiBuffer::build_from_buffer(buffer, cx),
20625            Some(project.clone()),
20626            window,
20627            cx,
20628        )
20629    });
20630
20631    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20632    let abs_path = project.read_with(cx, |project, cx| {
20633        project
20634            .absolute_path(&project_path, cx)
20635            .map(|path_buf| Arc::from(path_buf.to_owned()))
20636            .unwrap()
20637    });
20638
20639    editor.update_in(cx, |editor, window, cx| {
20640        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
20641    });
20642
20643    let breakpoints = editor.update(cx, |editor, cx| {
20644        editor
20645            .breakpoint_store()
20646            .as_ref()
20647            .unwrap()
20648            .read(cx)
20649            .all_source_breakpoints(cx)
20650            .clone()
20651    });
20652
20653    assert_breakpoint(
20654        &breakpoints,
20655        &abs_path,
20656        vec![(0, Breakpoint::new_log("hello world"))],
20657    );
20658
20659    // Removing a log message from a log breakpoint should remove it
20660    editor.update_in(cx, |editor, window, cx| {
20661        add_log_breakpoint_at_cursor(editor, "", window, cx);
20662    });
20663
20664    let breakpoints = editor.update(cx, |editor, cx| {
20665        editor
20666            .breakpoint_store()
20667            .as_ref()
20668            .unwrap()
20669            .read(cx)
20670            .all_source_breakpoints(cx)
20671            .clone()
20672    });
20673
20674    assert_breakpoint(&breakpoints, &abs_path, vec![]);
20675
20676    editor.update_in(cx, |editor, window, cx| {
20677        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20678        editor.move_to_end(&MoveToEnd, window, cx);
20679        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20680        // Not adding a log message to a standard breakpoint shouldn't remove it
20681        add_log_breakpoint_at_cursor(editor, "", window, cx);
20682    });
20683
20684    let breakpoints = editor.update(cx, |editor, cx| {
20685        editor
20686            .breakpoint_store()
20687            .as_ref()
20688            .unwrap()
20689            .read(cx)
20690            .all_source_breakpoints(cx)
20691            .clone()
20692    });
20693
20694    assert_breakpoint(
20695        &breakpoints,
20696        &abs_path,
20697        vec![
20698            (0, Breakpoint::new_standard()),
20699            (3, Breakpoint::new_standard()),
20700        ],
20701    );
20702
20703    editor.update_in(cx, |editor, window, cx| {
20704        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
20705    });
20706
20707    let breakpoints = editor.update(cx, |editor, cx| {
20708        editor
20709            .breakpoint_store()
20710            .as_ref()
20711            .unwrap()
20712            .read(cx)
20713            .all_source_breakpoints(cx)
20714            .clone()
20715    });
20716
20717    assert_breakpoint(
20718        &breakpoints,
20719        &abs_path,
20720        vec![
20721            (0, Breakpoint::new_standard()),
20722            (3, Breakpoint::new_log("hello world")),
20723        ],
20724    );
20725
20726    editor.update_in(cx, |editor, window, cx| {
20727        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
20728    });
20729
20730    let breakpoints = editor.update(cx, |editor, cx| {
20731        editor
20732            .breakpoint_store()
20733            .as_ref()
20734            .unwrap()
20735            .read(cx)
20736            .all_source_breakpoints(cx)
20737            .clone()
20738    });
20739
20740    assert_breakpoint(
20741        &breakpoints,
20742        &abs_path,
20743        vec![
20744            (0, Breakpoint::new_standard()),
20745            (3, Breakpoint::new_log("hello Earth!!")),
20746        ],
20747    );
20748}
20749
20750/// This also tests that Editor::breakpoint_at_cursor_head is working properly
20751/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
20752/// or when breakpoints were placed out of order. This tests for a regression too
20753#[gpui::test]
20754async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
20755    init_test(cx, |_| {});
20756
20757    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20758    let fs = FakeFs::new(cx.executor());
20759    fs.insert_tree(
20760        path!("/a"),
20761        json!({
20762            "main.rs": sample_text,
20763        }),
20764    )
20765    .await;
20766    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20767    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20768    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20769
20770    let fs = FakeFs::new(cx.executor());
20771    fs.insert_tree(
20772        path!("/a"),
20773        json!({
20774            "main.rs": sample_text,
20775        }),
20776    )
20777    .await;
20778    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20779    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20780    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20781    let worktree_id = workspace
20782        .update(cx, |workspace, _window, cx| {
20783            workspace.project().update(cx, |project, cx| {
20784                project.worktrees(cx).next().unwrap().read(cx).id()
20785            })
20786        })
20787        .unwrap();
20788
20789    let buffer = project
20790        .update(cx, |project, cx| {
20791            project.open_buffer((worktree_id, "main.rs"), cx)
20792        })
20793        .await
20794        .unwrap();
20795
20796    let (editor, cx) = cx.add_window_view(|window, cx| {
20797        Editor::new(
20798            EditorMode::full(),
20799            MultiBuffer::build_from_buffer(buffer, cx),
20800            Some(project.clone()),
20801            window,
20802            cx,
20803        )
20804    });
20805
20806    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20807    let abs_path = project.read_with(cx, |project, cx| {
20808        project
20809            .absolute_path(&project_path, cx)
20810            .map(|path_buf| Arc::from(path_buf.to_owned()))
20811            .unwrap()
20812    });
20813
20814    // assert we can add breakpoint on the first line
20815    editor.update_in(cx, |editor, window, cx| {
20816        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20817        editor.move_to_end(&MoveToEnd, window, cx);
20818        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20819        editor.move_up(&MoveUp, window, cx);
20820        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20821    });
20822
20823    let breakpoints = editor.update(cx, |editor, cx| {
20824        editor
20825            .breakpoint_store()
20826            .as_ref()
20827            .unwrap()
20828            .read(cx)
20829            .all_source_breakpoints(cx)
20830            .clone()
20831    });
20832
20833    assert_eq!(1, breakpoints.len());
20834    assert_breakpoint(
20835        &breakpoints,
20836        &abs_path,
20837        vec![
20838            (0, Breakpoint::new_standard()),
20839            (2, Breakpoint::new_standard()),
20840            (3, Breakpoint::new_standard()),
20841        ],
20842    );
20843
20844    editor.update_in(cx, |editor, window, cx| {
20845        editor.move_to_beginning(&MoveToBeginning, window, cx);
20846        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20847        editor.move_to_end(&MoveToEnd, window, cx);
20848        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20849        // Disabling a breakpoint that doesn't exist should do nothing
20850        editor.move_up(&MoveUp, window, cx);
20851        editor.move_up(&MoveUp, window, cx);
20852        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20853    });
20854
20855    let breakpoints = editor.update(cx, |editor, cx| {
20856        editor
20857            .breakpoint_store()
20858            .as_ref()
20859            .unwrap()
20860            .read(cx)
20861            .all_source_breakpoints(cx)
20862            .clone()
20863    });
20864
20865    let disable_breakpoint = {
20866        let mut bp = Breakpoint::new_standard();
20867        bp.state = BreakpointState::Disabled;
20868        bp
20869    };
20870
20871    assert_eq!(1, breakpoints.len());
20872    assert_breakpoint(
20873        &breakpoints,
20874        &abs_path,
20875        vec![
20876            (0, disable_breakpoint.clone()),
20877            (2, Breakpoint::new_standard()),
20878            (3, disable_breakpoint.clone()),
20879        ],
20880    );
20881
20882    editor.update_in(cx, |editor, window, cx| {
20883        editor.move_to_beginning(&MoveToBeginning, window, cx);
20884        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
20885        editor.move_to_end(&MoveToEnd, window, cx);
20886        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
20887        editor.move_up(&MoveUp, window, cx);
20888        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20889    });
20890
20891    let breakpoints = editor.update(cx, |editor, cx| {
20892        editor
20893            .breakpoint_store()
20894            .as_ref()
20895            .unwrap()
20896            .read(cx)
20897            .all_source_breakpoints(cx)
20898            .clone()
20899    });
20900
20901    assert_eq!(1, breakpoints.len());
20902    assert_breakpoint(
20903        &breakpoints,
20904        &abs_path,
20905        vec![
20906            (0, Breakpoint::new_standard()),
20907            (2, disable_breakpoint),
20908            (3, Breakpoint::new_standard()),
20909        ],
20910    );
20911}
20912
20913#[gpui::test]
20914async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
20915    init_test(cx, |_| {});
20916    let capabilities = lsp::ServerCapabilities {
20917        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
20918            prepare_provider: Some(true),
20919            work_done_progress_options: Default::default(),
20920        })),
20921        ..Default::default()
20922    };
20923    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
20924
20925    cx.set_state(indoc! {"
20926        struct Fˇoo {}
20927    "});
20928
20929    cx.update_editor(|editor, _, cx| {
20930        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
20931        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
20932        editor.highlight_background::<DocumentHighlightRead>(
20933            &[highlight_range],
20934            |theme| theme.colors().editor_document_highlight_read_background,
20935            cx,
20936        );
20937    });
20938
20939    let mut prepare_rename_handler = cx
20940        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
20941            move |_, _, _| async move {
20942                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
20943                    start: lsp::Position {
20944                        line: 0,
20945                        character: 7,
20946                    },
20947                    end: lsp::Position {
20948                        line: 0,
20949                        character: 10,
20950                    },
20951                })))
20952            },
20953        );
20954    let prepare_rename_task = cx
20955        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
20956        .expect("Prepare rename was not started");
20957    prepare_rename_handler.next().await.unwrap();
20958    prepare_rename_task.await.expect("Prepare rename failed");
20959
20960    let mut rename_handler =
20961        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
20962            let edit = lsp::TextEdit {
20963                range: lsp::Range {
20964                    start: lsp::Position {
20965                        line: 0,
20966                        character: 7,
20967                    },
20968                    end: lsp::Position {
20969                        line: 0,
20970                        character: 10,
20971                    },
20972                },
20973                new_text: "FooRenamed".to_string(),
20974            };
20975            Ok(Some(lsp::WorkspaceEdit::new(
20976                // Specify the same edit twice
20977                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
20978            )))
20979        });
20980    let rename_task = cx
20981        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
20982        .expect("Confirm rename was not started");
20983    rename_handler.next().await.unwrap();
20984    rename_task.await.expect("Confirm rename failed");
20985    cx.run_until_parked();
20986
20987    // Despite two edits, only one is actually applied as those are identical
20988    cx.assert_editor_state(indoc! {"
20989        struct FooRenamedˇ {}
20990    "});
20991}
20992
20993#[gpui::test]
20994async fn test_rename_without_prepare(cx: &mut TestAppContext) {
20995    init_test(cx, |_| {});
20996    // These capabilities indicate that the server does not support prepare rename.
20997    let capabilities = lsp::ServerCapabilities {
20998        rename_provider: Some(lsp::OneOf::Left(true)),
20999        ..Default::default()
21000    };
21001    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
21002
21003    cx.set_state(indoc! {"
21004        struct Fˇoo {}
21005    "});
21006
21007    cx.update_editor(|editor, _window, cx| {
21008        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
21009        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
21010        editor.highlight_background::<DocumentHighlightRead>(
21011            &[highlight_range],
21012            |theme| theme.colors().editor_document_highlight_read_background,
21013            cx,
21014        );
21015    });
21016
21017    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
21018        .expect("Prepare rename was not started")
21019        .await
21020        .expect("Prepare rename failed");
21021
21022    let mut rename_handler =
21023        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
21024            let edit = lsp::TextEdit {
21025                range: lsp::Range {
21026                    start: lsp::Position {
21027                        line: 0,
21028                        character: 7,
21029                    },
21030                    end: lsp::Position {
21031                        line: 0,
21032                        character: 10,
21033                    },
21034                },
21035                new_text: "FooRenamed".to_string(),
21036            };
21037            Ok(Some(lsp::WorkspaceEdit::new(
21038                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
21039            )))
21040        });
21041    let rename_task = cx
21042        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
21043        .expect("Confirm rename was not started");
21044    rename_handler.next().await.unwrap();
21045    rename_task.await.expect("Confirm rename failed");
21046    cx.run_until_parked();
21047
21048    // Correct range is renamed, as `surrounding_word` is used to find it.
21049    cx.assert_editor_state(indoc! {"
21050        struct FooRenamedˇ {}
21051    "});
21052}
21053
21054#[gpui::test]
21055async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
21056    init_test(cx, |_| {});
21057    let mut cx = EditorTestContext::new(cx).await;
21058
21059    let language = Arc::new(
21060        Language::new(
21061            LanguageConfig::default(),
21062            Some(tree_sitter_html::LANGUAGE.into()),
21063        )
21064        .with_brackets_query(
21065            r#"
21066            ("<" @open "/>" @close)
21067            ("</" @open ">" @close)
21068            ("<" @open ">" @close)
21069            ("\"" @open "\"" @close)
21070            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
21071        "#,
21072        )
21073        .unwrap(),
21074    );
21075    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
21076
21077    cx.set_state(indoc! {"
21078        <span>ˇ</span>
21079    "});
21080    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21081    cx.assert_editor_state(indoc! {"
21082        <span>
21083        ˇ
21084        </span>
21085    "});
21086
21087    cx.set_state(indoc! {"
21088        <span><span></span>ˇ</span>
21089    "});
21090    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21091    cx.assert_editor_state(indoc! {"
21092        <span><span></span>
21093        ˇ</span>
21094    "});
21095
21096    cx.set_state(indoc! {"
21097        <span>ˇ
21098        </span>
21099    "});
21100    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21101    cx.assert_editor_state(indoc! {"
21102        <span>
21103        ˇ
21104        </span>
21105    "});
21106}
21107
21108#[gpui::test(iterations = 10)]
21109async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
21110    init_test(cx, |_| {});
21111
21112    let fs = FakeFs::new(cx.executor());
21113    fs.insert_tree(
21114        path!("/dir"),
21115        json!({
21116            "a.ts": "a",
21117        }),
21118    )
21119    .await;
21120
21121    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
21122    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21123    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21124
21125    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21126    language_registry.add(Arc::new(Language::new(
21127        LanguageConfig {
21128            name: "TypeScript".into(),
21129            matcher: LanguageMatcher {
21130                path_suffixes: vec!["ts".to_string()],
21131                ..Default::default()
21132            },
21133            ..Default::default()
21134        },
21135        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
21136    )));
21137    let mut fake_language_servers = language_registry.register_fake_lsp(
21138        "TypeScript",
21139        FakeLspAdapter {
21140            capabilities: lsp::ServerCapabilities {
21141                code_lens_provider: Some(lsp::CodeLensOptions {
21142                    resolve_provider: Some(true),
21143                }),
21144                execute_command_provider: Some(lsp::ExecuteCommandOptions {
21145                    commands: vec!["_the/command".to_string()],
21146                    ..lsp::ExecuteCommandOptions::default()
21147                }),
21148                ..lsp::ServerCapabilities::default()
21149            },
21150            ..FakeLspAdapter::default()
21151        },
21152    );
21153
21154    let (buffer, _handle) = project
21155        .update(cx, |p, cx| {
21156            p.open_local_buffer_with_lsp(path!("/dir/a.ts"), cx)
21157        })
21158        .await
21159        .unwrap();
21160    cx.executor().run_until_parked();
21161
21162    let fake_server = fake_language_servers.next().await.unwrap();
21163
21164    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
21165    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
21166    drop(buffer_snapshot);
21167    let actions = cx
21168        .update_window(*workspace, |_, window, cx| {
21169            project.code_actions(&buffer, anchor..anchor, window, cx)
21170        })
21171        .unwrap();
21172
21173    fake_server
21174        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
21175            Ok(Some(vec![
21176                lsp::CodeLens {
21177                    range: lsp::Range::default(),
21178                    command: Some(lsp::Command {
21179                        title: "Code lens command".to_owned(),
21180                        command: "_the/command".to_owned(),
21181                        arguments: None,
21182                    }),
21183                    data: None,
21184                },
21185                lsp::CodeLens {
21186                    range: lsp::Range::default(),
21187                    command: Some(lsp::Command {
21188                        title: "Command not in capabilities".to_owned(),
21189                        command: "not in capabilities".to_owned(),
21190                        arguments: None,
21191                    }),
21192                    data: None,
21193                },
21194                lsp::CodeLens {
21195                    range: lsp::Range {
21196                        start: lsp::Position {
21197                            line: 1,
21198                            character: 1,
21199                        },
21200                        end: lsp::Position {
21201                            line: 1,
21202                            character: 1,
21203                        },
21204                    },
21205                    command: Some(lsp::Command {
21206                        title: "Command not in range".to_owned(),
21207                        command: "_the/command".to_owned(),
21208                        arguments: None,
21209                    }),
21210                    data: None,
21211                },
21212            ]))
21213        })
21214        .next()
21215        .await;
21216
21217    let actions = actions.await.unwrap();
21218    assert_eq!(
21219        actions.len(),
21220        1,
21221        "Should have only one valid action for the 0..0 range"
21222    );
21223    let action = actions[0].clone();
21224    let apply = project.update(cx, |project, cx| {
21225        project.apply_code_action(buffer.clone(), action, true, cx)
21226    });
21227
21228    // Resolving the code action does not populate its edits. In absence of
21229    // edits, we must execute the given command.
21230    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
21231        |mut lens, _| async move {
21232            let lens_command = lens.command.as_mut().expect("should have a command");
21233            assert_eq!(lens_command.title, "Code lens command");
21234            lens_command.arguments = Some(vec![json!("the-argument")]);
21235            Ok(lens)
21236        },
21237    );
21238
21239    // While executing the command, the language server sends the editor
21240    // a `workspaceEdit` request.
21241    fake_server
21242        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
21243            let fake = fake_server.clone();
21244            move |params, _| {
21245                assert_eq!(params.command, "_the/command");
21246                let fake = fake.clone();
21247                async move {
21248                    fake.server
21249                        .request::<lsp::request::ApplyWorkspaceEdit>(
21250                            lsp::ApplyWorkspaceEditParams {
21251                                label: None,
21252                                edit: lsp::WorkspaceEdit {
21253                                    changes: Some(
21254                                        [(
21255                                            lsp::Url::from_file_path(path!("/dir/a.ts")).unwrap(),
21256                                            vec![lsp::TextEdit {
21257                                                range: lsp::Range::new(
21258                                                    lsp::Position::new(0, 0),
21259                                                    lsp::Position::new(0, 0),
21260                                                ),
21261                                                new_text: "X".into(),
21262                                            }],
21263                                        )]
21264                                        .into_iter()
21265                                        .collect(),
21266                                    ),
21267                                    ..Default::default()
21268                                },
21269                            },
21270                        )
21271                        .await
21272                        .into_response()
21273                        .unwrap();
21274                    Ok(Some(json!(null)))
21275                }
21276            }
21277        })
21278        .next()
21279        .await;
21280
21281    // Applying the code lens command returns a project transaction containing the edits
21282    // sent by the language server in its `workspaceEdit` request.
21283    let transaction = apply.await.unwrap();
21284    assert!(transaction.0.contains_key(&buffer));
21285    buffer.update(cx, |buffer, cx| {
21286        assert_eq!(buffer.text(), "Xa");
21287        buffer.undo(cx);
21288        assert_eq!(buffer.text(), "a");
21289    });
21290}
21291
21292#[gpui::test]
21293async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
21294    init_test(cx, |_| {});
21295
21296    let fs = FakeFs::new(cx.executor());
21297    let main_text = r#"fn main() {
21298println!("1");
21299println!("2");
21300println!("3");
21301println!("4");
21302println!("5");
21303}"#;
21304    let lib_text = "mod foo {}";
21305    fs.insert_tree(
21306        path!("/a"),
21307        json!({
21308            "lib.rs": lib_text,
21309            "main.rs": main_text,
21310        }),
21311    )
21312    .await;
21313
21314    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21315    let (workspace, cx) =
21316        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21317    let worktree_id = workspace.update(cx, |workspace, cx| {
21318        workspace.project().update(cx, |project, cx| {
21319            project.worktrees(cx).next().unwrap().read(cx).id()
21320        })
21321    });
21322
21323    let expected_ranges = vec![
21324        Point::new(0, 0)..Point::new(0, 0),
21325        Point::new(1, 0)..Point::new(1, 1),
21326        Point::new(2, 0)..Point::new(2, 2),
21327        Point::new(3, 0)..Point::new(3, 3),
21328    ];
21329
21330    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21331    let editor_1 = workspace
21332        .update_in(cx, |workspace, window, cx| {
21333            workspace.open_path(
21334                (worktree_id, "main.rs"),
21335                Some(pane_1.downgrade()),
21336                true,
21337                window,
21338                cx,
21339            )
21340        })
21341        .unwrap()
21342        .await
21343        .downcast::<Editor>()
21344        .unwrap();
21345    pane_1.update(cx, |pane, cx| {
21346        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21347        open_editor.update(cx, |editor, cx| {
21348            assert_eq!(
21349                editor.display_text(cx),
21350                main_text,
21351                "Original main.rs text on initial open",
21352            );
21353            assert_eq!(
21354                editor
21355                    .selections
21356                    .all::<Point>(cx)
21357                    .into_iter()
21358                    .map(|s| s.range())
21359                    .collect::<Vec<_>>(),
21360                vec![Point::zero()..Point::zero()],
21361                "Default selections on initial open",
21362            );
21363        })
21364    });
21365    editor_1.update_in(cx, |editor, window, cx| {
21366        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21367            s.select_ranges(expected_ranges.clone());
21368        });
21369    });
21370
21371    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
21372        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
21373    });
21374    let editor_2 = workspace
21375        .update_in(cx, |workspace, window, cx| {
21376            workspace.open_path(
21377                (worktree_id, "main.rs"),
21378                Some(pane_2.downgrade()),
21379                true,
21380                window,
21381                cx,
21382            )
21383        })
21384        .unwrap()
21385        .await
21386        .downcast::<Editor>()
21387        .unwrap();
21388    pane_2.update(cx, |pane, cx| {
21389        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21390        open_editor.update(cx, |editor, cx| {
21391            assert_eq!(
21392                editor.display_text(cx),
21393                main_text,
21394                "Original main.rs text on initial open in another panel",
21395            );
21396            assert_eq!(
21397                editor
21398                    .selections
21399                    .all::<Point>(cx)
21400                    .into_iter()
21401                    .map(|s| s.range())
21402                    .collect::<Vec<_>>(),
21403                vec![Point::zero()..Point::zero()],
21404                "Default selections on initial open in another panel",
21405            );
21406        })
21407    });
21408
21409    editor_2.update_in(cx, |editor, window, cx| {
21410        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
21411    });
21412
21413    let _other_editor_1 = workspace
21414        .update_in(cx, |workspace, window, cx| {
21415            workspace.open_path(
21416                (worktree_id, "lib.rs"),
21417                Some(pane_1.downgrade()),
21418                true,
21419                window,
21420                cx,
21421            )
21422        })
21423        .unwrap()
21424        .await
21425        .downcast::<Editor>()
21426        .unwrap();
21427    pane_1
21428        .update_in(cx, |pane, window, cx| {
21429            pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
21430        })
21431        .await
21432        .unwrap();
21433    drop(editor_1);
21434    pane_1.update(cx, |pane, cx| {
21435        pane.active_item()
21436            .unwrap()
21437            .downcast::<Editor>()
21438            .unwrap()
21439            .update(cx, |editor, cx| {
21440                assert_eq!(
21441                    editor.display_text(cx),
21442                    lib_text,
21443                    "Other file should be open and active",
21444                );
21445            });
21446        assert_eq!(pane.items().count(), 1, "No other editors should be open");
21447    });
21448
21449    let _other_editor_2 = workspace
21450        .update_in(cx, |workspace, window, cx| {
21451            workspace.open_path(
21452                (worktree_id, "lib.rs"),
21453                Some(pane_2.downgrade()),
21454                true,
21455                window,
21456                cx,
21457            )
21458        })
21459        .unwrap()
21460        .await
21461        .downcast::<Editor>()
21462        .unwrap();
21463    pane_2
21464        .update_in(cx, |pane, window, cx| {
21465            pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
21466        })
21467        .await
21468        .unwrap();
21469    drop(editor_2);
21470    pane_2.update(cx, |pane, cx| {
21471        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21472        open_editor.update(cx, |editor, cx| {
21473            assert_eq!(
21474                editor.display_text(cx),
21475                lib_text,
21476                "Other file should be open and active in another panel too",
21477            );
21478        });
21479        assert_eq!(
21480            pane.items().count(),
21481            1,
21482            "No other editors should be open in another pane",
21483        );
21484    });
21485
21486    let _editor_1_reopened = workspace
21487        .update_in(cx, |workspace, window, cx| {
21488            workspace.open_path(
21489                (worktree_id, "main.rs"),
21490                Some(pane_1.downgrade()),
21491                true,
21492                window,
21493                cx,
21494            )
21495        })
21496        .unwrap()
21497        .await
21498        .downcast::<Editor>()
21499        .unwrap();
21500    let _editor_2_reopened = workspace
21501        .update_in(cx, |workspace, window, cx| {
21502            workspace.open_path(
21503                (worktree_id, "main.rs"),
21504                Some(pane_2.downgrade()),
21505                true,
21506                window,
21507                cx,
21508            )
21509        })
21510        .unwrap()
21511        .await
21512        .downcast::<Editor>()
21513        .unwrap();
21514    pane_1.update(cx, |pane, cx| {
21515        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21516        open_editor.update(cx, |editor, cx| {
21517            assert_eq!(
21518                editor.display_text(cx),
21519                main_text,
21520                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
21521            );
21522            assert_eq!(
21523                editor
21524                    .selections
21525                    .all::<Point>(cx)
21526                    .into_iter()
21527                    .map(|s| s.range())
21528                    .collect::<Vec<_>>(),
21529                expected_ranges,
21530                "Previous editor in the 1st panel had selections and should get them restored on reopen",
21531            );
21532        })
21533    });
21534    pane_2.update(cx, |pane, cx| {
21535        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21536        open_editor.update(cx, |editor, cx| {
21537            assert_eq!(
21538                editor.display_text(cx),
21539                r#"fn main() {
21540⋯rintln!("1");
21541⋯intln!("2");
21542⋯ntln!("3");
21543println!("4");
21544println!("5");
21545}"#,
21546                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
21547            );
21548            assert_eq!(
21549                editor
21550                    .selections
21551                    .all::<Point>(cx)
21552                    .into_iter()
21553                    .map(|s| s.range())
21554                    .collect::<Vec<_>>(),
21555                vec![Point::zero()..Point::zero()],
21556                "Previous editor in the 2nd pane had no selections changed hence should restore none",
21557            );
21558        })
21559    });
21560}
21561
21562#[gpui::test]
21563async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
21564    init_test(cx, |_| {});
21565
21566    let fs = FakeFs::new(cx.executor());
21567    let main_text = r#"fn main() {
21568println!("1");
21569println!("2");
21570println!("3");
21571println!("4");
21572println!("5");
21573}"#;
21574    let lib_text = "mod foo {}";
21575    fs.insert_tree(
21576        path!("/a"),
21577        json!({
21578            "lib.rs": lib_text,
21579            "main.rs": main_text,
21580        }),
21581    )
21582    .await;
21583
21584    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21585    let (workspace, cx) =
21586        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21587    let worktree_id = workspace.update(cx, |workspace, cx| {
21588        workspace.project().update(cx, |project, cx| {
21589            project.worktrees(cx).next().unwrap().read(cx).id()
21590        })
21591    });
21592
21593    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21594    let editor = workspace
21595        .update_in(cx, |workspace, window, cx| {
21596            workspace.open_path(
21597                (worktree_id, "main.rs"),
21598                Some(pane.downgrade()),
21599                true,
21600                window,
21601                cx,
21602            )
21603        })
21604        .unwrap()
21605        .await
21606        .downcast::<Editor>()
21607        .unwrap();
21608    pane.update(cx, |pane, cx| {
21609        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21610        open_editor.update(cx, |editor, cx| {
21611            assert_eq!(
21612                editor.display_text(cx),
21613                main_text,
21614                "Original main.rs text on initial open",
21615            );
21616        })
21617    });
21618    editor.update_in(cx, |editor, window, cx| {
21619        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
21620    });
21621
21622    cx.update_global(|store: &mut SettingsStore, cx| {
21623        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
21624            s.restore_on_file_reopen = Some(false);
21625        });
21626    });
21627    editor.update_in(cx, |editor, window, cx| {
21628        editor.fold_ranges(
21629            vec![
21630                Point::new(1, 0)..Point::new(1, 1),
21631                Point::new(2, 0)..Point::new(2, 2),
21632                Point::new(3, 0)..Point::new(3, 3),
21633            ],
21634            false,
21635            window,
21636            cx,
21637        );
21638    });
21639    pane.update_in(cx, |pane, window, cx| {
21640        pane.close_all_items(&CloseAllItems::default(), window, cx)
21641    })
21642    .await
21643    .unwrap();
21644    pane.update(cx, |pane, _| {
21645        assert!(pane.active_item().is_none());
21646    });
21647    cx.update_global(|store: &mut SettingsStore, cx| {
21648        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
21649            s.restore_on_file_reopen = Some(true);
21650        });
21651    });
21652
21653    let _editor_reopened = workspace
21654        .update_in(cx, |workspace, window, cx| {
21655            workspace.open_path(
21656                (worktree_id, "main.rs"),
21657                Some(pane.downgrade()),
21658                true,
21659                window,
21660                cx,
21661            )
21662        })
21663        .unwrap()
21664        .await
21665        .downcast::<Editor>()
21666        .unwrap();
21667    pane.update(cx, |pane, cx| {
21668        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21669        open_editor.update(cx, |editor, cx| {
21670            assert_eq!(
21671                editor.display_text(cx),
21672                main_text,
21673                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
21674            );
21675        })
21676    });
21677}
21678
21679#[gpui::test]
21680async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
21681    struct EmptyModalView {
21682        focus_handle: gpui::FocusHandle,
21683    }
21684    impl EventEmitter<DismissEvent> for EmptyModalView {}
21685    impl Render for EmptyModalView {
21686        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
21687            div()
21688        }
21689    }
21690    impl Focusable for EmptyModalView {
21691        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
21692            self.focus_handle.clone()
21693        }
21694    }
21695    impl workspace::ModalView for EmptyModalView {}
21696    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
21697        EmptyModalView {
21698            focus_handle: cx.focus_handle(),
21699        }
21700    }
21701
21702    init_test(cx, |_| {});
21703
21704    let fs = FakeFs::new(cx.executor());
21705    let project = Project::test(fs, [], cx).await;
21706    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21707    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
21708    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21709    let editor = cx.new_window_entity(|window, cx| {
21710        Editor::new(
21711            EditorMode::full(),
21712            buffer,
21713            Some(project.clone()),
21714            window,
21715            cx,
21716        )
21717    });
21718    workspace
21719        .update(cx, |workspace, window, cx| {
21720            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
21721        })
21722        .unwrap();
21723    editor.update_in(cx, |editor, window, cx| {
21724        editor.open_context_menu(&OpenContextMenu, window, cx);
21725        assert!(editor.mouse_context_menu.is_some());
21726    });
21727    workspace
21728        .update(cx, |workspace, window, cx| {
21729            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
21730        })
21731        .unwrap();
21732    cx.read(|cx| {
21733        assert!(editor.read(cx).mouse_context_menu.is_none());
21734    });
21735}
21736
21737#[gpui::test]
21738async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
21739    init_test(cx, |_| {});
21740
21741    let fs = FakeFs::new(cx.executor());
21742    fs.insert_file(path!("/file.html"), Default::default())
21743        .await;
21744
21745    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
21746
21747    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21748    let html_language = Arc::new(Language::new(
21749        LanguageConfig {
21750            name: "HTML".into(),
21751            matcher: LanguageMatcher {
21752                path_suffixes: vec!["html".to_string()],
21753                ..LanguageMatcher::default()
21754            },
21755            brackets: BracketPairConfig {
21756                pairs: vec![BracketPair {
21757                    start: "<".into(),
21758                    end: ">".into(),
21759                    close: true,
21760                    ..Default::default()
21761                }],
21762                ..Default::default()
21763            },
21764            ..Default::default()
21765        },
21766        Some(tree_sitter_html::LANGUAGE.into()),
21767    ));
21768    language_registry.add(html_language);
21769    let mut fake_servers = language_registry.register_fake_lsp(
21770        "HTML",
21771        FakeLspAdapter {
21772            capabilities: lsp::ServerCapabilities {
21773                completion_provider: Some(lsp::CompletionOptions {
21774                    resolve_provider: Some(true),
21775                    ..Default::default()
21776                }),
21777                ..Default::default()
21778            },
21779            ..Default::default()
21780        },
21781    );
21782
21783    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21784    let cx = &mut VisualTestContext::from_window(*workspace, cx);
21785
21786    let worktree_id = workspace
21787        .update(cx, |workspace, _window, cx| {
21788            workspace.project().update(cx, |project, cx| {
21789                project.worktrees(cx).next().unwrap().read(cx).id()
21790            })
21791        })
21792        .unwrap();
21793    project
21794        .update(cx, |project, cx| {
21795            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
21796        })
21797        .await
21798        .unwrap();
21799    let editor = workspace
21800        .update(cx, |workspace, window, cx| {
21801            workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
21802        })
21803        .unwrap()
21804        .await
21805        .unwrap()
21806        .downcast::<Editor>()
21807        .unwrap();
21808
21809    let fake_server = fake_servers.next().await.unwrap();
21810    editor.update_in(cx, |editor, window, cx| {
21811        editor.set_text("<ad></ad>", window, cx);
21812        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21813            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
21814        });
21815        let Some((buffer, _)) = editor
21816            .buffer
21817            .read(cx)
21818            .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
21819        else {
21820            panic!("Failed to get buffer for selection position");
21821        };
21822        let buffer = buffer.read(cx);
21823        let buffer_id = buffer.remote_id();
21824        let opening_range =
21825            buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3));
21826        let closing_range =
21827            buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8));
21828        let mut linked_ranges = HashMap::default();
21829        linked_ranges.insert(
21830            buffer_id,
21831            vec![(opening_range.clone(), vec![closing_range.clone()])],
21832        );
21833        editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
21834    });
21835    let mut completion_handle =
21836        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
21837            Ok(Some(lsp::CompletionResponse::Array(vec![
21838                lsp::CompletionItem {
21839                    label: "head".to_string(),
21840                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21841                        lsp::InsertReplaceEdit {
21842                            new_text: "head".to_string(),
21843                            insert: lsp::Range::new(
21844                                lsp::Position::new(0, 1),
21845                                lsp::Position::new(0, 3),
21846                            ),
21847                            replace: lsp::Range::new(
21848                                lsp::Position::new(0, 1),
21849                                lsp::Position::new(0, 3),
21850                            ),
21851                        },
21852                    )),
21853                    ..Default::default()
21854                },
21855            ])))
21856        });
21857    editor.update_in(cx, |editor, window, cx| {
21858        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
21859    });
21860    cx.run_until_parked();
21861    completion_handle.next().await.unwrap();
21862    editor.update(cx, |editor, _| {
21863        assert!(
21864            editor.context_menu_visible(),
21865            "Completion menu should be visible"
21866        );
21867    });
21868    editor.update_in(cx, |editor, window, cx| {
21869        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
21870    });
21871    cx.executor().run_until_parked();
21872    editor.update(cx, |editor, cx| {
21873        assert_eq!(editor.text(cx), "<head></head>");
21874    });
21875}
21876
21877#[gpui::test]
21878async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
21879    init_test(cx, |_| {});
21880
21881    let fs = FakeFs::new(cx.executor());
21882    fs.insert_tree(
21883        path!("/root"),
21884        json!({
21885            "a": {
21886                "main.rs": "fn main() {}",
21887            },
21888            "foo": {
21889                "bar": {
21890                    "external_file.rs": "pub mod external {}",
21891                }
21892            }
21893        }),
21894    )
21895    .await;
21896
21897    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
21898    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21899    language_registry.add(rust_lang());
21900    let _fake_servers = language_registry.register_fake_lsp(
21901        "Rust",
21902        FakeLspAdapter {
21903            ..FakeLspAdapter::default()
21904        },
21905    );
21906    let (workspace, cx) =
21907        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21908    let worktree_id = workspace.update(cx, |workspace, cx| {
21909        workspace.project().update(cx, |project, cx| {
21910            project.worktrees(cx).next().unwrap().read(cx).id()
21911        })
21912    });
21913
21914    let assert_language_servers_count =
21915        |expected: usize, context: &str, cx: &mut VisualTestContext| {
21916            project.update(cx, |project, cx| {
21917                let current = project
21918                    .lsp_store()
21919                    .read(cx)
21920                    .as_local()
21921                    .unwrap()
21922                    .language_servers
21923                    .len();
21924                assert_eq!(expected, current, "{context}");
21925            });
21926        };
21927
21928    assert_language_servers_count(
21929        0,
21930        "No servers should be running before any file is open",
21931        cx,
21932    );
21933    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21934    let main_editor = workspace
21935        .update_in(cx, |workspace, window, cx| {
21936            workspace.open_path(
21937                (worktree_id, "main.rs"),
21938                Some(pane.downgrade()),
21939                true,
21940                window,
21941                cx,
21942            )
21943        })
21944        .unwrap()
21945        .await
21946        .downcast::<Editor>()
21947        .unwrap();
21948    pane.update(cx, |pane, cx| {
21949        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21950        open_editor.update(cx, |editor, cx| {
21951            assert_eq!(
21952                editor.display_text(cx),
21953                "fn main() {}",
21954                "Original main.rs text on initial open",
21955            );
21956        });
21957        assert_eq!(open_editor, main_editor);
21958    });
21959    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
21960
21961    let external_editor = workspace
21962        .update_in(cx, |workspace, window, cx| {
21963            workspace.open_abs_path(
21964                PathBuf::from("/root/foo/bar/external_file.rs"),
21965                OpenOptions::default(),
21966                window,
21967                cx,
21968            )
21969        })
21970        .await
21971        .expect("opening external file")
21972        .downcast::<Editor>()
21973        .expect("downcasted external file's open element to editor");
21974    pane.update(cx, |pane, cx| {
21975        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21976        open_editor.update(cx, |editor, cx| {
21977            assert_eq!(
21978                editor.display_text(cx),
21979                "pub mod external {}",
21980                "External file is open now",
21981            );
21982        });
21983        assert_eq!(open_editor, external_editor);
21984    });
21985    assert_language_servers_count(
21986        1,
21987        "Second, external, *.rs file should join the existing server",
21988        cx,
21989    );
21990
21991    pane.update_in(cx, |pane, window, cx| {
21992        pane.close_active_item(&CloseActiveItem::default(), window, cx)
21993    })
21994    .await
21995    .unwrap();
21996    pane.update_in(cx, |pane, window, cx| {
21997        pane.navigate_backward(window, cx);
21998    });
21999    cx.run_until_parked();
22000    pane.update(cx, |pane, cx| {
22001        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22002        open_editor.update(cx, |editor, cx| {
22003            assert_eq!(
22004                editor.display_text(cx),
22005                "pub mod external {}",
22006                "External file is open now",
22007            );
22008        });
22009    });
22010    assert_language_servers_count(
22011        1,
22012        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
22013        cx,
22014    );
22015
22016    cx.update(|_, cx| {
22017        workspace::reload(&workspace::Reload::default(), cx);
22018    });
22019    assert_language_servers_count(
22020        1,
22021        "After reloading the worktree with local and external files opened, only one project should be started",
22022        cx,
22023    );
22024}
22025
22026#[gpui::test]
22027async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
22028    init_test(cx, |_| {});
22029
22030    let mut cx = EditorTestContext::new(cx).await;
22031    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22032    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22033
22034    // test cursor move to start of each line on tab
22035    // for `if`, `elif`, `else`, `while`, `with` and `for`
22036    cx.set_state(indoc! {"
22037        def main():
22038        ˇ    for item in items:
22039        ˇ        while item.active:
22040        ˇ            if item.value > 10:
22041        ˇ                continue
22042        ˇ            elif item.value < 0:
22043        ˇ                break
22044        ˇ            else:
22045        ˇ                with item.context() as ctx:
22046        ˇ                    yield count
22047        ˇ        else:
22048        ˇ            log('while else')
22049        ˇ    else:
22050        ˇ        log('for else')
22051    "});
22052    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22053    cx.assert_editor_state(indoc! {"
22054        def main():
22055            ˇfor item in items:
22056                ˇwhile item.active:
22057                    ˇif item.value > 10:
22058                        ˇcontinue
22059                    ˇelif item.value < 0:
22060                        ˇbreak
22061                    ˇelse:
22062                        ˇwith item.context() as ctx:
22063                            ˇyield count
22064                ˇelse:
22065                    ˇlog('while else')
22066            ˇelse:
22067                ˇlog('for else')
22068    "});
22069    // test relative indent is preserved when tab
22070    // for `if`, `elif`, `else`, `while`, `with` and `for`
22071    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22072    cx.assert_editor_state(indoc! {"
22073        def main():
22074                ˇfor item in items:
22075                    ˇwhile item.active:
22076                        ˇif item.value > 10:
22077                            ˇcontinue
22078                        ˇelif item.value < 0:
22079                            ˇbreak
22080                        ˇelse:
22081                            ˇwith item.context() as ctx:
22082                                ˇyield count
22083                    ˇelse:
22084                        ˇlog('while else')
22085                ˇelse:
22086                    ˇlog('for else')
22087    "});
22088
22089    // test cursor move to start of each line on tab
22090    // for `try`, `except`, `else`, `finally`, `match` and `def`
22091    cx.set_state(indoc! {"
22092        def main():
22093        ˇ    try:
22094        ˇ        fetch()
22095        ˇ    except ValueError:
22096        ˇ        handle_error()
22097        ˇ    else:
22098        ˇ        match value:
22099        ˇ            case _:
22100        ˇ    finally:
22101        ˇ        def status():
22102        ˇ            return 0
22103    "});
22104    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22105    cx.assert_editor_state(indoc! {"
22106        def main():
22107            ˇtry:
22108                ˇfetch()
22109            ˇexcept ValueError:
22110                ˇhandle_error()
22111            ˇelse:
22112                ˇmatch value:
22113                    ˇcase _:
22114            ˇfinally:
22115                ˇdef status():
22116                    ˇreturn 0
22117    "});
22118    // test relative indent is preserved when tab
22119    // for `try`, `except`, `else`, `finally`, `match` and `def`
22120    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22121    cx.assert_editor_state(indoc! {"
22122        def main():
22123                ˇtry:
22124                    ˇfetch()
22125                ˇexcept ValueError:
22126                    ˇhandle_error()
22127                ˇelse:
22128                    ˇmatch value:
22129                        ˇcase _:
22130                ˇfinally:
22131                    ˇdef status():
22132                        ˇreturn 0
22133    "});
22134}
22135
22136#[gpui::test]
22137async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
22138    init_test(cx, |_| {});
22139
22140    let mut cx = EditorTestContext::new(cx).await;
22141    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22142    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22143
22144    // test `else` auto outdents when typed inside `if` block
22145    cx.set_state(indoc! {"
22146        def main():
22147            if i == 2:
22148                return
22149                ˇ
22150    "});
22151    cx.update_editor(|editor, window, cx| {
22152        editor.handle_input("else:", window, cx);
22153    });
22154    cx.assert_editor_state(indoc! {"
22155        def main():
22156            if i == 2:
22157                return
22158            else:ˇ
22159    "});
22160
22161    // test `except` auto outdents when typed inside `try` block
22162    cx.set_state(indoc! {"
22163        def main():
22164            try:
22165                i = 2
22166                ˇ
22167    "});
22168    cx.update_editor(|editor, window, cx| {
22169        editor.handle_input("except:", window, cx);
22170    });
22171    cx.assert_editor_state(indoc! {"
22172        def main():
22173            try:
22174                i = 2
22175            except:ˇ
22176    "});
22177
22178    // test `else` auto outdents when typed inside `except` block
22179    cx.set_state(indoc! {"
22180        def main():
22181            try:
22182                i = 2
22183            except:
22184                j = 2
22185                ˇ
22186    "});
22187    cx.update_editor(|editor, window, cx| {
22188        editor.handle_input("else:", window, cx);
22189    });
22190    cx.assert_editor_state(indoc! {"
22191        def main():
22192            try:
22193                i = 2
22194            except:
22195                j = 2
22196            else:ˇ
22197    "});
22198
22199    // test `finally` auto outdents when typed inside `else` block
22200    cx.set_state(indoc! {"
22201        def main():
22202            try:
22203                i = 2
22204            except:
22205                j = 2
22206            else:
22207                k = 2
22208                ˇ
22209    "});
22210    cx.update_editor(|editor, window, cx| {
22211        editor.handle_input("finally:", window, cx);
22212    });
22213    cx.assert_editor_state(indoc! {"
22214        def main():
22215            try:
22216                i = 2
22217            except:
22218                j = 2
22219            else:
22220                k = 2
22221            finally:ˇ
22222    "});
22223
22224    // test `else` does not outdents when typed inside `except` block right after for block
22225    cx.set_state(indoc! {"
22226        def main():
22227            try:
22228                i = 2
22229            except:
22230                for i in range(n):
22231                    pass
22232                ˇ
22233    "});
22234    cx.update_editor(|editor, window, cx| {
22235        editor.handle_input("else:", window, cx);
22236    });
22237    cx.assert_editor_state(indoc! {"
22238        def main():
22239            try:
22240                i = 2
22241            except:
22242                for i in range(n):
22243                    pass
22244                else:ˇ
22245    "});
22246
22247    // test `finally` auto outdents when typed inside `else` block right after for block
22248    cx.set_state(indoc! {"
22249        def main():
22250            try:
22251                i = 2
22252            except:
22253                j = 2
22254            else:
22255                for i in range(n):
22256                    pass
22257                ˇ
22258    "});
22259    cx.update_editor(|editor, window, cx| {
22260        editor.handle_input("finally:", window, cx);
22261    });
22262    cx.assert_editor_state(indoc! {"
22263        def main():
22264            try:
22265                i = 2
22266            except:
22267                j = 2
22268            else:
22269                for i in range(n):
22270                    pass
22271            finally:ˇ
22272    "});
22273
22274    // test `except` outdents to inner "try" block
22275    cx.set_state(indoc! {"
22276        def main():
22277            try:
22278                i = 2
22279                if i == 2:
22280                    try:
22281                        i = 3
22282                        ˇ
22283    "});
22284    cx.update_editor(|editor, window, cx| {
22285        editor.handle_input("except:", window, cx);
22286    });
22287    cx.assert_editor_state(indoc! {"
22288        def main():
22289            try:
22290                i = 2
22291                if i == 2:
22292                    try:
22293                        i = 3
22294                    except:ˇ
22295    "});
22296
22297    // test `except` outdents to outer "try" block
22298    cx.set_state(indoc! {"
22299        def main():
22300            try:
22301                i = 2
22302                if i == 2:
22303                    try:
22304                        i = 3
22305                ˇ
22306    "});
22307    cx.update_editor(|editor, window, cx| {
22308        editor.handle_input("except:", window, cx);
22309    });
22310    cx.assert_editor_state(indoc! {"
22311        def main():
22312            try:
22313                i = 2
22314                if i == 2:
22315                    try:
22316                        i = 3
22317            except:ˇ
22318    "});
22319
22320    // test `else` stays at correct indent when typed after `for` block
22321    cx.set_state(indoc! {"
22322        def main():
22323            for i in range(10):
22324                if i == 3:
22325                    break
22326            ˇ
22327    "});
22328    cx.update_editor(|editor, window, cx| {
22329        editor.handle_input("else:", window, cx);
22330    });
22331    cx.assert_editor_state(indoc! {"
22332        def main():
22333            for i in range(10):
22334                if i == 3:
22335                    break
22336            else:ˇ
22337    "});
22338
22339    // test does not outdent on typing after line with square brackets
22340    cx.set_state(indoc! {"
22341        def f() -> list[str]:
22342            ˇ
22343    "});
22344    cx.update_editor(|editor, window, cx| {
22345        editor.handle_input("a", window, cx);
22346    });
22347    cx.assert_editor_state(indoc! {"
22348        def f() -> list[str]:
2234922350    "});
22351
22352    // test does not outdent on typing : after case keyword
22353    cx.set_state(indoc! {"
22354        match 1:
22355            caseˇ
22356    "});
22357    cx.update_editor(|editor, window, cx| {
22358        editor.handle_input(":", window, cx);
22359    });
22360    cx.assert_editor_state(indoc! {"
22361        match 1:
22362            case:ˇ
22363    "});
22364}
22365
22366#[gpui::test]
22367async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
22368    init_test(cx, |_| {});
22369    update_test_language_settings(cx, |settings| {
22370        settings.defaults.extend_comment_on_newline = Some(false);
22371    });
22372    let mut cx = EditorTestContext::new(cx).await;
22373    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22374    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22375
22376    // test correct indent after newline on comment
22377    cx.set_state(indoc! {"
22378        # COMMENT:ˇ
22379    "});
22380    cx.update_editor(|editor, window, cx| {
22381        editor.newline(&Newline, window, cx);
22382    });
22383    cx.assert_editor_state(indoc! {"
22384        # COMMENT:
22385        ˇ
22386    "});
22387
22388    // test correct indent after newline in brackets
22389    cx.set_state(indoc! {"
22390        {ˇ}
22391    "});
22392    cx.update_editor(|editor, window, cx| {
22393        editor.newline(&Newline, window, cx);
22394    });
22395    cx.run_until_parked();
22396    cx.assert_editor_state(indoc! {"
22397        {
22398            ˇ
22399        }
22400    "});
22401
22402    cx.set_state(indoc! {"
22403        (ˇ)
22404    "});
22405    cx.update_editor(|editor, window, cx| {
22406        editor.newline(&Newline, window, cx);
22407    });
22408    cx.run_until_parked();
22409    cx.assert_editor_state(indoc! {"
22410        (
22411            ˇ
22412        )
22413    "});
22414
22415    // do not indent after empty lists or dictionaries
22416    cx.set_state(indoc! {"
22417        a = []ˇ
22418    "});
22419    cx.update_editor(|editor, window, cx| {
22420        editor.newline(&Newline, window, cx);
22421    });
22422    cx.run_until_parked();
22423    cx.assert_editor_state(indoc! {"
22424        a = []
22425        ˇ
22426    "});
22427}
22428
22429fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
22430    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
22431    point..point
22432}
22433
22434#[track_caller]
22435fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
22436    let (text, ranges) = marked_text_ranges(marked_text, true);
22437    assert_eq!(editor.text(cx), text);
22438    assert_eq!(
22439        editor.selections.ranges(cx),
22440        ranges,
22441        "Assert selections are {}",
22442        marked_text
22443    );
22444}
22445
22446pub fn handle_signature_help_request(
22447    cx: &mut EditorLspTestContext,
22448    mocked_response: lsp::SignatureHelp,
22449) -> impl Future<Output = ()> + use<> {
22450    let mut request =
22451        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
22452            let mocked_response = mocked_response.clone();
22453            async move { Ok(Some(mocked_response)) }
22454        });
22455
22456    async move {
22457        request.next().await;
22458    }
22459}
22460
22461#[track_caller]
22462pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
22463    cx.update_editor(|editor, _, _| {
22464        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
22465            let entries = menu.entries.borrow();
22466            let entries = entries
22467                .iter()
22468                .map(|entry| entry.string.as_str())
22469                .collect::<Vec<_>>();
22470            assert_eq!(entries, expected);
22471        } else {
22472            panic!("Expected completions menu");
22473        }
22474    });
22475}
22476
22477/// Handle completion request passing a marked string specifying where the completion
22478/// should be triggered from using '|' character, what range should be replaced, and what completions
22479/// should be returned using '<' and '>' to delimit the range.
22480///
22481/// Also see `handle_completion_request_with_insert_and_replace`.
22482#[track_caller]
22483pub fn handle_completion_request(
22484    marked_string: &str,
22485    completions: Vec<&'static str>,
22486    is_incomplete: bool,
22487    counter: Arc<AtomicUsize>,
22488    cx: &mut EditorLspTestContext,
22489) -> impl Future<Output = ()> {
22490    let complete_from_marker: TextRangeMarker = '|'.into();
22491    let replace_range_marker: TextRangeMarker = ('<', '>').into();
22492    let (_, mut marked_ranges) = marked_text_ranges_by(
22493        marked_string,
22494        vec![complete_from_marker.clone(), replace_range_marker.clone()],
22495    );
22496
22497    let complete_from_position =
22498        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
22499    let replace_range =
22500        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
22501
22502    let mut request =
22503        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
22504            let completions = completions.clone();
22505            counter.fetch_add(1, atomic::Ordering::Release);
22506            async move {
22507                assert_eq!(params.text_document_position.text_document.uri, url.clone());
22508                assert_eq!(
22509                    params.text_document_position.position,
22510                    complete_from_position
22511                );
22512                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
22513                    is_incomplete: is_incomplete,
22514                    item_defaults: None,
22515                    items: completions
22516                        .iter()
22517                        .map(|completion_text| lsp::CompletionItem {
22518                            label: completion_text.to_string(),
22519                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
22520                                range: replace_range,
22521                                new_text: completion_text.to_string(),
22522                            })),
22523                            ..Default::default()
22524                        })
22525                        .collect(),
22526                })))
22527            }
22528        });
22529
22530    async move {
22531        request.next().await;
22532    }
22533}
22534
22535/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
22536/// given instead, which also contains an `insert` range.
22537///
22538/// This function uses markers to define ranges:
22539/// - `|` marks the cursor position
22540/// - `<>` marks the replace range
22541/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
22542pub fn handle_completion_request_with_insert_and_replace(
22543    cx: &mut EditorLspTestContext,
22544    marked_string: &str,
22545    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
22546    counter: Arc<AtomicUsize>,
22547) -> impl Future<Output = ()> {
22548    let complete_from_marker: TextRangeMarker = '|'.into();
22549    let replace_range_marker: TextRangeMarker = ('<', '>').into();
22550    let insert_range_marker: TextRangeMarker = ('{', '}').into();
22551
22552    let (_, mut marked_ranges) = marked_text_ranges_by(
22553        marked_string,
22554        vec![
22555            complete_from_marker.clone(),
22556            replace_range_marker.clone(),
22557            insert_range_marker.clone(),
22558        ],
22559    );
22560
22561    let complete_from_position =
22562        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
22563    let replace_range =
22564        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
22565
22566    let insert_range = match marked_ranges.remove(&insert_range_marker) {
22567        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
22568        _ => lsp::Range {
22569            start: replace_range.start,
22570            end: complete_from_position,
22571        },
22572    };
22573
22574    let mut request =
22575        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
22576            let completions = completions.clone();
22577            counter.fetch_add(1, atomic::Ordering::Release);
22578            async move {
22579                assert_eq!(params.text_document_position.text_document.uri, url.clone());
22580                assert_eq!(
22581                    params.text_document_position.position, complete_from_position,
22582                    "marker `|` position doesn't match",
22583                );
22584                Ok(Some(lsp::CompletionResponse::Array(
22585                    completions
22586                        .iter()
22587                        .map(|(label, new_text)| lsp::CompletionItem {
22588                            label: label.to_string(),
22589                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22590                                lsp::InsertReplaceEdit {
22591                                    insert: insert_range,
22592                                    replace: replace_range,
22593                                    new_text: new_text.to_string(),
22594                                },
22595                            )),
22596                            ..Default::default()
22597                        })
22598                        .collect(),
22599                )))
22600            }
22601        });
22602
22603    async move {
22604        request.next().await;
22605    }
22606}
22607
22608fn handle_resolve_completion_request(
22609    cx: &mut EditorLspTestContext,
22610    edits: Option<Vec<(&'static str, &'static str)>>,
22611) -> impl Future<Output = ()> {
22612    let edits = edits.map(|edits| {
22613        edits
22614            .iter()
22615            .map(|(marked_string, new_text)| {
22616                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
22617                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
22618                lsp::TextEdit::new(replace_range, new_text.to_string())
22619            })
22620            .collect::<Vec<_>>()
22621    });
22622
22623    let mut request =
22624        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
22625            let edits = edits.clone();
22626            async move {
22627                Ok(lsp::CompletionItem {
22628                    additional_text_edits: edits,
22629                    ..Default::default()
22630                })
22631            }
22632        });
22633
22634    async move {
22635        request.next().await;
22636    }
22637}
22638
22639pub(crate) fn update_test_language_settings(
22640    cx: &mut TestAppContext,
22641    f: impl Fn(&mut AllLanguageSettingsContent),
22642) {
22643    cx.update(|cx| {
22644        SettingsStore::update_global(cx, |store, cx| {
22645            store.update_user_settings::<AllLanguageSettings>(cx, f);
22646        });
22647    });
22648}
22649
22650pub(crate) fn update_test_project_settings(
22651    cx: &mut TestAppContext,
22652    f: impl Fn(&mut ProjectSettings),
22653) {
22654    cx.update(|cx| {
22655        SettingsStore::update_global(cx, |store, cx| {
22656            store.update_user_settings::<ProjectSettings>(cx, f);
22657        });
22658    });
22659}
22660
22661pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
22662    cx.update(|cx| {
22663        assets::Assets.load_test_fonts(cx);
22664        let store = SettingsStore::test(cx);
22665        cx.set_global(store);
22666        theme::init(theme::LoadThemes::JustBase, cx);
22667        release_channel::init(SemanticVersion::default(), cx);
22668        client::init_settings(cx);
22669        language::init(cx);
22670        Project::init_settings(cx);
22671        workspace::init_settings(cx);
22672        crate::init(cx);
22673    });
22674
22675    update_test_language_settings(cx, f);
22676}
22677
22678#[track_caller]
22679fn assert_hunk_revert(
22680    not_reverted_text_with_selections: &str,
22681    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
22682    expected_reverted_text_with_selections: &str,
22683    base_text: &str,
22684    cx: &mut EditorLspTestContext,
22685) {
22686    cx.set_state(not_reverted_text_with_selections);
22687    cx.set_head_text(base_text);
22688    cx.executor().run_until_parked();
22689
22690    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
22691        let snapshot = editor.snapshot(window, cx);
22692        let reverted_hunk_statuses = snapshot
22693            .buffer_snapshot
22694            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
22695            .map(|hunk| hunk.status().kind)
22696            .collect::<Vec<_>>();
22697
22698        editor.git_restore(&Default::default(), window, cx);
22699        reverted_hunk_statuses
22700    });
22701    cx.executor().run_until_parked();
22702    cx.assert_editor_state(expected_reverted_text_with_selections);
22703    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
22704}
22705
22706#[gpui::test(iterations = 10)]
22707async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
22708    init_test(cx, |_| {});
22709
22710    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
22711    let counter = diagnostic_requests.clone();
22712
22713    let fs = FakeFs::new(cx.executor());
22714    fs.insert_tree(
22715        path!("/a"),
22716        json!({
22717            "first.rs": "fn main() { let a = 5; }",
22718            "second.rs": "// Test file",
22719        }),
22720    )
22721    .await;
22722
22723    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22724    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22725    let cx = &mut VisualTestContext::from_window(*workspace, cx);
22726
22727    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22728    language_registry.add(rust_lang());
22729    let mut fake_servers = language_registry.register_fake_lsp(
22730        "Rust",
22731        FakeLspAdapter {
22732            capabilities: lsp::ServerCapabilities {
22733                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
22734                    lsp::DiagnosticOptions {
22735                        identifier: None,
22736                        inter_file_dependencies: true,
22737                        workspace_diagnostics: true,
22738                        work_done_progress_options: Default::default(),
22739                    },
22740                )),
22741                ..Default::default()
22742            },
22743            ..Default::default()
22744        },
22745    );
22746
22747    let editor = workspace
22748        .update(cx, |workspace, window, cx| {
22749            workspace.open_abs_path(
22750                PathBuf::from(path!("/a/first.rs")),
22751                OpenOptions::default(),
22752                window,
22753                cx,
22754            )
22755        })
22756        .unwrap()
22757        .await
22758        .unwrap()
22759        .downcast::<Editor>()
22760        .unwrap();
22761    let fake_server = fake_servers.next().await.unwrap();
22762    let server_id = fake_server.server.server_id();
22763    let mut first_request = fake_server
22764        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
22765            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
22766            let result_id = Some(new_result_id.to_string());
22767            assert_eq!(
22768                params.text_document.uri,
22769                lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
22770            );
22771            async move {
22772                Ok(lsp::DocumentDiagnosticReportResult::Report(
22773                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
22774                        related_documents: None,
22775                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
22776                            items: Vec::new(),
22777                            result_id,
22778                        },
22779                    }),
22780                ))
22781            }
22782        });
22783
22784    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
22785        project.update(cx, |project, cx| {
22786            let buffer_id = editor
22787                .read(cx)
22788                .buffer()
22789                .read(cx)
22790                .as_singleton()
22791                .expect("created a singleton buffer")
22792                .read(cx)
22793                .remote_id();
22794            let buffer_result_id = project
22795                .lsp_store()
22796                .read(cx)
22797                .result_id(server_id, buffer_id, cx);
22798            assert_eq!(expected, buffer_result_id);
22799        });
22800    };
22801
22802    ensure_result_id(None, cx);
22803    cx.executor().advance_clock(Duration::from_millis(60));
22804    cx.executor().run_until_parked();
22805    assert_eq!(
22806        diagnostic_requests.load(atomic::Ordering::Acquire),
22807        1,
22808        "Opening file should trigger diagnostic request"
22809    );
22810    first_request
22811        .next()
22812        .await
22813        .expect("should have sent the first diagnostics pull request");
22814    ensure_result_id(Some("1".to_string()), cx);
22815
22816    // Editing should trigger diagnostics
22817    editor.update_in(cx, |editor, window, cx| {
22818        editor.handle_input("2", window, cx)
22819    });
22820    cx.executor().advance_clock(Duration::from_millis(60));
22821    cx.executor().run_until_parked();
22822    assert_eq!(
22823        diagnostic_requests.load(atomic::Ordering::Acquire),
22824        2,
22825        "Editing should trigger diagnostic request"
22826    );
22827    ensure_result_id(Some("2".to_string()), cx);
22828
22829    // Moving cursor should not trigger diagnostic request
22830    editor.update_in(cx, |editor, window, cx| {
22831        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22832            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
22833        });
22834    });
22835    cx.executor().advance_clock(Duration::from_millis(60));
22836    cx.executor().run_until_parked();
22837    assert_eq!(
22838        diagnostic_requests.load(atomic::Ordering::Acquire),
22839        2,
22840        "Cursor movement should not trigger diagnostic request"
22841    );
22842    ensure_result_id(Some("2".to_string()), cx);
22843    // Multiple rapid edits should be debounced
22844    for _ in 0..5 {
22845        editor.update_in(cx, |editor, window, cx| {
22846            editor.handle_input("x", window, cx)
22847        });
22848    }
22849    cx.executor().advance_clock(Duration::from_millis(60));
22850    cx.executor().run_until_parked();
22851
22852    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
22853    assert!(
22854        final_requests <= 4,
22855        "Multiple rapid edits should be debounced (got {final_requests} requests)",
22856    );
22857    ensure_result_id(Some(final_requests.to_string()), cx);
22858}
22859
22860#[gpui::test]
22861async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
22862    // Regression test for issue #11671
22863    // Previously, adding a cursor after moving multiple cursors would reset
22864    // the cursor count instead of adding to the existing cursors.
22865    init_test(cx, |_| {});
22866    let mut cx = EditorTestContext::new(cx).await;
22867
22868    // Create a simple buffer with cursor at start
22869    cx.set_state(indoc! {"
22870        ˇaaaa
22871        bbbb
22872        cccc
22873        dddd
22874        eeee
22875        ffff
22876        gggg
22877        hhhh"});
22878
22879    // Add 2 cursors below (so we have 3 total)
22880    cx.update_editor(|editor, window, cx| {
22881        editor.add_selection_below(&Default::default(), window, cx);
22882        editor.add_selection_below(&Default::default(), window, cx);
22883    });
22884
22885    // Verify we have 3 cursors
22886    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
22887    assert_eq!(
22888        initial_count, 3,
22889        "Should have 3 cursors after adding 2 below"
22890    );
22891
22892    // Move down one line
22893    cx.update_editor(|editor, window, cx| {
22894        editor.move_down(&MoveDown, window, cx);
22895    });
22896
22897    // Add another cursor below
22898    cx.update_editor(|editor, window, cx| {
22899        editor.add_selection_below(&Default::default(), window, cx);
22900    });
22901
22902    // Should now have 4 cursors (3 original + 1 new)
22903    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
22904    assert_eq!(
22905        final_count, 4,
22906        "Should have 4 cursors after moving and adding another"
22907    );
22908}
22909
22910#[gpui::test(iterations = 10)]
22911async fn test_document_colors(cx: &mut TestAppContext) {
22912    let expected_color = Rgba {
22913        r: 0.33,
22914        g: 0.33,
22915        b: 0.33,
22916        a: 0.33,
22917    };
22918
22919    init_test(cx, |_| {});
22920
22921    let fs = FakeFs::new(cx.executor());
22922    fs.insert_tree(
22923        path!("/a"),
22924        json!({
22925            "first.rs": "fn main() { let a = 5; }",
22926        }),
22927    )
22928    .await;
22929
22930    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22931    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22932    let cx = &mut VisualTestContext::from_window(*workspace, cx);
22933
22934    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22935    language_registry.add(rust_lang());
22936    let mut fake_servers = language_registry.register_fake_lsp(
22937        "Rust",
22938        FakeLspAdapter {
22939            capabilities: lsp::ServerCapabilities {
22940                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
22941                ..lsp::ServerCapabilities::default()
22942            },
22943            name: "rust-analyzer",
22944            ..FakeLspAdapter::default()
22945        },
22946    );
22947    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
22948        "Rust",
22949        FakeLspAdapter {
22950            capabilities: lsp::ServerCapabilities {
22951                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
22952                ..lsp::ServerCapabilities::default()
22953            },
22954            name: "not-rust-analyzer",
22955            ..FakeLspAdapter::default()
22956        },
22957    );
22958
22959    let editor = workspace
22960        .update(cx, |workspace, window, cx| {
22961            workspace.open_abs_path(
22962                PathBuf::from(path!("/a/first.rs")),
22963                OpenOptions::default(),
22964                window,
22965                cx,
22966            )
22967        })
22968        .unwrap()
22969        .await
22970        .unwrap()
22971        .downcast::<Editor>()
22972        .unwrap();
22973    let fake_language_server = fake_servers.next().await.unwrap();
22974    let fake_language_server_without_capabilities =
22975        fake_servers_without_capabilities.next().await.unwrap();
22976    let requests_made = Arc::new(AtomicUsize::new(0));
22977    let closure_requests_made = Arc::clone(&requests_made);
22978    let mut color_request_handle = fake_language_server
22979        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
22980            let requests_made = Arc::clone(&closure_requests_made);
22981            async move {
22982                assert_eq!(
22983                    params.text_document.uri,
22984                    lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
22985                );
22986                requests_made.fetch_add(1, atomic::Ordering::Release);
22987                Ok(vec![
22988                    lsp::ColorInformation {
22989                        range: lsp::Range {
22990                            start: lsp::Position {
22991                                line: 0,
22992                                character: 0,
22993                            },
22994                            end: lsp::Position {
22995                                line: 0,
22996                                character: 1,
22997                            },
22998                        },
22999                        color: lsp::Color {
23000                            red: 0.33,
23001                            green: 0.33,
23002                            blue: 0.33,
23003                            alpha: 0.33,
23004                        },
23005                    },
23006                    lsp::ColorInformation {
23007                        range: lsp::Range {
23008                            start: lsp::Position {
23009                                line: 0,
23010                                character: 0,
23011                            },
23012                            end: lsp::Position {
23013                                line: 0,
23014                                character: 1,
23015                            },
23016                        },
23017                        color: lsp::Color {
23018                            red: 0.33,
23019                            green: 0.33,
23020                            blue: 0.33,
23021                            alpha: 0.33,
23022                        },
23023                    },
23024                ])
23025            }
23026        });
23027
23028    let _handle = fake_language_server_without_capabilities
23029        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
23030            panic!("Should not be called");
23031        });
23032    cx.executor().advance_clock(Duration::from_millis(100));
23033    color_request_handle.next().await.unwrap();
23034    cx.run_until_parked();
23035    assert_eq!(
23036        1,
23037        requests_made.load(atomic::Ordering::Acquire),
23038        "Should query for colors once per editor open"
23039    );
23040    editor.update_in(cx, |editor, _, cx| {
23041        assert_eq!(
23042            vec![expected_color],
23043            extract_color_inlays(editor, cx),
23044            "Should have an initial inlay"
23045        );
23046    });
23047
23048    // opening another file in a split should not influence the LSP query counter
23049    workspace
23050        .update(cx, |workspace, window, cx| {
23051            assert_eq!(
23052                workspace.panes().len(),
23053                1,
23054                "Should have one pane with one editor"
23055            );
23056            workspace.move_item_to_pane_in_direction(
23057                &MoveItemToPaneInDirection {
23058                    direction: SplitDirection::Right,
23059                    focus: false,
23060                    clone: true,
23061                },
23062                window,
23063                cx,
23064            );
23065        })
23066        .unwrap();
23067    cx.run_until_parked();
23068    workspace
23069        .update(cx, |workspace, _, cx| {
23070            let panes = workspace.panes();
23071            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
23072            for pane in panes {
23073                let editor = pane
23074                    .read(cx)
23075                    .active_item()
23076                    .and_then(|item| item.downcast::<Editor>())
23077                    .expect("Should have opened an editor in each split");
23078                let editor_file = editor
23079                    .read(cx)
23080                    .buffer()
23081                    .read(cx)
23082                    .as_singleton()
23083                    .expect("test deals with singleton buffers")
23084                    .read(cx)
23085                    .file()
23086                    .expect("test buffese should have a file")
23087                    .path();
23088                assert_eq!(
23089                    editor_file.as_ref(),
23090                    Path::new("first.rs"),
23091                    "Both editors should be opened for the same file"
23092                )
23093            }
23094        })
23095        .unwrap();
23096
23097    cx.executor().advance_clock(Duration::from_millis(500));
23098    let save = editor.update_in(cx, |editor, window, cx| {
23099        editor.move_to_end(&MoveToEnd, window, cx);
23100        editor.handle_input("dirty", window, cx);
23101        editor.save(
23102            SaveOptions {
23103                format: true,
23104                autosave: true,
23105            },
23106            project.clone(),
23107            window,
23108            cx,
23109        )
23110    });
23111    save.await.unwrap();
23112
23113    color_request_handle.next().await.unwrap();
23114    cx.run_until_parked();
23115    assert_eq!(
23116        3,
23117        requests_made.load(atomic::Ordering::Acquire),
23118        "Should query for colors once per save and once per formatting after save"
23119    );
23120
23121    drop(editor);
23122    let close = workspace
23123        .update(cx, |workspace, window, cx| {
23124            workspace.active_pane().update(cx, |pane, cx| {
23125                pane.close_active_item(&CloseActiveItem::default(), window, cx)
23126            })
23127        })
23128        .unwrap();
23129    close.await.unwrap();
23130    let close = workspace
23131        .update(cx, |workspace, window, cx| {
23132            workspace.active_pane().update(cx, |pane, cx| {
23133                pane.close_active_item(&CloseActiveItem::default(), window, cx)
23134            })
23135        })
23136        .unwrap();
23137    close.await.unwrap();
23138    assert_eq!(
23139        3,
23140        requests_made.load(atomic::Ordering::Acquire),
23141        "After saving and closing all editors, no extra requests should be made"
23142    );
23143    workspace
23144        .update(cx, |workspace, _, cx| {
23145            assert!(
23146                workspace.active_item(cx).is_none(),
23147                "Should close all editors"
23148            )
23149        })
23150        .unwrap();
23151
23152    workspace
23153        .update(cx, |workspace, window, cx| {
23154            workspace.active_pane().update(cx, |pane, cx| {
23155                pane.navigate_backward(window, cx);
23156            })
23157        })
23158        .unwrap();
23159    cx.executor().advance_clock(Duration::from_millis(100));
23160    cx.run_until_parked();
23161    let editor = workspace
23162        .update(cx, |workspace, _, cx| {
23163            workspace
23164                .active_item(cx)
23165                .expect("Should have reopened the editor again after navigating back")
23166                .downcast::<Editor>()
23167                .expect("Should be an editor")
23168        })
23169        .unwrap();
23170    color_request_handle.next().await.unwrap();
23171    assert_eq!(
23172        3,
23173        requests_made.load(atomic::Ordering::Acquire),
23174        "Cache should be reused on buffer close and reopen"
23175    );
23176    editor.update(cx, |editor, cx| {
23177        assert_eq!(
23178            vec![expected_color],
23179            extract_color_inlays(editor, cx),
23180            "Should have an initial inlay"
23181        );
23182    });
23183}
23184
23185#[gpui::test]
23186async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
23187    init_test(cx, |_| {});
23188    let (editor, cx) = cx.add_window_view(Editor::single_line);
23189    editor.update_in(cx, |editor, window, cx| {
23190        editor.set_text("oops\n\nwow\n", window, cx)
23191    });
23192    cx.run_until_parked();
23193    editor.update(cx, |editor, cx| {
23194        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
23195    });
23196    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
23197    cx.run_until_parked();
23198    editor.update(cx, |editor, cx| {
23199        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
23200    });
23201}
23202
23203#[track_caller]
23204fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
23205    editor
23206        .all_inlays(cx)
23207        .into_iter()
23208        .filter_map(|inlay| inlay.get_color())
23209        .map(Rgba::from)
23210        .collect()
23211}