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 reverse_lines()
 4079    cx.set_state(indoc! {"
 4080        «5
 4081        4
 4082        3
 4083        2
 4084        1ˇ»
 4085    "});
 4086    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4087    cx.assert_editor_state(indoc! {"
 4088        «1
 4089        2
 4090        3
 4091        4
 4092        5ˇ»
 4093    "});
 4094
 4095    // Skip testing shuffle_line()
 4096
 4097    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4098    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4099
 4100    // Don't manipulate when cursor is on single line, but expand the selection
 4101    cx.set_state(indoc! {"
 4102        ddˇdd
 4103        ccc
 4104        bb
 4105        a
 4106    "});
 4107    cx.update_editor(|e, window, cx| {
 4108        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4109    });
 4110    cx.assert_editor_state(indoc! {"
 4111        «ddddˇ»
 4112        ccc
 4113        bb
 4114        a
 4115    "});
 4116
 4117    // Basic manipulate case
 4118    // Start selection moves to column 0
 4119    // End of selection shrinks to fit shorter line
 4120    cx.set_state(indoc! {"
 4121        dd«d
 4122        ccc
 4123        bb
 4124        aaaaaˇ»
 4125    "});
 4126    cx.update_editor(|e, window, cx| {
 4127        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4128    });
 4129    cx.assert_editor_state(indoc! {"
 4130        «aaaaa
 4131        bb
 4132        ccc
 4133        dddˇ»
 4134    "});
 4135
 4136    // Manipulate case with newlines
 4137    cx.set_state(indoc! {"
 4138        dd«d
 4139        ccc
 4140
 4141        bb
 4142        aaaaa
 4143
 4144        ˇ»
 4145    "});
 4146    cx.update_editor(|e, window, cx| {
 4147        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4148    });
 4149    cx.assert_editor_state(indoc! {"
 4150        «
 4151
 4152        aaaaa
 4153        bb
 4154        ccc
 4155        dddˇ»
 4156
 4157    "});
 4158
 4159    // Adding new line
 4160    cx.set_state(indoc! {"
 4161        aa«a
 4162        bbˇ»b
 4163    "});
 4164    cx.update_editor(|e, window, cx| {
 4165        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4166    });
 4167    cx.assert_editor_state(indoc! {"
 4168        «aaa
 4169        bbb
 4170        added_lineˇ»
 4171    "});
 4172
 4173    // Removing line
 4174    cx.set_state(indoc! {"
 4175        aa«a
 4176        bbbˇ»
 4177    "});
 4178    cx.update_editor(|e, window, cx| {
 4179        e.manipulate_immutable_lines(window, cx, |lines| {
 4180            lines.pop();
 4181        })
 4182    });
 4183    cx.assert_editor_state(indoc! {"
 4184        «aaaˇ»
 4185    "});
 4186
 4187    // Removing all lines
 4188    cx.set_state(indoc! {"
 4189        aa«a
 4190        bbbˇ»
 4191    "});
 4192    cx.update_editor(|e, window, cx| {
 4193        e.manipulate_immutable_lines(window, cx, |lines| {
 4194            lines.drain(..);
 4195        })
 4196    });
 4197    cx.assert_editor_state(indoc! {"
 4198        ˇ
 4199    "});
 4200}
 4201
 4202#[gpui::test]
 4203async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4204    init_test(cx, |_| {});
 4205
 4206    let mut cx = EditorTestContext::new(cx).await;
 4207
 4208    // Consider continuous selection as single selection
 4209    cx.set_state(indoc! {"
 4210        Aaa«aa
 4211        cˇ»c«c
 4212        bb
 4213        aaaˇ»aa
 4214    "});
 4215    cx.update_editor(|e, window, cx| {
 4216        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4217    });
 4218    cx.assert_editor_state(indoc! {"
 4219        «Aaaaa
 4220        ccc
 4221        bb
 4222        aaaaaˇ»
 4223    "});
 4224
 4225    cx.set_state(indoc! {"
 4226        Aaa«aa
 4227        cˇ»c«c
 4228        bb
 4229        aaaˇ»aa
 4230    "});
 4231    cx.update_editor(|e, window, cx| {
 4232        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4233    });
 4234    cx.assert_editor_state(indoc! {"
 4235        «Aaaaa
 4236        ccc
 4237        bbˇ»
 4238    "});
 4239
 4240    // Consider non continuous selection as distinct dedup operations
 4241    cx.set_state(indoc! {"
 4242        «aaaaa
 4243        bb
 4244        aaaaa
 4245        aaaaaˇ»
 4246
 4247        aaa«aaˇ»
 4248    "});
 4249    cx.update_editor(|e, window, cx| {
 4250        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4251    });
 4252    cx.assert_editor_state(indoc! {"
 4253        «aaaaa
 4254        bbˇ»
 4255
 4256        «aaaaaˇ»
 4257    "});
 4258}
 4259
 4260#[gpui::test]
 4261async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4262    init_test(cx, |_| {});
 4263
 4264    let mut cx = EditorTestContext::new(cx).await;
 4265
 4266    cx.set_state(indoc! {"
 4267        «Aaa
 4268        aAa
 4269        Aaaˇ»
 4270    "});
 4271    cx.update_editor(|e, window, cx| {
 4272        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4273    });
 4274    cx.assert_editor_state(indoc! {"
 4275        «Aaa
 4276        aAaˇ»
 4277    "});
 4278
 4279    cx.set_state(indoc! {"
 4280        «Aaa
 4281        aAa
 4282        aaAˇ»
 4283    "});
 4284    cx.update_editor(|e, window, cx| {
 4285        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4286    });
 4287    cx.assert_editor_state(indoc! {"
 4288        «Aaaˇ»
 4289    "});
 4290}
 4291
 4292#[gpui::test]
 4293async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 4294    init_test(cx, |_| {});
 4295
 4296    let mut cx = EditorTestContext::new(cx).await;
 4297
 4298    // Manipulate with multiple selections on a single line
 4299    cx.set_state(indoc! {"
 4300        dd«dd
 4301        cˇ»c«c
 4302        bb
 4303        aaaˇ»aa
 4304    "});
 4305    cx.update_editor(|e, window, cx| {
 4306        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4307    });
 4308    cx.assert_editor_state(indoc! {"
 4309        «aaaaa
 4310        bb
 4311        ccc
 4312        ddddˇ»
 4313    "});
 4314
 4315    // Manipulate with multiple disjoin selections
 4316    cx.set_state(indoc! {"
 4317 4318        4
 4319        3
 4320        2
 4321        1ˇ»
 4322
 4323        dd«dd
 4324        ccc
 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        «1
 4333        2
 4334        3
 4335        4
 4336        5ˇ»
 4337
 4338        «aaaaa
 4339        bb
 4340        ccc
 4341        ddddˇ»
 4342    "});
 4343
 4344    // Adding lines on each selection
 4345    cx.set_state(indoc! {"
 4346 4347        1ˇ»
 4348
 4349        bb«bb
 4350        aaaˇ»aa
 4351    "});
 4352    cx.update_editor(|e, window, cx| {
 4353        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 4354    });
 4355    cx.assert_editor_state(indoc! {"
 4356        «2
 4357        1
 4358        added lineˇ»
 4359
 4360        «bbbb
 4361        aaaaa
 4362        added lineˇ»
 4363    "});
 4364
 4365    // Removing lines on each selection
 4366    cx.set_state(indoc! {"
 4367 4368        1ˇ»
 4369
 4370        bb«bb
 4371        aaaˇ»aa
 4372    "});
 4373    cx.update_editor(|e, window, cx| {
 4374        e.manipulate_immutable_lines(window, cx, |lines| {
 4375            lines.pop();
 4376        })
 4377    });
 4378    cx.assert_editor_state(indoc! {"
 4379        «2ˇ»
 4380
 4381        «bbbbˇ»
 4382    "});
 4383}
 4384
 4385#[gpui::test]
 4386async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 4387    init_test(cx, |settings| {
 4388        settings.defaults.tab_size = NonZeroU32::new(3)
 4389    });
 4390
 4391    let mut cx = EditorTestContext::new(cx).await;
 4392
 4393    // MULTI SELECTION
 4394    // Ln.1 "«" tests empty lines
 4395    // Ln.9 tests just leading whitespace
 4396    cx.set_state(indoc! {"
 4397        «
 4398        abc                 // No indentationˇ»
 4399        «\tabc              // 1 tabˇ»
 4400        \t\tabc «      ˇ»   // 2 tabs
 4401        \t ab«c             // Tab followed by space
 4402         \tabc              // Space followed by tab (3 spaces should be the result)
 4403        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4404           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 4405        \t
 4406        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4407    "});
 4408    cx.update_editor(|e, window, cx| {
 4409        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4410    });
 4411    cx.assert_editor_state(
 4412        indoc! {"
 4413            «
 4414            abc                 // No indentation
 4415               abc              // 1 tab
 4416                  abc          // 2 tabs
 4417                abc             // Tab followed by space
 4418               abc              // Space followed by tab (3 spaces should be the result)
 4419                           abc   // Mixed indentation (tab conversion depends on the column)
 4420               abc         // Already space indented
 4421               ·
 4422               abc\tdef          // Only the leading tab is manipulatedˇ»
 4423        "}
 4424        .replace("·", "")
 4425        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4426    );
 4427
 4428    // Test on just a few lines, the others should remain unchanged
 4429    // Only lines (3, 5, 10, 11) should change
 4430    cx.set_state(
 4431        indoc! {"
 4432            ·
 4433            abc                 // No indentation
 4434            \tabcˇ               // 1 tab
 4435            \t\tabc             // 2 tabs
 4436            \t abcˇ              // Tab followed by space
 4437             \tabc              // Space followed by tab (3 spaces should be the result)
 4438            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4439               abc              // Already space indented
 4440            «\t
 4441            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4442        "}
 4443        .replace("·", "")
 4444        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4445    );
 4446    cx.update_editor(|e, window, cx| {
 4447        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4448    });
 4449    cx.assert_editor_state(
 4450        indoc! {"
 4451            ·
 4452            abc                 // No indentation
 4453            «   abc               // 1 tabˇ»
 4454            \t\tabc             // 2 tabs
 4455            «    abc              // Tab followed by spaceˇ»
 4456             \tabc              // Space followed by tab (3 spaces should be the result)
 4457            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4458               abc              // Already space indented
 4459            «   ·
 4460               abc\tdef          // Only the leading tab is manipulatedˇ»
 4461        "}
 4462        .replace("·", "")
 4463        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4464    );
 4465
 4466    // SINGLE SELECTION
 4467    // Ln.1 "«" tests empty lines
 4468    // Ln.9 tests just leading whitespace
 4469    cx.set_state(indoc! {"
 4470        «
 4471        abc                 // No indentation
 4472        \tabc               // 1 tab
 4473        \t\tabc             // 2 tabs
 4474        \t abc              // Tab followed by space
 4475         \tabc              // Space followed by tab (3 spaces should be the result)
 4476        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4477           abc              // Already space indented
 4478        \t
 4479        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4480    "});
 4481    cx.update_editor(|e, window, cx| {
 4482        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4483    });
 4484    cx.assert_editor_state(
 4485        indoc! {"
 4486            «
 4487            abc                 // No indentation
 4488               abc               // 1 tab
 4489                  abc             // 2 tabs
 4490                abc              // Tab followed by space
 4491               abc              // Space followed by tab (3 spaces should be the result)
 4492                           abc   // Mixed indentation (tab conversion depends on the column)
 4493               abc              // Already space indented
 4494               ·
 4495               abc\tdef          // Only the leading tab is manipulatedˇ»
 4496        "}
 4497        .replace("·", "")
 4498        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4499    );
 4500}
 4501
 4502#[gpui::test]
 4503async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 4504    init_test(cx, |settings| {
 4505        settings.defaults.tab_size = NonZeroU32::new(3)
 4506    });
 4507
 4508    let mut cx = EditorTestContext::new(cx).await;
 4509
 4510    // MULTI SELECTION
 4511    // Ln.1 "«" tests empty lines
 4512    // Ln.11 tests just leading whitespace
 4513    cx.set_state(indoc! {"
 4514        «
 4515        abˇ»ˇc                 // No indentation
 4516         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 4517          abc  «             // 2 spaces (< 3 so dont convert)
 4518           abc              // 3 spaces (convert)
 4519             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 4520        «\tˇ»\t«\tˇ»abc           // Already tab indented
 4521        «\t abc              // Tab followed by space
 4522         \tabc              // Space followed by tab (should be consumed due to tab)
 4523        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4524           \tˇ»  «\t
 4525           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 4526    "});
 4527    cx.update_editor(|e, window, cx| {
 4528        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4529    });
 4530    cx.assert_editor_state(indoc! {"
 4531        «
 4532        abc                 // No indentation
 4533         abc                // 1 space (< 3 so dont convert)
 4534          abc               // 2 spaces (< 3 so dont convert)
 4535        \tabc              // 3 spaces (convert)
 4536        \t  abc            // 5 spaces (1 tab + 2 spaces)
 4537        \t\t\tabc           // Already tab indented
 4538        \t abc              // Tab followed by space
 4539        \tabc              // Space followed by tab (should be consumed due to tab)
 4540        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4541        \t\t\t
 4542        \tabc   \t         // Only the leading spaces should be convertedˇ»
 4543    "});
 4544
 4545    // Test on just a few lines, the other should remain unchanged
 4546    // Only lines (4, 8, 11, 12) should change
 4547    cx.set_state(
 4548        indoc! {"
 4549            ·
 4550            abc                 // No indentation
 4551             abc                // 1 space (< 3 so dont convert)
 4552              abc               // 2 spaces (< 3 so dont convert)
 4553            «   abc              // 3 spaces (convert)ˇ»
 4554                 abc            // 5 spaces (1 tab + 2 spaces)
 4555            \t\t\tabc           // Already tab indented
 4556            \t abc              // Tab followed by space
 4557             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 4558               \t\t  \tabc      // Mixed indentation
 4559            \t \t  \t   \tabc   // Mixed indentation
 4560               \t  \tˇ
 4561            «   abc   \t         // Only the leading spaces should be convertedˇ»
 4562        "}
 4563        .replace("·", "")
 4564        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4565    );
 4566    cx.update_editor(|e, window, cx| {
 4567        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4568    });
 4569    cx.assert_editor_state(
 4570        indoc! {"
 4571            ·
 4572            abc                 // No indentation
 4573             abc                // 1 space (< 3 so dont convert)
 4574              abc               // 2 spaces (< 3 so dont convert)
 4575            «\tabc              // 3 spaces (convert)ˇ»
 4576                 abc            // 5 spaces (1 tab + 2 spaces)
 4577            \t\t\tabc           // Already tab indented
 4578            \t abc              // Tab followed by space
 4579            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 4580               \t\t  \tabc      // Mixed indentation
 4581            \t \t  \t   \tabc   // Mixed indentation
 4582            «\t\t\t
 4583            \tabc   \t         // Only the leading spaces should be convertedˇ»
 4584        "}
 4585        .replace("·", "")
 4586        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4587    );
 4588
 4589    // SINGLE SELECTION
 4590    // Ln.1 "«" tests empty lines
 4591    // Ln.11 tests just leading whitespace
 4592    cx.set_state(indoc! {"
 4593        «
 4594        abc                 // No indentation
 4595         abc                // 1 space (< 3 so dont convert)
 4596          abc               // 2 spaces (< 3 so dont convert)
 4597           abc              // 3 spaces (convert)
 4598             abc            // 5 spaces (1 tab + 2 spaces)
 4599        \t\t\tabc           // Already tab indented
 4600        \t abc              // Tab followed by space
 4601         \tabc              // Space followed by tab (should be consumed due to tab)
 4602        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4603           \t  \t
 4604           abc   \t         // Only the leading spaces should be convertedˇ»
 4605    "});
 4606    cx.update_editor(|e, window, cx| {
 4607        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4608    });
 4609    cx.assert_editor_state(indoc! {"
 4610        «
 4611        abc                 // No indentation
 4612         abc                // 1 space (< 3 so dont convert)
 4613          abc               // 2 spaces (< 3 so dont convert)
 4614        \tabc              // 3 spaces (convert)
 4615        \t  abc            // 5 spaces (1 tab + 2 spaces)
 4616        \t\t\tabc           // Already tab indented
 4617        \t abc              // Tab followed by space
 4618        \tabc              // Space followed by tab (should be consumed due to tab)
 4619        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4620        \t\t\t
 4621        \tabc   \t         // Only the leading spaces should be convertedˇ»
 4622    "});
 4623}
 4624
 4625#[gpui::test]
 4626async fn test_toggle_case(cx: &mut TestAppContext) {
 4627    init_test(cx, |_| {});
 4628
 4629    let mut cx = EditorTestContext::new(cx).await;
 4630
 4631    // If all lower case -> upper case
 4632    cx.set_state(indoc! {"
 4633        «hello worldˇ»
 4634    "});
 4635    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4636    cx.assert_editor_state(indoc! {"
 4637        «HELLO WORLDˇ»
 4638    "});
 4639
 4640    // If all upper case -> lower case
 4641    cx.set_state(indoc! {"
 4642        «HELLO WORLDˇ»
 4643    "});
 4644    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4645    cx.assert_editor_state(indoc! {"
 4646        «hello worldˇ»
 4647    "});
 4648
 4649    // If any upper case characters are identified -> lower case
 4650    // This matches JetBrains IDEs
 4651    cx.set_state(indoc! {"
 4652        «hEllo worldˇ»
 4653    "});
 4654    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4655    cx.assert_editor_state(indoc! {"
 4656        «hello worldˇ»
 4657    "});
 4658}
 4659
 4660#[gpui::test]
 4661async fn test_manipulate_text(cx: &mut TestAppContext) {
 4662    init_test(cx, |_| {});
 4663
 4664    let mut cx = EditorTestContext::new(cx).await;
 4665
 4666    // Test convert_to_upper_case()
 4667    cx.set_state(indoc! {"
 4668        «hello worldˇ»
 4669    "});
 4670    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4671    cx.assert_editor_state(indoc! {"
 4672        «HELLO WORLDˇ»
 4673    "});
 4674
 4675    // Test convert_to_lower_case()
 4676    cx.set_state(indoc! {"
 4677        «HELLO WORLDˇ»
 4678    "});
 4679    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 4680    cx.assert_editor_state(indoc! {"
 4681        «hello worldˇ»
 4682    "});
 4683
 4684    // Test multiple line, single selection case
 4685    cx.set_state(indoc! {"
 4686        «The quick brown
 4687        fox jumps over
 4688        the lazy dogˇ»
 4689    "});
 4690    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 4691    cx.assert_editor_state(indoc! {"
 4692        «The Quick Brown
 4693        Fox Jumps Over
 4694        The Lazy Dogˇ»
 4695    "});
 4696
 4697    // Test multiple line, single selection case
 4698    cx.set_state(indoc! {"
 4699        «The quick brown
 4700        fox jumps over
 4701        the lazy dogˇ»
 4702    "});
 4703    cx.update_editor(|e, window, cx| {
 4704        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 4705    });
 4706    cx.assert_editor_state(indoc! {"
 4707        «TheQuickBrown
 4708        FoxJumpsOver
 4709        TheLazyDogˇ»
 4710    "});
 4711
 4712    // From here on out, test more complex cases of manipulate_text()
 4713
 4714    // Test no selection case - should affect words cursors are in
 4715    // Cursor at beginning, middle, and end of word
 4716    cx.set_state(indoc! {"
 4717        ˇhello big beauˇtiful worldˇ
 4718    "});
 4719    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4720    cx.assert_editor_state(indoc! {"
 4721        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 4722    "});
 4723
 4724    // Test multiple selections on a single line and across multiple lines
 4725    cx.set_state(indoc! {"
 4726        «Theˇ» quick «brown
 4727        foxˇ» jumps «overˇ»
 4728        the «lazyˇ» dog
 4729    "});
 4730    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4731    cx.assert_editor_state(indoc! {"
 4732        «THEˇ» quick «BROWN
 4733        FOXˇ» jumps «OVERˇ»
 4734        the «LAZYˇ» dog
 4735    "});
 4736
 4737    // Test case where text length grows
 4738    cx.set_state(indoc! {"
 4739        «tschüߡ»
 4740    "});
 4741    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4742    cx.assert_editor_state(indoc! {"
 4743        «TSCHÜSSˇ»
 4744    "});
 4745
 4746    // Test to make sure we don't crash when text shrinks
 4747    cx.set_state(indoc! {"
 4748        aaa_bbbˇ
 4749    "});
 4750    cx.update_editor(|e, window, cx| {
 4751        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4752    });
 4753    cx.assert_editor_state(indoc! {"
 4754        «aaaBbbˇ»
 4755    "});
 4756
 4757    // Test to make sure we all aware of the fact that each word can grow and shrink
 4758    // Final selections should be aware of this fact
 4759    cx.set_state(indoc! {"
 4760        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 4761    "});
 4762    cx.update_editor(|e, window, cx| {
 4763        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4764    });
 4765    cx.assert_editor_state(indoc! {"
 4766        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 4767    "});
 4768
 4769    cx.set_state(indoc! {"
 4770        «hElLo, WoRld!ˇ»
 4771    "});
 4772    cx.update_editor(|e, window, cx| {
 4773        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 4774    });
 4775    cx.assert_editor_state(indoc! {"
 4776        «HeLlO, wOrLD!ˇ»
 4777    "});
 4778}
 4779
 4780#[gpui::test]
 4781fn test_duplicate_line(cx: &mut TestAppContext) {
 4782    init_test(cx, |_| {});
 4783
 4784    let editor = cx.add_window(|window, cx| {
 4785        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4786        build_editor(buffer, window, cx)
 4787    });
 4788    _ = editor.update(cx, |editor, window, cx| {
 4789        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4790            s.select_display_ranges([
 4791                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4792                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4793                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4794                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4795            ])
 4796        });
 4797        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4798        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4799        assert_eq!(
 4800            editor.selections.display_ranges(cx),
 4801            vec![
 4802                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4803                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 4804                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4805                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4806            ]
 4807        );
 4808    });
 4809
 4810    let editor = cx.add_window(|window, cx| {
 4811        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4812        build_editor(buffer, window, cx)
 4813    });
 4814    _ = editor.update(cx, |editor, window, cx| {
 4815        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4816            s.select_display_ranges([
 4817                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4818                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4819            ])
 4820        });
 4821        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4822        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4823        assert_eq!(
 4824            editor.selections.display_ranges(cx),
 4825            vec![
 4826                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 4827                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 4828            ]
 4829        );
 4830    });
 4831
 4832    // With `move_upwards` the selections stay in place, except for
 4833    // the lines inserted above them
 4834    let editor = cx.add_window(|window, cx| {
 4835        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4836        build_editor(buffer, window, cx)
 4837    });
 4838    _ = editor.update(cx, |editor, window, cx| {
 4839        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4840            s.select_display_ranges([
 4841                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4842                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4843                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4844                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4845            ])
 4846        });
 4847        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4848        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4849        assert_eq!(
 4850            editor.selections.display_ranges(cx),
 4851            vec![
 4852                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4853                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4854                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 4855                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4856            ]
 4857        );
 4858    });
 4859
 4860    let editor = cx.add_window(|window, cx| {
 4861        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4862        build_editor(buffer, window, cx)
 4863    });
 4864    _ = editor.update(cx, |editor, window, cx| {
 4865        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4866            s.select_display_ranges([
 4867                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4868                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4869            ])
 4870        });
 4871        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4872        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4873        assert_eq!(
 4874            editor.selections.display_ranges(cx),
 4875            vec![
 4876                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4877                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4878            ]
 4879        );
 4880    });
 4881
 4882    let editor = cx.add_window(|window, cx| {
 4883        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4884        build_editor(buffer, window, cx)
 4885    });
 4886    _ = editor.update(cx, |editor, window, cx| {
 4887        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4888            s.select_display_ranges([
 4889                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4890                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4891            ])
 4892        });
 4893        editor.duplicate_selection(&DuplicateSelection, window, cx);
 4894        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 4895        assert_eq!(
 4896            editor.selections.display_ranges(cx),
 4897            vec![
 4898                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4899                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 4900            ]
 4901        );
 4902    });
 4903}
 4904
 4905#[gpui::test]
 4906fn test_move_line_up_down(cx: &mut TestAppContext) {
 4907    init_test(cx, |_| {});
 4908
 4909    let editor = cx.add_window(|window, cx| {
 4910        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 4911        build_editor(buffer, window, cx)
 4912    });
 4913    _ = editor.update(cx, |editor, window, cx| {
 4914        editor.fold_creases(
 4915            vec![
 4916                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 4917                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 4918                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 4919            ],
 4920            true,
 4921            window,
 4922            cx,
 4923        );
 4924        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4925            s.select_display_ranges([
 4926                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4927                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4928                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4929                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 4930            ])
 4931        });
 4932        assert_eq!(
 4933            editor.display_text(cx),
 4934            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 4935        );
 4936
 4937        editor.move_line_up(&MoveLineUp, window, cx);
 4938        assert_eq!(
 4939            editor.display_text(cx),
 4940            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 4941        );
 4942        assert_eq!(
 4943            editor.selections.display_ranges(cx),
 4944            vec![
 4945                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4946                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4947                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 4948                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 4949            ]
 4950        );
 4951    });
 4952
 4953    _ = editor.update(cx, |editor, window, cx| {
 4954        editor.move_line_down(&MoveLineDown, window, cx);
 4955        assert_eq!(
 4956            editor.display_text(cx),
 4957            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 4958        );
 4959        assert_eq!(
 4960            editor.selections.display_ranges(cx),
 4961            vec![
 4962                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4963                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4964                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4965                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 4966            ]
 4967        );
 4968    });
 4969
 4970    _ = editor.update(cx, |editor, window, cx| {
 4971        editor.move_line_down(&MoveLineDown, window, cx);
 4972        assert_eq!(
 4973            editor.display_text(cx),
 4974            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 4975        );
 4976        assert_eq!(
 4977            editor.selections.display_ranges(cx),
 4978            vec![
 4979                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4980                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4981                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4982                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 4983            ]
 4984        );
 4985    });
 4986
 4987    _ = editor.update(cx, |editor, window, cx| {
 4988        editor.move_line_up(&MoveLineUp, window, cx);
 4989        assert_eq!(
 4990            editor.display_text(cx),
 4991            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 4992        );
 4993        assert_eq!(
 4994            editor.selections.display_ranges(cx),
 4995            vec![
 4996                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4997                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4998                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 4999                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5000            ]
 5001        );
 5002    });
 5003}
 5004
 5005#[gpui::test]
 5006fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 5007    init_test(cx, |_| {});
 5008
 5009    let editor = cx.add_window(|window, cx| {
 5010        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5011        build_editor(buffer, window, cx)
 5012    });
 5013    _ = editor.update(cx, |editor, window, cx| {
 5014        let snapshot = editor.buffer.read(cx).snapshot(cx);
 5015        editor.insert_blocks(
 5016            [BlockProperties {
 5017                style: BlockStyle::Fixed,
 5018                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 5019                height: Some(1),
 5020                render: Arc::new(|_| div().into_any()),
 5021                priority: 0,
 5022                render_in_minimap: true,
 5023            }],
 5024            Some(Autoscroll::fit()),
 5025            cx,
 5026        );
 5027        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5028            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 5029        });
 5030        editor.move_line_down(&MoveLineDown, window, cx);
 5031    });
 5032}
 5033
 5034#[gpui::test]
 5035async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 5036    init_test(cx, |_| {});
 5037
 5038    let mut cx = EditorTestContext::new(cx).await;
 5039    cx.set_state(
 5040        &"
 5041            ˇzero
 5042            one
 5043            two
 5044            three
 5045            four
 5046            five
 5047        "
 5048        .unindent(),
 5049    );
 5050
 5051    // Create a four-line block that replaces three lines of text.
 5052    cx.update_editor(|editor, window, cx| {
 5053        let snapshot = editor.snapshot(window, cx);
 5054        let snapshot = &snapshot.buffer_snapshot;
 5055        let placement = BlockPlacement::Replace(
 5056            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 5057        );
 5058        editor.insert_blocks(
 5059            [BlockProperties {
 5060                placement,
 5061                height: Some(4),
 5062                style: BlockStyle::Sticky,
 5063                render: Arc::new(|_| gpui::div().into_any_element()),
 5064                priority: 0,
 5065                render_in_minimap: true,
 5066            }],
 5067            None,
 5068            cx,
 5069        );
 5070    });
 5071
 5072    // Move down so that the cursor touches the block.
 5073    cx.update_editor(|editor, window, cx| {
 5074        editor.move_down(&Default::default(), window, cx);
 5075    });
 5076    cx.assert_editor_state(
 5077        &"
 5078            zero
 5079            «one
 5080            two
 5081            threeˇ»
 5082            four
 5083            five
 5084        "
 5085        .unindent(),
 5086    );
 5087
 5088    // Move down past the block.
 5089    cx.update_editor(|editor, window, cx| {
 5090        editor.move_down(&Default::default(), window, cx);
 5091    });
 5092    cx.assert_editor_state(
 5093        &"
 5094            zero
 5095            one
 5096            two
 5097            three
 5098            ˇfour
 5099            five
 5100        "
 5101        .unindent(),
 5102    );
 5103}
 5104
 5105#[gpui::test]
 5106fn test_transpose(cx: &mut TestAppContext) {
 5107    init_test(cx, |_| {});
 5108
 5109    _ = cx.add_window(|window, cx| {
 5110        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 5111        editor.set_style(EditorStyle::default(), window, cx);
 5112        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5113            s.select_ranges([1..1])
 5114        });
 5115        editor.transpose(&Default::default(), window, cx);
 5116        assert_eq!(editor.text(cx), "bac");
 5117        assert_eq!(editor.selections.ranges(cx), [2..2]);
 5118
 5119        editor.transpose(&Default::default(), window, cx);
 5120        assert_eq!(editor.text(cx), "bca");
 5121        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5122
 5123        editor.transpose(&Default::default(), window, cx);
 5124        assert_eq!(editor.text(cx), "bac");
 5125        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5126
 5127        editor
 5128    });
 5129
 5130    _ = cx.add_window(|window, cx| {
 5131        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5132        editor.set_style(EditorStyle::default(), window, cx);
 5133        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5134            s.select_ranges([3..3])
 5135        });
 5136        editor.transpose(&Default::default(), window, cx);
 5137        assert_eq!(editor.text(cx), "acb\nde");
 5138        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5139
 5140        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5141            s.select_ranges([4..4])
 5142        });
 5143        editor.transpose(&Default::default(), window, cx);
 5144        assert_eq!(editor.text(cx), "acbd\ne");
 5145        assert_eq!(editor.selections.ranges(cx), [5..5]);
 5146
 5147        editor.transpose(&Default::default(), window, cx);
 5148        assert_eq!(editor.text(cx), "acbde\n");
 5149        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5150
 5151        editor.transpose(&Default::default(), window, cx);
 5152        assert_eq!(editor.text(cx), "acbd\ne");
 5153        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5154
 5155        editor
 5156    });
 5157
 5158    _ = cx.add_window(|window, cx| {
 5159        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5160        editor.set_style(EditorStyle::default(), window, cx);
 5161        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5162            s.select_ranges([1..1, 2..2, 4..4])
 5163        });
 5164        editor.transpose(&Default::default(), window, cx);
 5165        assert_eq!(editor.text(cx), "bacd\ne");
 5166        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 5167
 5168        editor.transpose(&Default::default(), window, cx);
 5169        assert_eq!(editor.text(cx), "bcade\n");
 5170        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 5171
 5172        editor.transpose(&Default::default(), window, cx);
 5173        assert_eq!(editor.text(cx), "bcda\ne");
 5174        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5175
 5176        editor.transpose(&Default::default(), window, cx);
 5177        assert_eq!(editor.text(cx), "bcade\n");
 5178        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5179
 5180        editor.transpose(&Default::default(), window, cx);
 5181        assert_eq!(editor.text(cx), "bcaed\n");
 5182        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 5183
 5184        editor
 5185    });
 5186
 5187    _ = cx.add_window(|window, cx| {
 5188        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 5189        editor.set_style(EditorStyle::default(), window, cx);
 5190        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5191            s.select_ranges([4..4])
 5192        });
 5193        editor.transpose(&Default::default(), window, cx);
 5194        assert_eq!(editor.text(cx), "🏀🍐✋");
 5195        assert_eq!(editor.selections.ranges(cx), [8..8]);
 5196
 5197        editor.transpose(&Default::default(), window, cx);
 5198        assert_eq!(editor.text(cx), "🏀✋🍐");
 5199        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5200
 5201        editor.transpose(&Default::default(), window, cx);
 5202        assert_eq!(editor.text(cx), "🏀🍐✋");
 5203        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5204
 5205        editor
 5206    });
 5207}
 5208
 5209#[gpui::test]
 5210async fn test_rewrap(cx: &mut TestAppContext) {
 5211    init_test(cx, |settings| {
 5212        settings.languages.0.extend([
 5213            (
 5214                "Markdown".into(),
 5215                LanguageSettingsContent {
 5216                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5217                    preferred_line_length: Some(40),
 5218                    ..Default::default()
 5219                },
 5220            ),
 5221            (
 5222                "Plain Text".into(),
 5223                LanguageSettingsContent {
 5224                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5225                    preferred_line_length: Some(40),
 5226                    ..Default::default()
 5227                },
 5228            ),
 5229            (
 5230                "C++".into(),
 5231                LanguageSettingsContent {
 5232                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5233                    preferred_line_length: Some(40),
 5234                    ..Default::default()
 5235                },
 5236            ),
 5237            (
 5238                "Python".into(),
 5239                LanguageSettingsContent {
 5240                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5241                    preferred_line_length: Some(40),
 5242                    ..Default::default()
 5243                },
 5244            ),
 5245            (
 5246                "Rust".into(),
 5247                LanguageSettingsContent {
 5248                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5249                    preferred_line_length: Some(40),
 5250                    ..Default::default()
 5251                },
 5252            ),
 5253        ])
 5254    });
 5255
 5256    let mut cx = EditorTestContext::new(cx).await;
 5257
 5258    let cpp_language = Arc::new(Language::new(
 5259        LanguageConfig {
 5260            name: "C++".into(),
 5261            line_comments: vec!["// ".into()],
 5262            ..LanguageConfig::default()
 5263        },
 5264        None,
 5265    ));
 5266    let python_language = Arc::new(Language::new(
 5267        LanguageConfig {
 5268            name: "Python".into(),
 5269            line_comments: vec!["# ".into()],
 5270            ..LanguageConfig::default()
 5271        },
 5272        None,
 5273    ));
 5274    let markdown_language = Arc::new(Language::new(
 5275        LanguageConfig {
 5276            name: "Markdown".into(),
 5277            rewrap_prefixes: vec![
 5278                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 5279                regex::Regex::new("[-*+]\\s+").unwrap(),
 5280            ],
 5281            ..LanguageConfig::default()
 5282        },
 5283        None,
 5284    ));
 5285    let rust_language = Arc::new(Language::new(
 5286        LanguageConfig {
 5287            name: "Rust".into(),
 5288            line_comments: vec!["// ".into(), "/// ".into()],
 5289            ..LanguageConfig::default()
 5290        },
 5291        Some(tree_sitter_rust::LANGUAGE.into()),
 5292    ));
 5293
 5294    let plaintext_language = Arc::new(Language::new(
 5295        LanguageConfig {
 5296            name: "Plain Text".into(),
 5297            ..LanguageConfig::default()
 5298        },
 5299        None,
 5300    ));
 5301
 5302    // Test basic rewrapping of a long line with a cursor
 5303    assert_rewrap(
 5304        indoc! {"
 5305            // ˇThis is a long comment that needs to be wrapped.
 5306        "},
 5307        indoc! {"
 5308            // ˇThis is a long comment that needs to
 5309            // be wrapped.
 5310        "},
 5311        cpp_language.clone(),
 5312        &mut cx,
 5313    );
 5314
 5315    // Test rewrapping a full selection
 5316    assert_rewrap(
 5317        indoc! {"
 5318            «// This selected long comment needs to be wrapped.ˇ»"
 5319        },
 5320        indoc! {"
 5321            «// This selected long comment needs to
 5322            // be wrapped.ˇ»"
 5323        },
 5324        cpp_language.clone(),
 5325        &mut cx,
 5326    );
 5327
 5328    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 5329    assert_rewrap(
 5330        indoc! {"
 5331            // ˇThis is the first line.
 5332            // Thisˇ is the second line.
 5333            // This is the thirdˇ line, all part of one paragraph.
 5334         "},
 5335        indoc! {"
 5336            // ˇThis is the first line. Thisˇ is the
 5337            // second line. This is the thirdˇ line,
 5338            // all part of one paragraph.
 5339         "},
 5340        cpp_language.clone(),
 5341        &mut cx,
 5342    );
 5343
 5344    // Test multiple cursors in different paragraphs trigger separate rewraps
 5345    assert_rewrap(
 5346        indoc! {"
 5347            // ˇThis is the first paragraph, first line.
 5348            // ˇThis is the first paragraph, second line.
 5349
 5350            // ˇThis is the second paragraph, first line.
 5351            // ˇThis is the second paragraph, second line.
 5352        "},
 5353        indoc! {"
 5354            // ˇThis is the first paragraph, first
 5355            // line. ˇThis is the first paragraph,
 5356            // second line.
 5357
 5358            // ˇThis is the second paragraph, first
 5359            // line. ˇThis is the second paragraph,
 5360            // second line.
 5361        "},
 5362        cpp_language.clone(),
 5363        &mut cx,
 5364    );
 5365
 5366    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 5367    assert_rewrap(
 5368        indoc! {"
 5369            «// A regular long long comment to be wrapped.
 5370            /// A documentation long comment to be wrapped.ˇ»
 5371          "},
 5372        indoc! {"
 5373            «// A regular long long comment to be
 5374            // wrapped.
 5375            /// A documentation long comment to be
 5376            /// wrapped.ˇ»
 5377          "},
 5378        rust_language.clone(),
 5379        &mut cx,
 5380    );
 5381
 5382    // Test that change in indentation level trigger seperate rewraps
 5383    assert_rewrap(
 5384        indoc! {"
 5385            fn foo() {
 5386                «// This is a long comment at the base indent.
 5387                    // This is a long comment at the next indent.ˇ»
 5388            }
 5389        "},
 5390        indoc! {"
 5391            fn foo() {
 5392                «// This is a long comment at the
 5393                // base indent.
 5394                    // This is a long comment at the
 5395                    // next indent.ˇ»
 5396            }
 5397        "},
 5398        rust_language.clone(),
 5399        &mut cx,
 5400    );
 5401
 5402    // Test that different comment prefix characters (e.g., '#') are handled correctly
 5403    assert_rewrap(
 5404        indoc! {"
 5405            # ˇThis is a long comment using a pound sign.
 5406        "},
 5407        indoc! {"
 5408            # ˇThis is a long comment using a pound
 5409            # sign.
 5410        "},
 5411        python_language.clone(),
 5412        &mut cx,
 5413    );
 5414
 5415    // Test rewrapping only affects comments, not code even when selected
 5416    assert_rewrap(
 5417        indoc! {"
 5418            «/// This doc comment is long and should be wrapped.
 5419            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 5420        "},
 5421        indoc! {"
 5422            «/// This doc comment is long and should
 5423            /// be wrapped.
 5424            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 5425        "},
 5426        rust_language.clone(),
 5427        &mut cx,
 5428    );
 5429
 5430    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 5431    assert_rewrap(
 5432        indoc! {"
 5433            # Header
 5434
 5435            A long long long line of markdown text to wrap.ˇ
 5436         "},
 5437        indoc! {"
 5438            # Header
 5439
 5440            A long long long line of markdown text
 5441            to wrap.ˇ
 5442         "},
 5443        markdown_language.clone(),
 5444        &mut cx,
 5445    );
 5446
 5447    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 5448    assert_rewrap(
 5449        indoc! {"
 5450            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 5451            2. This is a numbered list item that is very long and needs to be wrapped properly.
 5452            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 5453        "},
 5454        indoc! {"
 5455            «1. This is a numbered list item that is
 5456               very long and needs to be wrapped
 5457               properly.
 5458            2. This is a numbered list item that is
 5459               very long and needs to be wrapped
 5460               properly.
 5461            - This is an unordered list item that is
 5462              also very long and should not merge
 5463              with the numbered item.ˇ»
 5464        "},
 5465        markdown_language.clone(),
 5466        &mut cx,
 5467    );
 5468
 5469    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 5470    assert_rewrap(
 5471        indoc! {"
 5472            «1. This is a numbered list item that is
 5473            very long and needs to be wrapped
 5474            properly.
 5475            2. This is a numbered list item that is
 5476            very long and needs to be wrapped
 5477            properly.
 5478            - This is an unordered list item that is
 5479            also very long and should not merge with
 5480            the numbered item.ˇ»
 5481        "},
 5482        indoc! {"
 5483            «1. This is a numbered list item that is
 5484               very long and needs to be wrapped
 5485               properly.
 5486            2. This is a numbered list item that is
 5487               very long and needs to be wrapped
 5488               properly.
 5489            - This is an unordered list item that is
 5490              also very long and should not merge
 5491              with the numbered item.ˇ»
 5492        "},
 5493        markdown_language.clone(),
 5494        &mut cx,
 5495    );
 5496
 5497    // Test that rewrapping maintain indents even when they already exists.
 5498    assert_rewrap(
 5499        indoc! {"
 5500            «1. This is a numbered list
 5501               item that is very long and needs to be wrapped properly.
 5502            2. This is a numbered list
 5503               item that is very long and needs to be wrapped properly.
 5504            - This is an unordered list item that is also very long and
 5505              should not merge with the numbered item.ˇ»
 5506        "},
 5507        indoc! {"
 5508            «1. This is a numbered list item that is
 5509               very long and needs to be wrapped
 5510               properly.
 5511            2. This is a numbered list item that is
 5512               very long and needs to be wrapped
 5513               properly.
 5514            - This is an unordered list item that is
 5515              also very long and should not merge
 5516              with the numbered item.ˇ»
 5517        "},
 5518        markdown_language.clone(),
 5519        &mut cx,
 5520    );
 5521
 5522    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 5523    assert_rewrap(
 5524        indoc! {"
 5525            ˇThis is a very long line of plain text that will be wrapped.
 5526        "},
 5527        indoc! {"
 5528            ˇThis is a very long line of plain text
 5529            that will be wrapped.
 5530        "},
 5531        plaintext_language.clone(),
 5532        &mut cx,
 5533    );
 5534
 5535    // Test that non-commented code acts as a paragraph boundary within a selection
 5536    assert_rewrap(
 5537        indoc! {"
 5538               «// This is the first long comment block to be wrapped.
 5539               fn my_func(a: u32);
 5540               // This is the second long comment block to be wrapped.ˇ»
 5541           "},
 5542        indoc! {"
 5543               «// This is the first long comment block
 5544               // to be wrapped.
 5545               fn my_func(a: u32);
 5546               // This is the second long comment block
 5547               // to be wrapped.ˇ»
 5548           "},
 5549        rust_language.clone(),
 5550        &mut cx,
 5551    );
 5552
 5553    // Test rewrapping multiple selections, including ones with blank lines or tabs
 5554    assert_rewrap(
 5555        indoc! {"
 5556            «ˇThis is a very long line that will be wrapped.
 5557
 5558            This is another paragraph in the same selection.»
 5559
 5560            «\tThis is a very long indented line that will be wrapped.ˇ»
 5561         "},
 5562        indoc! {"
 5563            «ˇThis is a very long line that will be
 5564            wrapped.
 5565
 5566            This is another paragraph in the same
 5567            selection.»
 5568
 5569            «\tThis is a very long indented line
 5570            \tthat will be wrapped.ˇ»
 5571         "},
 5572        plaintext_language.clone(),
 5573        &mut cx,
 5574    );
 5575
 5576    // Test that an empty comment line acts as a paragraph boundary
 5577    assert_rewrap(
 5578        indoc! {"
 5579            // ˇThis is a long comment that will be wrapped.
 5580            //
 5581            // And this is another long comment that will also be wrapped.ˇ
 5582         "},
 5583        indoc! {"
 5584            // ˇThis is a long comment that will be
 5585            // wrapped.
 5586            //
 5587            // And this is another long comment that
 5588            // will also be wrapped.ˇ
 5589         "},
 5590        cpp_language,
 5591        &mut cx,
 5592    );
 5593
 5594    #[track_caller]
 5595    fn assert_rewrap(
 5596        unwrapped_text: &str,
 5597        wrapped_text: &str,
 5598        language: Arc<Language>,
 5599        cx: &mut EditorTestContext,
 5600    ) {
 5601        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 5602        cx.set_state(unwrapped_text);
 5603        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 5604        cx.assert_editor_state(wrapped_text);
 5605    }
 5606}
 5607
 5608#[gpui::test]
 5609async fn test_hard_wrap(cx: &mut TestAppContext) {
 5610    init_test(cx, |_| {});
 5611    let mut cx = EditorTestContext::new(cx).await;
 5612
 5613    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 5614    cx.update_editor(|editor, _, cx| {
 5615        editor.set_hard_wrap(Some(14), cx);
 5616    });
 5617
 5618    cx.set_state(indoc!(
 5619        "
 5620        one two three ˇ
 5621        "
 5622    ));
 5623    cx.simulate_input("four");
 5624    cx.run_until_parked();
 5625
 5626    cx.assert_editor_state(indoc!(
 5627        "
 5628        one two three
 5629        fourˇ
 5630        "
 5631    ));
 5632
 5633    cx.update_editor(|editor, window, cx| {
 5634        editor.newline(&Default::default(), window, cx);
 5635    });
 5636    cx.run_until_parked();
 5637    cx.assert_editor_state(indoc!(
 5638        "
 5639        one two three
 5640        four
 5641        ˇ
 5642        "
 5643    ));
 5644
 5645    cx.simulate_input("five");
 5646    cx.run_until_parked();
 5647    cx.assert_editor_state(indoc!(
 5648        "
 5649        one two three
 5650        four
 5651        fiveˇ
 5652        "
 5653    ));
 5654
 5655    cx.update_editor(|editor, window, cx| {
 5656        editor.newline(&Default::default(), window, cx);
 5657    });
 5658    cx.run_until_parked();
 5659    cx.simulate_input("# ");
 5660    cx.run_until_parked();
 5661    cx.assert_editor_state(indoc!(
 5662        "
 5663        one two three
 5664        four
 5665        five
 5666        # ˇ
 5667        "
 5668    ));
 5669
 5670    cx.update_editor(|editor, window, cx| {
 5671        editor.newline(&Default::default(), window, cx);
 5672    });
 5673    cx.run_until_parked();
 5674    cx.assert_editor_state(indoc!(
 5675        "
 5676        one two three
 5677        four
 5678        five
 5679        #\x20
 5680 5681        "
 5682    ));
 5683
 5684    cx.simulate_input(" 6");
 5685    cx.run_until_parked();
 5686    cx.assert_editor_state(indoc!(
 5687        "
 5688        one two three
 5689        four
 5690        five
 5691        #
 5692        # 6ˇ
 5693        "
 5694    ));
 5695}
 5696
 5697#[gpui::test]
 5698async fn test_clipboard(cx: &mut TestAppContext) {
 5699    init_test(cx, |_| {});
 5700
 5701    let mut cx = EditorTestContext::new(cx).await;
 5702
 5703    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 5704    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5705    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 5706
 5707    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 5708    cx.set_state("two ˇfour ˇsix ˇ");
 5709    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5710    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 5711
 5712    // Paste again but with only two cursors. Since the number of cursors doesn't
 5713    // match the number of slices in the clipboard, the entire clipboard text
 5714    // is pasted at each cursor.
 5715    cx.set_state("ˇtwo one✅ four three six five ˇ");
 5716    cx.update_editor(|e, window, cx| {
 5717        e.handle_input("( ", window, cx);
 5718        e.paste(&Paste, window, cx);
 5719        e.handle_input(") ", window, cx);
 5720    });
 5721    cx.assert_editor_state(
 5722        &([
 5723            "( one✅ ",
 5724            "three ",
 5725            "five ) ˇtwo one✅ four three six five ( one✅ ",
 5726            "three ",
 5727            "five ) ˇ",
 5728        ]
 5729        .join("\n")),
 5730    );
 5731
 5732    // Cut with three selections, one of which is full-line.
 5733    cx.set_state(indoc! {"
 5734        1«2ˇ»3
 5735        4ˇ567
 5736        «8ˇ»9"});
 5737    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5738    cx.assert_editor_state(indoc! {"
 5739        1ˇ3
 5740        ˇ9"});
 5741
 5742    // Paste with three selections, noticing how the copied selection that was full-line
 5743    // gets inserted before the second cursor.
 5744    cx.set_state(indoc! {"
 5745        1ˇ3
 5746 5747        «oˇ»ne"});
 5748    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5749    cx.assert_editor_state(indoc! {"
 5750        12ˇ3
 5751        4567
 5752 5753        8ˇne"});
 5754
 5755    // Copy with a single cursor only, which writes the whole line into the clipboard.
 5756    cx.set_state(indoc! {"
 5757        The quick brown
 5758        fox juˇmps over
 5759        the lazy dog"});
 5760    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5761    assert_eq!(
 5762        cx.read_from_clipboard()
 5763            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5764        Some("fox jumps over\n".to_string())
 5765    );
 5766
 5767    // Paste with three selections, noticing how the copied full-line selection is inserted
 5768    // before the empty selections but replaces the selection that is non-empty.
 5769    cx.set_state(indoc! {"
 5770        Tˇhe quick brown
 5771        «foˇ»x jumps over
 5772        tˇhe lazy dog"});
 5773    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5774    cx.assert_editor_state(indoc! {"
 5775        fox jumps over
 5776        Tˇhe quick brown
 5777        fox jumps over
 5778        ˇx jumps over
 5779        fox jumps over
 5780        tˇhe lazy dog"});
 5781}
 5782
 5783#[gpui::test]
 5784async fn test_copy_trim(cx: &mut TestAppContext) {
 5785    init_test(cx, |_| {});
 5786
 5787    let mut cx = EditorTestContext::new(cx).await;
 5788    cx.set_state(
 5789        r#"            «for selection in selections.iter() {
 5790            let mut start = selection.start;
 5791            let mut end = selection.end;
 5792            let is_entire_line = selection.is_empty();
 5793            if is_entire_line {
 5794                start = Point::new(start.row, 0);ˇ»
 5795                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5796            }
 5797        "#,
 5798    );
 5799    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5800    assert_eq!(
 5801        cx.read_from_clipboard()
 5802            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5803        Some(
 5804            "for selection in selections.iter() {
 5805            let mut start = selection.start;
 5806            let mut end = selection.end;
 5807            let is_entire_line = selection.is_empty();
 5808            if is_entire_line {
 5809                start = Point::new(start.row, 0);"
 5810                .to_string()
 5811        ),
 5812        "Regular copying preserves all indentation selected",
 5813    );
 5814    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5815    assert_eq!(
 5816        cx.read_from_clipboard()
 5817            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5818        Some(
 5819            "for selection in selections.iter() {
 5820let mut start = selection.start;
 5821let mut end = selection.end;
 5822let is_entire_line = selection.is_empty();
 5823if is_entire_line {
 5824    start = Point::new(start.row, 0);"
 5825                .to_string()
 5826        ),
 5827        "Copying with stripping should strip all leading whitespaces"
 5828    );
 5829
 5830    cx.set_state(
 5831        r#"       «     for selection in selections.iter() {
 5832            let mut start = selection.start;
 5833            let mut end = selection.end;
 5834            let is_entire_line = selection.is_empty();
 5835            if is_entire_line {
 5836                start = Point::new(start.row, 0);ˇ»
 5837                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5838            }
 5839        "#,
 5840    );
 5841    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5842    assert_eq!(
 5843        cx.read_from_clipboard()
 5844            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5845        Some(
 5846            "     for selection in selections.iter() {
 5847            let mut start = selection.start;
 5848            let mut end = selection.end;
 5849            let is_entire_line = selection.is_empty();
 5850            if is_entire_line {
 5851                start = Point::new(start.row, 0);"
 5852                .to_string()
 5853        ),
 5854        "Regular copying preserves all indentation selected",
 5855    );
 5856    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5857    assert_eq!(
 5858        cx.read_from_clipboard()
 5859            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5860        Some(
 5861            "for selection in selections.iter() {
 5862let mut start = selection.start;
 5863let mut end = selection.end;
 5864let is_entire_line = selection.is_empty();
 5865if is_entire_line {
 5866    start = Point::new(start.row, 0);"
 5867                .to_string()
 5868        ),
 5869        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 5870    );
 5871
 5872    cx.set_state(
 5873        r#"       «ˇ     for selection in selections.iter() {
 5874            let mut start = selection.start;
 5875            let mut end = selection.end;
 5876            let is_entire_line = selection.is_empty();
 5877            if is_entire_line {
 5878                start = Point::new(start.row, 0);»
 5879                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5880            }
 5881        "#,
 5882    );
 5883    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5884    assert_eq!(
 5885        cx.read_from_clipboard()
 5886            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5887        Some(
 5888            "     for selection in selections.iter() {
 5889            let mut start = selection.start;
 5890            let mut end = selection.end;
 5891            let is_entire_line = selection.is_empty();
 5892            if is_entire_line {
 5893                start = Point::new(start.row, 0);"
 5894                .to_string()
 5895        ),
 5896        "Regular copying for reverse selection works the same",
 5897    );
 5898    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5899    assert_eq!(
 5900        cx.read_from_clipboard()
 5901            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5902        Some(
 5903            "for selection in selections.iter() {
 5904let mut start = selection.start;
 5905let mut end = selection.end;
 5906let is_entire_line = selection.is_empty();
 5907if is_entire_line {
 5908    start = Point::new(start.row, 0);"
 5909                .to_string()
 5910        ),
 5911        "Copying with stripping for reverse selection works the same"
 5912    );
 5913
 5914    cx.set_state(
 5915        r#"            for selection «in selections.iter() {
 5916            let mut start = selection.start;
 5917            let mut end = selection.end;
 5918            let is_entire_line = selection.is_empty();
 5919            if is_entire_line {
 5920                start = Point::new(start.row, 0);ˇ»
 5921                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5922            }
 5923        "#,
 5924    );
 5925    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5926    assert_eq!(
 5927        cx.read_from_clipboard()
 5928            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5929        Some(
 5930            "in selections.iter() {
 5931            let mut start = selection.start;
 5932            let mut end = selection.end;
 5933            let is_entire_line = selection.is_empty();
 5934            if is_entire_line {
 5935                start = Point::new(start.row, 0);"
 5936                .to_string()
 5937        ),
 5938        "When selecting past the indent, the copying works as usual",
 5939    );
 5940    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5941    assert_eq!(
 5942        cx.read_from_clipboard()
 5943            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5944        Some(
 5945            "in selections.iter() {
 5946            let mut start = selection.start;
 5947            let mut end = selection.end;
 5948            let is_entire_line = selection.is_empty();
 5949            if is_entire_line {
 5950                start = Point::new(start.row, 0);"
 5951                .to_string()
 5952        ),
 5953        "When selecting past the indent, nothing is trimmed"
 5954    );
 5955
 5956    cx.set_state(
 5957        r#"            «for selection in selections.iter() {
 5958            let mut start = selection.start;
 5959
 5960            let mut end = selection.end;
 5961            let is_entire_line = selection.is_empty();
 5962            if is_entire_line {
 5963                start = Point::new(start.row, 0);
 5964ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5965            }
 5966        "#,
 5967    );
 5968    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5969    assert_eq!(
 5970        cx.read_from_clipboard()
 5971            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5972        Some(
 5973            "for selection in selections.iter() {
 5974let mut start = selection.start;
 5975
 5976let mut end = selection.end;
 5977let is_entire_line = selection.is_empty();
 5978if is_entire_line {
 5979    start = Point::new(start.row, 0);
 5980"
 5981            .to_string()
 5982        ),
 5983        "Copying with stripping should ignore empty lines"
 5984    );
 5985}
 5986
 5987#[gpui::test]
 5988async fn test_paste_multiline(cx: &mut TestAppContext) {
 5989    init_test(cx, |_| {});
 5990
 5991    let mut cx = EditorTestContext::new(cx).await;
 5992    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 5993
 5994    // Cut an indented block, without the leading whitespace.
 5995    cx.set_state(indoc! {"
 5996        const a: B = (
 5997            c(),
 5998            «d(
 5999                e,
 6000                f
 6001            )ˇ»
 6002        );
 6003    "});
 6004    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6005    cx.assert_editor_state(indoc! {"
 6006        const a: B = (
 6007            c(),
 6008            ˇ
 6009        );
 6010    "});
 6011
 6012    // Paste it at the same position.
 6013    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6014    cx.assert_editor_state(indoc! {"
 6015        const a: B = (
 6016            c(),
 6017            d(
 6018                e,
 6019                f
 6020 6021        );
 6022    "});
 6023
 6024    // Paste it at a line with a lower indent level.
 6025    cx.set_state(indoc! {"
 6026        ˇ
 6027        const a: B = (
 6028            c(),
 6029        );
 6030    "});
 6031    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6032    cx.assert_editor_state(indoc! {"
 6033        d(
 6034            e,
 6035            f
 6036 6037        const a: B = (
 6038            c(),
 6039        );
 6040    "});
 6041
 6042    // Cut an indented block, with the leading whitespace.
 6043    cx.set_state(indoc! {"
 6044        const a: B = (
 6045            c(),
 6046        «    d(
 6047                e,
 6048                f
 6049            )
 6050        ˇ»);
 6051    "});
 6052    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6053    cx.assert_editor_state(indoc! {"
 6054        const a: B = (
 6055            c(),
 6056        ˇ);
 6057    "});
 6058
 6059    // Paste it at the same position.
 6060    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6061    cx.assert_editor_state(indoc! {"
 6062        const a: B = (
 6063            c(),
 6064            d(
 6065                e,
 6066                f
 6067            )
 6068        ˇ);
 6069    "});
 6070
 6071    // Paste it at a line with a higher indent level.
 6072    cx.set_state(indoc! {"
 6073        const a: B = (
 6074            c(),
 6075            d(
 6076                e,
 6077 6078            )
 6079        );
 6080    "});
 6081    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6082    cx.assert_editor_state(indoc! {"
 6083        const a: B = (
 6084            c(),
 6085            d(
 6086                e,
 6087                f    d(
 6088                    e,
 6089                    f
 6090                )
 6091        ˇ
 6092            )
 6093        );
 6094    "});
 6095
 6096    // Copy an indented block, starting mid-line
 6097    cx.set_state(indoc! {"
 6098        const a: B = (
 6099            c(),
 6100            somethin«g(
 6101                e,
 6102                f
 6103            )ˇ»
 6104        );
 6105    "});
 6106    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6107
 6108    // Paste it on a line with a lower indent level
 6109    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 6110    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6111    cx.assert_editor_state(indoc! {"
 6112        const a: B = (
 6113            c(),
 6114            something(
 6115                e,
 6116                f
 6117            )
 6118        );
 6119        g(
 6120            e,
 6121            f
 6122"});
 6123}
 6124
 6125#[gpui::test]
 6126async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 6127    init_test(cx, |_| {});
 6128
 6129    cx.write_to_clipboard(ClipboardItem::new_string(
 6130        "    d(\n        e\n    );\n".into(),
 6131    ));
 6132
 6133    let mut cx = EditorTestContext::new(cx).await;
 6134    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 6135
 6136    cx.set_state(indoc! {"
 6137        fn a() {
 6138            b();
 6139            if c() {
 6140                ˇ
 6141            }
 6142        }
 6143    "});
 6144
 6145    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6146    cx.assert_editor_state(indoc! {"
 6147        fn a() {
 6148            b();
 6149            if c() {
 6150                d(
 6151                    e
 6152                );
 6153        ˇ
 6154            }
 6155        }
 6156    "});
 6157
 6158    cx.set_state(indoc! {"
 6159        fn a() {
 6160            b();
 6161            ˇ
 6162        }
 6163    "});
 6164
 6165    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6166    cx.assert_editor_state(indoc! {"
 6167        fn a() {
 6168            b();
 6169            d(
 6170                e
 6171            );
 6172        ˇ
 6173        }
 6174    "});
 6175}
 6176
 6177#[gpui::test]
 6178fn test_select_all(cx: &mut TestAppContext) {
 6179    init_test(cx, |_| {});
 6180
 6181    let editor = cx.add_window(|window, cx| {
 6182        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 6183        build_editor(buffer, window, cx)
 6184    });
 6185    _ = editor.update(cx, |editor, window, cx| {
 6186        editor.select_all(&SelectAll, window, cx);
 6187        assert_eq!(
 6188            editor.selections.display_ranges(cx),
 6189            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 6190        );
 6191    });
 6192}
 6193
 6194#[gpui::test]
 6195fn test_select_line(cx: &mut TestAppContext) {
 6196    init_test(cx, |_| {});
 6197
 6198    let editor = cx.add_window(|window, cx| {
 6199        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 6200        build_editor(buffer, window, cx)
 6201    });
 6202    _ = editor.update(cx, |editor, window, cx| {
 6203        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6204            s.select_display_ranges([
 6205                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6206                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6207                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6208                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 6209            ])
 6210        });
 6211        editor.select_line(&SelectLine, window, cx);
 6212        assert_eq!(
 6213            editor.selections.display_ranges(cx),
 6214            vec![
 6215                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 6216                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 6217            ]
 6218        );
 6219    });
 6220
 6221    _ = editor.update(cx, |editor, window, cx| {
 6222        editor.select_line(&SelectLine, window, cx);
 6223        assert_eq!(
 6224            editor.selections.display_ranges(cx),
 6225            vec![
 6226                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 6227                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 6228            ]
 6229        );
 6230    });
 6231
 6232    _ = editor.update(cx, |editor, window, cx| {
 6233        editor.select_line(&SelectLine, window, cx);
 6234        assert_eq!(
 6235            editor.selections.display_ranges(cx),
 6236            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 6237        );
 6238    });
 6239}
 6240
 6241#[gpui::test]
 6242async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 6243    init_test(cx, |_| {});
 6244    let mut cx = EditorTestContext::new(cx).await;
 6245
 6246    #[track_caller]
 6247    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 6248        cx.set_state(initial_state);
 6249        cx.update_editor(|e, window, cx| {
 6250            e.split_selection_into_lines(&SplitSelectionIntoLines, window, cx)
 6251        });
 6252        cx.assert_editor_state(expected_state);
 6253    }
 6254
 6255    // Selection starts and ends at the middle of lines, left-to-right
 6256    test(
 6257        &mut cx,
 6258        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 6259        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 6260    );
 6261    // Same thing, right-to-left
 6262    test(
 6263        &mut cx,
 6264        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 6265        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 6266    );
 6267
 6268    // Whole buffer, left-to-right, last line *doesn't* end with newline
 6269    test(
 6270        &mut cx,
 6271        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 6272        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 6273    );
 6274    // Same thing, right-to-left
 6275    test(
 6276        &mut cx,
 6277        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 6278        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 6279    );
 6280
 6281    // Whole buffer, left-to-right, last line ends with newline
 6282    test(
 6283        &mut cx,
 6284        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 6285        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 6286    );
 6287    // Same thing, right-to-left
 6288    test(
 6289        &mut cx,
 6290        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 6291        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 6292    );
 6293
 6294    // Starts at the end of a line, ends at the start of another
 6295    test(
 6296        &mut cx,
 6297        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 6298        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 6299    );
 6300}
 6301
 6302#[gpui::test]
 6303async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 6304    init_test(cx, |_| {});
 6305
 6306    let editor = cx.add_window(|window, cx| {
 6307        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 6308        build_editor(buffer, window, cx)
 6309    });
 6310
 6311    // setup
 6312    _ = editor.update(cx, |editor, window, cx| {
 6313        editor.fold_creases(
 6314            vec![
 6315                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 6316                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 6317                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 6318            ],
 6319            true,
 6320            window,
 6321            cx,
 6322        );
 6323        assert_eq!(
 6324            editor.display_text(cx),
 6325            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 6326        );
 6327    });
 6328
 6329    _ = editor.update(cx, |editor, window, cx| {
 6330        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6331            s.select_display_ranges([
 6332                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6333                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6334                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6335                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 6336            ])
 6337        });
 6338        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 6339        assert_eq!(
 6340            editor.display_text(cx),
 6341            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 6342        );
 6343    });
 6344    EditorTestContext::for_editor(editor, cx)
 6345        .await
 6346        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 6347
 6348    _ = editor.update(cx, |editor, window, cx| {
 6349        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6350            s.select_display_ranges([
 6351                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 6352            ])
 6353        });
 6354        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 6355        assert_eq!(
 6356            editor.display_text(cx),
 6357            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 6358        );
 6359        assert_eq!(
 6360            editor.selections.display_ranges(cx),
 6361            [
 6362                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 6363                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 6364                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 6365                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 6366                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 6367                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 6368                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 6369            ]
 6370        );
 6371    });
 6372    EditorTestContext::for_editor(editor, cx)
 6373        .await
 6374        .assert_editor_state(
 6375            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 6376        );
 6377}
 6378
 6379#[gpui::test]
 6380async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 6381    init_test(cx, |_| {});
 6382
 6383    let mut cx = EditorTestContext::new(cx).await;
 6384
 6385    cx.set_state(indoc!(
 6386        r#"abc
 6387           defˇghi
 6388
 6389           jk
 6390           nlmo
 6391           "#
 6392    ));
 6393
 6394    cx.update_editor(|editor, window, cx| {
 6395        editor.add_selection_above(&Default::default(), window, cx);
 6396    });
 6397
 6398    cx.assert_editor_state(indoc!(
 6399        r#"abcˇ
 6400           defˇghi
 6401
 6402           jk
 6403           nlmo
 6404           "#
 6405    ));
 6406
 6407    cx.update_editor(|editor, window, cx| {
 6408        editor.add_selection_above(&Default::default(), window, cx);
 6409    });
 6410
 6411    cx.assert_editor_state(indoc!(
 6412        r#"abcˇ
 6413            defˇghi
 6414
 6415            jk
 6416            nlmo
 6417            "#
 6418    ));
 6419
 6420    cx.update_editor(|editor, window, cx| {
 6421        editor.add_selection_below(&Default::default(), window, cx);
 6422    });
 6423
 6424    cx.assert_editor_state(indoc!(
 6425        r#"abc
 6426           defˇghi
 6427
 6428           jk
 6429           nlmo
 6430           "#
 6431    ));
 6432
 6433    cx.update_editor(|editor, window, cx| {
 6434        editor.undo_selection(&Default::default(), window, cx);
 6435    });
 6436
 6437    cx.assert_editor_state(indoc!(
 6438        r#"abcˇ
 6439           defˇghi
 6440
 6441           jk
 6442           nlmo
 6443           "#
 6444    ));
 6445
 6446    cx.update_editor(|editor, window, cx| {
 6447        editor.redo_selection(&Default::default(), window, cx);
 6448    });
 6449
 6450    cx.assert_editor_state(indoc!(
 6451        r#"abc
 6452           defˇghi
 6453
 6454           jk
 6455           nlmo
 6456           "#
 6457    ));
 6458
 6459    cx.update_editor(|editor, window, cx| {
 6460        editor.add_selection_below(&Default::default(), window, cx);
 6461    });
 6462
 6463    cx.assert_editor_state(indoc!(
 6464        r#"abc
 6465           defˇghi
 6466           ˇ
 6467           jk
 6468           nlmo
 6469           "#
 6470    ));
 6471
 6472    cx.update_editor(|editor, window, cx| {
 6473        editor.add_selection_below(&Default::default(), window, cx);
 6474    });
 6475
 6476    cx.assert_editor_state(indoc!(
 6477        r#"abc
 6478           defˇghi
 6479           ˇ
 6480           jkˇ
 6481           nlmo
 6482           "#
 6483    ));
 6484
 6485    cx.update_editor(|editor, window, cx| {
 6486        editor.add_selection_below(&Default::default(), window, cx);
 6487    });
 6488
 6489    cx.assert_editor_state(indoc!(
 6490        r#"abc
 6491           defˇghi
 6492           ˇ
 6493           jkˇ
 6494           nlmˇo
 6495           "#
 6496    ));
 6497
 6498    cx.update_editor(|editor, window, cx| {
 6499        editor.add_selection_below(&Default::default(), window, cx);
 6500    });
 6501
 6502    cx.assert_editor_state(indoc!(
 6503        r#"abc
 6504           defˇghi
 6505           ˇ
 6506           jkˇ
 6507           nlmˇo
 6508           ˇ"#
 6509    ));
 6510
 6511    // change selections
 6512    cx.set_state(indoc!(
 6513        r#"abc
 6514           def«ˇg»hi
 6515
 6516           jk
 6517           nlmo
 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«ˇg»hi
 6528
 6529           jk
 6530           nlm«ˇo»
 6531           "#
 6532    ));
 6533
 6534    cx.update_editor(|editor, window, cx| {
 6535        editor.add_selection_below(&Default::default(), window, cx);
 6536    });
 6537
 6538    cx.assert_editor_state(indoc!(
 6539        r#"abc
 6540           def«ˇg»hi
 6541
 6542           jk
 6543           nlm«ˇo»
 6544           "#
 6545    ));
 6546
 6547    cx.update_editor(|editor, window, cx| {
 6548        editor.add_selection_above(&Default::default(), window, cx);
 6549    });
 6550
 6551    cx.assert_editor_state(indoc!(
 6552        r#"abc
 6553           def«ˇg»hi
 6554
 6555           jk
 6556           nlmo
 6557           "#
 6558    ));
 6559
 6560    cx.update_editor(|editor, window, cx| {
 6561        editor.add_selection_above(&Default::default(), window, cx);
 6562    });
 6563
 6564    cx.assert_editor_state(indoc!(
 6565        r#"abc
 6566           def«ˇg»hi
 6567
 6568           jk
 6569           nlmo
 6570           "#
 6571    ));
 6572
 6573    // Change selections again
 6574    cx.set_state(indoc!(
 6575        r#"a«bc
 6576           defgˇ»hi
 6577
 6578           jk
 6579           nlmo
 6580           "#
 6581    ));
 6582
 6583    cx.update_editor(|editor, window, cx| {
 6584        editor.add_selection_below(&Default::default(), window, cx);
 6585    });
 6586
 6587    cx.assert_editor_state(indoc!(
 6588        r#"a«bcˇ»
 6589           d«efgˇ»hi
 6590
 6591           j«kˇ»
 6592           nlmo
 6593           "#
 6594    ));
 6595
 6596    cx.update_editor(|editor, window, cx| {
 6597        editor.add_selection_below(&Default::default(), window, cx);
 6598    });
 6599    cx.assert_editor_state(indoc!(
 6600        r#"a«bcˇ»
 6601           d«efgˇ»hi
 6602
 6603           j«kˇ»
 6604           n«lmoˇ»
 6605           "#
 6606    ));
 6607    cx.update_editor(|editor, window, cx| {
 6608        editor.add_selection_above(&Default::default(), window, cx);
 6609    });
 6610
 6611    cx.assert_editor_state(indoc!(
 6612        r#"a«bcˇ»
 6613           d«efgˇ»hi
 6614
 6615           j«kˇ»
 6616           nlmo
 6617           "#
 6618    ));
 6619
 6620    // Change selections again
 6621    cx.set_state(indoc!(
 6622        r#"abc
 6623           d«ˇefghi
 6624
 6625           jk
 6626           nlm»o
 6627           "#
 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«ˇef»ghi
 6637
 6638           j«ˇk»
 6639           n«ˇlm»o
 6640           "#
 6641    ));
 6642
 6643    cx.update_editor(|editor, window, cx| {
 6644        editor.add_selection_below(&Default::default(), window, cx);
 6645    });
 6646
 6647    cx.assert_editor_state(indoc!(
 6648        r#"abc
 6649           d«ˇef»ghi
 6650
 6651           j«ˇk»
 6652           n«ˇlm»o
 6653           "#
 6654    ));
 6655}
 6656
 6657#[gpui::test]
 6658async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 6659    init_test(cx, |_| {});
 6660    let mut cx = EditorTestContext::new(cx).await;
 6661
 6662    cx.set_state(indoc!(
 6663        r#"line onˇe
 6664           liˇne two
 6665           line three
 6666           line four"#
 6667    ));
 6668
 6669    cx.update_editor(|editor, window, cx| {
 6670        editor.add_selection_below(&Default::default(), window, cx);
 6671    });
 6672
 6673    // test multiple cursors expand in the same direction
 6674    cx.assert_editor_state(indoc!(
 6675        r#"line onˇe
 6676           liˇne twˇo
 6677           liˇne three
 6678           line four"#
 6679    ));
 6680
 6681    cx.update_editor(|editor, window, cx| {
 6682        editor.add_selection_below(&Default::default(), window, cx);
 6683    });
 6684
 6685    cx.update_editor(|editor, window, cx| {
 6686        editor.add_selection_below(&Default::default(), window, cx);
 6687    });
 6688
 6689    // test multiple cursors expand below overflow
 6690    cx.assert_editor_state(indoc!(
 6691        r#"line onˇe
 6692           liˇne twˇo
 6693           liˇne thˇree
 6694           liˇne foˇur"#
 6695    ));
 6696
 6697    cx.update_editor(|editor, window, cx| {
 6698        editor.add_selection_above(&Default::default(), window, cx);
 6699    });
 6700
 6701    // test multiple cursors retrieves back correctly
 6702    cx.assert_editor_state(indoc!(
 6703        r#"line onˇe
 6704           liˇne twˇo
 6705           liˇne thˇree
 6706           line four"#
 6707    ));
 6708
 6709    cx.update_editor(|editor, window, cx| {
 6710        editor.add_selection_above(&Default::default(), window, cx);
 6711    });
 6712
 6713    cx.update_editor(|editor, window, cx| {
 6714        editor.add_selection_above(&Default::default(), window, cx);
 6715    });
 6716
 6717    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 6718    cx.assert_editor_state(indoc!(
 6719        r#"liˇne onˇe
 6720           liˇne two
 6721           line three
 6722           line four"#
 6723    ));
 6724
 6725    cx.update_editor(|editor, window, cx| {
 6726        editor.undo_selection(&Default::default(), window, cx);
 6727    });
 6728
 6729    // test undo
 6730    cx.assert_editor_state(indoc!(
 6731        r#"line onˇe
 6732           liˇne twˇo
 6733           line three
 6734           line four"#
 6735    ));
 6736
 6737    cx.update_editor(|editor, window, cx| {
 6738        editor.redo_selection(&Default::default(), window, cx);
 6739    });
 6740
 6741    // test redo
 6742    cx.assert_editor_state(indoc!(
 6743        r#"liˇne onˇe
 6744           liˇne two
 6745           line three
 6746           line four"#
 6747    ));
 6748
 6749    cx.set_state(indoc!(
 6750        r#"abcd
 6751           ef«ghˇ»
 6752           ijkl
 6753           «mˇ»nop"#
 6754    ));
 6755
 6756    cx.update_editor(|editor, window, cx| {
 6757        editor.add_selection_above(&Default::default(), window, cx);
 6758    });
 6759
 6760    // test multiple selections expand in the same direction
 6761    cx.assert_editor_state(indoc!(
 6762        r#"ab«cdˇ»
 6763           ef«ghˇ»
 6764           «iˇ»jkl
 6765           «mˇ»nop"#
 6766    ));
 6767
 6768    cx.update_editor(|editor, window, cx| {
 6769        editor.add_selection_above(&Default::default(), window, cx);
 6770    });
 6771
 6772    // test multiple selection upward overflow
 6773    cx.assert_editor_state(indoc!(
 6774        r#"ab«cdˇ»
 6775           «eˇ»f«ghˇ»
 6776           «iˇ»jkl
 6777           «mˇ»nop"#
 6778    ));
 6779
 6780    cx.update_editor(|editor, window, cx| {
 6781        editor.add_selection_below(&Default::default(), window, cx);
 6782    });
 6783
 6784    // test multiple selection retrieves back correctly
 6785    cx.assert_editor_state(indoc!(
 6786        r#"abcd
 6787           ef«ghˇ»
 6788           «iˇ»jkl
 6789           «mˇ»nop"#
 6790    ));
 6791
 6792    cx.update_editor(|editor, window, cx| {
 6793        editor.add_selection_below(&Default::default(), window, cx);
 6794    });
 6795
 6796    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 6797    cx.assert_editor_state(indoc!(
 6798        r#"abcd
 6799           ef«ghˇ»
 6800           ij«klˇ»
 6801           «mˇ»nop"#
 6802    ));
 6803
 6804    cx.update_editor(|editor, window, cx| {
 6805        editor.undo_selection(&Default::default(), window, cx);
 6806    });
 6807
 6808    // test undo
 6809    cx.assert_editor_state(indoc!(
 6810        r#"abcd
 6811           ef«ghˇ»
 6812           «iˇ»jkl
 6813           «mˇ»nop"#
 6814    ));
 6815
 6816    cx.update_editor(|editor, window, cx| {
 6817        editor.redo_selection(&Default::default(), window, cx);
 6818    });
 6819
 6820    // test redo
 6821    cx.assert_editor_state(indoc!(
 6822        r#"abcd
 6823           ef«ghˇ»
 6824           ij«klˇ»
 6825           «mˇ»nop"#
 6826    ));
 6827}
 6828
 6829#[gpui::test]
 6830async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 6831    init_test(cx, |_| {});
 6832    let mut cx = EditorTestContext::new(cx).await;
 6833
 6834    cx.set_state(indoc!(
 6835        r#"line onˇe
 6836           liˇne two
 6837           line three
 6838           line four"#
 6839    ));
 6840
 6841    cx.update_editor(|editor, window, cx| {
 6842        editor.add_selection_below(&Default::default(), window, cx);
 6843        editor.add_selection_below(&Default::default(), window, cx);
 6844        editor.add_selection_below(&Default::default(), window, cx);
 6845    });
 6846
 6847    // initial state with two multi cursor groups
 6848    cx.assert_editor_state(indoc!(
 6849        r#"line onˇe
 6850           liˇne twˇo
 6851           liˇne thˇree
 6852           liˇne foˇur"#
 6853    ));
 6854
 6855    // add single cursor in middle - simulate opt click
 6856    cx.update_editor(|editor, window, cx| {
 6857        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 6858        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 6859        editor.end_selection(window, cx);
 6860    });
 6861
 6862    cx.assert_editor_state(indoc!(
 6863        r#"line onˇe
 6864           liˇne twˇo
 6865           liˇneˇ thˇree
 6866           liˇne foˇur"#
 6867    ));
 6868
 6869    cx.update_editor(|editor, window, cx| {
 6870        editor.add_selection_above(&Default::default(), window, cx);
 6871    });
 6872
 6873    // test new added selection expands above and existing selection shrinks
 6874    cx.assert_editor_state(indoc!(
 6875        r#"line onˇe
 6876           liˇneˇ twˇo
 6877           liˇneˇ thˇree
 6878           line four"#
 6879    ));
 6880
 6881    cx.update_editor(|editor, window, cx| {
 6882        editor.add_selection_above(&Default::default(), window, cx);
 6883    });
 6884
 6885    // test new added selection expands above and existing selection shrinks
 6886    cx.assert_editor_state(indoc!(
 6887        r#"lineˇ onˇe
 6888           liˇneˇ twˇo
 6889           lineˇ three
 6890           line four"#
 6891    ));
 6892
 6893    // intial state with two selection groups
 6894    cx.set_state(indoc!(
 6895        r#"abcd
 6896           ef«ghˇ»
 6897           ijkl
 6898           «mˇ»nop"#
 6899    ));
 6900
 6901    cx.update_editor(|editor, window, cx| {
 6902        editor.add_selection_above(&Default::default(), window, cx);
 6903        editor.add_selection_above(&Default::default(), window, cx);
 6904    });
 6905
 6906    cx.assert_editor_state(indoc!(
 6907        r#"ab«cdˇ»
 6908           «eˇ»f«ghˇ»
 6909           «iˇ»jkl
 6910           «mˇ»nop"#
 6911    ));
 6912
 6913    // add single selection in middle - simulate opt drag
 6914    cx.update_editor(|editor, window, cx| {
 6915        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 6916        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 6917        editor.update_selection(
 6918            DisplayPoint::new(DisplayRow(2), 4),
 6919            0,
 6920            gpui::Point::<f32>::default(),
 6921            window,
 6922            cx,
 6923        );
 6924        editor.end_selection(window, cx);
 6925    });
 6926
 6927    cx.assert_editor_state(indoc!(
 6928        r#"ab«cdˇ»
 6929           «eˇ»f«ghˇ»
 6930           «iˇ»jk«lˇ»
 6931           «mˇ»nop"#
 6932    ));
 6933
 6934    cx.update_editor(|editor, window, cx| {
 6935        editor.add_selection_below(&Default::default(), window, cx);
 6936    });
 6937
 6938    // test new added selection expands below, others shrinks from above
 6939    cx.assert_editor_state(indoc!(
 6940        r#"abcd
 6941           ef«ghˇ»
 6942           «iˇ»jk«lˇ»
 6943           «mˇ»no«pˇ»"#
 6944    ));
 6945}
 6946
 6947#[gpui::test]
 6948async fn test_select_next(cx: &mut TestAppContext) {
 6949    init_test(cx, |_| {});
 6950
 6951    let mut cx = EditorTestContext::new(cx).await;
 6952    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 6953
 6954    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6955        .unwrap();
 6956    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6957
 6958    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6959        .unwrap();
 6960    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 6961
 6962    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 6963    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6964
 6965    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 6966    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 6967
 6968    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6969        .unwrap();
 6970    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6971
 6972    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6973        .unwrap();
 6974    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6975
 6976    // Test selection direction should be preserved
 6977    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 6978
 6979    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6980        .unwrap();
 6981    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 6982}
 6983
 6984#[gpui::test]
 6985async fn test_select_all_matches(cx: &mut TestAppContext) {
 6986    init_test(cx, |_| {});
 6987
 6988    let mut cx = EditorTestContext::new(cx).await;
 6989
 6990    // Test caret-only selections
 6991    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 6992    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6993        .unwrap();
 6994    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6995
 6996    // Test left-to-right selections
 6997    cx.set_state("abc\n«abcˇ»\nabc");
 6998    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6999        .unwrap();
 7000    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 7001
 7002    // Test right-to-left selections
 7003    cx.set_state("abc\n«ˇabc»\nabc");
 7004    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7005        .unwrap();
 7006    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 7007
 7008    // Test selecting whitespace with caret selection
 7009    cx.set_state("abc\nˇ   abc\nabc");
 7010    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7011        .unwrap();
 7012    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 7013
 7014    // Test selecting whitespace with left-to-right selection
 7015    cx.set_state("abc\n«ˇ  »abc\nabc");
 7016    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7017        .unwrap();
 7018    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 7019
 7020    // Test no matches with right-to-left selection
 7021    cx.set_state("abc\n«  ˇ»abc\nabc");
 7022    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7023        .unwrap();
 7024    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 7025
 7026    // Test with a single word and clip_at_line_ends=true (#29823)
 7027    cx.set_state("aˇbc");
 7028    cx.update_editor(|e, window, cx| {
 7029        e.set_clip_at_line_ends(true, cx);
 7030        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 7031        e.set_clip_at_line_ends(false, cx);
 7032    });
 7033    cx.assert_editor_state("«abcˇ»");
 7034}
 7035
 7036#[gpui::test]
 7037async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 7038    init_test(cx, |_| {});
 7039
 7040    let mut cx = EditorTestContext::new(cx).await;
 7041
 7042    let large_body_1 = "\nd".repeat(200);
 7043    let large_body_2 = "\ne".repeat(200);
 7044
 7045    cx.set_state(&format!(
 7046        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 7047    ));
 7048    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 7049        let scroll_position = editor.scroll_position(cx);
 7050        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 7051        scroll_position
 7052    });
 7053
 7054    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7055        .unwrap();
 7056    cx.assert_editor_state(&format!(
 7057        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 7058    ));
 7059    let scroll_position_after_selection =
 7060        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 7061    assert_eq!(
 7062        initial_scroll_position, scroll_position_after_selection,
 7063        "Scroll position should not change after selecting all matches"
 7064    );
 7065}
 7066
 7067#[gpui::test]
 7068async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 7069    init_test(cx, |_| {});
 7070
 7071    let mut cx = EditorLspTestContext::new_rust(
 7072        lsp::ServerCapabilities {
 7073            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 7074            ..Default::default()
 7075        },
 7076        cx,
 7077    )
 7078    .await;
 7079
 7080    cx.set_state(indoc! {"
 7081        line 1
 7082        line 2
 7083        linˇe 3
 7084        line 4
 7085        line 5
 7086    "});
 7087
 7088    // Make an edit
 7089    cx.update_editor(|editor, window, cx| {
 7090        editor.handle_input("X", window, cx);
 7091    });
 7092
 7093    // Move cursor to a different position
 7094    cx.update_editor(|editor, window, cx| {
 7095        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7096            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 7097        });
 7098    });
 7099
 7100    cx.assert_editor_state(indoc! {"
 7101        line 1
 7102        line 2
 7103        linXe 3
 7104        line 4
 7105        liˇne 5
 7106    "});
 7107
 7108    cx.lsp
 7109        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 7110            Ok(Some(vec![lsp::TextEdit::new(
 7111                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 7112                "PREFIX ".to_string(),
 7113            )]))
 7114        });
 7115
 7116    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 7117        .unwrap()
 7118        .await
 7119        .unwrap();
 7120
 7121    cx.assert_editor_state(indoc! {"
 7122        PREFIX line 1
 7123        line 2
 7124        linXe 3
 7125        line 4
 7126        liˇne 5
 7127    "});
 7128
 7129    // Undo formatting
 7130    cx.update_editor(|editor, window, cx| {
 7131        editor.undo(&Default::default(), window, cx);
 7132    });
 7133
 7134    // Verify cursor moved back to position after edit
 7135    cx.assert_editor_state(indoc! {"
 7136        line 1
 7137        line 2
 7138        linXˇe 3
 7139        line 4
 7140        line 5
 7141    "});
 7142}
 7143
 7144#[gpui::test]
 7145async fn test_undo_inline_completion_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 7146    init_test(cx, |_| {});
 7147
 7148    let mut cx = EditorTestContext::new(cx).await;
 7149
 7150    let provider = cx.new(|_| FakeInlineCompletionProvider::default());
 7151    cx.update_editor(|editor, window, cx| {
 7152        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 7153    });
 7154
 7155    cx.set_state(indoc! {"
 7156        line 1
 7157        line 2
 7158        linˇe 3
 7159        line 4
 7160        line 5
 7161        line 6
 7162        line 7
 7163        line 8
 7164        line 9
 7165        line 10
 7166    "});
 7167
 7168    let snapshot = cx.buffer_snapshot();
 7169    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 7170
 7171    cx.update(|_, cx| {
 7172        provider.update(cx, |provider, _| {
 7173            provider.set_inline_completion(Some(inline_completion::InlineCompletion {
 7174                id: None,
 7175                edits: vec![(edit_position..edit_position, "X".into())],
 7176                edit_preview: None,
 7177            }))
 7178        })
 7179    });
 7180
 7181    cx.update_editor(|editor, window, cx| editor.update_visible_inline_completion(window, cx));
 7182    cx.update_editor(|editor, window, cx| {
 7183        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 7184    });
 7185
 7186    cx.assert_editor_state(indoc! {"
 7187        line 1
 7188        line 2
 7189        lineXˇ 3
 7190        line 4
 7191        line 5
 7192        line 6
 7193        line 7
 7194        line 8
 7195        line 9
 7196        line 10
 7197    "});
 7198
 7199    cx.update_editor(|editor, window, cx| {
 7200        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7201            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 7202        });
 7203    });
 7204
 7205    cx.assert_editor_state(indoc! {"
 7206        line 1
 7207        line 2
 7208        lineX 3
 7209        line 4
 7210        line 5
 7211        line 6
 7212        line 7
 7213        line 8
 7214        line 9
 7215        liˇne 10
 7216    "});
 7217
 7218    cx.update_editor(|editor, window, cx| {
 7219        editor.undo(&Default::default(), window, cx);
 7220    });
 7221
 7222    cx.assert_editor_state(indoc! {"
 7223        line 1
 7224        line 2
 7225        lineˇ 3
 7226        line 4
 7227        line 5
 7228        line 6
 7229        line 7
 7230        line 8
 7231        line 9
 7232        line 10
 7233    "});
 7234}
 7235
 7236#[gpui::test]
 7237async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 7238    init_test(cx, |_| {});
 7239
 7240    let mut cx = EditorTestContext::new(cx).await;
 7241    cx.set_state(
 7242        r#"let foo = 2;
 7243lˇet foo = 2;
 7244let fooˇ = 2;
 7245let foo = 2;
 7246let foo = ˇ2;"#,
 7247    );
 7248
 7249    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7250        .unwrap();
 7251    cx.assert_editor_state(
 7252        r#"let foo = 2;
 7253«letˇ» foo = 2;
 7254let «fooˇ» = 2;
 7255let foo = 2;
 7256let foo = «2ˇ»;"#,
 7257    );
 7258
 7259    // noop for multiple selections with different contents
 7260    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7261        .unwrap();
 7262    cx.assert_editor_state(
 7263        r#"let foo = 2;
 7264«letˇ» foo = 2;
 7265let «fooˇ» = 2;
 7266let foo = 2;
 7267let foo = «2ˇ»;"#,
 7268    );
 7269
 7270    // Test last selection direction should be preserved
 7271    cx.set_state(
 7272        r#"let foo = 2;
 7273let foo = 2;
 7274let «fooˇ» = 2;
 7275let «ˇfoo» = 2;
 7276let foo = 2;"#,
 7277    );
 7278
 7279    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7280        .unwrap();
 7281    cx.assert_editor_state(
 7282        r#"let foo = 2;
 7283let foo = 2;
 7284let «fooˇ» = 2;
 7285let «ˇfoo» = 2;
 7286let «ˇfoo» = 2;"#,
 7287    );
 7288}
 7289
 7290#[gpui::test]
 7291async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 7292    init_test(cx, |_| {});
 7293
 7294    let mut cx =
 7295        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 7296
 7297    cx.assert_editor_state(indoc! {"
 7298        ˇbbb
 7299        ccc
 7300
 7301        bbb
 7302        ccc
 7303        "});
 7304    cx.dispatch_action(SelectPrevious::default());
 7305    cx.assert_editor_state(indoc! {"
 7306                «bbbˇ»
 7307                ccc
 7308
 7309                bbb
 7310                ccc
 7311                "});
 7312    cx.dispatch_action(SelectPrevious::default());
 7313    cx.assert_editor_state(indoc! {"
 7314                «bbbˇ»
 7315                ccc
 7316
 7317                «bbbˇ»
 7318                ccc
 7319                "});
 7320}
 7321
 7322#[gpui::test]
 7323async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 7324    init_test(cx, |_| {});
 7325
 7326    let mut cx = EditorTestContext::new(cx).await;
 7327    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7328
 7329    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7330        .unwrap();
 7331    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7332
 7333    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7334        .unwrap();
 7335    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 7336
 7337    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7338    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7339
 7340    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7341    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 7342
 7343    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7344        .unwrap();
 7345    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 7346
 7347    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7348        .unwrap();
 7349    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7350}
 7351
 7352#[gpui::test]
 7353async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 7354    init_test(cx, |_| {});
 7355
 7356    let mut cx = EditorTestContext::new(cx).await;
 7357    cx.set_state("");
 7358
 7359    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7360        .unwrap();
 7361    cx.assert_editor_state("«aˇ»");
 7362    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7363        .unwrap();
 7364    cx.assert_editor_state("«aˇ»");
 7365}
 7366
 7367#[gpui::test]
 7368async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 7369    init_test(cx, |_| {});
 7370
 7371    let mut cx = EditorTestContext::new(cx).await;
 7372    cx.set_state(
 7373        r#"let foo = 2;
 7374lˇet foo = 2;
 7375let fooˇ = 2;
 7376let foo = 2;
 7377let foo = ˇ2;"#,
 7378    );
 7379
 7380    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7381        .unwrap();
 7382    cx.assert_editor_state(
 7383        r#"let foo = 2;
 7384«letˇ» foo = 2;
 7385let «fooˇ» = 2;
 7386let foo = 2;
 7387let foo = «2ˇ»;"#,
 7388    );
 7389
 7390    // noop for multiple selections with different contents
 7391    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7392        .unwrap();
 7393    cx.assert_editor_state(
 7394        r#"let foo = 2;
 7395«letˇ» foo = 2;
 7396let «fooˇ» = 2;
 7397let foo = 2;
 7398let foo = «2ˇ»;"#,
 7399    );
 7400}
 7401
 7402#[gpui::test]
 7403async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 7404    init_test(cx, |_| {});
 7405
 7406    let mut cx = EditorTestContext::new(cx).await;
 7407    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 7408
 7409    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7410        .unwrap();
 7411    // selection direction is preserved
 7412    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 7413
 7414    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7415        .unwrap();
 7416    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7417
 7418    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7419    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 7420
 7421    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7422    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7423
 7424    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7425        .unwrap();
 7426    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 7427
 7428    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7429        .unwrap();
 7430    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 7431}
 7432
 7433#[gpui::test]
 7434async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 7435    init_test(cx, |_| {});
 7436
 7437    let language = Arc::new(Language::new(
 7438        LanguageConfig::default(),
 7439        Some(tree_sitter_rust::LANGUAGE.into()),
 7440    ));
 7441
 7442    let text = r#"
 7443        use mod1::mod2::{mod3, mod4};
 7444
 7445        fn fn_1(param1: bool, param2: &str) {
 7446            let var1 = "text";
 7447        }
 7448    "#
 7449    .unindent();
 7450
 7451    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7452    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7453    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7454
 7455    editor
 7456        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7457        .await;
 7458
 7459    editor.update_in(cx, |editor, window, cx| {
 7460        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7461            s.select_display_ranges([
 7462                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 7463                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 7464                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 7465            ]);
 7466        });
 7467        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7468    });
 7469    editor.update(cx, |editor, cx| {
 7470        assert_text_with_selections(
 7471            editor,
 7472            indoc! {r#"
 7473                use mod1::mod2::{mod3, «mod4ˇ»};
 7474
 7475                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7476                    let var1 = "«ˇtext»";
 7477                }
 7478            "#},
 7479            cx,
 7480        );
 7481    });
 7482
 7483    editor.update_in(cx, |editor, window, cx| {
 7484        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7485    });
 7486    editor.update(cx, |editor, cx| {
 7487        assert_text_with_selections(
 7488            editor,
 7489            indoc! {r#"
 7490                use mod1::mod2::«{mod3, mod4}ˇ»;
 7491
 7492                «ˇfn fn_1(param1: bool, param2: &str) {
 7493                    let var1 = "text";
 7494 7495            "#},
 7496            cx,
 7497        );
 7498    });
 7499
 7500    editor.update_in(cx, |editor, window, cx| {
 7501        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7502    });
 7503    assert_eq!(
 7504        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7505        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7506    );
 7507
 7508    // Trying to expand the selected syntax node one more time has no effect.
 7509    editor.update_in(cx, |editor, window, cx| {
 7510        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7511    });
 7512    assert_eq!(
 7513        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7514        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7515    );
 7516
 7517    editor.update_in(cx, |editor, window, cx| {
 7518        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7519    });
 7520    editor.update(cx, |editor, cx| {
 7521        assert_text_with_selections(
 7522            editor,
 7523            indoc! {r#"
 7524                use mod1::mod2::«{mod3, mod4}ˇ»;
 7525
 7526                «ˇfn fn_1(param1: bool, param2: &str) {
 7527                    let var1 = "text";
 7528 7529            "#},
 7530            cx,
 7531        );
 7532    });
 7533
 7534    editor.update_in(cx, |editor, window, cx| {
 7535        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7536    });
 7537    editor.update(cx, |editor, cx| {
 7538        assert_text_with_selections(
 7539            editor,
 7540            indoc! {r#"
 7541                use mod1::mod2::{mod3, «mod4ˇ»};
 7542
 7543                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7544                    let var1 = "«ˇtext»";
 7545                }
 7546            "#},
 7547            cx,
 7548        );
 7549    });
 7550
 7551    editor.update_in(cx, |editor, window, cx| {
 7552        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7553    });
 7554    editor.update(cx, |editor, cx| {
 7555        assert_text_with_selections(
 7556            editor,
 7557            indoc! {r#"
 7558                use mod1::mod2::{mod3, mo«ˇ»d4};
 7559
 7560                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7561                    let var1 = "te«ˇ»xt";
 7562                }
 7563            "#},
 7564            cx,
 7565        );
 7566    });
 7567
 7568    // Trying to shrink the selected syntax node one more time has no effect.
 7569    editor.update_in(cx, |editor, window, cx| {
 7570        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7571    });
 7572    editor.update_in(cx, |editor, _, cx| {
 7573        assert_text_with_selections(
 7574            editor,
 7575            indoc! {r#"
 7576                use mod1::mod2::{mod3, mo«ˇ»d4};
 7577
 7578                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7579                    let var1 = "te«ˇ»xt";
 7580                }
 7581            "#},
 7582            cx,
 7583        );
 7584    });
 7585
 7586    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 7587    // a fold.
 7588    editor.update_in(cx, |editor, window, cx| {
 7589        editor.fold_creases(
 7590            vec![
 7591                Crease::simple(
 7592                    Point::new(0, 21)..Point::new(0, 24),
 7593                    FoldPlaceholder::test(),
 7594                ),
 7595                Crease::simple(
 7596                    Point::new(3, 20)..Point::new(3, 22),
 7597                    FoldPlaceholder::test(),
 7598                ),
 7599            ],
 7600            true,
 7601            window,
 7602            cx,
 7603        );
 7604        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7605    });
 7606    editor.update(cx, |editor, cx| {
 7607        assert_text_with_selections(
 7608            editor,
 7609            indoc! {r#"
 7610                use mod1::mod2::«{mod3, mod4}ˇ»;
 7611
 7612                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7613                    let var1 = "«ˇtext»";
 7614                }
 7615            "#},
 7616            cx,
 7617        );
 7618    });
 7619}
 7620
 7621#[gpui::test]
 7622async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 7623    init_test(cx, |_| {});
 7624
 7625    let language = Arc::new(Language::new(
 7626        LanguageConfig::default(),
 7627        Some(tree_sitter_rust::LANGUAGE.into()),
 7628    ));
 7629
 7630    let text = "let a = 2;";
 7631
 7632    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7633    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7634    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7635
 7636    editor
 7637        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7638        .await;
 7639
 7640    // Test case 1: Cursor at end of word
 7641    editor.update_in(cx, |editor, window, cx| {
 7642        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7643            s.select_display_ranges([
 7644                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 7645            ]);
 7646        });
 7647    });
 7648    editor.update(cx, |editor, cx| {
 7649        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 7650    });
 7651    editor.update_in(cx, |editor, window, cx| {
 7652        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7653    });
 7654    editor.update(cx, |editor, cx| {
 7655        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 7656    });
 7657    editor.update_in(cx, |editor, window, cx| {
 7658        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7659    });
 7660    editor.update(cx, |editor, cx| {
 7661        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7662    });
 7663
 7664    // Test case 2: Cursor at end of statement
 7665    editor.update_in(cx, |editor, window, cx| {
 7666        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7667            s.select_display_ranges([
 7668                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 7669            ]);
 7670        });
 7671    });
 7672    editor.update(cx, |editor, cx| {
 7673        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 7674    });
 7675    editor.update_in(cx, |editor, window, cx| {
 7676        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7677    });
 7678    editor.update(cx, |editor, cx| {
 7679        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7680    });
 7681}
 7682
 7683#[gpui::test]
 7684async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 7685    init_test(cx, |_| {});
 7686
 7687    let language = Arc::new(Language::new(
 7688        LanguageConfig::default(),
 7689        Some(tree_sitter_rust::LANGUAGE.into()),
 7690    ));
 7691
 7692    let text = r#"
 7693        use mod1::mod2::{mod3, mod4};
 7694
 7695        fn fn_1(param1: bool, param2: &str) {
 7696            let var1 = "hello world";
 7697        }
 7698    "#
 7699    .unindent();
 7700
 7701    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7702    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7703    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7704
 7705    editor
 7706        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7707        .await;
 7708
 7709    // Test 1: Cursor on a letter of a string word
 7710    editor.update_in(cx, |editor, window, cx| {
 7711        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7712            s.select_display_ranges([
 7713                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 7714            ]);
 7715        });
 7716    });
 7717    editor.update_in(cx, |editor, window, cx| {
 7718        assert_text_with_selections(
 7719            editor,
 7720            indoc! {r#"
 7721                use mod1::mod2::{mod3, mod4};
 7722
 7723                fn fn_1(param1: bool, param2: &str) {
 7724                    let var1 = "hˇello world";
 7725                }
 7726            "#},
 7727            cx,
 7728        );
 7729        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7730        assert_text_with_selections(
 7731            editor,
 7732            indoc! {r#"
 7733                use mod1::mod2::{mod3, mod4};
 7734
 7735                fn fn_1(param1: bool, param2: &str) {
 7736                    let var1 = "«ˇhello» world";
 7737                }
 7738            "#},
 7739            cx,
 7740        );
 7741    });
 7742
 7743    // Test 2: Partial selection within a word
 7744    editor.update_in(cx, |editor, window, cx| {
 7745        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7746            s.select_display_ranges([
 7747                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 7748            ]);
 7749        });
 7750    });
 7751    editor.update_in(cx, |editor, window, cx| {
 7752        assert_text_with_selections(
 7753            editor,
 7754            indoc! {r#"
 7755                use mod1::mod2::{mod3, mod4};
 7756
 7757                fn fn_1(param1: bool, param2: &str) {
 7758                    let var1 = "h«elˇ»lo world";
 7759                }
 7760            "#},
 7761            cx,
 7762        );
 7763        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7764        assert_text_with_selections(
 7765            editor,
 7766            indoc! {r#"
 7767                use mod1::mod2::{mod3, mod4};
 7768
 7769                fn fn_1(param1: bool, param2: &str) {
 7770                    let var1 = "«ˇhello» world";
 7771                }
 7772            "#},
 7773            cx,
 7774        );
 7775    });
 7776
 7777    // Test 3: Complete word already selected
 7778    editor.update_in(cx, |editor, window, cx| {
 7779        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7780            s.select_display_ranges([
 7781                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 7782            ]);
 7783        });
 7784    });
 7785    editor.update_in(cx, |editor, window, cx| {
 7786        assert_text_with_selections(
 7787            editor,
 7788            indoc! {r#"
 7789                use mod1::mod2::{mod3, mod4};
 7790
 7791                fn fn_1(param1: bool, param2: &str) {
 7792                    let var1 = "«helloˇ» world";
 7793                }
 7794            "#},
 7795            cx,
 7796        );
 7797        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7798        assert_text_with_selections(
 7799            editor,
 7800            indoc! {r#"
 7801                use mod1::mod2::{mod3, mod4};
 7802
 7803                fn fn_1(param1: bool, param2: &str) {
 7804                    let var1 = "«hello worldˇ»";
 7805                }
 7806            "#},
 7807            cx,
 7808        );
 7809    });
 7810
 7811    // Test 4: Selection spanning across words
 7812    editor.update_in(cx, |editor, window, cx| {
 7813        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7814            s.select_display_ranges([
 7815                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 7816            ]);
 7817        });
 7818    });
 7819    editor.update_in(cx, |editor, window, cx| {
 7820        assert_text_with_selections(
 7821            editor,
 7822            indoc! {r#"
 7823                use mod1::mod2::{mod3, mod4};
 7824
 7825                fn fn_1(param1: bool, param2: &str) {
 7826                    let var1 = "hel«lo woˇ»rld";
 7827                }
 7828            "#},
 7829            cx,
 7830        );
 7831        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7832        assert_text_with_selections(
 7833            editor,
 7834            indoc! {r#"
 7835                use mod1::mod2::{mod3, mod4};
 7836
 7837                fn fn_1(param1: bool, param2: &str) {
 7838                    let var1 = "«ˇhello world»";
 7839                }
 7840            "#},
 7841            cx,
 7842        );
 7843    });
 7844
 7845    // Test 5: Expansion beyond string
 7846    editor.update_in(cx, |editor, window, cx| {
 7847        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7848        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7849        assert_text_with_selections(
 7850            editor,
 7851            indoc! {r#"
 7852                use mod1::mod2::{mod3, mod4};
 7853
 7854                fn fn_1(param1: bool, param2: &str) {
 7855                    «ˇlet var1 = "hello world";»
 7856                }
 7857            "#},
 7858            cx,
 7859        );
 7860    });
 7861}
 7862
 7863#[gpui::test]
 7864async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 7865    init_test(cx, |_| {});
 7866
 7867    let base_text = r#"
 7868        impl A {
 7869            // this is an uncommitted comment
 7870
 7871            fn b() {
 7872                c();
 7873            }
 7874
 7875            // this is another uncommitted comment
 7876
 7877            fn d() {
 7878                // e
 7879                // f
 7880            }
 7881        }
 7882
 7883        fn g() {
 7884            // h
 7885        }
 7886    "#
 7887    .unindent();
 7888
 7889    let text = r#"
 7890        ˇimpl A {
 7891
 7892            fn b() {
 7893                c();
 7894            }
 7895
 7896            fn d() {
 7897                // e
 7898                // f
 7899            }
 7900        }
 7901
 7902        fn g() {
 7903            // h
 7904        }
 7905    "#
 7906    .unindent();
 7907
 7908    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 7909    cx.set_state(&text);
 7910    cx.set_head_text(&base_text);
 7911    cx.update_editor(|editor, window, cx| {
 7912        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 7913    });
 7914
 7915    cx.assert_state_with_diff(
 7916        "
 7917        ˇimpl A {
 7918      -     // this is an uncommitted comment
 7919
 7920            fn b() {
 7921                c();
 7922            }
 7923
 7924      -     // this is another uncommitted comment
 7925      -
 7926            fn d() {
 7927                // e
 7928                // f
 7929            }
 7930        }
 7931
 7932        fn g() {
 7933            // h
 7934        }
 7935    "
 7936        .unindent(),
 7937    );
 7938
 7939    let expected_display_text = "
 7940        impl A {
 7941            // this is an uncommitted comment
 7942
 7943            fn b() {
 7944 7945            }
 7946
 7947            // this is another uncommitted comment
 7948
 7949            fn d() {
 7950 7951            }
 7952        }
 7953
 7954        fn g() {
 7955 7956        }
 7957        "
 7958    .unindent();
 7959
 7960    cx.update_editor(|editor, window, cx| {
 7961        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 7962        assert_eq!(editor.display_text(cx), expected_display_text);
 7963    });
 7964}
 7965
 7966#[gpui::test]
 7967async fn test_autoindent(cx: &mut TestAppContext) {
 7968    init_test(cx, |_| {});
 7969
 7970    let language = Arc::new(
 7971        Language::new(
 7972            LanguageConfig {
 7973                brackets: BracketPairConfig {
 7974                    pairs: vec![
 7975                        BracketPair {
 7976                            start: "{".to_string(),
 7977                            end: "}".to_string(),
 7978                            close: false,
 7979                            surround: false,
 7980                            newline: true,
 7981                        },
 7982                        BracketPair {
 7983                            start: "(".to_string(),
 7984                            end: ")".to_string(),
 7985                            close: false,
 7986                            surround: false,
 7987                            newline: true,
 7988                        },
 7989                    ],
 7990                    ..Default::default()
 7991                },
 7992                ..Default::default()
 7993            },
 7994            Some(tree_sitter_rust::LANGUAGE.into()),
 7995        )
 7996        .with_indents_query(
 7997            r#"
 7998                (_ "(" ")" @end) @indent
 7999                (_ "{" "}" @end) @indent
 8000            "#,
 8001        )
 8002        .unwrap(),
 8003    );
 8004
 8005    let text = "fn a() {}";
 8006
 8007    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8008    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8009    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8010    editor
 8011        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8012        .await;
 8013
 8014    editor.update_in(cx, |editor, window, cx| {
 8015        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8016            s.select_ranges([5..5, 8..8, 9..9])
 8017        });
 8018        editor.newline(&Newline, window, cx);
 8019        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 8020        assert_eq!(
 8021            editor.selections.ranges(cx),
 8022            &[
 8023                Point::new(1, 4)..Point::new(1, 4),
 8024                Point::new(3, 4)..Point::new(3, 4),
 8025                Point::new(5, 0)..Point::new(5, 0)
 8026            ]
 8027        );
 8028    });
 8029}
 8030
 8031#[gpui::test]
 8032async fn test_autoindent_selections(cx: &mut TestAppContext) {
 8033    init_test(cx, |_| {});
 8034
 8035    {
 8036        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 8037        cx.set_state(indoc! {"
 8038            impl A {
 8039
 8040                fn b() {}
 8041
 8042            «fn c() {
 8043
 8044            }ˇ»
 8045            }
 8046        "});
 8047
 8048        cx.update_editor(|editor, window, cx| {
 8049            editor.autoindent(&Default::default(), window, cx);
 8050        });
 8051
 8052        cx.assert_editor_state(indoc! {"
 8053            impl A {
 8054
 8055                fn b() {}
 8056
 8057                «fn c() {
 8058
 8059                }ˇ»
 8060            }
 8061        "});
 8062    }
 8063
 8064    {
 8065        let mut cx = EditorTestContext::new_multibuffer(
 8066            cx,
 8067            [indoc! { "
 8068                impl A {
 8069                «
 8070                // a
 8071                fn b(){}
 8072                »
 8073                «
 8074                    }
 8075                    fn c(){}
 8076                »
 8077            "}],
 8078        );
 8079
 8080        let buffer = cx.update_editor(|editor, _, cx| {
 8081            let buffer = editor.buffer().update(cx, |buffer, _| {
 8082                buffer.all_buffers().iter().next().unwrap().clone()
 8083            });
 8084            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 8085            buffer
 8086        });
 8087
 8088        cx.run_until_parked();
 8089        cx.update_editor(|editor, window, cx| {
 8090            editor.select_all(&Default::default(), window, cx);
 8091            editor.autoindent(&Default::default(), window, cx)
 8092        });
 8093        cx.run_until_parked();
 8094
 8095        cx.update(|_, cx| {
 8096            assert_eq!(
 8097                buffer.read(cx).text(),
 8098                indoc! { "
 8099                    impl A {
 8100
 8101                        // a
 8102                        fn b(){}
 8103
 8104
 8105                    }
 8106                    fn c(){}
 8107
 8108                " }
 8109            )
 8110        });
 8111    }
 8112}
 8113
 8114#[gpui::test]
 8115async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 8116    init_test(cx, |_| {});
 8117
 8118    let mut cx = EditorTestContext::new(cx).await;
 8119
 8120    let language = Arc::new(Language::new(
 8121        LanguageConfig {
 8122            brackets: BracketPairConfig {
 8123                pairs: vec![
 8124                    BracketPair {
 8125                        start: "{".to_string(),
 8126                        end: "}".to_string(),
 8127                        close: true,
 8128                        surround: true,
 8129                        newline: true,
 8130                    },
 8131                    BracketPair {
 8132                        start: "(".to_string(),
 8133                        end: ")".to_string(),
 8134                        close: true,
 8135                        surround: true,
 8136                        newline: true,
 8137                    },
 8138                    BracketPair {
 8139                        start: "/*".to_string(),
 8140                        end: " */".to_string(),
 8141                        close: true,
 8142                        surround: true,
 8143                        newline: true,
 8144                    },
 8145                    BracketPair {
 8146                        start: "[".to_string(),
 8147                        end: "]".to_string(),
 8148                        close: false,
 8149                        surround: false,
 8150                        newline: true,
 8151                    },
 8152                    BracketPair {
 8153                        start: "\"".to_string(),
 8154                        end: "\"".to_string(),
 8155                        close: true,
 8156                        surround: true,
 8157                        newline: false,
 8158                    },
 8159                    BracketPair {
 8160                        start: "<".to_string(),
 8161                        end: ">".to_string(),
 8162                        close: false,
 8163                        surround: true,
 8164                        newline: true,
 8165                    },
 8166                ],
 8167                ..Default::default()
 8168            },
 8169            autoclose_before: "})]".to_string(),
 8170            ..Default::default()
 8171        },
 8172        Some(tree_sitter_rust::LANGUAGE.into()),
 8173    ));
 8174
 8175    cx.language_registry().add(language.clone());
 8176    cx.update_buffer(|buffer, cx| {
 8177        buffer.set_language(Some(language), cx);
 8178    });
 8179
 8180    cx.set_state(
 8181        &r#"
 8182            🏀ˇ
 8183            εˇ
 8184            ❤️ˇ
 8185        "#
 8186        .unindent(),
 8187    );
 8188
 8189    // autoclose multiple nested brackets at multiple cursors
 8190    cx.update_editor(|editor, window, cx| {
 8191        editor.handle_input("{", window, cx);
 8192        editor.handle_input("{", window, cx);
 8193        editor.handle_input("{", window, cx);
 8194    });
 8195    cx.assert_editor_state(
 8196        &"
 8197            🏀{{{ˇ}}}
 8198            ε{{{ˇ}}}
 8199            ❤️{{{ˇ}}}
 8200        "
 8201        .unindent(),
 8202    );
 8203
 8204    // insert a different closing bracket
 8205    cx.update_editor(|editor, window, cx| {
 8206        editor.handle_input(")", window, cx);
 8207    });
 8208    cx.assert_editor_state(
 8209        &"
 8210            🏀{{{)ˇ}}}
 8211            ε{{{)ˇ}}}
 8212            ❤️{{{)ˇ}}}
 8213        "
 8214        .unindent(),
 8215    );
 8216
 8217    // skip over the auto-closed brackets when typing a closing bracket
 8218    cx.update_editor(|editor, window, cx| {
 8219        editor.move_right(&MoveRight, window, cx);
 8220        editor.handle_input("}", window, cx);
 8221        editor.handle_input("}", window, cx);
 8222        editor.handle_input("}", window, cx);
 8223    });
 8224    cx.assert_editor_state(
 8225        &"
 8226            🏀{{{)}}}}ˇ
 8227            ε{{{)}}}}ˇ
 8228            ❤️{{{)}}}}ˇ
 8229        "
 8230        .unindent(),
 8231    );
 8232
 8233    // autoclose multi-character pairs
 8234    cx.set_state(
 8235        &"
 8236            ˇ
 8237            ˇ
 8238        "
 8239        .unindent(),
 8240    );
 8241    cx.update_editor(|editor, window, cx| {
 8242        editor.handle_input("/", window, cx);
 8243        editor.handle_input("*", window, cx);
 8244    });
 8245    cx.assert_editor_state(
 8246        &"
 8247            /*ˇ */
 8248            /*ˇ */
 8249        "
 8250        .unindent(),
 8251    );
 8252
 8253    // one cursor autocloses a multi-character pair, one cursor
 8254    // does not autoclose.
 8255    cx.set_state(
 8256        &"
 8257 8258            ˇ
 8259        "
 8260        .unindent(),
 8261    );
 8262    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 8263    cx.assert_editor_state(
 8264        &"
 8265            /*ˇ */
 8266 8267        "
 8268        .unindent(),
 8269    );
 8270
 8271    // Don't autoclose if the next character isn't whitespace and isn't
 8272    // listed in the language's "autoclose_before" section.
 8273    cx.set_state("ˇa b");
 8274    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8275    cx.assert_editor_state("{ˇa b");
 8276
 8277    // Don't autoclose if `close` is false for the bracket pair
 8278    cx.set_state("ˇ");
 8279    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 8280    cx.assert_editor_state("");
 8281
 8282    // Surround with brackets if text is selected
 8283    cx.set_state("«aˇ» b");
 8284    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8285    cx.assert_editor_state("{«aˇ»} b");
 8286
 8287    // Autoclose when not immediately after a word character
 8288    cx.set_state("a ˇ");
 8289    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8290    cx.assert_editor_state("a \"ˇ\"");
 8291
 8292    // Autoclose pair where the start and end characters are the same
 8293    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8294    cx.assert_editor_state("a \"\"ˇ");
 8295
 8296    // Don't autoclose when immediately after a word character
 8297    cx.set_state("");
 8298    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8299    cx.assert_editor_state("a\"ˇ");
 8300
 8301    // Do autoclose when after a non-word character
 8302    cx.set_state("");
 8303    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8304    cx.assert_editor_state("{\"ˇ\"");
 8305
 8306    // Non identical pairs autoclose regardless of preceding character
 8307    cx.set_state("");
 8308    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8309    cx.assert_editor_state("a{ˇ}");
 8310
 8311    // Don't autoclose pair if autoclose is disabled
 8312    cx.set_state("ˇ");
 8313    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 8314    cx.assert_editor_state("");
 8315
 8316    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 8317    cx.set_state("«aˇ» b");
 8318    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 8319    cx.assert_editor_state("<«aˇ»> b");
 8320}
 8321
 8322#[gpui::test]
 8323async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 8324    init_test(cx, |settings| {
 8325        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 8326    });
 8327
 8328    let mut cx = EditorTestContext::new(cx).await;
 8329
 8330    let language = Arc::new(Language::new(
 8331        LanguageConfig {
 8332            brackets: BracketPairConfig {
 8333                pairs: vec![
 8334                    BracketPair {
 8335                        start: "{".to_string(),
 8336                        end: "}".to_string(),
 8337                        close: true,
 8338                        surround: true,
 8339                        newline: true,
 8340                    },
 8341                    BracketPair {
 8342                        start: "(".to_string(),
 8343                        end: ")".to_string(),
 8344                        close: true,
 8345                        surround: true,
 8346                        newline: true,
 8347                    },
 8348                    BracketPair {
 8349                        start: "[".to_string(),
 8350                        end: "]".to_string(),
 8351                        close: false,
 8352                        surround: false,
 8353                        newline: true,
 8354                    },
 8355                ],
 8356                ..Default::default()
 8357            },
 8358            autoclose_before: "})]".to_string(),
 8359            ..Default::default()
 8360        },
 8361        Some(tree_sitter_rust::LANGUAGE.into()),
 8362    ));
 8363
 8364    cx.language_registry().add(language.clone());
 8365    cx.update_buffer(|buffer, cx| {
 8366        buffer.set_language(Some(language), cx);
 8367    });
 8368
 8369    cx.set_state(
 8370        &"
 8371            ˇ
 8372            ˇ
 8373            ˇ
 8374        "
 8375        .unindent(),
 8376    );
 8377
 8378    // ensure only matching closing brackets are skipped over
 8379    cx.update_editor(|editor, window, cx| {
 8380        editor.handle_input("}", window, cx);
 8381        editor.move_left(&MoveLeft, window, cx);
 8382        editor.handle_input(")", window, cx);
 8383        editor.move_left(&MoveLeft, window, cx);
 8384    });
 8385    cx.assert_editor_state(
 8386        &"
 8387            ˇ)}
 8388            ˇ)}
 8389            ˇ)}
 8390        "
 8391        .unindent(),
 8392    );
 8393
 8394    // skip-over closing brackets at multiple cursors
 8395    cx.update_editor(|editor, window, cx| {
 8396        editor.handle_input(")", window, cx);
 8397        editor.handle_input("}", window, cx);
 8398    });
 8399    cx.assert_editor_state(
 8400        &"
 8401            )}ˇ
 8402            )}ˇ
 8403            )}ˇ
 8404        "
 8405        .unindent(),
 8406    );
 8407
 8408    // ignore non-close brackets
 8409    cx.update_editor(|editor, window, cx| {
 8410        editor.handle_input("]", window, cx);
 8411        editor.move_left(&MoveLeft, window, cx);
 8412        editor.handle_input("]", window, cx);
 8413    });
 8414    cx.assert_editor_state(
 8415        &"
 8416            )}]ˇ]
 8417            )}]ˇ]
 8418            )}]ˇ]
 8419        "
 8420        .unindent(),
 8421    );
 8422}
 8423
 8424#[gpui::test]
 8425async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
 8426    init_test(cx, |_| {});
 8427
 8428    let mut cx = EditorTestContext::new(cx).await;
 8429
 8430    let html_language = Arc::new(
 8431        Language::new(
 8432            LanguageConfig {
 8433                name: "HTML".into(),
 8434                brackets: BracketPairConfig {
 8435                    pairs: vec![
 8436                        BracketPair {
 8437                            start: "<".into(),
 8438                            end: ">".into(),
 8439                            close: true,
 8440                            ..Default::default()
 8441                        },
 8442                        BracketPair {
 8443                            start: "{".into(),
 8444                            end: "}".into(),
 8445                            close: true,
 8446                            ..Default::default()
 8447                        },
 8448                        BracketPair {
 8449                            start: "(".into(),
 8450                            end: ")".into(),
 8451                            close: true,
 8452                            ..Default::default()
 8453                        },
 8454                    ],
 8455                    ..Default::default()
 8456                },
 8457                autoclose_before: "})]>".into(),
 8458                ..Default::default()
 8459            },
 8460            Some(tree_sitter_html::LANGUAGE.into()),
 8461        )
 8462        .with_injection_query(
 8463            r#"
 8464            (script_element
 8465                (raw_text) @injection.content
 8466                (#set! injection.language "javascript"))
 8467            "#,
 8468        )
 8469        .unwrap(),
 8470    );
 8471
 8472    let javascript_language = Arc::new(Language::new(
 8473        LanguageConfig {
 8474            name: "JavaScript".into(),
 8475            brackets: BracketPairConfig {
 8476                pairs: vec![
 8477                    BracketPair {
 8478                        start: "/*".into(),
 8479                        end: " */".into(),
 8480                        close: true,
 8481                        ..Default::default()
 8482                    },
 8483                    BracketPair {
 8484                        start: "{".into(),
 8485                        end: "}".into(),
 8486                        close: true,
 8487                        ..Default::default()
 8488                    },
 8489                    BracketPair {
 8490                        start: "(".into(),
 8491                        end: ")".into(),
 8492                        close: true,
 8493                        ..Default::default()
 8494                    },
 8495                ],
 8496                ..Default::default()
 8497            },
 8498            autoclose_before: "})]>".into(),
 8499            ..Default::default()
 8500        },
 8501        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 8502    ));
 8503
 8504    cx.language_registry().add(html_language.clone());
 8505    cx.language_registry().add(javascript_language.clone());
 8506
 8507    cx.update_buffer(|buffer, cx| {
 8508        buffer.set_language(Some(html_language), cx);
 8509    });
 8510
 8511    cx.set_state(
 8512        &r#"
 8513            <body>ˇ
 8514                <script>
 8515                    var x = 1;ˇ
 8516                </script>
 8517            </body>ˇ
 8518        "#
 8519        .unindent(),
 8520    );
 8521
 8522    // Precondition: different languages are active at different locations.
 8523    cx.update_editor(|editor, window, cx| {
 8524        let snapshot = editor.snapshot(window, cx);
 8525        let cursors = editor.selections.ranges::<usize>(cx);
 8526        let languages = cursors
 8527            .iter()
 8528            .map(|c| snapshot.language_at(c.start).unwrap().name())
 8529            .collect::<Vec<_>>();
 8530        assert_eq!(
 8531            languages,
 8532            &["HTML".into(), "JavaScript".into(), "HTML".into()]
 8533        );
 8534    });
 8535
 8536    // Angle brackets autoclose in HTML, but not JavaScript.
 8537    cx.update_editor(|editor, window, cx| {
 8538        editor.handle_input("<", window, cx);
 8539        editor.handle_input("a", window, cx);
 8540    });
 8541    cx.assert_editor_state(
 8542        &r#"
 8543            <body><aˇ>
 8544                <script>
 8545                    var x = 1;<aˇ
 8546                </script>
 8547            </body><aˇ>
 8548        "#
 8549        .unindent(),
 8550    );
 8551
 8552    // Curly braces and parens autoclose in both HTML and JavaScript.
 8553    cx.update_editor(|editor, window, cx| {
 8554        editor.handle_input(" b=", window, cx);
 8555        editor.handle_input("{", window, cx);
 8556        editor.handle_input("c", window, cx);
 8557        editor.handle_input("(", window, cx);
 8558    });
 8559    cx.assert_editor_state(
 8560        &r#"
 8561            <body><a b={c(ˇ)}>
 8562                <script>
 8563                    var x = 1;<a b={c(ˇ)}
 8564                </script>
 8565            </body><a b={c(ˇ)}>
 8566        "#
 8567        .unindent(),
 8568    );
 8569
 8570    // Brackets that were already autoclosed are skipped.
 8571    cx.update_editor(|editor, window, cx| {
 8572        editor.handle_input(")", window, cx);
 8573        editor.handle_input("d", window, cx);
 8574        editor.handle_input("}", window, cx);
 8575    });
 8576    cx.assert_editor_state(
 8577        &r#"
 8578            <body><a b={c()d}ˇ>
 8579                <script>
 8580                    var x = 1;<a b={c()d}ˇ
 8581                </script>
 8582            </body><a b={c()d}ˇ>
 8583        "#
 8584        .unindent(),
 8585    );
 8586    cx.update_editor(|editor, window, cx| {
 8587        editor.handle_input(">", window, cx);
 8588    });
 8589    cx.assert_editor_state(
 8590        &r#"
 8591            <body><a b={c()d}>ˇ
 8592                <script>
 8593                    var x = 1;<a b={c()d}>ˇ
 8594                </script>
 8595            </body><a b={c()d}>ˇ
 8596        "#
 8597        .unindent(),
 8598    );
 8599
 8600    // Reset
 8601    cx.set_state(
 8602        &r#"
 8603            <body>ˇ
 8604                <script>
 8605                    var x = 1;ˇ
 8606                </script>
 8607            </body>ˇ
 8608        "#
 8609        .unindent(),
 8610    );
 8611
 8612    cx.update_editor(|editor, window, cx| {
 8613        editor.handle_input("<", window, cx);
 8614    });
 8615    cx.assert_editor_state(
 8616        &r#"
 8617            <body><ˇ>
 8618                <script>
 8619                    var x = 1;<ˇ
 8620                </script>
 8621            </body><ˇ>
 8622        "#
 8623        .unindent(),
 8624    );
 8625
 8626    // When backspacing, the closing angle brackets are removed.
 8627    cx.update_editor(|editor, window, cx| {
 8628        editor.backspace(&Backspace, window, cx);
 8629    });
 8630    cx.assert_editor_state(
 8631        &r#"
 8632            <body>ˇ
 8633                <script>
 8634                    var x = 1;ˇ
 8635                </script>
 8636            </body>ˇ
 8637        "#
 8638        .unindent(),
 8639    );
 8640
 8641    // Block comments autoclose in JavaScript, but not HTML.
 8642    cx.update_editor(|editor, window, cx| {
 8643        editor.handle_input("/", window, cx);
 8644        editor.handle_input("*", window, cx);
 8645    });
 8646    cx.assert_editor_state(
 8647        &r#"
 8648            <body>/*ˇ
 8649                <script>
 8650                    var x = 1;/*ˇ */
 8651                </script>
 8652            </body>/*ˇ
 8653        "#
 8654        .unindent(),
 8655    );
 8656}
 8657
 8658#[gpui::test]
 8659async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
 8660    init_test(cx, |_| {});
 8661
 8662    let mut cx = EditorTestContext::new(cx).await;
 8663
 8664    let rust_language = Arc::new(
 8665        Language::new(
 8666            LanguageConfig {
 8667                name: "Rust".into(),
 8668                brackets: serde_json::from_value(json!([
 8669                    { "start": "{", "end": "}", "close": true, "newline": true },
 8670                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
 8671                ]))
 8672                .unwrap(),
 8673                autoclose_before: "})]>".into(),
 8674                ..Default::default()
 8675            },
 8676            Some(tree_sitter_rust::LANGUAGE.into()),
 8677        )
 8678        .with_override_query("(string_literal) @string")
 8679        .unwrap(),
 8680    );
 8681
 8682    cx.language_registry().add(rust_language.clone());
 8683    cx.update_buffer(|buffer, cx| {
 8684        buffer.set_language(Some(rust_language), cx);
 8685    });
 8686
 8687    cx.set_state(
 8688        &r#"
 8689            let x = ˇ
 8690        "#
 8691        .unindent(),
 8692    );
 8693
 8694    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
 8695    cx.update_editor(|editor, window, cx| {
 8696        editor.handle_input("\"", window, cx);
 8697    });
 8698    cx.assert_editor_state(
 8699        &r#"
 8700            let x = "ˇ"
 8701        "#
 8702        .unindent(),
 8703    );
 8704
 8705    // Inserting another quotation mark. The cursor moves across the existing
 8706    // automatically-inserted quotation mark.
 8707    cx.update_editor(|editor, window, cx| {
 8708        editor.handle_input("\"", window, cx);
 8709    });
 8710    cx.assert_editor_state(
 8711        &r#"
 8712            let x = ""ˇ
 8713        "#
 8714        .unindent(),
 8715    );
 8716
 8717    // Reset
 8718    cx.set_state(
 8719        &r#"
 8720            let x = ˇ
 8721        "#
 8722        .unindent(),
 8723    );
 8724
 8725    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
 8726    cx.update_editor(|editor, window, cx| {
 8727        editor.handle_input("\"", window, cx);
 8728        editor.handle_input(" ", window, cx);
 8729        editor.move_left(&Default::default(), window, cx);
 8730        editor.handle_input("\\", 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    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
 8741    // mark. Nothing is inserted.
 8742    cx.update_editor(|editor, window, cx| {
 8743        editor.move_right(&Default::default(), window, cx);
 8744        editor.handle_input("\"", window, cx);
 8745    });
 8746    cx.assert_editor_state(
 8747        &r#"
 8748            let x = "\" "ˇ
 8749        "#
 8750        .unindent(),
 8751    );
 8752}
 8753
 8754#[gpui::test]
 8755async fn test_surround_with_pair(cx: &mut TestAppContext) {
 8756    init_test(cx, |_| {});
 8757
 8758    let language = Arc::new(Language::new(
 8759        LanguageConfig {
 8760            brackets: BracketPairConfig {
 8761                pairs: vec![
 8762                    BracketPair {
 8763                        start: "{".to_string(),
 8764                        end: "}".to_string(),
 8765                        close: true,
 8766                        surround: true,
 8767                        newline: true,
 8768                    },
 8769                    BracketPair {
 8770                        start: "/* ".to_string(),
 8771                        end: "*/".to_string(),
 8772                        close: true,
 8773                        surround: true,
 8774                        ..Default::default()
 8775                    },
 8776                ],
 8777                ..Default::default()
 8778            },
 8779            ..Default::default()
 8780        },
 8781        Some(tree_sitter_rust::LANGUAGE.into()),
 8782    ));
 8783
 8784    let text = r#"
 8785        a
 8786        b
 8787        c
 8788    "#
 8789    .unindent();
 8790
 8791    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8792    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8793    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8794    editor
 8795        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8796        .await;
 8797
 8798    editor.update_in(cx, |editor, window, cx| {
 8799        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8800            s.select_display_ranges([
 8801                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8802                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8803                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
 8804            ])
 8805        });
 8806
 8807        editor.handle_input("{", window, cx);
 8808        editor.handle_input("{", window, cx);
 8809        editor.handle_input("{", window, cx);
 8810        assert_eq!(
 8811            editor.text(cx),
 8812            "
 8813                {{{a}}}
 8814                {{{b}}}
 8815                {{{c}}}
 8816            "
 8817            .unindent()
 8818        );
 8819        assert_eq!(
 8820            editor.selections.display_ranges(cx),
 8821            [
 8822                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
 8823                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
 8824                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
 8825            ]
 8826        );
 8827
 8828        editor.undo(&Undo, window, cx);
 8829        editor.undo(&Undo, window, cx);
 8830        editor.undo(&Undo, window, cx);
 8831        assert_eq!(
 8832            editor.text(cx),
 8833            "
 8834                a
 8835                b
 8836                c
 8837            "
 8838            .unindent()
 8839        );
 8840        assert_eq!(
 8841            editor.selections.display_ranges(cx),
 8842            [
 8843                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8844                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8845                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 8846            ]
 8847        );
 8848
 8849        // Ensure inserting the first character of a multi-byte bracket pair
 8850        // doesn't surround the selections with the bracket.
 8851        editor.handle_input("/", window, cx);
 8852        assert_eq!(
 8853            editor.text(cx),
 8854            "
 8855                /
 8856                /
 8857                /
 8858            "
 8859            .unindent()
 8860        );
 8861        assert_eq!(
 8862            editor.selections.display_ranges(cx),
 8863            [
 8864                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 8865                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 8866                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 8867            ]
 8868        );
 8869
 8870        editor.undo(&Undo, window, cx);
 8871        assert_eq!(
 8872            editor.text(cx),
 8873            "
 8874                a
 8875                b
 8876                c
 8877            "
 8878            .unindent()
 8879        );
 8880        assert_eq!(
 8881            editor.selections.display_ranges(cx),
 8882            [
 8883                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8884                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8885                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 8886            ]
 8887        );
 8888
 8889        // Ensure inserting the last character of a multi-byte bracket pair
 8890        // doesn't surround the selections with the bracket.
 8891        editor.handle_input("*", window, cx);
 8892        assert_eq!(
 8893            editor.text(cx),
 8894            "
 8895                *
 8896                *
 8897                *
 8898            "
 8899            .unindent()
 8900        );
 8901        assert_eq!(
 8902            editor.selections.display_ranges(cx),
 8903            [
 8904                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 8905                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 8906                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 8907            ]
 8908        );
 8909    });
 8910}
 8911
 8912#[gpui::test]
 8913async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
 8914    init_test(cx, |_| {});
 8915
 8916    let language = Arc::new(Language::new(
 8917        LanguageConfig {
 8918            brackets: BracketPairConfig {
 8919                pairs: vec![BracketPair {
 8920                    start: "{".to_string(),
 8921                    end: "}".to_string(),
 8922                    close: true,
 8923                    surround: true,
 8924                    newline: true,
 8925                }],
 8926                ..Default::default()
 8927            },
 8928            autoclose_before: "}".to_string(),
 8929            ..Default::default()
 8930        },
 8931        Some(tree_sitter_rust::LANGUAGE.into()),
 8932    ));
 8933
 8934    let text = r#"
 8935        a
 8936        b
 8937        c
 8938    "#
 8939    .unindent();
 8940
 8941    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8942    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8943    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8944    editor
 8945        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8946        .await;
 8947
 8948    editor.update_in(cx, |editor, window, cx| {
 8949        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8950            s.select_ranges([
 8951                Point::new(0, 1)..Point::new(0, 1),
 8952                Point::new(1, 1)..Point::new(1, 1),
 8953                Point::new(2, 1)..Point::new(2, 1),
 8954            ])
 8955        });
 8956
 8957        editor.handle_input("{", window, cx);
 8958        editor.handle_input("{", window, cx);
 8959        editor.handle_input("_", window, cx);
 8960        assert_eq!(
 8961            editor.text(cx),
 8962            "
 8963                a{{_}}
 8964                b{{_}}
 8965                c{{_}}
 8966            "
 8967            .unindent()
 8968        );
 8969        assert_eq!(
 8970            editor.selections.ranges::<Point>(cx),
 8971            [
 8972                Point::new(0, 4)..Point::new(0, 4),
 8973                Point::new(1, 4)..Point::new(1, 4),
 8974                Point::new(2, 4)..Point::new(2, 4)
 8975            ]
 8976        );
 8977
 8978        editor.backspace(&Default::default(), window, cx);
 8979        editor.backspace(&Default::default(), window, cx);
 8980        assert_eq!(
 8981            editor.text(cx),
 8982            "
 8983                a{}
 8984                b{}
 8985                c{}
 8986            "
 8987            .unindent()
 8988        );
 8989        assert_eq!(
 8990            editor.selections.ranges::<Point>(cx),
 8991            [
 8992                Point::new(0, 2)..Point::new(0, 2),
 8993                Point::new(1, 2)..Point::new(1, 2),
 8994                Point::new(2, 2)..Point::new(2, 2)
 8995            ]
 8996        );
 8997
 8998        editor.delete_to_previous_word_start(&Default::default(), window, cx);
 8999        assert_eq!(
 9000            editor.text(cx),
 9001            "
 9002                a
 9003                b
 9004                c
 9005            "
 9006            .unindent()
 9007        );
 9008        assert_eq!(
 9009            editor.selections.ranges::<Point>(cx),
 9010            [
 9011                Point::new(0, 1)..Point::new(0, 1),
 9012                Point::new(1, 1)..Point::new(1, 1),
 9013                Point::new(2, 1)..Point::new(2, 1)
 9014            ]
 9015        );
 9016    });
 9017}
 9018
 9019#[gpui::test]
 9020async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
 9021    init_test(cx, |settings| {
 9022        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 9023    });
 9024
 9025    let mut cx = EditorTestContext::new(cx).await;
 9026
 9027    let language = Arc::new(Language::new(
 9028        LanguageConfig {
 9029            brackets: BracketPairConfig {
 9030                pairs: vec![
 9031                    BracketPair {
 9032                        start: "{".to_string(),
 9033                        end: "}".to_string(),
 9034                        close: true,
 9035                        surround: true,
 9036                        newline: true,
 9037                    },
 9038                    BracketPair {
 9039                        start: "(".to_string(),
 9040                        end: ")".to_string(),
 9041                        close: true,
 9042                        surround: true,
 9043                        newline: true,
 9044                    },
 9045                    BracketPair {
 9046                        start: "[".to_string(),
 9047                        end: "]".to_string(),
 9048                        close: false,
 9049                        surround: true,
 9050                        newline: true,
 9051                    },
 9052                ],
 9053                ..Default::default()
 9054            },
 9055            autoclose_before: "})]".to_string(),
 9056            ..Default::default()
 9057        },
 9058        Some(tree_sitter_rust::LANGUAGE.into()),
 9059    ));
 9060
 9061    cx.language_registry().add(language.clone());
 9062    cx.update_buffer(|buffer, cx| {
 9063        buffer.set_language(Some(language), cx);
 9064    });
 9065
 9066    cx.set_state(
 9067        &"
 9068            {(ˇ)}
 9069            [[ˇ]]
 9070            {(ˇ)}
 9071        "
 9072        .unindent(),
 9073    );
 9074
 9075    cx.update_editor(|editor, window, cx| {
 9076        editor.backspace(&Default::default(), window, cx);
 9077        editor.backspace(&Default::default(), window, cx);
 9078    });
 9079
 9080    cx.assert_editor_state(
 9081        &"
 9082            ˇ
 9083            ˇ]]
 9084            ˇ
 9085        "
 9086        .unindent(),
 9087    );
 9088
 9089    cx.update_editor(|editor, window, cx| {
 9090        editor.handle_input("{", window, cx);
 9091        editor.handle_input("{", window, cx);
 9092        editor.move_right(&MoveRight, window, cx);
 9093        editor.move_right(&MoveRight, window, cx);
 9094        editor.move_left(&MoveLeft, window, cx);
 9095        editor.move_left(&MoveLeft, window, cx);
 9096        editor.backspace(&Default::default(), window, cx);
 9097    });
 9098
 9099    cx.assert_editor_state(
 9100        &"
 9101            {ˇ}
 9102            {ˇ}]]
 9103            {ˇ}
 9104        "
 9105        .unindent(),
 9106    );
 9107
 9108    cx.update_editor(|editor, window, cx| {
 9109        editor.backspace(&Default::default(), window, cx);
 9110    });
 9111
 9112    cx.assert_editor_state(
 9113        &"
 9114            ˇ
 9115            ˇ]]
 9116            ˇ
 9117        "
 9118        .unindent(),
 9119    );
 9120}
 9121
 9122#[gpui::test]
 9123async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
 9124    init_test(cx, |_| {});
 9125
 9126    let language = Arc::new(Language::new(
 9127        LanguageConfig::default(),
 9128        Some(tree_sitter_rust::LANGUAGE.into()),
 9129    ));
 9130
 9131    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
 9132    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9133    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9134    editor
 9135        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9136        .await;
 9137
 9138    editor.update_in(cx, |editor, window, cx| {
 9139        editor.set_auto_replace_emoji_shortcode(true);
 9140
 9141        editor.handle_input("Hello ", window, cx);
 9142        editor.handle_input(":wave", window, cx);
 9143        assert_eq!(editor.text(cx), "Hello :wave".unindent());
 9144
 9145        editor.handle_input(":", window, cx);
 9146        assert_eq!(editor.text(cx), "Hello 👋".unindent());
 9147
 9148        editor.handle_input(" :smile", window, cx);
 9149        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
 9150
 9151        editor.handle_input(":", window, cx);
 9152        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
 9153
 9154        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
 9155        editor.handle_input(":wave", window, cx);
 9156        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
 9157
 9158        editor.handle_input(":", window, cx);
 9159        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
 9160
 9161        editor.handle_input(":1", window, cx);
 9162        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
 9163
 9164        editor.handle_input(":", window, cx);
 9165        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
 9166
 9167        // Ensure shortcode does not get replaced when it is part of a word
 9168        editor.handle_input(" Test:wave", window, cx);
 9169        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
 9170
 9171        editor.handle_input(":", window, cx);
 9172        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
 9173
 9174        editor.set_auto_replace_emoji_shortcode(false);
 9175
 9176        // Ensure shortcode does not get replaced when auto replace is off
 9177        editor.handle_input(" :wave", window, cx);
 9178        assert_eq!(
 9179            editor.text(cx),
 9180            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
 9181        );
 9182
 9183        editor.handle_input(":", window, cx);
 9184        assert_eq!(
 9185            editor.text(cx),
 9186            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
 9187        );
 9188    });
 9189}
 9190
 9191#[gpui::test]
 9192async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
 9193    init_test(cx, |_| {});
 9194
 9195    let (text, insertion_ranges) = marked_text_ranges(
 9196        indoc! {"
 9197            ˇ
 9198        "},
 9199        false,
 9200    );
 9201
 9202    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
 9203    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9204
 9205    _ = editor.update_in(cx, |editor, window, cx| {
 9206        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
 9207
 9208        editor
 9209            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9210            .unwrap();
 9211
 9212        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
 9213            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
 9214            assert_eq!(editor.text(cx), expected_text);
 9215            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
 9216        }
 9217
 9218        assert(
 9219            editor,
 9220            cx,
 9221            indoc! {"
 9222            type «» =•
 9223            "},
 9224        );
 9225
 9226        assert!(editor.context_menu_visible(), "There should be a matches");
 9227    });
 9228}
 9229
 9230#[gpui::test]
 9231async fn test_snippets(cx: &mut TestAppContext) {
 9232    init_test(cx, |_| {});
 9233
 9234    let mut cx = EditorTestContext::new(cx).await;
 9235
 9236    cx.set_state(indoc! {"
 9237        a.ˇ b
 9238        a.ˇ b
 9239        a.ˇ b
 9240    "});
 9241
 9242    cx.update_editor(|editor, window, cx| {
 9243        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
 9244        let insertion_ranges = editor
 9245            .selections
 9246            .all(cx)
 9247            .iter()
 9248            .map(|s| s.range().clone())
 9249            .collect::<Vec<_>>();
 9250        editor
 9251            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9252            .unwrap();
 9253    });
 9254
 9255    cx.assert_editor_state(indoc! {"
 9256        a.f(«oneˇ», two, «threeˇ») b
 9257        a.f(«oneˇ», two, «threeˇ») b
 9258        a.f(«oneˇ», two, «threeˇ») b
 9259    "});
 9260
 9261    // Can't move earlier than the first tab stop
 9262    cx.update_editor(|editor, window, cx| {
 9263        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 9264    });
 9265    cx.assert_editor_state(indoc! {"
 9266        a.f(«oneˇ», two, «threeˇ») b
 9267        a.f(«oneˇ», two, «threeˇ») b
 9268        a.f(«oneˇ», two, «threeˇ») b
 9269    "});
 9270
 9271    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9272    cx.assert_editor_state(indoc! {"
 9273        a.f(one, «twoˇ», three) b
 9274        a.f(one, «twoˇ», three) b
 9275        a.f(one, «twoˇ», three) b
 9276    "});
 9277
 9278    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
 9279    cx.assert_editor_state(indoc! {"
 9280        a.f(«oneˇ», two, «threeˇ») b
 9281        a.f(«oneˇ», two, «threeˇ») b
 9282        a.f(«oneˇ», two, «threeˇ») b
 9283    "});
 9284
 9285    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9286    cx.assert_editor_state(indoc! {"
 9287        a.f(one, «twoˇ», three) b
 9288        a.f(one, «twoˇ», three) b
 9289        a.f(one, «twoˇ», three) b
 9290    "});
 9291    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9292    cx.assert_editor_state(indoc! {"
 9293        a.f(one, two, three)ˇ b
 9294        a.f(one, two, three)ˇ b
 9295        a.f(one, two, three)ˇ b
 9296    "});
 9297
 9298    // As soon as the last tab stop is reached, snippet state is gone
 9299    cx.update_editor(|editor, window, cx| {
 9300        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 9301    });
 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
 9309#[gpui::test]
 9310async fn test_snippet_indentation(cx: &mut TestAppContext) {
 9311    init_test(cx, |_| {});
 9312
 9313    let mut cx = EditorTestContext::new(cx).await;
 9314
 9315    cx.update_editor(|editor, window, cx| {
 9316        let snippet = Snippet::parse(indoc! {"
 9317            /*
 9318             * Multiline comment with leading indentation
 9319             *
 9320             * $1
 9321             */
 9322            $0"})
 9323        .unwrap();
 9324        let insertion_ranges = editor
 9325            .selections
 9326            .all(cx)
 9327            .iter()
 9328            .map(|s| s.range().clone())
 9329            .collect::<Vec<_>>();
 9330        editor
 9331            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9332            .unwrap();
 9333    });
 9334
 9335    cx.assert_editor_state(indoc! {"
 9336        /*
 9337         * Multiline comment with leading indentation
 9338         *
 9339         * ˇ
 9340         */
 9341    "});
 9342
 9343    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9344    cx.assert_editor_state(indoc! {"
 9345        /*
 9346         * Multiline comment with leading indentation
 9347         *
 9348         *•
 9349         */
 9350        ˇ"});
 9351}
 9352
 9353#[gpui::test]
 9354async fn test_document_format_during_save(cx: &mut TestAppContext) {
 9355    init_test(cx, |_| {});
 9356
 9357    let fs = FakeFs::new(cx.executor());
 9358    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9359
 9360    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
 9361
 9362    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9363    language_registry.add(rust_lang());
 9364    let mut fake_servers = language_registry.register_fake_lsp(
 9365        "Rust",
 9366        FakeLspAdapter {
 9367            capabilities: lsp::ServerCapabilities {
 9368                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9369                ..Default::default()
 9370            },
 9371            ..Default::default()
 9372        },
 9373    );
 9374
 9375    let buffer = project
 9376        .update(cx, |project, cx| {
 9377            project.open_local_buffer(path!("/file.rs"), cx)
 9378        })
 9379        .await
 9380        .unwrap();
 9381
 9382    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9383    let (editor, cx) = cx.add_window_view(|window, cx| {
 9384        build_editor_with_project(project.clone(), buffer, window, cx)
 9385    });
 9386    editor.update_in(cx, |editor, window, cx| {
 9387        editor.set_text("one\ntwo\nthree\n", window, cx)
 9388    });
 9389    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9390
 9391    cx.executor().start_waiting();
 9392    let fake_server = fake_servers.next().await.unwrap();
 9393
 9394    {
 9395        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9396            move |params, _| async move {
 9397                assert_eq!(
 9398                    params.text_document.uri,
 9399                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9400                );
 9401                assert_eq!(params.options.tab_size, 4);
 9402                Ok(Some(vec![lsp::TextEdit::new(
 9403                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9404                    ", ".to_string(),
 9405                )]))
 9406            },
 9407        );
 9408        let save = editor
 9409            .update_in(cx, |editor, window, cx| {
 9410                editor.save(
 9411                    SaveOptions {
 9412                        format: true,
 9413                        autosave: false,
 9414                    },
 9415                    project.clone(),
 9416                    window,
 9417                    cx,
 9418                )
 9419            })
 9420            .unwrap();
 9421        cx.executor().start_waiting();
 9422        save.await;
 9423
 9424        assert_eq!(
 9425            editor.update(cx, |editor, cx| editor.text(cx)),
 9426            "one, two\nthree\n"
 9427        );
 9428        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9429    }
 9430
 9431    {
 9432        editor.update_in(cx, |editor, window, cx| {
 9433            editor.set_text("one\ntwo\nthree\n", window, cx)
 9434        });
 9435        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9436
 9437        // Ensure we can still save even if formatting hangs.
 9438        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9439            move |params, _| async move {
 9440                assert_eq!(
 9441                    params.text_document.uri,
 9442                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9443                );
 9444                futures::future::pending::<()>().await;
 9445                unreachable!()
 9446            },
 9447        );
 9448        let save = editor
 9449            .update_in(cx, |editor, window, cx| {
 9450                editor.save(
 9451                    SaveOptions {
 9452                        format: true,
 9453                        autosave: false,
 9454                    },
 9455                    project.clone(),
 9456                    window,
 9457                    cx,
 9458                )
 9459            })
 9460            .unwrap();
 9461        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9462        cx.executor().start_waiting();
 9463        save.await;
 9464        assert_eq!(
 9465            editor.update(cx, |editor, cx| editor.text(cx)),
 9466            "one\ntwo\nthree\n"
 9467        );
 9468    }
 9469
 9470    // Set rust language override and assert overridden tabsize is sent to language server
 9471    update_test_language_settings(cx, |settings| {
 9472        settings.languages.0.insert(
 9473            "Rust".into(),
 9474            LanguageSettingsContent {
 9475                tab_size: NonZeroU32::new(8),
 9476                ..Default::default()
 9477            },
 9478        );
 9479    });
 9480
 9481    {
 9482        editor.update_in(cx, |editor, window, cx| {
 9483            editor.set_text("somehting_new\n", window, cx)
 9484        });
 9485        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9486        let _formatting_request_signal = fake_server
 9487            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9488                assert_eq!(
 9489                    params.text_document.uri,
 9490                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9491                );
 9492                assert_eq!(params.options.tab_size, 8);
 9493                Ok(Some(vec![]))
 9494            });
 9495        let save = editor
 9496            .update_in(cx, |editor, window, cx| {
 9497                editor.save(
 9498                    SaveOptions {
 9499                        format: true,
 9500                        autosave: false,
 9501                    },
 9502                    project.clone(),
 9503                    window,
 9504                    cx,
 9505                )
 9506            })
 9507            .unwrap();
 9508        cx.executor().start_waiting();
 9509        save.await;
 9510    }
 9511}
 9512
 9513#[gpui::test]
 9514async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
 9515    init_test(cx, |_| {});
 9516
 9517    let cols = 4;
 9518    let rows = 10;
 9519    let sample_text_1 = sample_text(rows, cols, 'a');
 9520    assert_eq!(
 9521        sample_text_1,
 9522        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
 9523    );
 9524    let sample_text_2 = sample_text(rows, cols, 'l');
 9525    assert_eq!(
 9526        sample_text_2,
 9527        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
 9528    );
 9529    let sample_text_3 = sample_text(rows, cols, 'v');
 9530    assert_eq!(
 9531        sample_text_3,
 9532        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
 9533    );
 9534
 9535    let fs = FakeFs::new(cx.executor());
 9536    fs.insert_tree(
 9537        path!("/a"),
 9538        json!({
 9539            "main.rs": sample_text_1,
 9540            "other.rs": sample_text_2,
 9541            "lib.rs": sample_text_3,
 9542        }),
 9543    )
 9544    .await;
 9545
 9546    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
 9547    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9548    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 9549
 9550    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9551    language_registry.add(rust_lang());
 9552    let mut fake_servers = language_registry.register_fake_lsp(
 9553        "Rust",
 9554        FakeLspAdapter {
 9555            capabilities: lsp::ServerCapabilities {
 9556                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9557                ..Default::default()
 9558            },
 9559            ..Default::default()
 9560        },
 9561    );
 9562
 9563    let worktree = project.update(cx, |project, cx| {
 9564        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
 9565        assert_eq!(worktrees.len(), 1);
 9566        worktrees.pop().unwrap()
 9567    });
 9568    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 9569
 9570    let buffer_1 = project
 9571        .update(cx, |project, cx| {
 9572            project.open_buffer((worktree_id, "main.rs"), cx)
 9573        })
 9574        .await
 9575        .unwrap();
 9576    let buffer_2 = project
 9577        .update(cx, |project, cx| {
 9578            project.open_buffer((worktree_id, "other.rs"), cx)
 9579        })
 9580        .await
 9581        .unwrap();
 9582    let buffer_3 = project
 9583        .update(cx, |project, cx| {
 9584            project.open_buffer((worktree_id, "lib.rs"), cx)
 9585        })
 9586        .await
 9587        .unwrap();
 9588
 9589    let multi_buffer = cx.new(|cx| {
 9590        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 9591        multi_buffer.push_excerpts(
 9592            buffer_1.clone(),
 9593            [
 9594                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9595                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9596                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9597            ],
 9598            cx,
 9599        );
 9600        multi_buffer.push_excerpts(
 9601            buffer_2.clone(),
 9602            [
 9603                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9604                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9605                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9606            ],
 9607            cx,
 9608        );
 9609        multi_buffer.push_excerpts(
 9610            buffer_3.clone(),
 9611            [
 9612                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9613                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9614                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9615            ],
 9616            cx,
 9617        );
 9618        multi_buffer
 9619    });
 9620    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
 9621        Editor::new(
 9622            EditorMode::full(),
 9623            multi_buffer,
 9624            Some(project.clone()),
 9625            window,
 9626            cx,
 9627        )
 9628    });
 9629
 9630    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 9631        editor.change_selections(
 9632            SelectionEffects::scroll(Autoscroll::Next),
 9633            window,
 9634            cx,
 9635            |s| s.select_ranges(Some(1..2)),
 9636        );
 9637        editor.insert("|one|two|three|", window, cx);
 9638    });
 9639    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 9640    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 9641        editor.change_selections(
 9642            SelectionEffects::scroll(Autoscroll::Next),
 9643            window,
 9644            cx,
 9645            |s| s.select_ranges(Some(60..70)),
 9646        );
 9647        editor.insert("|four|five|six|", window, cx);
 9648    });
 9649    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 9650
 9651    // First two buffers should be edited, but not the third one.
 9652    assert_eq!(
 9653        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 9654        "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}",
 9655    );
 9656    buffer_1.update(cx, |buffer, _| {
 9657        assert!(buffer.is_dirty());
 9658        assert_eq!(
 9659            buffer.text(),
 9660            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
 9661        )
 9662    });
 9663    buffer_2.update(cx, |buffer, _| {
 9664        assert!(buffer.is_dirty());
 9665        assert_eq!(
 9666            buffer.text(),
 9667            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
 9668        )
 9669    });
 9670    buffer_3.update(cx, |buffer, _| {
 9671        assert!(!buffer.is_dirty());
 9672        assert_eq!(buffer.text(), sample_text_3,)
 9673    });
 9674    cx.executor().run_until_parked();
 9675
 9676    cx.executor().start_waiting();
 9677    let save = multi_buffer_editor
 9678        .update_in(cx, |editor, window, cx| {
 9679            editor.save(
 9680                SaveOptions {
 9681                    format: true,
 9682                    autosave: false,
 9683                },
 9684                project.clone(),
 9685                window,
 9686                cx,
 9687            )
 9688        })
 9689        .unwrap();
 9690
 9691    let fake_server = fake_servers.next().await.unwrap();
 9692    fake_server
 9693        .server
 9694        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9695            Ok(Some(vec![lsp::TextEdit::new(
 9696                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9697                format!("[{} formatted]", params.text_document.uri),
 9698            )]))
 9699        })
 9700        .detach();
 9701    save.await;
 9702
 9703    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
 9704    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
 9705    assert_eq!(
 9706        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 9707        uri!(
 9708            "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}"
 9709        ),
 9710    );
 9711    buffer_1.update(cx, |buffer, _| {
 9712        assert!(!buffer.is_dirty());
 9713        assert_eq!(
 9714            buffer.text(),
 9715            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
 9716        )
 9717    });
 9718    buffer_2.update(cx, |buffer, _| {
 9719        assert!(!buffer.is_dirty());
 9720        assert_eq!(
 9721            buffer.text(),
 9722            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
 9723        )
 9724    });
 9725    buffer_3.update(cx, |buffer, _| {
 9726        assert!(!buffer.is_dirty());
 9727        assert_eq!(buffer.text(), sample_text_3,)
 9728    });
 9729}
 9730
 9731#[gpui::test]
 9732async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
 9733    init_test(cx, |_| {});
 9734
 9735    let fs = FakeFs::new(cx.executor());
 9736    fs.insert_tree(
 9737        path!("/dir"),
 9738        json!({
 9739            "file1.rs": "fn main() { println!(\"hello\"); }",
 9740            "file2.rs": "fn test() { println!(\"test\"); }",
 9741            "file3.rs": "fn other() { println!(\"other\"); }\n",
 9742        }),
 9743    )
 9744    .await;
 9745
 9746    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
 9747    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9748    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 9749
 9750    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9751    language_registry.add(rust_lang());
 9752
 9753    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
 9754    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 9755
 9756    // Open three buffers
 9757    let buffer_1 = project
 9758        .update(cx, |project, cx| {
 9759            project.open_buffer((worktree_id, "file1.rs"), cx)
 9760        })
 9761        .await
 9762        .unwrap();
 9763    let buffer_2 = project
 9764        .update(cx, |project, cx| {
 9765            project.open_buffer((worktree_id, "file2.rs"), cx)
 9766        })
 9767        .await
 9768        .unwrap();
 9769    let buffer_3 = project
 9770        .update(cx, |project, cx| {
 9771            project.open_buffer((worktree_id, "file3.rs"), cx)
 9772        })
 9773        .await
 9774        .unwrap();
 9775
 9776    // Create a multi-buffer with all three buffers
 9777    let multi_buffer = cx.new(|cx| {
 9778        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 9779        multi_buffer.push_excerpts(
 9780            buffer_1.clone(),
 9781            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9782            cx,
 9783        );
 9784        multi_buffer.push_excerpts(
 9785            buffer_2.clone(),
 9786            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9787            cx,
 9788        );
 9789        multi_buffer.push_excerpts(
 9790            buffer_3.clone(),
 9791            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9792            cx,
 9793        );
 9794        multi_buffer
 9795    });
 9796
 9797    let editor = cx.new_window_entity(|window, cx| {
 9798        Editor::new(
 9799            EditorMode::full(),
 9800            multi_buffer,
 9801            Some(project.clone()),
 9802            window,
 9803            cx,
 9804        )
 9805    });
 9806
 9807    // Edit only the first buffer
 9808    editor.update_in(cx, |editor, window, cx| {
 9809        editor.change_selections(
 9810            SelectionEffects::scroll(Autoscroll::Next),
 9811            window,
 9812            cx,
 9813            |s| s.select_ranges(Some(10..10)),
 9814        );
 9815        editor.insert("// edited", window, cx);
 9816    });
 9817
 9818    // Verify that only buffer 1 is dirty
 9819    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
 9820    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9821    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9822
 9823    // Get write counts after file creation (files were created with initial content)
 9824    // We expect each file to have been written once during creation
 9825    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
 9826    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
 9827    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
 9828
 9829    // Perform autosave
 9830    let save_task = editor.update_in(cx, |editor, window, cx| {
 9831        editor.save(
 9832            SaveOptions {
 9833                format: true,
 9834                autosave: true,
 9835            },
 9836            project.clone(),
 9837            window,
 9838            cx,
 9839        )
 9840    });
 9841    save_task.await.unwrap();
 9842
 9843    // Only the dirty buffer should have been saved
 9844    assert_eq!(
 9845        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
 9846        1,
 9847        "Buffer 1 was dirty, so it should have been written once during autosave"
 9848    );
 9849    assert_eq!(
 9850        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
 9851        0,
 9852        "Buffer 2 was clean, so it should not have been written during autosave"
 9853    );
 9854    assert_eq!(
 9855        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
 9856        0,
 9857        "Buffer 3 was clean, so it should not have been written during autosave"
 9858    );
 9859
 9860    // Verify buffer states after autosave
 9861    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9862    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9863    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9864
 9865    // Now perform a manual save (format = true)
 9866    let save_task = editor.update_in(cx, |editor, window, cx| {
 9867        editor.save(
 9868            SaveOptions {
 9869                format: true,
 9870                autosave: false,
 9871            },
 9872            project.clone(),
 9873            window,
 9874            cx,
 9875        )
 9876    });
 9877    save_task.await.unwrap();
 9878
 9879    // During manual save, clean buffers don't get written to disk
 9880    // They just get did_save called for language server notifications
 9881    assert_eq!(
 9882        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
 9883        1,
 9884        "Buffer 1 should only have been written once total (during autosave, not manual save)"
 9885    );
 9886    assert_eq!(
 9887        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
 9888        0,
 9889        "Buffer 2 should not have been written at all"
 9890    );
 9891    assert_eq!(
 9892        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
 9893        0,
 9894        "Buffer 3 should not have been written at all"
 9895    );
 9896}
 9897
 9898#[gpui::test]
 9899async fn test_range_format_during_save(cx: &mut TestAppContext) {
 9900    init_test(cx, |_| {});
 9901
 9902    let fs = FakeFs::new(cx.executor());
 9903    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9904
 9905    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 9906
 9907    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9908    language_registry.add(rust_lang());
 9909    let mut fake_servers = language_registry.register_fake_lsp(
 9910        "Rust",
 9911        FakeLspAdapter {
 9912            capabilities: lsp::ServerCapabilities {
 9913                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
 9914                ..Default::default()
 9915            },
 9916            ..Default::default()
 9917        },
 9918    );
 9919
 9920    let buffer = project
 9921        .update(cx, |project, cx| {
 9922            project.open_local_buffer(path!("/file.rs"), cx)
 9923        })
 9924        .await
 9925        .unwrap();
 9926
 9927    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9928    let (editor, cx) = cx.add_window_view(|window, cx| {
 9929        build_editor_with_project(project.clone(), buffer, window, cx)
 9930    });
 9931    editor.update_in(cx, |editor, window, cx| {
 9932        editor.set_text("one\ntwo\nthree\n", window, cx)
 9933    });
 9934    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9935
 9936    cx.executor().start_waiting();
 9937    let fake_server = fake_servers.next().await.unwrap();
 9938
 9939    let save = editor
 9940        .update_in(cx, |editor, window, cx| {
 9941            editor.save(
 9942                SaveOptions {
 9943                    format: true,
 9944                    autosave: false,
 9945                },
 9946                project.clone(),
 9947                window,
 9948                cx,
 9949            )
 9950        })
 9951        .unwrap();
 9952    fake_server
 9953        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
 9954            assert_eq!(
 9955                params.text_document.uri,
 9956                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9957            );
 9958            assert_eq!(params.options.tab_size, 4);
 9959            Ok(Some(vec![lsp::TextEdit::new(
 9960                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9961                ", ".to_string(),
 9962            )]))
 9963        })
 9964        .next()
 9965        .await;
 9966    cx.executor().start_waiting();
 9967    save.await;
 9968    assert_eq!(
 9969        editor.update(cx, |editor, cx| editor.text(cx)),
 9970        "one, two\nthree\n"
 9971    );
 9972    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9973
 9974    editor.update_in(cx, |editor, window, cx| {
 9975        editor.set_text("one\ntwo\nthree\n", window, cx)
 9976    });
 9977    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9978
 9979    // Ensure we can still save even if formatting hangs.
 9980    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
 9981        move |params, _| async move {
 9982            assert_eq!(
 9983                params.text_document.uri,
 9984                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9985            );
 9986            futures::future::pending::<()>().await;
 9987            unreachable!()
 9988        },
 9989    );
 9990    let save = editor
 9991        .update_in(cx, |editor, window, cx| {
 9992            editor.save(
 9993                SaveOptions {
 9994                    format: true,
 9995                    autosave: false,
 9996                },
 9997                project.clone(),
 9998                window,
 9999                cx,
10000            )
10001        })
10002        .unwrap();
10003    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10004    cx.executor().start_waiting();
10005    save.await;
10006    assert_eq!(
10007        editor.update(cx, |editor, cx| editor.text(cx)),
10008        "one\ntwo\nthree\n"
10009    );
10010    assert!(!cx.read(|cx| editor.is_dirty(cx)));
10011
10012    // For non-dirty buffer, no formatting request should be sent
10013    let save = editor
10014        .update_in(cx, |editor, window, cx| {
10015            editor.save(
10016                SaveOptions {
10017                    format: false,
10018                    autosave: false,
10019                },
10020                project.clone(),
10021                window,
10022                cx,
10023            )
10024        })
10025        .unwrap();
10026    let _pending_format_request = fake_server
10027        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
10028            panic!("Should not be invoked");
10029        })
10030        .next();
10031    cx.executor().start_waiting();
10032    save.await;
10033
10034    // Set Rust language override and assert overridden tabsize is sent to language server
10035    update_test_language_settings(cx, |settings| {
10036        settings.languages.0.insert(
10037            "Rust".into(),
10038            LanguageSettingsContent {
10039                tab_size: NonZeroU32::new(8),
10040                ..Default::default()
10041            },
10042        );
10043    });
10044
10045    editor.update_in(cx, |editor, window, cx| {
10046        editor.set_text("somehting_new\n", window, cx)
10047    });
10048    assert!(cx.read(|cx| editor.is_dirty(cx)));
10049    let save = editor
10050        .update_in(cx, |editor, window, cx| {
10051            editor.save(
10052                SaveOptions {
10053                    format: true,
10054                    autosave: false,
10055                },
10056                project.clone(),
10057                window,
10058                cx,
10059            )
10060        })
10061        .unwrap();
10062    fake_server
10063        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
10064            assert_eq!(
10065                params.text_document.uri,
10066                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10067            );
10068            assert_eq!(params.options.tab_size, 8);
10069            Ok(Some(Vec::new()))
10070        })
10071        .next()
10072        .await;
10073    save.await;
10074}
10075
10076#[gpui::test]
10077async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
10078    init_test(cx, |settings| {
10079        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
10080            Formatter::LanguageServer { name: None },
10081        )))
10082    });
10083
10084    let fs = FakeFs::new(cx.executor());
10085    fs.insert_file(path!("/file.rs"), Default::default()).await;
10086
10087    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10088
10089    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10090    language_registry.add(Arc::new(Language::new(
10091        LanguageConfig {
10092            name: "Rust".into(),
10093            matcher: LanguageMatcher {
10094                path_suffixes: vec!["rs".to_string()],
10095                ..Default::default()
10096            },
10097            ..LanguageConfig::default()
10098        },
10099        Some(tree_sitter_rust::LANGUAGE.into()),
10100    )));
10101    update_test_language_settings(cx, |settings| {
10102        // Enable Prettier formatting for the same buffer, and ensure
10103        // LSP is called instead of Prettier.
10104        settings.defaults.prettier = Some(PrettierSettings {
10105            allowed: true,
10106            ..PrettierSettings::default()
10107        });
10108    });
10109    let mut fake_servers = language_registry.register_fake_lsp(
10110        "Rust",
10111        FakeLspAdapter {
10112            capabilities: lsp::ServerCapabilities {
10113                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10114                ..Default::default()
10115            },
10116            ..Default::default()
10117        },
10118    );
10119
10120    let buffer = project
10121        .update(cx, |project, cx| {
10122            project.open_local_buffer(path!("/file.rs"), cx)
10123        })
10124        .await
10125        .unwrap();
10126
10127    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10128    let (editor, cx) = cx.add_window_view(|window, cx| {
10129        build_editor_with_project(project.clone(), buffer, window, cx)
10130    });
10131    editor.update_in(cx, |editor, window, cx| {
10132        editor.set_text("one\ntwo\nthree\n", window, cx)
10133    });
10134
10135    cx.executor().start_waiting();
10136    let fake_server = fake_servers.next().await.unwrap();
10137
10138    let format = editor
10139        .update_in(cx, |editor, window, cx| {
10140            editor.perform_format(
10141                project.clone(),
10142                FormatTrigger::Manual,
10143                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10144                window,
10145                cx,
10146            )
10147        })
10148        .unwrap();
10149    fake_server
10150        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
10151            assert_eq!(
10152                params.text_document.uri,
10153                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10154            );
10155            assert_eq!(params.options.tab_size, 4);
10156            Ok(Some(vec![lsp::TextEdit::new(
10157                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10158                ", ".to_string(),
10159            )]))
10160        })
10161        .next()
10162        .await;
10163    cx.executor().start_waiting();
10164    format.await;
10165    assert_eq!(
10166        editor.update(cx, |editor, cx| editor.text(cx)),
10167        "one, two\nthree\n"
10168    );
10169
10170    editor.update_in(cx, |editor, window, cx| {
10171        editor.set_text("one\ntwo\nthree\n", window, cx)
10172    });
10173    // Ensure we don't lock if formatting hangs.
10174    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10175        move |params, _| async move {
10176            assert_eq!(
10177                params.text_document.uri,
10178                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10179            );
10180            futures::future::pending::<()>().await;
10181            unreachable!()
10182        },
10183    );
10184    let format = editor
10185        .update_in(cx, |editor, window, cx| {
10186            editor.perform_format(
10187                project,
10188                FormatTrigger::Manual,
10189                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10190                window,
10191                cx,
10192            )
10193        })
10194        .unwrap();
10195    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10196    cx.executor().start_waiting();
10197    format.await;
10198    assert_eq!(
10199        editor.update(cx, |editor, cx| editor.text(cx)),
10200        "one\ntwo\nthree\n"
10201    );
10202}
10203
10204#[gpui::test]
10205async fn test_multiple_formatters(cx: &mut TestAppContext) {
10206    init_test(cx, |settings| {
10207        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
10208        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
10209            Formatter::LanguageServer { name: None },
10210            Formatter::CodeActions(
10211                [
10212                    ("code-action-1".into(), true),
10213                    ("code-action-2".into(), true),
10214                ]
10215                .into_iter()
10216                .collect(),
10217            ),
10218        ])))
10219    });
10220
10221    let fs = FakeFs::new(cx.executor());
10222    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
10223        .await;
10224
10225    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10226    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10227    language_registry.add(rust_lang());
10228
10229    let mut fake_servers = language_registry.register_fake_lsp(
10230        "Rust",
10231        FakeLspAdapter {
10232            capabilities: lsp::ServerCapabilities {
10233                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10234                execute_command_provider: Some(lsp::ExecuteCommandOptions {
10235                    commands: vec!["the-command-for-code-action-1".into()],
10236                    ..Default::default()
10237                }),
10238                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10239                ..Default::default()
10240            },
10241            ..Default::default()
10242        },
10243    );
10244
10245    let buffer = project
10246        .update(cx, |project, cx| {
10247            project.open_local_buffer(path!("/file.rs"), cx)
10248        })
10249        .await
10250        .unwrap();
10251
10252    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10253    let (editor, cx) = cx.add_window_view(|window, cx| {
10254        build_editor_with_project(project.clone(), buffer, window, cx)
10255    });
10256
10257    cx.executor().start_waiting();
10258
10259    let fake_server = fake_servers.next().await.unwrap();
10260    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10261        move |_params, _| async move {
10262            Ok(Some(vec![lsp::TextEdit::new(
10263                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10264                "applied-formatting\n".to_string(),
10265            )]))
10266        },
10267    );
10268    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
10269        move |params, _| async move {
10270            assert_eq!(
10271                params.context.only,
10272                Some(vec!["code-action-1".into(), "code-action-2".into()])
10273            );
10274            let uri = lsp::Url::from_file_path(path!("/file.rs")).unwrap();
10275            Ok(Some(vec![
10276                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10277                    kind: Some("code-action-1".into()),
10278                    edit: Some(lsp::WorkspaceEdit::new(
10279                        [(
10280                            uri.clone(),
10281                            vec![lsp::TextEdit::new(
10282                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10283                                "applied-code-action-1-edit\n".to_string(),
10284                            )],
10285                        )]
10286                        .into_iter()
10287                        .collect(),
10288                    )),
10289                    command: Some(lsp::Command {
10290                        command: "the-command-for-code-action-1".into(),
10291                        ..Default::default()
10292                    }),
10293                    ..Default::default()
10294                }),
10295                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10296                    kind: Some("code-action-2".into()),
10297                    edit: Some(lsp::WorkspaceEdit::new(
10298                        [(
10299                            uri.clone(),
10300                            vec![lsp::TextEdit::new(
10301                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10302                                "applied-code-action-2-edit\n".to_string(),
10303                            )],
10304                        )]
10305                        .into_iter()
10306                        .collect(),
10307                    )),
10308                    ..Default::default()
10309                }),
10310            ]))
10311        },
10312    );
10313
10314    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
10315        move |params, _| async move { Ok(params) }
10316    });
10317
10318    let command_lock = Arc::new(futures::lock::Mutex::new(()));
10319    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
10320        let fake = fake_server.clone();
10321        let lock = command_lock.clone();
10322        move |params, _| {
10323            assert_eq!(params.command, "the-command-for-code-action-1");
10324            let fake = fake.clone();
10325            let lock = lock.clone();
10326            async move {
10327                lock.lock().await;
10328                fake.server
10329                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
10330                        label: None,
10331                        edit: lsp::WorkspaceEdit {
10332                            changes: Some(
10333                                [(
10334                                    lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
10335                                    vec![lsp::TextEdit {
10336                                        range: lsp::Range::new(
10337                                            lsp::Position::new(0, 0),
10338                                            lsp::Position::new(0, 0),
10339                                        ),
10340                                        new_text: "applied-code-action-1-command\n".into(),
10341                                    }],
10342                                )]
10343                                .into_iter()
10344                                .collect(),
10345                            ),
10346                            ..Default::default()
10347                        },
10348                    })
10349                    .await
10350                    .into_response()
10351                    .unwrap();
10352                Ok(Some(json!(null)))
10353            }
10354        }
10355    });
10356
10357    cx.executor().start_waiting();
10358    editor
10359        .update_in(cx, |editor, window, cx| {
10360            editor.perform_format(
10361                project.clone(),
10362                FormatTrigger::Manual,
10363                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10364                window,
10365                cx,
10366            )
10367        })
10368        .unwrap()
10369        .await;
10370    editor.update(cx, |editor, cx| {
10371        assert_eq!(
10372            editor.text(cx),
10373            r#"
10374                applied-code-action-2-edit
10375                applied-code-action-1-command
10376                applied-code-action-1-edit
10377                applied-formatting
10378                one
10379                two
10380                three
10381            "#
10382            .unindent()
10383        );
10384    });
10385
10386    editor.update_in(cx, |editor, window, cx| {
10387        editor.undo(&Default::default(), window, cx);
10388        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10389    });
10390
10391    // Perform a manual edit while waiting for an LSP command
10392    // that's being run as part of a formatting code action.
10393    let lock_guard = command_lock.lock().await;
10394    let format = editor
10395        .update_in(cx, |editor, window, cx| {
10396            editor.perform_format(
10397                project.clone(),
10398                FormatTrigger::Manual,
10399                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10400                window,
10401                cx,
10402            )
10403        })
10404        .unwrap();
10405    cx.run_until_parked();
10406    editor.update(cx, |editor, cx| {
10407        assert_eq!(
10408            editor.text(cx),
10409            r#"
10410                applied-code-action-1-edit
10411                applied-formatting
10412                one
10413                two
10414                three
10415            "#
10416            .unindent()
10417        );
10418
10419        editor.buffer.update(cx, |buffer, cx| {
10420            let ix = buffer.len(cx);
10421            buffer.edit([(ix..ix, "edited\n")], None, cx);
10422        });
10423    });
10424
10425    // Allow the LSP command to proceed. Because the buffer was edited,
10426    // the second code action will not be run.
10427    drop(lock_guard);
10428    format.await;
10429    editor.update_in(cx, |editor, window, cx| {
10430        assert_eq!(
10431            editor.text(cx),
10432            r#"
10433                applied-code-action-1-command
10434                applied-code-action-1-edit
10435                applied-formatting
10436                one
10437                two
10438                three
10439                edited
10440            "#
10441            .unindent()
10442        );
10443
10444        // The manual edit is undone first, because it is the last thing the user did
10445        // (even though the command completed afterwards).
10446        editor.undo(&Default::default(), window, cx);
10447        assert_eq!(
10448            editor.text(cx),
10449            r#"
10450                applied-code-action-1-command
10451                applied-code-action-1-edit
10452                applied-formatting
10453                one
10454                two
10455                three
10456            "#
10457            .unindent()
10458        );
10459
10460        // All the formatting (including the command, which completed after the manual edit)
10461        // is undone together.
10462        editor.undo(&Default::default(), window, cx);
10463        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10464    });
10465}
10466
10467#[gpui::test]
10468async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
10469    init_test(cx, |settings| {
10470        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
10471            Formatter::LanguageServer { name: None },
10472        ])))
10473    });
10474
10475    let fs = FakeFs::new(cx.executor());
10476    fs.insert_file(path!("/file.ts"), Default::default()).await;
10477
10478    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10479
10480    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10481    language_registry.add(Arc::new(Language::new(
10482        LanguageConfig {
10483            name: "TypeScript".into(),
10484            matcher: LanguageMatcher {
10485                path_suffixes: vec!["ts".to_string()],
10486                ..Default::default()
10487            },
10488            ..LanguageConfig::default()
10489        },
10490        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
10491    )));
10492    update_test_language_settings(cx, |settings| {
10493        settings.defaults.prettier = Some(PrettierSettings {
10494            allowed: true,
10495            ..PrettierSettings::default()
10496        });
10497    });
10498    let mut fake_servers = language_registry.register_fake_lsp(
10499        "TypeScript",
10500        FakeLspAdapter {
10501            capabilities: lsp::ServerCapabilities {
10502                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10503                ..Default::default()
10504            },
10505            ..Default::default()
10506        },
10507    );
10508
10509    let buffer = project
10510        .update(cx, |project, cx| {
10511            project.open_local_buffer(path!("/file.ts"), cx)
10512        })
10513        .await
10514        .unwrap();
10515
10516    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10517    let (editor, cx) = cx.add_window_view(|window, cx| {
10518        build_editor_with_project(project.clone(), buffer, window, cx)
10519    });
10520    editor.update_in(cx, |editor, window, cx| {
10521        editor.set_text(
10522            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
10523            window,
10524            cx,
10525        )
10526    });
10527
10528    cx.executor().start_waiting();
10529    let fake_server = fake_servers.next().await.unwrap();
10530
10531    let format = editor
10532        .update_in(cx, |editor, window, cx| {
10533            editor.perform_code_action_kind(
10534                project.clone(),
10535                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
10536                window,
10537                cx,
10538            )
10539        })
10540        .unwrap();
10541    fake_server
10542        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
10543            assert_eq!(
10544                params.text_document.uri,
10545                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
10546            );
10547            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
10548                lsp::CodeAction {
10549                    title: "Organize Imports".to_string(),
10550                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
10551                    edit: Some(lsp::WorkspaceEdit {
10552                        changes: Some(
10553                            [(
10554                                params.text_document.uri.clone(),
10555                                vec![lsp::TextEdit::new(
10556                                    lsp::Range::new(
10557                                        lsp::Position::new(1, 0),
10558                                        lsp::Position::new(2, 0),
10559                                    ),
10560                                    "".to_string(),
10561                                )],
10562                            )]
10563                            .into_iter()
10564                            .collect(),
10565                        ),
10566                        ..Default::default()
10567                    }),
10568                    ..Default::default()
10569                },
10570            )]))
10571        })
10572        .next()
10573        .await;
10574    cx.executor().start_waiting();
10575    format.await;
10576    assert_eq!(
10577        editor.update(cx, |editor, cx| editor.text(cx)),
10578        "import { a } from 'module';\n\nconst x = a;\n"
10579    );
10580
10581    editor.update_in(cx, |editor, window, cx| {
10582        editor.set_text(
10583            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
10584            window,
10585            cx,
10586        )
10587    });
10588    // Ensure we don't lock if code action hangs.
10589    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
10590        move |params, _| async move {
10591            assert_eq!(
10592                params.text_document.uri,
10593                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
10594            );
10595            futures::future::pending::<()>().await;
10596            unreachable!()
10597        },
10598    );
10599    let format = editor
10600        .update_in(cx, |editor, window, cx| {
10601            editor.perform_code_action_kind(
10602                project,
10603                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
10604                window,
10605                cx,
10606            )
10607        })
10608        .unwrap();
10609    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
10610    cx.executor().start_waiting();
10611    format.await;
10612    assert_eq!(
10613        editor.update(cx, |editor, cx| editor.text(cx)),
10614        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
10615    );
10616}
10617
10618#[gpui::test]
10619async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
10620    init_test(cx, |_| {});
10621
10622    let mut cx = EditorLspTestContext::new_rust(
10623        lsp::ServerCapabilities {
10624            document_formatting_provider: Some(lsp::OneOf::Left(true)),
10625            ..Default::default()
10626        },
10627        cx,
10628    )
10629    .await;
10630
10631    cx.set_state(indoc! {"
10632        one.twoˇ
10633    "});
10634
10635    // The format request takes a long time. When it completes, it inserts
10636    // a newline and an indent before the `.`
10637    cx.lsp
10638        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
10639            let executor = cx.background_executor().clone();
10640            async move {
10641                executor.timer(Duration::from_millis(100)).await;
10642                Ok(Some(vec![lsp::TextEdit {
10643                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
10644                    new_text: "\n    ".into(),
10645                }]))
10646            }
10647        });
10648
10649    // Submit a format request.
10650    let format_1 = cx
10651        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10652        .unwrap();
10653    cx.executor().run_until_parked();
10654
10655    // Submit a second format request.
10656    let format_2 = cx
10657        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10658        .unwrap();
10659    cx.executor().run_until_parked();
10660
10661    // Wait for both format requests to complete
10662    cx.executor().advance_clock(Duration::from_millis(200));
10663    cx.executor().start_waiting();
10664    format_1.await.unwrap();
10665    cx.executor().start_waiting();
10666    format_2.await.unwrap();
10667
10668    // The formatting edits only happens once.
10669    cx.assert_editor_state(indoc! {"
10670        one
10671            .twoˇ
10672    "});
10673}
10674
10675#[gpui::test]
10676async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
10677    init_test(cx, |settings| {
10678        settings.defaults.formatter = Some(SelectedFormatter::Auto)
10679    });
10680
10681    let mut cx = EditorLspTestContext::new_rust(
10682        lsp::ServerCapabilities {
10683            document_formatting_provider: Some(lsp::OneOf::Left(true)),
10684            ..Default::default()
10685        },
10686        cx,
10687    )
10688    .await;
10689
10690    // Set up a buffer white some trailing whitespace and no trailing newline.
10691    cx.set_state(
10692        &[
10693            "one ",   //
10694            "twoˇ",   //
10695            "three ", //
10696            "four",   //
10697        ]
10698        .join("\n"),
10699    );
10700
10701    // Submit a format request.
10702    let format = cx
10703        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10704        .unwrap();
10705
10706    // Record which buffer changes have been sent to the language server
10707    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
10708    cx.lsp
10709        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
10710            let buffer_changes = buffer_changes.clone();
10711            move |params, _| {
10712                buffer_changes.lock().extend(
10713                    params
10714                        .content_changes
10715                        .into_iter()
10716                        .map(|e| (e.range.unwrap(), e.text)),
10717                );
10718            }
10719        });
10720
10721    // Handle formatting requests to the language server.
10722    cx.lsp
10723        .set_request_handler::<lsp::request::Formatting, _, _>({
10724            let buffer_changes = buffer_changes.clone();
10725            move |_, _| {
10726                // When formatting is requested, trailing whitespace has already been stripped,
10727                // and the trailing newline has already been added.
10728                assert_eq!(
10729                    &buffer_changes.lock()[1..],
10730                    &[
10731                        (
10732                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
10733                            "".into()
10734                        ),
10735                        (
10736                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
10737                            "".into()
10738                        ),
10739                        (
10740                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
10741                            "\n".into()
10742                        ),
10743                    ]
10744                );
10745
10746                // Insert blank lines between each line of the buffer.
10747                async move {
10748                    Ok(Some(vec![
10749                        lsp::TextEdit {
10750                            range: lsp::Range::new(
10751                                lsp::Position::new(1, 0),
10752                                lsp::Position::new(1, 0),
10753                            ),
10754                            new_text: "\n".into(),
10755                        },
10756                        lsp::TextEdit {
10757                            range: lsp::Range::new(
10758                                lsp::Position::new(2, 0),
10759                                lsp::Position::new(2, 0),
10760                            ),
10761                            new_text: "\n".into(),
10762                        },
10763                    ]))
10764                }
10765            }
10766        });
10767
10768    // After formatting the buffer, the trailing whitespace is stripped,
10769    // a newline is appended, and the edits provided by the language server
10770    // have been applied.
10771    format.await.unwrap();
10772    cx.assert_editor_state(
10773        &[
10774            "one",   //
10775            "",      //
10776            "twoˇ",  //
10777            "",      //
10778            "three", //
10779            "four",  //
10780            "",      //
10781        ]
10782        .join("\n"),
10783    );
10784
10785    // Undoing the formatting undoes the trailing whitespace removal, the
10786    // trailing newline, and the LSP edits.
10787    cx.update_buffer(|buffer, cx| buffer.undo(cx));
10788    cx.assert_editor_state(
10789        &[
10790            "one ",   //
10791            "twoˇ",   //
10792            "three ", //
10793            "four",   //
10794        ]
10795        .join("\n"),
10796    );
10797}
10798
10799#[gpui::test]
10800async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
10801    cx: &mut TestAppContext,
10802) {
10803    init_test(cx, |_| {});
10804
10805    cx.update(|cx| {
10806        cx.update_global::<SettingsStore, _>(|settings, cx| {
10807            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10808                settings.auto_signature_help = Some(true);
10809            });
10810        });
10811    });
10812
10813    let mut cx = EditorLspTestContext::new_rust(
10814        lsp::ServerCapabilities {
10815            signature_help_provider: Some(lsp::SignatureHelpOptions {
10816                ..Default::default()
10817            }),
10818            ..Default::default()
10819        },
10820        cx,
10821    )
10822    .await;
10823
10824    let language = Language::new(
10825        LanguageConfig {
10826            name: "Rust".into(),
10827            brackets: BracketPairConfig {
10828                pairs: vec![
10829                    BracketPair {
10830                        start: "{".to_string(),
10831                        end: "}".to_string(),
10832                        close: true,
10833                        surround: true,
10834                        newline: true,
10835                    },
10836                    BracketPair {
10837                        start: "(".to_string(),
10838                        end: ")".to_string(),
10839                        close: true,
10840                        surround: true,
10841                        newline: true,
10842                    },
10843                    BracketPair {
10844                        start: "/*".to_string(),
10845                        end: " */".to_string(),
10846                        close: true,
10847                        surround: true,
10848                        newline: true,
10849                    },
10850                    BracketPair {
10851                        start: "[".to_string(),
10852                        end: "]".to_string(),
10853                        close: false,
10854                        surround: false,
10855                        newline: true,
10856                    },
10857                    BracketPair {
10858                        start: "\"".to_string(),
10859                        end: "\"".to_string(),
10860                        close: true,
10861                        surround: true,
10862                        newline: false,
10863                    },
10864                    BracketPair {
10865                        start: "<".to_string(),
10866                        end: ">".to_string(),
10867                        close: false,
10868                        surround: true,
10869                        newline: true,
10870                    },
10871                ],
10872                ..Default::default()
10873            },
10874            autoclose_before: "})]".to_string(),
10875            ..Default::default()
10876        },
10877        Some(tree_sitter_rust::LANGUAGE.into()),
10878    );
10879    let language = Arc::new(language);
10880
10881    cx.language_registry().add(language.clone());
10882    cx.update_buffer(|buffer, cx| {
10883        buffer.set_language(Some(language), cx);
10884    });
10885
10886    cx.set_state(
10887        &r#"
10888            fn main() {
10889                sampleˇ
10890            }
10891        "#
10892        .unindent(),
10893    );
10894
10895    cx.update_editor(|editor, window, cx| {
10896        editor.handle_input("(", window, cx);
10897    });
10898    cx.assert_editor_state(
10899        &"
10900            fn main() {
10901                sample(ˇ)
10902            }
10903        "
10904        .unindent(),
10905    );
10906
10907    let mocked_response = lsp::SignatureHelp {
10908        signatures: vec![lsp::SignatureInformation {
10909            label: "fn sample(param1: u8, param2: u8)".to_string(),
10910            documentation: None,
10911            parameters: Some(vec![
10912                lsp::ParameterInformation {
10913                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10914                    documentation: None,
10915                },
10916                lsp::ParameterInformation {
10917                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10918                    documentation: None,
10919                },
10920            ]),
10921            active_parameter: None,
10922        }],
10923        active_signature: Some(0),
10924        active_parameter: Some(0),
10925    };
10926    handle_signature_help_request(&mut cx, mocked_response).await;
10927
10928    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10929        .await;
10930
10931    cx.editor(|editor, _, _| {
10932        let signature_help_state = editor.signature_help_state.popover().cloned();
10933        let signature = signature_help_state.unwrap();
10934        assert_eq!(
10935            signature.signatures[signature.current_signature].label,
10936            "fn sample(param1: u8, param2: u8)"
10937        );
10938    });
10939}
10940
10941#[gpui::test]
10942async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
10943    init_test(cx, |_| {});
10944
10945    cx.update(|cx| {
10946        cx.update_global::<SettingsStore, _>(|settings, cx| {
10947            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10948                settings.auto_signature_help = Some(false);
10949                settings.show_signature_help_after_edits = Some(false);
10950            });
10951        });
10952    });
10953
10954    let mut cx = EditorLspTestContext::new_rust(
10955        lsp::ServerCapabilities {
10956            signature_help_provider: Some(lsp::SignatureHelpOptions {
10957                ..Default::default()
10958            }),
10959            ..Default::default()
10960        },
10961        cx,
10962    )
10963    .await;
10964
10965    let language = Language::new(
10966        LanguageConfig {
10967            name: "Rust".into(),
10968            brackets: BracketPairConfig {
10969                pairs: vec![
10970                    BracketPair {
10971                        start: "{".to_string(),
10972                        end: "}".to_string(),
10973                        close: true,
10974                        surround: true,
10975                        newline: true,
10976                    },
10977                    BracketPair {
10978                        start: "(".to_string(),
10979                        end: ")".to_string(),
10980                        close: true,
10981                        surround: true,
10982                        newline: true,
10983                    },
10984                    BracketPair {
10985                        start: "/*".to_string(),
10986                        end: " */".to_string(),
10987                        close: true,
10988                        surround: true,
10989                        newline: true,
10990                    },
10991                    BracketPair {
10992                        start: "[".to_string(),
10993                        end: "]".to_string(),
10994                        close: false,
10995                        surround: false,
10996                        newline: true,
10997                    },
10998                    BracketPair {
10999                        start: "\"".to_string(),
11000                        end: "\"".to_string(),
11001                        close: true,
11002                        surround: true,
11003                        newline: false,
11004                    },
11005                    BracketPair {
11006                        start: "<".to_string(),
11007                        end: ">".to_string(),
11008                        close: false,
11009                        surround: true,
11010                        newline: true,
11011                    },
11012                ],
11013                ..Default::default()
11014            },
11015            autoclose_before: "})]".to_string(),
11016            ..Default::default()
11017        },
11018        Some(tree_sitter_rust::LANGUAGE.into()),
11019    );
11020    let language = Arc::new(language);
11021
11022    cx.language_registry().add(language.clone());
11023    cx.update_buffer(|buffer, cx| {
11024        buffer.set_language(Some(language), cx);
11025    });
11026
11027    // Ensure that signature_help is not called when no signature help is enabled.
11028    cx.set_state(
11029        &r#"
11030            fn main() {
11031                sampleˇ
11032            }
11033        "#
11034        .unindent(),
11035    );
11036    cx.update_editor(|editor, window, cx| {
11037        editor.handle_input("(", window, cx);
11038    });
11039    cx.assert_editor_state(
11040        &"
11041            fn main() {
11042                sample(ˇ)
11043            }
11044        "
11045        .unindent(),
11046    );
11047    cx.editor(|editor, _, _| {
11048        assert!(editor.signature_help_state.task().is_none());
11049    });
11050
11051    let mocked_response = lsp::SignatureHelp {
11052        signatures: vec![lsp::SignatureInformation {
11053            label: "fn sample(param1: u8, param2: u8)".to_string(),
11054            documentation: None,
11055            parameters: Some(vec![
11056                lsp::ParameterInformation {
11057                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11058                    documentation: None,
11059                },
11060                lsp::ParameterInformation {
11061                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11062                    documentation: None,
11063                },
11064            ]),
11065            active_parameter: None,
11066        }],
11067        active_signature: Some(0),
11068        active_parameter: Some(0),
11069    };
11070
11071    // Ensure that signature_help is called when enabled afte edits
11072    cx.update(|_, cx| {
11073        cx.update_global::<SettingsStore, _>(|settings, cx| {
11074            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11075                settings.auto_signature_help = Some(false);
11076                settings.show_signature_help_after_edits = Some(true);
11077            });
11078        });
11079    });
11080    cx.set_state(
11081        &r#"
11082            fn main() {
11083                sampleˇ
11084            }
11085        "#
11086        .unindent(),
11087    );
11088    cx.update_editor(|editor, window, cx| {
11089        editor.handle_input("(", window, cx);
11090    });
11091    cx.assert_editor_state(
11092        &"
11093            fn main() {
11094                sample(ˇ)
11095            }
11096        "
11097        .unindent(),
11098    );
11099    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11100    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11101        .await;
11102    cx.update_editor(|editor, _, _| {
11103        let signature_help_state = editor.signature_help_state.popover().cloned();
11104        assert!(signature_help_state.is_some());
11105        let signature = signature_help_state.unwrap();
11106        assert_eq!(
11107            signature.signatures[signature.current_signature].label,
11108            "fn sample(param1: u8, param2: u8)"
11109        );
11110        editor.signature_help_state = SignatureHelpState::default();
11111    });
11112
11113    // Ensure that signature_help is called when auto signature help override is enabled
11114    cx.update(|_, cx| {
11115        cx.update_global::<SettingsStore, _>(|settings, cx| {
11116            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11117                settings.auto_signature_help = Some(true);
11118                settings.show_signature_help_after_edits = Some(false);
11119            });
11120        });
11121    });
11122    cx.set_state(
11123        &r#"
11124            fn main() {
11125                sampleˇ
11126            }
11127        "#
11128        .unindent(),
11129    );
11130    cx.update_editor(|editor, window, cx| {
11131        editor.handle_input("(", window, cx);
11132    });
11133    cx.assert_editor_state(
11134        &"
11135            fn main() {
11136                sample(ˇ)
11137            }
11138        "
11139        .unindent(),
11140    );
11141    handle_signature_help_request(&mut cx, mocked_response).await;
11142    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11143        .await;
11144    cx.editor(|editor, _, _| {
11145        let signature_help_state = editor.signature_help_state.popover().cloned();
11146        assert!(signature_help_state.is_some());
11147        let signature = signature_help_state.unwrap();
11148        assert_eq!(
11149            signature.signatures[signature.current_signature].label,
11150            "fn sample(param1: u8, param2: u8)"
11151        );
11152    });
11153}
11154
11155#[gpui::test]
11156async fn test_signature_help(cx: &mut TestAppContext) {
11157    init_test(cx, |_| {});
11158    cx.update(|cx| {
11159        cx.update_global::<SettingsStore, _>(|settings, cx| {
11160            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11161                settings.auto_signature_help = Some(true);
11162            });
11163        });
11164    });
11165
11166    let mut cx = EditorLspTestContext::new_rust(
11167        lsp::ServerCapabilities {
11168            signature_help_provider: Some(lsp::SignatureHelpOptions {
11169                ..Default::default()
11170            }),
11171            ..Default::default()
11172        },
11173        cx,
11174    )
11175    .await;
11176
11177    // A test that directly calls `show_signature_help`
11178    cx.update_editor(|editor, window, cx| {
11179        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11180    });
11181
11182    let mocked_response = lsp::SignatureHelp {
11183        signatures: vec![lsp::SignatureInformation {
11184            label: "fn sample(param1: u8, param2: u8)".to_string(),
11185            documentation: None,
11186            parameters: Some(vec![
11187                lsp::ParameterInformation {
11188                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11189                    documentation: None,
11190                },
11191                lsp::ParameterInformation {
11192                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11193                    documentation: None,
11194                },
11195            ]),
11196            active_parameter: None,
11197        }],
11198        active_signature: Some(0),
11199        active_parameter: Some(0),
11200    };
11201    handle_signature_help_request(&mut cx, mocked_response).await;
11202
11203    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11204        .await;
11205
11206    cx.editor(|editor, _, _| {
11207        let signature_help_state = editor.signature_help_state.popover().cloned();
11208        assert!(signature_help_state.is_some());
11209        let signature = signature_help_state.unwrap();
11210        assert_eq!(
11211            signature.signatures[signature.current_signature].label,
11212            "fn sample(param1: u8, param2: u8)"
11213        );
11214    });
11215
11216    // When exiting outside from inside the brackets, `signature_help` is closed.
11217    cx.set_state(indoc! {"
11218        fn main() {
11219            sample(ˇ);
11220        }
11221
11222        fn sample(param1: u8, param2: u8) {}
11223    "});
11224
11225    cx.update_editor(|editor, window, cx| {
11226        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11227            s.select_ranges([0..0])
11228        });
11229    });
11230
11231    let mocked_response = lsp::SignatureHelp {
11232        signatures: Vec::new(),
11233        active_signature: None,
11234        active_parameter: None,
11235    };
11236    handle_signature_help_request(&mut cx, mocked_response).await;
11237
11238    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11239        .await;
11240
11241    cx.editor(|editor, _, _| {
11242        assert!(!editor.signature_help_state.is_shown());
11243    });
11244
11245    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
11246    cx.set_state(indoc! {"
11247        fn main() {
11248            sample(ˇ);
11249        }
11250
11251        fn sample(param1: u8, param2: u8) {}
11252    "});
11253
11254    let mocked_response = lsp::SignatureHelp {
11255        signatures: vec![lsp::SignatureInformation {
11256            label: "fn sample(param1: u8, param2: u8)".to_string(),
11257            documentation: None,
11258            parameters: Some(vec![
11259                lsp::ParameterInformation {
11260                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11261                    documentation: None,
11262                },
11263                lsp::ParameterInformation {
11264                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11265                    documentation: None,
11266                },
11267            ]),
11268            active_parameter: None,
11269        }],
11270        active_signature: Some(0),
11271        active_parameter: Some(0),
11272    };
11273    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11274    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11275        .await;
11276    cx.editor(|editor, _, _| {
11277        assert!(editor.signature_help_state.is_shown());
11278    });
11279
11280    // Restore the popover with more parameter input
11281    cx.set_state(indoc! {"
11282        fn main() {
11283            sample(param1, param2ˇ);
11284        }
11285
11286        fn sample(param1: u8, param2: u8) {}
11287    "});
11288
11289    let mocked_response = lsp::SignatureHelp {
11290        signatures: vec![lsp::SignatureInformation {
11291            label: "fn sample(param1: u8, param2: u8)".to_string(),
11292            documentation: None,
11293            parameters: Some(vec![
11294                lsp::ParameterInformation {
11295                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11296                    documentation: None,
11297                },
11298                lsp::ParameterInformation {
11299                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11300                    documentation: None,
11301                },
11302            ]),
11303            active_parameter: None,
11304        }],
11305        active_signature: Some(0),
11306        active_parameter: Some(1),
11307    };
11308    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11309    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11310        .await;
11311
11312    // When selecting a range, the popover is gone.
11313    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
11314    cx.update_editor(|editor, window, cx| {
11315        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11316            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11317        })
11318    });
11319    cx.assert_editor_state(indoc! {"
11320        fn main() {
11321            sample(param1, «ˇparam2»);
11322        }
11323
11324        fn sample(param1: u8, param2: u8) {}
11325    "});
11326    cx.editor(|editor, _, _| {
11327        assert!(!editor.signature_help_state.is_shown());
11328    });
11329
11330    // When unselecting again, the popover is back if within the brackets.
11331    cx.update_editor(|editor, window, cx| {
11332        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11333            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11334        })
11335    });
11336    cx.assert_editor_state(indoc! {"
11337        fn main() {
11338            sample(param1, ˇparam2);
11339        }
11340
11341        fn sample(param1: u8, param2: u8) {}
11342    "});
11343    handle_signature_help_request(&mut cx, mocked_response).await;
11344    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11345        .await;
11346    cx.editor(|editor, _, _| {
11347        assert!(editor.signature_help_state.is_shown());
11348    });
11349
11350    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
11351    cx.update_editor(|editor, window, cx| {
11352        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11353            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
11354            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11355        })
11356    });
11357    cx.assert_editor_state(indoc! {"
11358        fn main() {
11359            sample(param1, ˇparam2);
11360        }
11361
11362        fn sample(param1: u8, param2: u8) {}
11363    "});
11364
11365    let mocked_response = lsp::SignatureHelp {
11366        signatures: vec![lsp::SignatureInformation {
11367            label: "fn sample(param1: u8, param2: u8)".to_string(),
11368            documentation: None,
11369            parameters: Some(vec![
11370                lsp::ParameterInformation {
11371                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11372                    documentation: None,
11373                },
11374                lsp::ParameterInformation {
11375                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11376                    documentation: None,
11377                },
11378            ]),
11379            active_parameter: None,
11380        }],
11381        active_signature: Some(0),
11382        active_parameter: Some(1),
11383    };
11384    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11385    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11386        .await;
11387    cx.update_editor(|editor, _, cx| {
11388        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
11389    });
11390    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11391        .await;
11392    cx.update_editor(|editor, window, cx| {
11393        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11394            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11395        })
11396    });
11397    cx.assert_editor_state(indoc! {"
11398        fn main() {
11399            sample(param1, «ˇparam2»);
11400        }
11401
11402        fn sample(param1: u8, param2: u8) {}
11403    "});
11404    cx.update_editor(|editor, window, cx| {
11405        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11406            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11407        })
11408    });
11409    cx.assert_editor_state(indoc! {"
11410        fn main() {
11411            sample(param1, ˇparam2);
11412        }
11413
11414        fn sample(param1: u8, param2: u8) {}
11415    "});
11416    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
11417        .await;
11418}
11419
11420#[gpui::test]
11421async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
11422    init_test(cx, |_| {});
11423
11424    let mut cx = EditorLspTestContext::new_rust(
11425        lsp::ServerCapabilities {
11426            signature_help_provider: Some(lsp::SignatureHelpOptions {
11427                ..Default::default()
11428            }),
11429            ..Default::default()
11430        },
11431        cx,
11432    )
11433    .await;
11434
11435    cx.set_state(indoc! {"
11436        fn main() {
11437            overloadedˇ
11438        }
11439    "});
11440
11441    cx.update_editor(|editor, window, cx| {
11442        editor.handle_input("(", window, cx);
11443        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11444    });
11445
11446    // Mock response with 3 signatures
11447    let mocked_response = lsp::SignatureHelp {
11448        signatures: vec![
11449            lsp::SignatureInformation {
11450                label: "fn overloaded(x: i32)".to_string(),
11451                documentation: None,
11452                parameters: Some(vec![lsp::ParameterInformation {
11453                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11454                    documentation: None,
11455                }]),
11456                active_parameter: None,
11457            },
11458            lsp::SignatureInformation {
11459                label: "fn overloaded(x: i32, y: i32)".to_string(),
11460                documentation: None,
11461                parameters: Some(vec![
11462                    lsp::ParameterInformation {
11463                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11464                        documentation: None,
11465                    },
11466                    lsp::ParameterInformation {
11467                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
11468                        documentation: None,
11469                    },
11470                ]),
11471                active_parameter: None,
11472            },
11473            lsp::SignatureInformation {
11474                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
11475                documentation: None,
11476                parameters: Some(vec![
11477                    lsp::ParameterInformation {
11478                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11479                        documentation: None,
11480                    },
11481                    lsp::ParameterInformation {
11482                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
11483                        documentation: None,
11484                    },
11485                    lsp::ParameterInformation {
11486                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
11487                        documentation: None,
11488                    },
11489                ]),
11490                active_parameter: None,
11491            },
11492        ],
11493        active_signature: Some(1),
11494        active_parameter: Some(0),
11495    };
11496    handle_signature_help_request(&mut cx, mocked_response).await;
11497
11498    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11499        .await;
11500
11501    // Verify we have multiple signatures and the right one is selected
11502    cx.editor(|editor, _, _| {
11503        let popover = editor.signature_help_state.popover().cloned().unwrap();
11504        assert_eq!(popover.signatures.len(), 3);
11505        // active_signature was 1, so that should be the current
11506        assert_eq!(popover.current_signature, 1);
11507        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
11508        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
11509        assert_eq!(
11510            popover.signatures[2].label,
11511            "fn overloaded(x: i32, y: i32, z: i32)"
11512        );
11513    });
11514
11515    // Test navigation functionality
11516    cx.update_editor(|editor, window, cx| {
11517        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
11518    });
11519
11520    cx.editor(|editor, _, _| {
11521        let popover = editor.signature_help_state.popover().cloned().unwrap();
11522        assert_eq!(popover.current_signature, 2);
11523    });
11524
11525    // Test wrap around
11526    cx.update_editor(|editor, window, cx| {
11527        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
11528    });
11529
11530    cx.editor(|editor, _, _| {
11531        let popover = editor.signature_help_state.popover().cloned().unwrap();
11532        assert_eq!(popover.current_signature, 0);
11533    });
11534
11535    // Test previous navigation
11536    cx.update_editor(|editor, window, cx| {
11537        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
11538    });
11539
11540    cx.editor(|editor, _, _| {
11541        let popover = editor.signature_help_state.popover().cloned().unwrap();
11542        assert_eq!(popover.current_signature, 2);
11543    });
11544}
11545
11546#[gpui::test]
11547async fn test_completion_mode(cx: &mut TestAppContext) {
11548    init_test(cx, |_| {});
11549    let mut cx = EditorLspTestContext::new_rust(
11550        lsp::ServerCapabilities {
11551            completion_provider: Some(lsp::CompletionOptions {
11552                resolve_provider: Some(true),
11553                ..Default::default()
11554            }),
11555            ..Default::default()
11556        },
11557        cx,
11558    )
11559    .await;
11560
11561    struct Run {
11562        run_description: &'static str,
11563        initial_state: String,
11564        buffer_marked_text: String,
11565        completion_label: &'static str,
11566        completion_text: &'static str,
11567        expected_with_insert_mode: String,
11568        expected_with_replace_mode: String,
11569        expected_with_replace_subsequence_mode: String,
11570        expected_with_replace_suffix_mode: String,
11571    }
11572
11573    let runs = [
11574        Run {
11575            run_description: "Start of word matches completion text",
11576            initial_state: "before ediˇ after".into(),
11577            buffer_marked_text: "before <edi|> after".into(),
11578            completion_label: "editor",
11579            completion_text: "editor",
11580            expected_with_insert_mode: "before editorˇ after".into(),
11581            expected_with_replace_mode: "before editorˇ after".into(),
11582            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11583            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11584        },
11585        Run {
11586            run_description: "Accept same text at the middle of the word",
11587            initial_state: "before ediˇtor after".into(),
11588            buffer_marked_text: "before <edi|tor> after".into(),
11589            completion_label: "editor",
11590            completion_text: "editor",
11591            expected_with_insert_mode: "before editorˇtor after".into(),
11592            expected_with_replace_mode: "before editorˇ after".into(),
11593            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11594            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11595        },
11596        Run {
11597            run_description: "End of word matches completion text -- cursor at end",
11598            initial_state: "before torˇ after".into(),
11599            buffer_marked_text: "before <tor|> after".into(),
11600            completion_label: "editor",
11601            completion_text: "editor",
11602            expected_with_insert_mode: "before editorˇ after".into(),
11603            expected_with_replace_mode: "before editorˇ after".into(),
11604            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11605            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11606        },
11607        Run {
11608            run_description: "End of word matches completion text -- cursor at start",
11609            initial_state: "before ˇtor after".into(),
11610            buffer_marked_text: "before <|tor> after".into(),
11611            completion_label: "editor",
11612            completion_text: "editor",
11613            expected_with_insert_mode: "before editorˇtor after".into(),
11614            expected_with_replace_mode: "before editorˇ after".into(),
11615            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11616            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11617        },
11618        Run {
11619            run_description: "Prepend text containing whitespace",
11620            initial_state: "pˇfield: bool".into(),
11621            buffer_marked_text: "<p|field>: bool".into(),
11622            completion_label: "pub ",
11623            completion_text: "pub ",
11624            expected_with_insert_mode: "pub ˇfield: bool".into(),
11625            expected_with_replace_mode: "pub ˇ: bool".into(),
11626            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
11627            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
11628        },
11629        Run {
11630            run_description: "Add element to start of list",
11631            initial_state: "[element_ˇelement_2]".into(),
11632            buffer_marked_text: "[<element_|element_2>]".into(),
11633            completion_label: "element_1",
11634            completion_text: "element_1",
11635            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
11636            expected_with_replace_mode: "[element_1ˇ]".into(),
11637            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
11638            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
11639        },
11640        Run {
11641            run_description: "Add element to start of list -- first and second elements are equal",
11642            initial_state: "[elˇelement]".into(),
11643            buffer_marked_text: "[<el|element>]".into(),
11644            completion_label: "element",
11645            completion_text: "element",
11646            expected_with_insert_mode: "[elementˇelement]".into(),
11647            expected_with_replace_mode: "[elementˇ]".into(),
11648            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
11649            expected_with_replace_suffix_mode: "[elementˇ]".into(),
11650        },
11651        Run {
11652            run_description: "Ends with matching suffix",
11653            initial_state: "SubˇError".into(),
11654            buffer_marked_text: "<Sub|Error>".into(),
11655            completion_label: "SubscriptionError",
11656            completion_text: "SubscriptionError",
11657            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
11658            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11659            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11660            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
11661        },
11662        Run {
11663            run_description: "Suffix is a subsequence -- contiguous",
11664            initial_state: "SubˇErr".into(),
11665            buffer_marked_text: "<Sub|Err>".into(),
11666            completion_label: "SubscriptionError",
11667            completion_text: "SubscriptionError",
11668            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
11669            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11670            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11671            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
11672        },
11673        Run {
11674            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
11675            initial_state: "Suˇscrirr".into(),
11676            buffer_marked_text: "<Su|scrirr>".into(),
11677            completion_label: "SubscriptionError",
11678            completion_text: "SubscriptionError",
11679            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
11680            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11681            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11682            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
11683        },
11684        Run {
11685            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
11686            initial_state: "foo(indˇix)".into(),
11687            buffer_marked_text: "foo(<ind|ix>)".into(),
11688            completion_label: "node_index",
11689            completion_text: "node_index",
11690            expected_with_insert_mode: "foo(node_indexˇix)".into(),
11691            expected_with_replace_mode: "foo(node_indexˇ)".into(),
11692            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
11693            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
11694        },
11695        Run {
11696            run_description: "Replace range ends before cursor - should extend to cursor",
11697            initial_state: "before editˇo after".into(),
11698            buffer_marked_text: "before <{ed}>it|o after".into(),
11699            completion_label: "editor",
11700            completion_text: "editor",
11701            expected_with_insert_mode: "before editorˇo after".into(),
11702            expected_with_replace_mode: "before editorˇo after".into(),
11703            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
11704            expected_with_replace_suffix_mode: "before editorˇo after".into(),
11705        },
11706        Run {
11707            run_description: "Uses label for suffix matching",
11708            initial_state: "before ediˇtor after".into(),
11709            buffer_marked_text: "before <edi|tor> after".into(),
11710            completion_label: "editor",
11711            completion_text: "editor()",
11712            expected_with_insert_mode: "before editor()ˇtor after".into(),
11713            expected_with_replace_mode: "before editor()ˇ after".into(),
11714            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
11715            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
11716        },
11717        Run {
11718            run_description: "Case insensitive subsequence and suffix matching",
11719            initial_state: "before EDiˇtoR after".into(),
11720            buffer_marked_text: "before <EDi|toR> after".into(),
11721            completion_label: "editor",
11722            completion_text: "editor",
11723            expected_with_insert_mode: "before editorˇtoR after".into(),
11724            expected_with_replace_mode: "before editorˇ after".into(),
11725            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11726            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11727        },
11728    ];
11729
11730    for run in runs {
11731        let run_variations = [
11732            (LspInsertMode::Insert, run.expected_with_insert_mode),
11733            (LspInsertMode::Replace, run.expected_with_replace_mode),
11734            (
11735                LspInsertMode::ReplaceSubsequence,
11736                run.expected_with_replace_subsequence_mode,
11737            ),
11738            (
11739                LspInsertMode::ReplaceSuffix,
11740                run.expected_with_replace_suffix_mode,
11741            ),
11742        ];
11743
11744        for (lsp_insert_mode, expected_text) in run_variations {
11745            eprintln!(
11746                "run = {:?}, mode = {lsp_insert_mode:.?}",
11747                run.run_description,
11748            );
11749
11750            update_test_language_settings(&mut cx, |settings| {
11751                settings.defaults.completions = Some(CompletionSettings {
11752                    lsp_insert_mode,
11753                    words: WordsCompletionMode::Disabled,
11754                    lsp: true,
11755                    lsp_fetch_timeout_ms: 0,
11756                });
11757            });
11758
11759            cx.set_state(&run.initial_state);
11760            cx.update_editor(|editor, window, cx| {
11761                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11762            });
11763
11764            let counter = Arc::new(AtomicUsize::new(0));
11765            handle_completion_request_with_insert_and_replace(
11766                &mut cx,
11767                &run.buffer_marked_text,
11768                vec![(run.completion_label, run.completion_text)],
11769                counter.clone(),
11770            )
11771            .await;
11772            cx.condition(|editor, _| editor.context_menu_visible())
11773                .await;
11774            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11775
11776            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11777                editor
11778                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
11779                    .unwrap()
11780            });
11781            cx.assert_editor_state(&expected_text);
11782            handle_resolve_completion_request(&mut cx, None).await;
11783            apply_additional_edits.await.unwrap();
11784        }
11785    }
11786}
11787
11788#[gpui::test]
11789async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
11790    init_test(cx, |_| {});
11791    let mut cx = EditorLspTestContext::new_rust(
11792        lsp::ServerCapabilities {
11793            completion_provider: Some(lsp::CompletionOptions {
11794                resolve_provider: Some(true),
11795                ..Default::default()
11796            }),
11797            ..Default::default()
11798        },
11799        cx,
11800    )
11801    .await;
11802
11803    let initial_state = "SubˇError";
11804    let buffer_marked_text = "<Sub|Error>";
11805    let completion_text = "SubscriptionError";
11806    let expected_with_insert_mode = "SubscriptionErrorˇError";
11807    let expected_with_replace_mode = "SubscriptionErrorˇ";
11808
11809    update_test_language_settings(&mut cx, |settings| {
11810        settings.defaults.completions = Some(CompletionSettings {
11811            words: WordsCompletionMode::Disabled,
11812            // set the opposite here to ensure that the action is overriding the default behavior
11813            lsp_insert_mode: LspInsertMode::Insert,
11814            lsp: true,
11815            lsp_fetch_timeout_ms: 0,
11816        });
11817    });
11818
11819    cx.set_state(initial_state);
11820    cx.update_editor(|editor, window, cx| {
11821        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11822    });
11823
11824    let counter = Arc::new(AtomicUsize::new(0));
11825    handle_completion_request_with_insert_and_replace(
11826        &mut cx,
11827        &buffer_marked_text,
11828        vec![(completion_text, completion_text)],
11829        counter.clone(),
11830    )
11831    .await;
11832    cx.condition(|editor, _| editor.context_menu_visible())
11833        .await;
11834    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11835
11836    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11837        editor
11838            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11839            .unwrap()
11840    });
11841    cx.assert_editor_state(&expected_with_replace_mode);
11842    handle_resolve_completion_request(&mut cx, None).await;
11843    apply_additional_edits.await.unwrap();
11844
11845    update_test_language_settings(&mut cx, |settings| {
11846        settings.defaults.completions = Some(CompletionSettings {
11847            words: WordsCompletionMode::Disabled,
11848            // set the opposite here to ensure that the action is overriding the default behavior
11849            lsp_insert_mode: LspInsertMode::Replace,
11850            lsp: true,
11851            lsp_fetch_timeout_ms: 0,
11852        });
11853    });
11854
11855    cx.set_state(initial_state);
11856    cx.update_editor(|editor, window, cx| {
11857        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11858    });
11859    handle_completion_request_with_insert_and_replace(
11860        &mut cx,
11861        &buffer_marked_text,
11862        vec![(completion_text, completion_text)],
11863        counter.clone(),
11864    )
11865    .await;
11866    cx.condition(|editor, _| editor.context_menu_visible())
11867        .await;
11868    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
11869
11870    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11871        editor
11872            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
11873            .unwrap()
11874    });
11875    cx.assert_editor_state(&expected_with_insert_mode);
11876    handle_resolve_completion_request(&mut cx, None).await;
11877    apply_additional_edits.await.unwrap();
11878}
11879
11880#[gpui::test]
11881async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
11882    init_test(cx, |_| {});
11883    let mut cx = EditorLspTestContext::new_rust(
11884        lsp::ServerCapabilities {
11885            completion_provider: Some(lsp::CompletionOptions {
11886                resolve_provider: Some(true),
11887                ..Default::default()
11888            }),
11889            ..Default::default()
11890        },
11891        cx,
11892    )
11893    .await;
11894
11895    // scenario: surrounding text matches completion text
11896    let completion_text = "to_offset";
11897    let initial_state = indoc! {"
11898        1. buf.to_offˇsuffix
11899        2. buf.to_offˇsuf
11900        3. buf.to_offˇfix
11901        4. buf.to_offˇ
11902        5. into_offˇensive
11903        6. ˇsuffix
11904        7. let ˇ //
11905        8. aaˇzz
11906        9. buf.to_off«zzzzzˇ»suffix
11907        10. buf.«ˇzzzzz»suffix
11908        11. to_off«ˇzzzzz»
11909
11910        buf.to_offˇsuffix  // newest cursor
11911    "};
11912    let completion_marked_buffer = indoc! {"
11913        1. buf.to_offsuffix
11914        2. buf.to_offsuf
11915        3. buf.to_offfix
11916        4. buf.to_off
11917        5. into_offensive
11918        6. suffix
11919        7. let  //
11920        8. aazz
11921        9. buf.to_offzzzzzsuffix
11922        10. buf.zzzzzsuffix
11923        11. to_offzzzzz
11924
11925        buf.<to_off|suffix>  // newest cursor
11926    "};
11927    let expected = indoc! {"
11928        1. buf.to_offsetˇ
11929        2. buf.to_offsetˇsuf
11930        3. buf.to_offsetˇfix
11931        4. buf.to_offsetˇ
11932        5. into_offsetˇensive
11933        6. to_offsetˇsuffix
11934        7. let to_offsetˇ //
11935        8. aato_offsetˇzz
11936        9. buf.to_offsetˇ
11937        10. buf.to_offsetˇsuffix
11938        11. to_offsetˇ
11939
11940        buf.to_offsetˇ  // newest cursor
11941    "};
11942    cx.set_state(initial_state);
11943    cx.update_editor(|editor, window, cx| {
11944        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11945    });
11946    handle_completion_request_with_insert_and_replace(
11947        &mut cx,
11948        completion_marked_buffer,
11949        vec![(completion_text, completion_text)],
11950        Arc::new(AtomicUsize::new(0)),
11951    )
11952    .await;
11953    cx.condition(|editor, _| editor.context_menu_visible())
11954        .await;
11955    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11956        editor
11957            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11958            .unwrap()
11959    });
11960    cx.assert_editor_state(expected);
11961    handle_resolve_completion_request(&mut cx, None).await;
11962    apply_additional_edits.await.unwrap();
11963
11964    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
11965    let completion_text = "foo_and_bar";
11966    let initial_state = indoc! {"
11967        1. ooanbˇ
11968        2. zooanbˇ
11969        3. ooanbˇz
11970        4. zooanbˇz
11971        5. ooanˇ
11972        6. oanbˇ
11973
11974        ooanbˇ
11975    "};
11976    let completion_marked_buffer = indoc! {"
11977        1. ooanb
11978        2. zooanb
11979        3. ooanbz
11980        4. zooanbz
11981        5. ooan
11982        6. oanb
11983
11984        <ooanb|>
11985    "};
11986    let expected = indoc! {"
11987        1. foo_and_barˇ
11988        2. zfoo_and_barˇ
11989        3. foo_and_barˇz
11990        4. zfoo_and_barˇz
11991        5. ooanfoo_and_barˇ
11992        6. oanbfoo_and_barˇ
11993
11994        foo_and_barˇ
11995    "};
11996    cx.set_state(initial_state);
11997    cx.update_editor(|editor, window, cx| {
11998        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11999    });
12000    handle_completion_request_with_insert_and_replace(
12001        &mut cx,
12002        completion_marked_buffer,
12003        vec![(completion_text, completion_text)],
12004        Arc::new(AtomicUsize::new(0)),
12005    )
12006    .await;
12007    cx.condition(|editor, _| editor.context_menu_visible())
12008        .await;
12009    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12010        editor
12011            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12012            .unwrap()
12013    });
12014    cx.assert_editor_state(expected);
12015    handle_resolve_completion_request(&mut cx, None).await;
12016    apply_additional_edits.await.unwrap();
12017
12018    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
12019    // (expects the same as if it was inserted at the end)
12020    let completion_text = "foo_and_bar";
12021    let initial_state = indoc! {"
12022        1. ooˇanb
12023        2. zooˇanb
12024        3. ooˇanbz
12025        4. zooˇanbz
12026
12027        ooˇanb
12028    "};
12029    let completion_marked_buffer = indoc! {"
12030        1. ooanb
12031        2. zooanb
12032        3. ooanbz
12033        4. zooanbz
12034
12035        <oo|anb>
12036    "};
12037    let expected = indoc! {"
12038        1. foo_and_barˇ
12039        2. zfoo_and_barˇ
12040        3. foo_and_barˇz
12041        4. zfoo_and_barˇz
12042
12043        foo_and_barˇ
12044    "};
12045    cx.set_state(initial_state);
12046    cx.update_editor(|editor, window, cx| {
12047        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12048    });
12049    handle_completion_request_with_insert_and_replace(
12050        &mut cx,
12051        completion_marked_buffer,
12052        vec![(completion_text, completion_text)],
12053        Arc::new(AtomicUsize::new(0)),
12054    )
12055    .await;
12056    cx.condition(|editor, _| editor.context_menu_visible())
12057        .await;
12058    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12059        editor
12060            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12061            .unwrap()
12062    });
12063    cx.assert_editor_state(expected);
12064    handle_resolve_completion_request(&mut cx, None).await;
12065    apply_additional_edits.await.unwrap();
12066}
12067
12068// This used to crash
12069#[gpui::test]
12070async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
12071    init_test(cx, |_| {});
12072
12073    let buffer_text = indoc! {"
12074        fn main() {
12075            10.satu;
12076
12077            //
12078            // separate cursors so they open in different excerpts (manually reproducible)
12079            //
12080
12081            10.satu20;
12082        }
12083    "};
12084    let multibuffer_text_with_selections = indoc! {"
12085        fn main() {
12086            10.satuˇ;
12087
12088            //
12089
12090            //
12091
12092            10.satuˇ20;
12093        }
12094    "};
12095    let expected_multibuffer = indoc! {"
12096        fn main() {
12097            10.saturating_sub()ˇ;
12098
12099            //
12100
12101            //
12102
12103            10.saturating_sub()ˇ;
12104        }
12105    "};
12106
12107    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
12108    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
12109
12110    let fs = FakeFs::new(cx.executor());
12111    fs.insert_tree(
12112        path!("/a"),
12113        json!({
12114            "main.rs": buffer_text,
12115        }),
12116    )
12117    .await;
12118
12119    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12120    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12121    language_registry.add(rust_lang());
12122    let mut fake_servers = language_registry.register_fake_lsp(
12123        "Rust",
12124        FakeLspAdapter {
12125            capabilities: lsp::ServerCapabilities {
12126                completion_provider: Some(lsp::CompletionOptions {
12127                    resolve_provider: None,
12128                    ..lsp::CompletionOptions::default()
12129                }),
12130                ..lsp::ServerCapabilities::default()
12131            },
12132            ..FakeLspAdapter::default()
12133        },
12134    );
12135    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12136    let cx = &mut VisualTestContext::from_window(*workspace, cx);
12137    let buffer = project
12138        .update(cx, |project, cx| {
12139            project.open_local_buffer(path!("/a/main.rs"), cx)
12140        })
12141        .await
12142        .unwrap();
12143
12144    let multi_buffer = cx.new(|cx| {
12145        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
12146        multi_buffer.push_excerpts(
12147            buffer.clone(),
12148            [ExcerptRange::new(0..first_excerpt_end)],
12149            cx,
12150        );
12151        multi_buffer.push_excerpts(
12152            buffer.clone(),
12153            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
12154            cx,
12155        );
12156        multi_buffer
12157    });
12158
12159    let editor = workspace
12160        .update(cx, |_, window, cx| {
12161            cx.new(|cx| {
12162                Editor::new(
12163                    EditorMode::Full {
12164                        scale_ui_elements_with_buffer_font_size: false,
12165                        show_active_line_background: false,
12166                        sized_by_content: false,
12167                    },
12168                    multi_buffer.clone(),
12169                    Some(project.clone()),
12170                    window,
12171                    cx,
12172                )
12173            })
12174        })
12175        .unwrap();
12176
12177    let pane = workspace
12178        .update(cx, |workspace, _, _| workspace.active_pane().clone())
12179        .unwrap();
12180    pane.update_in(cx, |pane, window, cx| {
12181        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
12182    });
12183
12184    let fake_server = fake_servers.next().await.unwrap();
12185
12186    editor.update_in(cx, |editor, window, cx| {
12187        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12188            s.select_ranges([
12189                Point::new(1, 11)..Point::new(1, 11),
12190                Point::new(7, 11)..Point::new(7, 11),
12191            ])
12192        });
12193
12194        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
12195    });
12196
12197    editor.update_in(cx, |editor, window, cx| {
12198        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12199    });
12200
12201    fake_server
12202        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
12203            let completion_item = lsp::CompletionItem {
12204                label: "saturating_sub()".into(),
12205                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
12206                    lsp::InsertReplaceEdit {
12207                        new_text: "saturating_sub()".to_owned(),
12208                        insert: lsp::Range::new(
12209                            lsp::Position::new(7, 7),
12210                            lsp::Position::new(7, 11),
12211                        ),
12212                        replace: lsp::Range::new(
12213                            lsp::Position::new(7, 7),
12214                            lsp::Position::new(7, 13),
12215                        ),
12216                    },
12217                )),
12218                ..lsp::CompletionItem::default()
12219            };
12220
12221            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
12222        })
12223        .next()
12224        .await
12225        .unwrap();
12226
12227    cx.condition(&editor, |editor, _| editor.context_menu_visible())
12228        .await;
12229
12230    editor
12231        .update_in(cx, |editor, window, cx| {
12232            editor
12233                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12234                .unwrap()
12235        })
12236        .await
12237        .unwrap();
12238
12239    editor.update(cx, |editor, cx| {
12240        assert_text_with_selections(editor, expected_multibuffer, cx);
12241    })
12242}
12243
12244#[gpui::test]
12245async fn test_completion(cx: &mut TestAppContext) {
12246    init_test(cx, |_| {});
12247
12248    let mut cx = EditorLspTestContext::new_rust(
12249        lsp::ServerCapabilities {
12250            completion_provider: Some(lsp::CompletionOptions {
12251                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12252                resolve_provider: Some(true),
12253                ..Default::default()
12254            }),
12255            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12256            ..Default::default()
12257        },
12258        cx,
12259    )
12260    .await;
12261    let counter = Arc::new(AtomicUsize::new(0));
12262
12263    cx.set_state(indoc! {"
12264        oneˇ
12265        two
12266        three
12267    "});
12268    cx.simulate_keystroke(".");
12269    handle_completion_request(
12270        indoc! {"
12271            one.|<>
12272            two
12273            three
12274        "},
12275        vec!["first_completion", "second_completion"],
12276        true,
12277        counter.clone(),
12278        &mut cx,
12279    )
12280    .await;
12281    cx.condition(|editor, _| editor.context_menu_visible())
12282        .await;
12283    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12284
12285    let _handler = handle_signature_help_request(
12286        &mut cx,
12287        lsp::SignatureHelp {
12288            signatures: vec![lsp::SignatureInformation {
12289                label: "test signature".to_string(),
12290                documentation: None,
12291                parameters: Some(vec![lsp::ParameterInformation {
12292                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
12293                    documentation: None,
12294                }]),
12295                active_parameter: None,
12296            }],
12297            active_signature: None,
12298            active_parameter: None,
12299        },
12300    );
12301    cx.update_editor(|editor, window, cx| {
12302        assert!(
12303            !editor.signature_help_state.is_shown(),
12304            "No signature help was called for"
12305        );
12306        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12307    });
12308    cx.run_until_parked();
12309    cx.update_editor(|editor, _, _| {
12310        assert!(
12311            !editor.signature_help_state.is_shown(),
12312            "No signature help should be shown when completions menu is open"
12313        );
12314    });
12315
12316    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12317        editor.context_menu_next(&Default::default(), window, cx);
12318        editor
12319            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12320            .unwrap()
12321    });
12322    cx.assert_editor_state(indoc! {"
12323        one.second_completionˇ
12324        two
12325        three
12326    "});
12327
12328    handle_resolve_completion_request(
12329        &mut cx,
12330        Some(vec![
12331            (
12332                //This overlaps with the primary completion edit which is
12333                //misbehavior from the LSP spec, test that we filter it out
12334                indoc! {"
12335                    one.second_ˇcompletion
12336                    two
12337                    threeˇ
12338                "},
12339                "overlapping additional edit",
12340            ),
12341            (
12342                indoc! {"
12343                    one.second_completion
12344                    two
12345                    threeˇ
12346                "},
12347                "\nadditional edit",
12348            ),
12349        ]),
12350    )
12351    .await;
12352    apply_additional_edits.await.unwrap();
12353    cx.assert_editor_state(indoc! {"
12354        one.second_completionˇ
12355        two
12356        three
12357        additional edit
12358    "});
12359
12360    cx.set_state(indoc! {"
12361        one.second_completion
12362        twoˇ
12363        threeˇ
12364        additional edit
12365    "});
12366    cx.simulate_keystroke(" ");
12367    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12368    cx.simulate_keystroke("s");
12369    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12370
12371    cx.assert_editor_state(indoc! {"
12372        one.second_completion
12373        two sˇ
12374        three sˇ
12375        additional edit
12376    "});
12377    handle_completion_request(
12378        indoc! {"
12379            one.second_completion
12380            two s
12381            three <s|>
12382            additional edit
12383        "},
12384        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
12385        true,
12386        counter.clone(),
12387        &mut cx,
12388    )
12389    .await;
12390    cx.condition(|editor, _| editor.context_menu_visible())
12391        .await;
12392    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12393
12394    cx.simulate_keystroke("i");
12395
12396    handle_completion_request(
12397        indoc! {"
12398            one.second_completion
12399            two si
12400            three <si|>
12401            additional edit
12402        "},
12403        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
12404        true,
12405        counter.clone(),
12406        &mut cx,
12407    )
12408    .await;
12409    cx.condition(|editor, _| editor.context_menu_visible())
12410        .await;
12411    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12412
12413    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12414        editor
12415            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12416            .unwrap()
12417    });
12418    cx.assert_editor_state(indoc! {"
12419        one.second_completion
12420        two sixth_completionˇ
12421        three sixth_completionˇ
12422        additional edit
12423    "});
12424
12425    apply_additional_edits.await.unwrap();
12426
12427    update_test_language_settings(&mut cx, |settings| {
12428        settings.defaults.show_completions_on_input = Some(false);
12429    });
12430    cx.set_state("editorˇ");
12431    cx.simulate_keystroke(".");
12432    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12433    cx.simulate_keystrokes("c l o");
12434    cx.assert_editor_state("editor.cloˇ");
12435    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12436    cx.update_editor(|editor, window, cx| {
12437        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12438    });
12439    handle_completion_request(
12440        "editor.<clo|>",
12441        vec!["close", "clobber"],
12442        true,
12443        counter.clone(),
12444        &mut cx,
12445    )
12446    .await;
12447    cx.condition(|editor, _| editor.context_menu_visible())
12448        .await;
12449    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
12450
12451    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12452        editor
12453            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12454            .unwrap()
12455    });
12456    cx.assert_editor_state("editor.clobberˇ");
12457    handle_resolve_completion_request(&mut cx, None).await;
12458    apply_additional_edits.await.unwrap();
12459}
12460
12461#[gpui::test]
12462async fn test_completion_reuse(cx: &mut TestAppContext) {
12463    init_test(cx, |_| {});
12464
12465    let mut cx = EditorLspTestContext::new_rust(
12466        lsp::ServerCapabilities {
12467            completion_provider: Some(lsp::CompletionOptions {
12468                trigger_characters: Some(vec![".".to_string()]),
12469                ..Default::default()
12470            }),
12471            ..Default::default()
12472        },
12473        cx,
12474    )
12475    .await;
12476
12477    let counter = Arc::new(AtomicUsize::new(0));
12478    cx.set_state("objˇ");
12479    cx.simulate_keystroke(".");
12480
12481    // Initial completion request returns complete results
12482    let is_incomplete = false;
12483    handle_completion_request(
12484        "obj.|<>",
12485        vec!["a", "ab", "abc"],
12486        is_incomplete,
12487        counter.clone(),
12488        &mut cx,
12489    )
12490    .await;
12491    cx.run_until_parked();
12492    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12493    cx.assert_editor_state("obj.ˇ");
12494    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12495
12496    // Type "a" - filters existing completions
12497    cx.simulate_keystroke("a");
12498    cx.run_until_parked();
12499    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12500    cx.assert_editor_state("obj.aˇ");
12501    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12502
12503    // Type "b" - filters existing completions
12504    cx.simulate_keystroke("b");
12505    cx.run_until_parked();
12506    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12507    cx.assert_editor_state("obj.abˇ");
12508    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12509
12510    // Type "c" - filters existing completions
12511    cx.simulate_keystroke("c");
12512    cx.run_until_parked();
12513    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12514    cx.assert_editor_state("obj.abcˇ");
12515    check_displayed_completions(vec!["abc"], &mut cx);
12516
12517    // Backspace to delete "c" - filters existing completions
12518    cx.update_editor(|editor, window, cx| {
12519        editor.backspace(&Backspace, window, cx);
12520    });
12521    cx.run_until_parked();
12522    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12523    cx.assert_editor_state("obj.abˇ");
12524    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12525
12526    // Moving cursor to the left dismisses menu.
12527    cx.update_editor(|editor, window, cx| {
12528        editor.move_left(&MoveLeft, window, cx);
12529    });
12530    cx.run_until_parked();
12531    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12532    cx.assert_editor_state("obj.aˇb");
12533    cx.update_editor(|editor, _, _| {
12534        assert_eq!(editor.context_menu_visible(), false);
12535    });
12536
12537    // Type "b" - new request
12538    cx.simulate_keystroke("b");
12539    let is_incomplete = false;
12540    handle_completion_request(
12541        "obj.<ab|>a",
12542        vec!["ab", "abc"],
12543        is_incomplete,
12544        counter.clone(),
12545        &mut cx,
12546    )
12547    .await;
12548    cx.run_until_parked();
12549    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12550    cx.assert_editor_state("obj.abˇb");
12551    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12552
12553    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
12554    cx.update_editor(|editor, window, cx| {
12555        editor.backspace(&Backspace, window, cx);
12556    });
12557    let is_incomplete = false;
12558    handle_completion_request(
12559        "obj.<a|>b",
12560        vec!["a", "ab", "abc"],
12561        is_incomplete,
12562        counter.clone(),
12563        &mut cx,
12564    )
12565    .await;
12566    cx.run_until_parked();
12567    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12568    cx.assert_editor_state("obj.aˇb");
12569    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12570
12571    // Backspace to delete "a" - dismisses menu.
12572    cx.update_editor(|editor, window, cx| {
12573        editor.backspace(&Backspace, window, cx);
12574    });
12575    cx.run_until_parked();
12576    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12577    cx.assert_editor_state("obj.ˇb");
12578    cx.update_editor(|editor, _, _| {
12579        assert_eq!(editor.context_menu_visible(), false);
12580    });
12581}
12582
12583#[gpui::test]
12584async fn test_word_completion(cx: &mut TestAppContext) {
12585    let lsp_fetch_timeout_ms = 10;
12586    init_test(cx, |language_settings| {
12587        language_settings.defaults.completions = Some(CompletionSettings {
12588            words: WordsCompletionMode::Fallback,
12589            lsp: true,
12590            lsp_fetch_timeout_ms: 10,
12591            lsp_insert_mode: LspInsertMode::Insert,
12592        });
12593    });
12594
12595    let mut cx = EditorLspTestContext::new_rust(
12596        lsp::ServerCapabilities {
12597            completion_provider: Some(lsp::CompletionOptions {
12598                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12599                ..lsp::CompletionOptions::default()
12600            }),
12601            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12602            ..lsp::ServerCapabilities::default()
12603        },
12604        cx,
12605    )
12606    .await;
12607
12608    let throttle_completions = Arc::new(AtomicBool::new(false));
12609
12610    let lsp_throttle_completions = throttle_completions.clone();
12611    let _completion_requests_handler =
12612        cx.lsp
12613            .server
12614            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
12615                let lsp_throttle_completions = lsp_throttle_completions.clone();
12616                let cx = cx.clone();
12617                async move {
12618                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
12619                        cx.background_executor()
12620                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
12621                            .await;
12622                    }
12623                    Ok(Some(lsp::CompletionResponse::Array(vec![
12624                        lsp::CompletionItem {
12625                            label: "first".into(),
12626                            ..lsp::CompletionItem::default()
12627                        },
12628                        lsp::CompletionItem {
12629                            label: "last".into(),
12630                            ..lsp::CompletionItem::default()
12631                        },
12632                    ])))
12633                }
12634            });
12635
12636    cx.set_state(indoc! {"
12637        oneˇ
12638        two
12639        three
12640    "});
12641    cx.simulate_keystroke(".");
12642    cx.executor().run_until_parked();
12643    cx.condition(|editor, _| editor.context_menu_visible())
12644        .await;
12645    cx.update_editor(|editor, window, cx| {
12646        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12647        {
12648            assert_eq!(
12649                completion_menu_entries(&menu),
12650                &["first", "last"],
12651                "When LSP server is fast to reply, no fallback word completions are used"
12652            );
12653        } else {
12654            panic!("expected completion menu to be open");
12655        }
12656        editor.cancel(&Cancel, window, cx);
12657    });
12658    cx.executor().run_until_parked();
12659    cx.condition(|editor, _| !editor.context_menu_visible())
12660        .await;
12661
12662    throttle_completions.store(true, atomic::Ordering::Release);
12663    cx.simulate_keystroke(".");
12664    cx.executor()
12665        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
12666    cx.executor().run_until_parked();
12667    cx.condition(|editor, _| editor.context_menu_visible())
12668        .await;
12669    cx.update_editor(|editor, _, _| {
12670        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12671        {
12672            assert_eq!(completion_menu_entries(&menu), &["one", "three", "two"],
12673                "When LSP server is slow, document words can be shown instead, if configured accordingly");
12674        } else {
12675            panic!("expected completion menu to be open");
12676        }
12677    });
12678}
12679
12680#[gpui::test]
12681async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
12682    init_test(cx, |language_settings| {
12683        language_settings.defaults.completions = Some(CompletionSettings {
12684            words: WordsCompletionMode::Enabled,
12685            lsp: true,
12686            lsp_fetch_timeout_ms: 0,
12687            lsp_insert_mode: LspInsertMode::Insert,
12688        });
12689    });
12690
12691    let mut cx = EditorLspTestContext::new_rust(
12692        lsp::ServerCapabilities {
12693            completion_provider: Some(lsp::CompletionOptions {
12694                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12695                ..lsp::CompletionOptions::default()
12696            }),
12697            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12698            ..lsp::ServerCapabilities::default()
12699        },
12700        cx,
12701    )
12702    .await;
12703
12704    let _completion_requests_handler =
12705        cx.lsp
12706            .server
12707            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12708                Ok(Some(lsp::CompletionResponse::Array(vec![
12709                    lsp::CompletionItem {
12710                        label: "first".into(),
12711                        ..lsp::CompletionItem::default()
12712                    },
12713                    lsp::CompletionItem {
12714                        label: "last".into(),
12715                        ..lsp::CompletionItem::default()
12716                    },
12717                ])))
12718            });
12719
12720    cx.set_state(indoc! {"ˇ
12721        first
12722        last
12723        second
12724    "});
12725    cx.simulate_keystroke(".");
12726    cx.executor().run_until_parked();
12727    cx.condition(|editor, _| editor.context_menu_visible())
12728        .await;
12729    cx.update_editor(|editor, _, _| {
12730        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12731        {
12732            assert_eq!(
12733                completion_menu_entries(&menu),
12734                &["first", "last", "second"],
12735                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
12736            );
12737        } else {
12738            panic!("expected completion menu to be open");
12739        }
12740    });
12741}
12742
12743#[gpui::test]
12744async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
12745    init_test(cx, |language_settings| {
12746        language_settings.defaults.completions = Some(CompletionSettings {
12747            words: WordsCompletionMode::Disabled,
12748            lsp: true,
12749            lsp_fetch_timeout_ms: 0,
12750            lsp_insert_mode: LspInsertMode::Insert,
12751        });
12752    });
12753
12754    let mut cx = EditorLspTestContext::new_rust(
12755        lsp::ServerCapabilities {
12756            completion_provider: Some(lsp::CompletionOptions {
12757                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12758                ..lsp::CompletionOptions::default()
12759            }),
12760            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12761            ..lsp::ServerCapabilities::default()
12762        },
12763        cx,
12764    )
12765    .await;
12766
12767    let _completion_requests_handler =
12768        cx.lsp
12769            .server
12770            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12771                panic!("LSP completions should not be queried when dealing with word completions")
12772            });
12773
12774    cx.set_state(indoc! {"ˇ
12775        first
12776        last
12777        second
12778    "});
12779    cx.update_editor(|editor, window, cx| {
12780        editor.show_word_completions(&ShowWordCompletions, window, cx);
12781    });
12782    cx.executor().run_until_parked();
12783    cx.condition(|editor, _| editor.context_menu_visible())
12784        .await;
12785    cx.update_editor(|editor, _, _| {
12786        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12787        {
12788            assert_eq!(
12789                completion_menu_entries(&menu),
12790                &["first", "last", "second"],
12791                "`ShowWordCompletions` action should show word completions"
12792            );
12793        } else {
12794            panic!("expected completion menu to be open");
12795        }
12796    });
12797
12798    cx.simulate_keystroke("l");
12799    cx.executor().run_until_parked();
12800    cx.condition(|editor, _| editor.context_menu_visible())
12801        .await;
12802    cx.update_editor(|editor, _, _| {
12803        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12804        {
12805            assert_eq!(
12806                completion_menu_entries(&menu),
12807                &["last"],
12808                "After showing word completions, further editing should filter them and not query the LSP"
12809            );
12810        } else {
12811            panic!("expected completion menu to be open");
12812        }
12813    });
12814}
12815
12816#[gpui::test]
12817async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
12818    init_test(cx, |language_settings| {
12819        language_settings.defaults.completions = Some(CompletionSettings {
12820            words: WordsCompletionMode::Fallback,
12821            lsp: false,
12822            lsp_fetch_timeout_ms: 0,
12823            lsp_insert_mode: LspInsertMode::Insert,
12824        });
12825    });
12826
12827    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12828
12829    cx.set_state(indoc! {"ˇ
12830        0_usize
12831        let
12832        33
12833        4.5f32
12834    "});
12835    cx.update_editor(|editor, window, cx| {
12836        editor.show_completions(&ShowCompletions::default(), window, cx);
12837    });
12838    cx.executor().run_until_parked();
12839    cx.condition(|editor, _| editor.context_menu_visible())
12840        .await;
12841    cx.update_editor(|editor, window, cx| {
12842        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12843        {
12844            assert_eq!(
12845                completion_menu_entries(&menu),
12846                &["let"],
12847                "With no digits in the completion query, no digits should be in the word completions"
12848            );
12849        } else {
12850            panic!("expected completion menu to be open");
12851        }
12852        editor.cancel(&Cancel, window, cx);
12853    });
12854
12855    cx.set_state(indoc! {"12856        0_usize
12857        let
12858        3
12859        33.35f32
12860    "});
12861    cx.update_editor(|editor, window, cx| {
12862        editor.show_completions(&ShowCompletions::default(), window, cx);
12863    });
12864    cx.executor().run_until_parked();
12865    cx.condition(|editor, _| editor.context_menu_visible())
12866        .await;
12867    cx.update_editor(|editor, _, _| {
12868        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12869        {
12870            assert_eq!(completion_menu_entries(&menu), &["33", "35f32"], "The digit is in the completion query, \
12871                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
12872        } else {
12873            panic!("expected completion menu to be open");
12874        }
12875    });
12876}
12877
12878fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
12879    let position = || lsp::Position {
12880        line: params.text_document_position.position.line,
12881        character: params.text_document_position.position.character,
12882    };
12883    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12884        range: lsp::Range {
12885            start: position(),
12886            end: position(),
12887        },
12888        new_text: text.to_string(),
12889    }))
12890}
12891
12892#[gpui::test]
12893async fn test_multiline_completion(cx: &mut TestAppContext) {
12894    init_test(cx, |_| {});
12895
12896    let fs = FakeFs::new(cx.executor());
12897    fs.insert_tree(
12898        path!("/a"),
12899        json!({
12900            "main.ts": "a",
12901        }),
12902    )
12903    .await;
12904
12905    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12906    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12907    let typescript_language = Arc::new(Language::new(
12908        LanguageConfig {
12909            name: "TypeScript".into(),
12910            matcher: LanguageMatcher {
12911                path_suffixes: vec!["ts".to_string()],
12912                ..LanguageMatcher::default()
12913            },
12914            line_comments: vec!["// ".into()],
12915            ..LanguageConfig::default()
12916        },
12917        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12918    ));
12919    language_registry.add(typescript_language.clone());
12920    let mut fake_servers = language_registry.register_fake_lsp(
12921        "TypeScript",
12922        FakeLspAdapter {
12923            capabilities: lsp::ServerCapabilities {
12924                completion_provider: Some(lsp::CompletionOptions {
12925                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12926                    ..lsp::CompletionOptions::default()
12927                }),
12928                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12929                ..lsp::ServerCapabilities::default()
12930            },
12931            // Emulate vtsls label generation
12932            label_for_completion: Some(Box::new(|item, _| {
12933                let text = if let Some(description) = item
12934                    .label_details
12935                    .as_ref()
12936                    .and_then(|label_details| label_details.description.as_ref())
12937                {
12938                    format!("{} {}", item.label, description)
12939                } else if let Some(detail) = &item.detail {
12940                    format!("{} {}", item.label, detail)
12941                } else {
12942                    item.label.clone()
12943                };
12944                let len = text.len();
12945                Some(language::CodeLabel {
12946                    text,
12947                    runs: Vec::new(),
12948                    filter_range: 0..len,
12949                })
12950            })),
12951            ..FakeLspAdapter::default()
12952        },
12953    );
12954    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12955    let cx = &mut VisualTestContext::from_window(*workspace, cx);
12956    let worktree_id = workspace
12957        .update(cx, |workspace, _window, cx| {
12958            workspace.project().update(cx, |project, cx| {
12959                project.worktrees(cx).next().unwrap().read(cx).id()
12960            })
12961        })
12962        .unwrap();
12963    let _buffer = project
12964        .update(cx, |project, cx| {
12965            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
12966        })
12967        .await
12968        .unwrap();
12969    let editor = workspace
12970        .update(cx, |workspace, window, cx| {
12971            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
12972        })
12973        .unwrap()
12974        .await
12975        .unwrap()
12976        .downcast::<Editor>()
12977        .unwrap();
12978    let fake_server = fake_servers.next().await.unwrap();
12979
12980    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
12981    let multiline_label_2 = "a\nb\nc\n";
12982    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
12983    let multiline_description = "d\ne\nf\n";
12984    let multiline_detail_2 = "g\nh\ni\n";
12985
12986    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
12987        move |params, _| async move {
12988            Ok(Some(lsp::CompletionResponse::Array(vec![
12989                lsp::CompletionItem {
12990                    label: multiline_label.to_string(),
12991                    text_edit: gen_text_edit(&params, "new_text_1"),
12992                    ..lsp::CompletionItem::default()
12993                },
12994                lsp::CompletionItem {
12995                    label: "single line label 1".to_string(),
12996                    detail: Some(multiline_detail.to_string()),
12997                    text_edit: gen_text_edit(&params, "new_text_2"),
12998                    ..lsp::CompletionItem::default()
12999                },
13000                lsp::CompletionItem {
13001                    label: "single line label 2".to_string(),
13002                    label_details: Some(lsp::CompletionItemLabelDetails {
13003                        description: Some(multiline_description.to_string()),
13004                        detail: None,
13005                    }),
13006                    text_edit: gen_text_edit(&params, "new_text_2"),
13007                    ..lsp::CompletionItem::default()
13008                },
13009                lsp::CompletionItem {
13010                    label: multiline_label_2.to_string(),
13011                    detail: Some(multiline_detail_2.to_string()),
13012                    text_edit: gen_text_edit(&params, "new_text_3"),
13013                    ..lsp::CompletionItem::default()
13014                },
13015                lsp::CompletionItem {
13016                    label: "Label with many     spaces and \t but without newlines".to_string(),
13017                    detail: Some(
13018                        "Details with many     spaces and \t but without newlines".to_string(),
13019                    ),
13020                    text_edit: gen_text_edit(&params, "new_text_4"),
13021                    ..lsp::CompletionItem::default()
13022                },
13023            ])))
13024        },
13025    );
13026
13027    editor.update_in(cx, |editor, window, cx| {
13028        cx.focus_self(window);
13029        editor.move_to_end(&MoveToEnd, window, cx);
13030        editor.handle_input(".", window, cx);
13031    });
13032    cx.run_until_parked();
13033    completion_handle.next().await.unwrap();
13034
13035    editor.update(cx, |editor, _| {
13036        assert!(editor.context_menu_visible());
13037        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13038        {
13039            let completion_labels = menu
13040                .completions
13041                .borrow()
13042                .iter()
13043                .map(|c| c.label.text.clone())
13044                .collect::<Vec<_>>();
13045            assert_eq!(
13046                completion_labels,
13047                &[
13048                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
13049                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
13050                    "single line label 2 d e f ",
13051                    "a b c g h i ",
13052                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
13053                ],
13054                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
13055            );
13056
13057            for completion in menu
13058                .completions
13059                .borrow()
13060                .iter() {
13061                    assert_eq!(
13062                        completion.label.filter_range,
13063                        0..completion.label.text.len(),
13064                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
13065                    );
13066                }
13067        } else {
13068            panic!("expected completion menu to be open");
13069        }
13070    });
13071}
13072
13073#[gpui::test]
13074async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
13075    init_test(cx, |_| {});
13076    let mut cx = EditorLspTestContext::new_rust(
13077        lsp::ServerCapabilities {
13078            completion_provider: Some(lsp::CompletionOptions {
13079                trigger_characters: Some(vec![".".to_string()]),
13080                ..Default::default()
13081            }),
13082            ..Default::default()
13083        },
13084        cx,
13085    )
13086    .await;
13087    cx.lsp
13088        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13089            Ok(Some(lsp::CompletionResponse::Array(vec![
13090                lsp::CompletionItem {
13091                    label: "first".into(),
13092                    ..Default::default()
13093                },
13094                lsp::CompletionItem {
13095                    label: "last".into(),
13096                    ..Default::default()
13097                },
13098            ])))
13099        });
13100    cx.set_state("variableˇ");
13101    cx.simulate_keystroke(".");
13102    cx.executor().run_until_parked();
13103
13104    cx.update_editor(|editor, _, _| {
13105        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13106        {
13107            assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
13108        } else {
13109            panic!("expected completion menu to be open");
13110        }
13111    });
13112
13113    cx.update_editor(|editor, window, cx| {
13114        editor.move_page_down(&MovePageDown::default(), window, cx);
13115        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13116        {
13117            assert!(
13118                menu.selected_item == 1,
13119                "expected PageDown to select the last item from the context menu"
13120            );
13121        } else {
13122            panic!("expected completion menu to stay open after PageDown");
13123        }
13124    });
13125
13126    cx.update_editor(|editor, window, cx| {
13127        editor.move_page_up(&MovePageUp::default(), window, cx);
13128        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13129        {
13130            assert!(
13131                menu.selected_item == 0,
13132                "expected PageUp to select the first item from the context menu"
13133            );
13134        } else {
13135            panic!("expected completion menu to stay open after PageUp");
13136        }
13137    });
13138}
13139
13140#[gpui::test]
13141async fn test_as_is_completions(cx: &mut TestAppContext) {
13142    init_test(cx, |_| {});
13143    let mut cx = EditorLspTestContext::new_rust(
13144        lsp::ServerCapabilities {
13145            completion_provider: Some(lsp::CompletionOptions {
13146                ..Default::default()
13147            }),
13148            ..Default::default()
13149        },
13150        cx,
13151    )
13152    .await;
13153    cx.lsp
13154        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13155            Ok(Some(lsp::CompletionResponse::Array(vec![
13156                lsp::CompletionItem {
13157                    label: "unsafe".into(),
13158                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13159                        range: lsp::Range {
13160                            start: lsp::Position {
13161                                line: 1,
13162                                character: 2,
13163                            },
13164                            end: lsp::Position {
13165                                line: 1,
13166                                character: 3,
13167                            },
13168                        },
13169                        new_text: "unsafe".to_string(),
13170                    })),
13171                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
13172                    ..Default::default()
13173                },
13174            ])))
13175        });
13176    cx.set_state("fn a() {}\n");
13177    cx.executor().run_until_parked();
13178    cx.update_editor(|editor, window, cx| {
13179        editor.show_completions(
13180            &ShowCompletions {
13181                trigger: Some("\n".into()),
13182            },
13183            window,
13184            cx,
13185        );
13186    });
13187    cx.executor().run_until_parked();
13188
13189    cx.update_editor(|editor, window, cx| {
13190        editor.confirm_completion(&Default::default(), window, cx)
13191    });
13192    cx.executor().run_until_parked();
13193    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
13194}
13195
13196#[gpui::test]
13197async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
13198    init_test(cx, |_| {});
13199
13200    let mut cx = EditorLspTestContext::new_rust(
13201        lsp::ServerCapabilities {
13202            completion_provider: Some(lsp::CompletionOptions {
13203                trigger_characters: Some(vec![".".to_string()]),
13204                resolve_provider: Some(true),
13205                ..Default::default()
13206            }),
13207            ..Default::default()
13208        },
13209        cx,
13210    )
13211    .await;
13212
13213    cx.set_state("fn main() { let a = 2ˇ; }");
13214    cx.simulate_keystroke(".");
13215    let completion_item = lsp::CompletionItem {
13216        label: "Some".into(),
13217        kind: Some(lsp::CompletionItemKind::SNIPPET),
13218        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
13219        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
13220            kind: lsp::MarkupKind::Markdown,
13221            value: "```rust\nSome(2)\n```".to_string(),
13222        })),
13223        deprecated: Some(false),
13224        sort_text: Some("Some".to_string()),
13225        filter_text: Some("Some".to_string()),
13226        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
13227        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13228            range: lsp::Range {
13229                start: lsp::Position {
13230                    line: 0,
13231                    character: 22,
13232                },
13233                end: lsp::Position {
13234                    line: 0,
13235                    character: 22,
13236                },
13237            },
13238            new_text: "Some(2)".to_string(),
13239        })),
13240        additional_text_edits: Some(vec![lsp::TextEdit {
13241            range: lsp::Range {
13242                start: lsp::Position {
13243                    line: 0,
13244                    character: 20,
13245                },
13246                end: lsp::Position {
13247                    line: 0,
13248                    character: 22,
13249                },
13250            },
13251            new_text: "".to_string(),
13252        }]),
13253        ..Default::default()
13254    };
13255
13256    let closure_completion_item = completion_item.clone();
13257    let counter = Arc::new(AtomicUsize::new(0));
13258    let counter_clone = counter.clone();
13259    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
13260        let task_completion_item = closure_completion_item.clone();
13261        counter_clone.fetch_add(1, atomic::Ordering::Release);
13262        async move {
13263            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
13264                is_incomplete: true,
13265                item_defaults: None,
13266                items: vec![task_completion_item],
13267            })))
13268        }
13269    });
13270
13271    cx.condition(|editor, _| editor.context_menu_visible())
13272        .await;
13273    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
13274    assert!(request.next().await.is_some());
13275    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13276
13277    cx.simulate_keystrokes("S o m");
13278    cx.condition(|editor, _| editor.context_menu_visible())
13279        .await;
13280    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
13281    assert!(request.next().await.is_some());
13282    assert!(request.next().await.is_some());
13283    assert!(request.next().await.is_some());
13284    request.close();
13285    assert!(request.next().await.is_none());
13286    assert_eq!(
13287        counter.load(atomic::Ordering::Acquire),
13288        4,
13289        "With the completions menu open, only one LSP request should happen per input"
13290    );
13291}
13292
13293#[gpui::test]
13294async fn test_toggle_comment(cx: &mut TestAppContext) {
13295    init_test(cx, |_| {});
13296    let mut cx = EditorTestContext::new(cx).await;
13297    let language = Arc::new(Language::new(
13298        LanguageConfig {
13299            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
13300            ..Default::default()
13301        },
13302        Some(tree_sitter_rust::LANGUAGE.into()),
13303    ));
13304    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
13305
13306    // If multiple selections intersect a line, the line is only toggled once.
13307    cx.set_state(indoc! {"
13308        fn a() {
13309            «//b();
13310            ˇ»// «c();
13311            //ˇ»  d();
13312        }
13313    "});
13314
13315    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13316
13317    cx.assert_editor_state(indoc! {"
13318        fn a() {
13319            «b();
13320            c();
13321            ˇ» d();
13322        }
13323    "});
13324
13325    // The comment prefix is inserted at the same column for every line in a
13326    // selection.
13327    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13328
13329    cx.assert_editor_state(indoc! {"
13330        fn a() {
13331            // «b();
13332            // c();
13333            ˇ»//  d();
13334        }
13335    "});
13336
13337    // If a selection ends at the beginning of a line, that line is not toggled.
13338    cx.set_selections_state(indoc! {"
13339        fn a() {
13340            // b();
13341            «// c();
13342        ˇ»    //  d();
13343        }
13344    "});
13345
13346    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13347
13348    cx.assert_editor_state(indoc! {"
13349        fn a() {
13350            // b();
13351            «c();
13352        ˇ»    //  d();
13353        }
13354    "});
13355
13356    // If a selection span a single line and is empty, the line is toggled.
13357    cx.set_state(indoc! {"
13358        fn a() {
13359            a();
13360            b();
13361        ˇ
13362        }
13363    "});
13364
13365    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13366
13367    cx.assert_editor_state(indoc! {"
13368        fn a() {
13369            a();
13370            b();
13371        //•ˇ
13372        }
13373    "});
13374
13375    // If a selection span multiple lines, empty lines are not toggled.
13376    cx.set_state(indoc! {"
13377        fn a() {
13378            «a();
13379
13380            c();ˇ»
13381        }
13382    "});
13383
13384    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13385
13386    cx.assert_editor_state(indoc! {"
13387        fn a() {
13388            // «a();
13389
13390            // c();ˇ»
13391        }
13392    "});
13393
13394    // If a selection includes multiple comment prefixes, all lines are uncommented.
13395    cx.set_state(indoc! {"
13396        fn a() {
13397            «// a();
13398            /// b();
13399            //! c();ˇ»
13400        }
13401    "});
13402
13403    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13404
13405    cx.assert_editor_state(indoc! {"
13406        fn a() {
13407            «a();
13408            b();
13409            c();ˇ»
13410        }
13411    "});
13412}
13413
13414#[gpui::test]
13415async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
13416    init_test(cx, |_| {});
13417    let mut cx = EditorTestContext::new(cx).await;
13418    let language = Arc::new(Language::new(
13419        LanguageConfig {
13420            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
13421            ..Default::default()
13422        },
13423        Some(tree_sitter_rust::LANGUAGE.into()),
13424    ));
13425    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
13426
13427    let toggle_comments = &ToggleComments {
13428        advance_downwards: false,
13429        ignore_indent: true,
13430    };
13431
13432    // If multiple selections intersect a line, the line is only toggled once.
13433    cx.set_state(indoc! {"
13434        fn a() {
13435        //    «b();
13436        //    c();
13437        //    ˇ» d();
13438        }
13439    "});
13440
13441    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13442
13443    cx.assert_editor_state(indoc! {"
13444        fn a() {
13445            «b();
13446            c();
13447            ˇ» d();
13448        }
13449    "});
13450
13451    // The comment prefix is inserted at the beginning of each line
13452    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13453
13454    cx.assert_editor_state(indoc! {"
13455        fn a() {
13456        //    «b();
13457        //    c();
13458        //    ˇ» d();
13459        }
13460    "});
13461
13462    // If a selection ends at the beginning of a line, that line is not toggled.
13463    cx.set_selections_state(indoc! {"
13464        fn a() {
13465        //    b();
13466        //    «c();
13467        ˇ»//     d();
13468        }
13469    "});
13470
13471    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13472
13473    cx.assert_editor_state(indoc! {"
13474        fn a() {
13475        //    b();
13476            «c();
13477        ˇ»//     d();
13478        }
13479    "});
13480
13481    // If a selection span a single line and is empty, the line is toggled.
13482    cx.set_state(indoc! {"
13483        fn a() {
13484            a();
13485            b();
13486        ˇ
13487        }
13488    "});
13489
13490    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13491
13492    cx.assert_editor_state(indoc! {"
13493        fn a() {
13494            a();
13495            b();
13496        //ˇ
13497        }
13498    "});
13499
13500    // If a selection span multiple lines, empty lines are not toggled.
13501    cx.set_state(indoc! {"
13502        fn a() {
13503            «a();
13504
13505            c();ˇ»
13506        }
13507    "});
13508
13509    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13510
13511    cx.assert_editor_state(indoc! {"
13512        fn a() {
13513        //    «a();
13514
13515        //    c();ˇ»
13516        }
13517    "});
13518
13519    // If a selection includes multiple comment prefixes, all lines are uncommented.
13520    cx.set_state(indoc! {"
13521        fn a() {
13522        //    «a();
13523        ///    b();
13524        //!    c();ˇ»
13525        }
13526    "});
13527
13528    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13529
13530    cx.assert_editor_state(indoc! {"
13531        fn a() {
13532            «a();
13533            b();
13534            c();ˇ»
13535        }
13536    "});
13537}
13538
13539#[gpui::test]
13540async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
13541    init_test(cx, |_| {});
13542
13543    let language = Arc::new(Language::new(
13544        LanguageConfig {
13545            line_comments: vec!["// ".into()],
13546            ..Default::default()
13547        },
13548        Some(tree_sitter_rust::LANGUAGE.into()),
13549    ));
13550
13551    let mut cx = EditorTestContext::new(cx).await;
13552
13553    cx.language_registry().add(language.clone());
13554    cx.update_buffer(|buffer, cx| {
13555        buffer.set_language(Some(language), cx);
13556    });
13557
13558    let toggle_comments = &ToggleComments {
13559        advance_downwards: true,
13560        ignore_indent: false,
13561    };
13562
13563    // Single cursor on one line -> advance
13564    // Cursor moves horizontally 3 characters as well on non-blank line
13565    cx.set_state(indoc!(
13566        "fn a() {
13567             ˇdog();
13568             cat();
13569        }"
13570    ));
13571    cx.update_editor(|editor, window, cx| {
13572        editor.toggle_comments(toggle_comments, window, cx);
13573    });
13574    cx.assert_editor_state(indoc!(
13575        "fn a() {
13576             // dog();
13577             catˇ();
13578        }"
13579    ));
13580
13581    // Single selection on one line -> don't advance
13582    cx.set_state(indoc!(
13583        "fn a() {
13584             «dog()ˇ»;
13585             cat();
13586        }"
13587    ));
13588    cx.update_editor(|editor, window, cx| {
13589        editor.toggle_comments(toggle_comments, window, cx);
13590    });
13591    cx.assert_editor_state(indoc!(
13592        "fn a() {
13593             // «dog()ˇ»;
13594             cat();
13595        }"
13596    ));
13597
13598    // Multiple cursors on one line -> advance
13599    cx.set_state(indoc!(
13600        "fn a() {
13601             ˇdˇog();
13602             cat();
13603        }"
13604    ));
13605    cx.update_editor(|editor, window, cx| {
13606        editor.toggle_comments(toggle_comments, window, cx);
13607    });
13608    cx.assert_editor_state(indoc!(
13609        "fn a() {
13610             // dog();
13611             catˇ(ˇ);
13612        }"
13613    ));
13614
13615    // Multiple cursors on one line, with selection -> don't advance
13616    cx.set_state(indoc!(
13617        "fn a() {
13618             ˇdˇog«()ˇ»;
13619             cat();
13620        }"
13621    ));
13622    cx.update_editor(|editor, window, cx| {
13623        editor.toggle_comments(toggle_comments, window, cx);
13624    });
13625    cx.assert_editor_state(indoc!(
13626        "fn a() {
13627             // ˇdˇog«()ˇ»;
13628             cat();
13629        }"
13630    ));
13631
13632    // Single cursor on one line -> advance
13633    // Cursor moves to column 0 on blank line
13634    cx.set_state(indoc!(
13635        "fn a() {
13636             ˇdog();
13637
13638             cat();
13639        }"
13640    ));
13641    cx.update_editor(|editor, window, cx| {
13642        editor.toggle_comments(toggle_comments, window, cx);
13643    });
13644    cx.assert_editor_state(indoc!(
13645        "fn a() {
13646             // dog();
13647        ˇ
13648             cat();
13649        }"
13650    ));
13651
13652    // Single cursor on one line -> advance
13653    // Cursor starts and ends at column 0
13654    cx.set_state(indoc!(
13655        "fn a() {
13656         ˇ    dog();
13657             cat();
13658        }"
13659    ));
13660    cx.update_editor(|editor, window, cx| {
13661        editor.toggle_comments(toggle_comments, window, cx);
13662    });
13663    cx.assert_editor_state(indoc!(
13664        "fn a() {
13665             // dog();
13666         ˇ    cat();
13667        }"
13668    ));
13669}
13670
13671#[gpui::test]
13672async fn test_toggle_block_comment(cx: &mut TestAppContext) {
13673    init_test(cx, |_| {});
13674
13675    let mut cx = EditorTestContext::new(cx).await;
13676
13677    let html_language = Arc::new(
13678        Language::new(
13679            LanguageConfig {
13680                name: "HTML".into(),
13681                block_comment: Some(("<!-- ".into(), " -->".into())),
13682                ..Default::default()
13683            },
13684            Some(tree_sitter_html::LANGUAGE.into()),
13685        )
13686        .with_injection_query(
13687            r#"
13688            (script_element
13689                (raw_text) @injection.content
13690                (#set! injection.language "javascript"))
13691            "#,
13692        )
13693        .unwrap(),
13694    );
13695
13696    let javascript_language = Arc::new(Language::new(
13697        LanguageConfig {
13698            name: "JavaScript".into(),
13699            line_comments: vec!["// ".into()],
13700            ..Default::default()
13701        },
13702        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
13703    ));
13704
13705    cx.language_registry().add(html_language.clone());
13706    cx.language_registry().add(javascript_language.clone());
13707    cx.update_buffer(|buffer, cx| {
13708        buffer.set_language(Some(html_language), cx);
13709    });
13710
13711    // Toggle comments for empty selections
13712    cx.set_state(
13713        &r#"
13714            <p>A</p>ˇ
13715            <p>B</p>ˇ
13716            <p>C</p>ˇ
13717        "#
13718        .unindent(),
13719    );
13720    cx.update_editor(|editor, window, cx| {
13721        editor.toggle_comments(&ToggleComments::default(), window, cx)
13722    });
13723    cx.assert_editor_state(
13724        &r#"
13725            <!-- <p>A</p>ˇ -->
13726            <!-- <p>B</p>ˇ -->
13727            <!-- <p>C</p>ˇ -->
13728        "#
13729        .unindent(),
13730    );
13731    cx.update_editor(|editor, window, cx| {
13732        editor.toggle_comments(&ToggleComments::default(), window, cx)
13733    });
13734    cx.assert_editor_state(
13735        &r#"
13736            <p>A</p>ˇ
13737            <p>B</p>ˇ
13738            <p>C</p>ˇ
13739        "#
13740        .unindent(),
13741    );
13742
13743    // Toggle comments for mixture of empty and non-empty selections, where
13744    // multiple selections occupy a given line.
13745    cx.set_state(
13746        &r#"
13747            <p>A«</p>
13748            <p>ˇ»B</p>ˇ
13749            <p>C«</p>
13750            <p>ˇ»D</p>ˇ
13751        "#
13752        .unindent(),
13753    );
13754
13755    cx.update_editor(|editor, window, cx| {
13756        editor.toggle_comments(&ToggleComments::default(), window, cx)
13757    });
13758    cx.assert_editor_state(
13759        &r#"
13760            <!-- <p>A«</p>
13761            <p>ˇ»B</p>ˇ -->
13762            <!-- <p>C«</p>
13763            <p>ˇ»D</p>ˇ -->
13764        "#
13765        .unindent(),
13766    );
13767    cx.update_editor(|editor, window, cx| {
13768        editor.toggle_comments(&ToggleComments::default(), window, cx)
13769    });
13770    cx.assert_editor_state(
13771        &r#"
13772            <p>A«</p>
13773            <p>ˇ»B</p>ˇ
13774            <p>C«</p>
13775            <p>ˇ»D</p>ˇ
13776        "#
13777        .unindent(),
13778    );
13779
13780    // Toggle comments when different languages are active for different
13781    // selections.
13782    cx.set_state(
13783        &r#"
13784            ˇ<script>
13785                ˇvar x = new Y();
13786            ˇ</script>
13787        "#
13788        .unindent(),
13789    );
13790    cx.executor().run_until_parked();
13791    cx.update_editor(|editor, window, cx| {
13792        editor.toggle_comments(&ToggleComments::default(), window, cx)
13793    });
13794    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
13795    // Uncommenting and commenting from this position brings in even more wrong artifacts.
13796    cx.assert_editor_state(
13797        &r#"
13798            <!-- ˇ<script> -->
13799                // ˇvar x = new Y();
13800            <!-- ˇ</script> -->
13801        "#
13802        .unindent(),
13803    );
13804}
13805
13806#[gpui::test]
13807fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
13808    init_test(cx, |_| {});
13809
13810    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
13811    let multibuffer = cx.new(|cx| {
13812        let mut multibuffer = MultiBuffer::new(ReadWrite);
13813        multibuffer.push_excerpts(
13814            buffer.clone(),
13815            [
13816                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
13817                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
13818            ],
13819            cx,
13820        );
13821        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
13822        multibuffer
13823    });
13824
13825    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
13826    editor.update_in(cx, |editor, window, cx| {
13827        assert_eq!(editor.text(cx), "aaaa\nbbbb");
13828        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13829            s.select_ranges([
13830                Point::new(0, 0)..Point::new(0, 0),
13831                Point::new(1, 0)..Point::new(1, 0),
13832            ])
13833        });
13834
13835        editor.handle_input("X", window, cx);
13836        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
13837        assert_eq!(
13838            editor.selections.ranges(cx),
13839            [
13840                Point::new(0, 1)..Point::new(0, 1),
13841                Point::new(1, 1)..Point::new(1, 1),
13842            ]
13843        );
13844
13845        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
13846        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13847            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
13848        });
13849        editor.backspace(&Default::default(), window, cx);
13850        assert_eq!(editor.text(cx), "Xa\nbbb");
13851        assert_eq!(
13852            editor.selections.ranges(cx),
13853            [Point::new(1, 0)..Point::new(1, 0)]
13854        );
13855
13856        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13857            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
13858        });
13859        editor.backspace(&Default::default(), window, cx);
13860        assert_eq!(editor.text(cx), "X\nbb");
13861        assert_eq!(
13862            editor.selections.ranges(cx),
13863            [Point::new(0, 1)..Point::new(0, 1)]
13864        );
13865    });
13866}
13867
13868#[gpui::test]
13869fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
13870    init_test(cx, |_| {});
13871
13872    let markers = vec![('[', ']').into(), ('(', ')').into()];
13873    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
13874        indoc! {"
13875            [aaaa
13876            (bbbb]
13877            cccc)",
13878        },
13879        markers.clone(),
13880    );
13881    let excerpt_ranges = markers.into_iter().map(|marker| {
13882        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
13883        ExcerptRange::new(context.clone())
13884    });
13885    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
13886    let multibuffer = cx.new(|cx| {
13887        let mut multibuffer = MultiBuffer::new(ReadWrite);
13888        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
13889        multibuffer
13890    });
13891
13892    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
13893    editor.update_in(cx, |editor, window, cx| {
13894        let (expected_text, selection_ranges) = marked_text_ranges(
13895            indoc! {"
13896                aaaa
13897                bˇbbb
13898                bˇbbˇb
13899                cccc"
13900            },
13901            true,
13902        );
13903        assert_eq!(editor.text(cx), expected_text);
13904        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13905            s.select_ranges(selection_ranges)
13906        });
13907
13908        editor.handle_input("X", window, cx);
13909
13910        let (expected_text, expected_selections) = marked_text_ranges(
13911            indoc! {"
13912                aaaa
13913                bXˇbbXb
13914                bXˇbbXˇb
13915                cccc"
13916            },
13917            false,
13918        );
13919        assert_eq!(editor.text(cx), expected_text);
13920        assert_eq!(editor.selections.ranges(cx), expected_selections);
13921
13922        editor.newline(&Newline, window, cx);
13923        let (expected_text, expected_selections) = marked_text_ranges(
13924            indoc! {"
13925                aaaa
13926                bX
13927                ˇbbX
13928                b
13929                bX
13930                ˇbbX
13931                ˇb
13932                cccc"
13933            },
13934            false,
13935        );
13936        assert_eq!(editor.text(cx), expected_text);
13937        assert_eq!(editor.selections.ranges(cx), expected_selections);
13938    });
13939}
13940
13941#[gpui::test]
13942fn test_refresh_selections(cx: &mut TestAppContext) {
13943    init_test(cx, |_| {});
13944
13945    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
13946    let mut excerpt1_id = None;
13947    let multibuffer = cx.new(|cx| {
13948        let mut multibuffer = MultiBuffer::new(ReadWrite);
13949        excerpt1_id = multibuffer
13950            .push_excerpts(
13951                buffer.clone(),
13952                [
13953                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
13954                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
13955                ],
13956                cx,
13957            )
13958            .into_iter()
13959            .next();
13960        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
13961        multibuffer
13962    });
13963
13964    let editor = cx.add_window(|window, cx| {
13965        let mut editor = build_editor(multibuffer.clone(), window, cx);
13966        let snapshot = editor.snapshot(window, cx);
13967        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13968            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
13969        });
13970        editor.begin_selection(
13971            Point::new(2, 1).to_display_point(&snapshot),
13972            true,
13973            1,
13974            window,
13975            cx,
13976        );
13977        assert_eq!(
13978            editor.selections.ranges(cx),
13979            [
13980                Point::new(1, 3)..Point::new(1, 3),
13981                Point::new(2, 1)..Point::new(2, 1),
13982            ]
13983        );
13984        editor
13985    });
13986
13987    // Refreshing selections is a no-op when excerpts haven't changed.
13988    _ = editor.update(cx, |editor, window, cx| {
13989        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
13990        assert_eq!(
13991            editor.selections.ranges(cx),
13992            [
13993                Point::new(1, 3)..Point::new(1, 3),
13994                Point::new(2, 1)..Point::new(2, 1),
13995            ]
13996        );
13997    });
13998
13999    multibuffer.update(cx, |multibuffer, cx| {
14000        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
14001    });
14002    _ = editor.update(cx, |editor, window, cx| {
14003        // Removing an excerpt causes the first selection to become degenerate.
14004        assert_eq!(
14005            editor.selections.ranges(cx),
14006            [
14007                Point::new(0, 0)..Point::new(0, 0),
14008                Point::new(0, 1)..Point::new(0, 1)
14009            ]
14010        );
14011
14012        // Refreshing selections will relocate the first selection to the original buffer
14013        // location.
14014        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14015        assert_eq!(
14016            editor.selections.ranges(cx),
14017            [
14018                Point::new(0, 1)..Point::new(0, 1),
14019                Point::new(0, 3)..Point::new(0, 3)
14020            ]
14021        );
14022        assert!(editor.selections.pending_anchor().is_some());
14023    });
14024}
14025
14026#[gpui::test]
14027fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
14028    init_test(cx, |_| {});
14029
14030    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
14031    let mut excerpt1_id = None;
14032    let multibuffer = cx.new(|cx| {
14033        let mut multibuffer = MultiBuffer::new(ReadWrite);
14034        excerpt1_id = multibuffer
14035            .push_excerpts(
14036                buffer.clone(),
14037                [
14038                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
14039                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
14040                ],
14041                cx,
14042            )
14043            .into_iter()
14044            .next();
14045        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
14046        multibuffer
14047    });
14048
14049    let editor = cx.add_window(|window, cx| {
14050        let mut editor = build_editor(multibuffer.clone(), window, cx);
14051        let snapshot = editor.snapshot(window, cx);
14052        editor.begin_selection(
14053            Point::new(1, 3).to_display_point(&snapshot),
14054            false,
14055            1,
14056            window,
14057            cx,
14058        );
14059        assert_eq!(
14060            editor.selections.ranges(cx),
14061            [Point::new(1, 3)..Point::new(1, 3)]
14062        );
14063        editor
14064    });
14065
14066    multibuffer.update(cx, |multibuffer, cx| {
14067        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
14068    });
14069    _ = editor.update(cx, |editor, window, cx| {
14070        assert_eq!(
14071            editor.selections.ranges(cx),
14072            [Point::new(0, 0)..Point::new(0, 0)]
14073        );
14074
14075        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
14076        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14077        assert_eq!(
14078            editor.selections.ranges(cx),
14079            [Point::new(0, 3)..Point::new(0, 3)]
14080        );
14081        assert!(editor.selections.pending_anchor().is_some());
14082    });
14083}
14084
14085#[gpui::test]
14086async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
14087    init_test(cx, |_| {});
14088
14089    let language = Arc::new(
14090        Language::new(
14091            LanguageConfig {
14092                brackets: BracketPairConfig {
14093                    pairs: vec![
14094                        BracketPair {
14095                            start: "{".to_string(),
14096                            end: "}".to_string(),
14097                            close: true,
14098                            surround: true,
14099                            newline: true,
14100                        },
14101                        BracketPair {
14102                            start: "/* ".to_string(),
14103                            end: " */".to_string(),
14104                            close: true,
14105                            surround: true,
14106                            newline: true,
14107                        },
14108                    ],
14109                    ..Default::default()
14110                },
14111                ..Default::default()
14112            },
14113            Some(tree_sitter_rust::LANGUAGE.into()),
14114        )
14115        .with_indents_query("")
14116        .unwrap(),
14117    );
14118
14119    let text = concat!(
14120        "{   }\n",     //
14121        "  x\n",       //
14122        "  /*   */\n", //
14123        "x\n",         //
14124        "{{} }\n",     //
14125    );
14126
14127    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
14128    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
14129    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
14130    editor
14131        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
14132        .await;
14133
14134    editor.update_in(cx, |editor, window, cx| {
14135        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14136            s.select_display_ranges([
14137                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
14138                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
14139                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
14140            ])
14141        });
14142        editor.newline(&Newline, window, cx);
14143
14144        assert_eq!(
14145            editor.buffer().read(cx).read(cx).text(),
14146            concat!(
14147                "{ \n",    // Suppress rustfmt
14148                "\n",      //
14149                "}\n",     //
14150                "  x\n",   //
14151                "  /* \n", //
14152                "  \n",    //
14153                "  */\n",  //
14154                "x\n",     //
14155                "{{} \n",  //
14156                "}\n",     //
14157            )
14158        );
14159    });
14160}
14161
14162#[gpui::test]
14163fn test_highlighted_ranges(cx: &mut TestAppContext) {
14164    init_test(cx, |_| {});
14165
14166    let editor = cx.add_window(|window, cx| {
14167        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
14168        build_editor(buffer.clone(), window, cx)
14169    });
14170
14171    _ = editor.update(cx, |editor, window, cx| {
14172        struct Type1;
14173        struct Type2;
14174
14175        let buffer = editor.buffer.read(cx).snapshot(cx);
14176
14177        let anchor_range =
14178            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
14179
14180        editor.highlight_background::<Type1>(
14181            &[
14182                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
14183                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
14184                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
14185                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
14186            ],
14187            |_| Hsla::red(),
14188            cx,
14189        );
14190        editor.highlight_background::<Type2>(
14191            &[
14192                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
14193                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
14194                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
14195                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
14196            ],
14197            |_| Hsla::green(),
14198            cx,
14199        );
14200
14201        let snapshot = editor.snapshot(window, cx);
14202        let mut highlighted_ranges = editor.background_highlights_in_range(
14203            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
14204            &snapshot,
14205            cx.theme(),
14206        );
14207        // Enforce a consistent ordering based on color without relying on the ordering of the
14208        // highlight's `TypeId` which is non-executor.
14209        highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
14210        assert_eq!(
14211            highlighted_ranges,
14212            &[
14213                (
14214                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
14215                    Hsla::red(),
14216                ),
14217                (
14218                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
14219                    Hsla::red(),
14220                ),
14221                (
14222                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
14223                    Hsla::green(),
14224                ),
14225                (
14226                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
14227                    Hsla::green(),
14228                ),
14229            ]
14230        );
14231        assert_eq!(
14232            editor.background_highlights_in_range(
14233                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
14234                &snapshot,
14235                cx.theme(),
14236            ),
14237            &[(
14238                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
14239                Hsla::red(),
14240            )]
14241        );
14242    });
14243}
14244
14245#[gpui::test]
14246async fn test_following(cx: &mut TestAppContext) {
14247    init_test(cx, |_| {});
14248
14249    let fs = FakeFs::new(cx.executor());
14250    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
14251
14252    let buffer = project.update(cx, |project, cx| {
14253        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
14254        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
14255    });
14256    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
14257    let follower = cx.update(|cx| {
14258        cx.open_window(
14259            WindowOptions {
14260                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
14261                    gpui::Point::new(px(0.), px(0.)),
14262                    gpui::Point::new(px(10.), px(80.)),
14263                ))),
14264                ..Default::default()
14265            },
14266            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
14267        )
14268        .unwrap()
14269    });
14270
14271    let is_still_following = Rc::new(RefCell::new(true));
14272    let follower_edit_event_count = Rc::new(RefCell::new(0));
14273    let pending_update = Rc::new(RefCell::new(None));
14274    let leader_entity = leader.root(cx).unwrap();
14275    let follower_entity = follower.root(cx).unwrap();
14276    _ = follower.update(cx, {
14277        let update = pending_update.clone();
14278        let is_still_following = is_still_following.clone();
14279        let follower_edit_event_count = follower_edit_event_count.clone();
14280        |_, window, cx| {
14281            cx.subscribe_in(
14282                &leader_entity,
14283                window,
14284                move |_, leader, event, window, cx| {
14285                    leader.read(cx).add_event_to_update_proto(
14286                        event,
14287                        &mut update.borrow_mut(),
14288                        window,
14289                        cx,
14290                    );
14291                },
14292            )
14293            .detach();
14294
14295            cx.subscribe_in(
14296                &follower_entity,
14297                window,
14298                move |_, _, event: &EditorEvent, _window, _cx| {
14299                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
14300                        *is_still_following.borrow_mut() = false;
14301                    }
14302
14303                    if let EditorEvent::BufferEdited = event {
14304                        *follower_edit_event_count.borrow_mut() += 1;
14305                    }
14306                },
14307            )
14308            .detach();
14309        }
14310    });
14311
14312    // Update the selections only
14313    _ = leader.update(cx, |leader, window, cx| {
14314        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14315            s.select_ranges([1..1])
14316        });
14317    });
14318    follower
14319        .update(cx, |follower, window, cx| {
14320            follower.apply_update_proto(
14321                &project,
14322                pending_update.borrow_mut().take().unwrap(),
14323                window,
14324                cx,
14325            )
14326        })
14327        .unwrap()
14328        .await
14329        .unwrap();
14330    _ = follower.update(cx, |follower, _, cx| {
14331        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
14332    });
14333    assert!(*is_still_following.borrow());
14334    assert_eq!(*follower_edit_event_count.borrow(), 0);
14335
14336    // Update the scroll position only
14337    _ = leader.update(cx, |leader, window, cx| {
14338        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
14339    });
14340    follower
14341        .update(cx, |follower, window, cx| {
14342            follower.apply_update_proto(
14343                &project,
14344                pending_update.borrow_mut().take().unwrap(),
14345                window,
14346                cx,
14347            )
14348        })
14349        .unwrap()
14350        .await
14351        .unwrap();
14352    assert_eq!(
14353        follower
14354            .update(cx, |follower, _, cx| follower.scroll_position(cx))
14355            .unwrap(),
14356        gpui::Point::new(1.5, 3.5)
14357    );
14358    assert!(*is_still_following.borrow());
14359    assert_eq!(*follower_edit_event_count.borrow(), 0);
14360
14361    // Update the selections and scroll position. The follower's scroll position is updated
14362    // via autoscroll, not via the leader's exact scroll position.
14363    _ = leader.update(cx, |leader, window, cx| {
14364        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14365            s.select_ranges([0..0])
14366        });
14367        leader.request_autoscroll(Autoscroll::newest(), cx);
14368        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
14369    });
14370    follower
14371        .update(cx, |follower, window, cx| {
14372            follower.apply_update_proto(
14373                &project,
14374                pending_update.borrow_mut().take().unwrap(),
14375                window,
14376                cx,
14377            )
14378        })
14379        .unwrap()
14380        .await
14381        .unwrap();
14382    _ = follower.update(cx, |follower, _, cx| {
14383        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
14384        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
14385    });
14386    assert!(*is_still_following.borrow());
14387
14388    // Creating a pending selection that precedes another selection
14389    _ = leader.update(cx, |leader, window, cx| {
14390        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14391            s.select_ranges([1..1])
14392        });
14393        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
14394    });
14395    follower
14396        .update(cx, |follower, window, cx| {
14397            follower.apply_update_proto(
14398                &project,
14399                pending_update.borrow_mut().take().unwrap(),
14400                window,
14401                cx,
14402            )
14403        })
14404        .unwrap()
14405        .await
14406        .unwrap();
14407    _ = follower.update(cx, |follower, _, cx| {
14408        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
14409    });
14410    assert!(*is_still_following.borrow());
14411
14412    // Extend the pending selection so that it surrounds another selection
14413    _ = leader.update(cx, |leader, window, cx| {
14414        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
14415    });
14416    follower
14417        .update(cx, |follower, window, cx| {
14418            follower.apply_update_proto(
14419                &project,
14420                pending_update.borrow_mut().take().unwrap(),
14421                window,
14422                cx,
14423            )
14424        })
14425        .unwrap()
14426        .await
14427        .unwrap();
14428    _ = follower.update(cx, |follower, _, cx| {
14429        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
14430    });
14431
14432    // Scrolling locally breaks the follow
14433    _ = follower.update(cx, |follower, window, cx| {
14434        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
14435        follower.set_scroll_anchor(
14436            ScrollAnchor {
14437                anchor: top_anchor,
14438                offset: gpui::Point::new(0.0, 0.5),
14439            },
14440            window,
14441            cx,
14442        );
14443    });
14444    assert!(!(*is_still_following.borrow()));
14445}
14446
14447#[gpui::test]
14448async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
14449    init_test(cx, |_| {});
14450
14451    let fs = FakeFs::new(cx.executor());
14452    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
14453    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14454    let pane = workspace
14455        .update(cx, |workspace, _, _| workspace.active_pane().clone())
14456        .unwrap();
14457
14458    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14459
14460    let leader = pane.update_in(cx, |_, window, cx| {
14461        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
14462        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
14463    });
14464
14465    // Start following the editor when it has no excerpts.
14466    let mut state_message =
14467        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
14468    let workspace_entity = workspace.root(cx).unwrap();
14469    let follower_1 = cx
14470        .update_window(*workspace.deref(), |_, window, cx| {
14471            Editor::from_state_proto(
14472                workspace_entity,
14473                ViewId {
14474                    creator: CollaboratorId::PeerId(PeerId::default()),
14475                    id: 0,
14476                },
14477                &mut state_message,
14478                window,
14479                cx,
14480            )
14481        })
14482        .unwrap()
14483        .unwrap()
14484        .await
14485        .unwrap();
14486
14487    let update_message = Rc::new(RefCell::new(None));
14488    follower_1.update_in(cx, {
14489        let update = update_message.clone();
14490        |_, window, cx| {
14491            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
14492                leader.read(cx).add_event_to_update_proto(
14493                    event,
14494                    &mut update.borrow_mut(),
14495                    window,
14496                    cx,
14497                );
14498            })
14499            .detach();
14500        }
14501    });
14502
14503    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
14504        (
14505            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
14506            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
14507        )
14508    });
14509
14510    // Insert some excerpts.
14511    leader.update(cx, |leader, cx| {
14512        leader.buffer.update(cx, |multibuffer, cx| {
14513            multibuffer.set_excerpts_for_path(
14514                PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
14515                buffer_1.clone(),
14516                vec![
14517                    Point::row_range(0..3),
14518                    Point::row_range(1..6),
14519                    Point::row_range(12..15),
14520                ],
14521                0,
14522                cx,
14523            );
14524            multibuffer.set_excerpts_for_path(
14525                PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
14526                buffer_2.clone(),
14527                vec![Point::row_range(0..6), Point::row_range(8..12)],
14528                0,
14529                cx,
14530            );
14531        });
14532    });
14533
14534    // Apply the update of adding the excerpts.
14535    follower_1
14536        .update_in(cx, |follower, window, cx| {
14537            follower.apply_update_proto(
14538                &project,
14539                update_message.borrow().clone().unwrap(),
14540                window,
14541                cx,
14542            )
14543        })
14544        .await
14545        .unwrap();
14546    assert_eq!(
14547        follower_1.update(cx, |editor, cx| editor.text(cx)),
14548        leader.update(cx, |editor, cx| editor.text(cx))
14549    );
14550    update_message.borrow_mut().take();
14551
14552    // Start following separately after it already has excerpts.
14553    let mut state_message =
14554        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
14555    let workspace_entity = workspace.root(cx).unwrap();
14556    let follower_2 = cx
14557        .update_window(*workspace.deref(), |_, window, cx| {
14558            Editor::from_state_proto(
14559                workspace_entity,
14560                ViewId {
14561                    creator: CollaboratorId::PeerId(PeerId::default()),
14562                    id: 0,
14563                },
14564                &mut state_message,
14565                window,
14566                cx,
14567            )
14568        })
14569        .unwrap()
14570        .unwrap()
14571        .await
14572        .unwrap();
14573    assert_eq!(
14574        follower_2.update(cx, |editor, cx| editor.text(cx)),
14575        leader.update(cx, |editor, cx| editor.text(cx))
14576    );
14577
14578    // Remove some excerpts.
14579    leader.update(cx, |leader, cx| {
14580        leader.buffer.update(cx, |multibuffer, cx| {
14581            let excerpt_ids = multibuffer.excerpt_ids();
14582            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
14583            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
14584        });
14585    });
14586
14587    // Apply the update of removing the excerpts.
14588    follower_1
14589        .update_in(cx, |follower, window, cx| {
14590            follower.apply_update_proto(
14591                &project,
14592                update_message.borrow().clone().unwrap(),
14593                window,
14594                cx,
14595            )
14596        })
14597        .await
14598        .unwrap();
14599    follower_2
14600        .update_in(cx, |follower, window, cx| {
14601            follower.apply_update_proto(
14602                &project,
14603                update_message.borrow().clone().unwrap(),
14604                window,
14605                cx,
14606            )
14607        })
14608        .await
14609        .unwrap();
14610    update_message.borrow_mut().take();
14611    assert_eq!(
14612        follower_1.update(cx, |editor, cx| editor.text(cx)),
14613        leader.update(cx, |editor, cx| editor.text(cx))
14614    );
14615}
14616
14617#[gpui::test]
14618async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14619    init_test(cx, |_| {});
14620
14621    let mut cx = EditorTestContext::new(cx).await;
14622    let lsp_store =
14623        cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
14624
14625    cx.set_state(indoc! {"
14626        ˇfn func(abc def: i32) -> u32 {
14627        }
14628    "});
14629
14630    cx.update(|_, cx| {
14631        lsp_store.update(cx, |lsp_store, cx| {
14632            lsp_store
14633                .update_diagnostics(
14634                    LanguageServerId(0),
14635                    lsp::PublishDiagnosticsParams {
14636                        uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
14637                        version: None,
14638                        diagnostics: vec![
14639                            lsp::Diagnostic {
14640                                range: lsp::Range::new(
14641                                    lsp::Position::new(0, 11),
14642                                    lsp::Position::new(0, 12),
14643                                ),
14644                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14645                                ..Default::default()
14646                            },
14647                            lsp::Diagnostic {
14648                                range: lsp::Range::new(
14649                                    lsp::Position::new(0, 12),
14650                                    lsp::Position::new(0, 15),
14651                                ),
14652                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14653                                ..Default::default()
14654                            },
14655                            lsp::Diagnostic {
14656                                range: lsp::Range::new(
14657                                    lsp::Position::new(0, 25),
14658                                    lsp::Position::new(0, 28),
14659                                ),
14660                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14661                                ..Default::default()
14662                            },
14663                        ],
14664                    },
14665                    None,
14666                    DiagnosticSourceKind::Pushed,
14667                    &[],
14668                    cx,
14669                )
14670                .unwrap()
14671        });
14672    });
14673
14674    executor.run_until_parked();
14675
14676    cx.update_editor(|editor, window, cx| {
14677        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
14678    });
14679
14680    cx.assert_editor_state(indoc! {"
14681        fn func(abc def: i32) -> ˇu32 {
14682        }
14683    "});
14684
14685    cx.update_editor(|editor, window, cx| {
14686        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
14687    });
14688
14689    cx.assert_editor_state(indoc! {"
14690        fn func(abc ˇdef: i32) -> u32 {
14691        }
14692    "});
14693
14694    cx.update_editor(|editor, window, cx| {
14695        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
14696    });
14697
14698    cx.assert_editor_state(indoc! {"
14699        fn func(abcˇ def: i32) -> u32 {
14700        }
14701    "});
14702
14703    cx.update_editor(|editor, window, cx| {
14704        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
14705    });
14706
14707    cx.assert_editor_state(indoc! {"
14708        fn func(abc def: i32) -> ˇu32 {
14709        }
14710    "});
14711}
14712
14713#[gpui::test]
14714async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14715    init_test(cx, |_| {});
14716
14717    let mut cx = EditorTestContext::new(cx).await;
14718
14719    let diff_base = r#"
14720        use some::mod;
14721
14722        const A: u32 = 42;
14723
14724        fn main() {
14725            println!("hello");
14726
14727            println!("world");
14728        }
14729        "#
14730    .unindent();
14731
14732    // Edits are modified, removed, modified, added
14733    cx.set_state(
14734        &r#"
14735        use some::modified;
14736
14737        ˇ
14738        fn main() {
14739            println!("hello there");
14740
14741            println!("around the");
14742            println!("world");
14743        }
14744        "#
14745        .unindent(),
14746    );
14747
14748    cx.set_head_text(&diff_base);
14749    executor.run_until_parked();
14750
14751    cx.update_editor(|editor, window, cx| {
14752        //Wrap around the bottom of the buffer
14753        for _ in 0..3 {
14754            editor.go_to_next_hunk(&GoToHunk, window, cx);
14755        }
14756    });
14757
14758    cx.assert_editor_state(
14759        &r#"
14760        ˇuse some::modified;
14761
14762
14763        fn main() {
14764            println!("hello there");
14765
14766            println!("around the");
14767            println!("world");
14768        }
14769        "#
14770        .unindent(),
14771    );
14772
14773    cx.update_editor(|editor, window, cx| {
14774        //Wrap around the top of the buffer
14775        for _ in 0..2 {
14776            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14777        }
14778    });
14779
14780    cx.assert_editor_state(
14781        &r#"
14782        use some::modified;
14783
14784
14785        fn main() {
14786        ˇ    println!("hello there");
14787
14788            println!("around the");
14789            println!("world");
14790        }
14791        "#
14792        .unindent(),
14793    );
14794
14795    cx.update_editor(|editor, window, cx| {
14796        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14797    });
14798
14799    cx.assert_editor_state(
14800        &r#"
14801        use some::modified;
14802
14803        ˇ
14804        fn main() {
14805            println!("hello there");
14806
14807            println!("around the");
14808            println!("world");
14809        }
14810        "#
14811        .unindent(),
14812    );
14813
14814    cx.update_editor(|editor, window, cx| {
14815        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14816    });
14817
14818    cx.assert_editor_state(
14819        &r#"
14820        ˇuse some::modified;
14821
14822
14823        fn main() {
14824            println!("hello there");
14825
14826            println!("around the");
14827            println!("world");
14828        }
14829        "#
14830        .unindent(),
14831    );
14832
14833    cx.update_editor(|editor, window, cx| {
14834        for _ in 0..2 {
14835            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14836        }
14837    });
14838
14839    cx.assert_editor_state(
14840        &r#"
14841        use some::modified;
14842
14843
14844        fn main() {
14845        ˇ    println!("hello there");
14846
14847            println!("around the");
14848            println!("world");
14849        }
14850        "#
14851        .unindent(),
14852    );
14853
14854    cx.update_editor(|editor, window, cx| {
14855        editor.fold(&Fold, window, cx);
14856    });
14857
14858    cx.update_editor(|editor, window, cx| {
14859        editor.go_to_next_hunk(&GoToHunk, window, cx);
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
14878#[test]
14879fn test_split_words() {
14880    fn split(text: &str) -> Vec<&str> {
14881        split_words(text).collect()
14882    }
14883
14884    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
14885    assert_eq!(split("hello_world"), &["hello_", "world"]);
14886    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
14887    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
14888    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
14889    assert_eq!(split("helloworld"), &["helloworld"]);
14890
14891    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
14892}
14893
14894#[gpui::test]
14895async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
14896    init_test(cx, |_| {});
14897
14898    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
14899    let mut assert = |before, after| {
14900        let _state_context = cx.set_state(before);
14901        cx.run_until_parked();
14902        cx.update_editor(|editor, window, cx| {
14903            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
14904        });
14905        cx.run_until_parked();
14906        cx.assert_editor_state(after);
14907    };
14908
14909    // Outside bracket jumps to outside of matching bracket
14910    assert("console.logˇ(var);", "console.log(var)ˇ;");
14911    assert("console.log(var)ˇ;", "console.logˇ(var);");
14912
14913    // Inside bracket jumps to inside of matching bracket
14914    assert("console.log(ˇvar);", "console.log(varˇ);");
14915    assert("console.log(varˇ);", "console.log(ˇvar);");
14916
14917    // When outside a bracket and inside, favor jumping to the inside bracket
14918    assert(
14919        "console.log('foo', [1, 2, 3]ˇ);",
14920        "console.log(ˇ'foo', [1, 2, 3]);",
14921    );
14922    assert(
14923        "console.log(ˇ'foo', [1, 2, 3]);",
14924        "console.log('foo', [1, 2, 3]ˇ);",
14925    );
14926
14927    // Bias forward if two options are equally likely
14928    assert(
14929        "let result = curried_fun()ˇ();",
14930        "let result = curried_fun()()ˇ;",
14931    );
14932
14933    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
14934    assert(
14935        indoc! {"
14936            function test() {
14937                console.log('test')ˇ
14938            }"},
14939        indoc! {"
14940            function test() {
14941                console.logˇ('test')
14942            }"},
14943    );
14944}
14945
14946#[gpui::test]
14947async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
14948    init_test(cx, |_| {});
14949
14950    let fs = FakeFs::new(cx.executor());
14951    fs.insert_tree(
14952        path!("/a"),
14953        json!({
14954            "main.rs": "fn main() { let a = 5; }",
14955            "other.rs": "// Test file",
14956        }),
14957    )
14958    .await;
14959    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14960
14961    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14962    language_registry.add(Arc::new(Language::new(
14963        LanguageConfig {
14964            name: "Rust".into(),
14965            matcher: LanguageMatcher {
14966                path_suffixes: vec!["rs".to_string()],
14967                ..Default::default()
14968            },
14969            brackets: BracketPairConfig {
14970                pairs: vec![BracketPair {
14971                    start: "{".to_string(),
14972                    end: "}".to_string(),
14973                    close: true,
14974                    surround: true,
14975                    newline: true,
14976                }],
14977                disabled_scopes_by_bracket_ix: Vec::new(),
14978            },
14979            ..Default::default()
14980        },
14981        Some(tree_sitter_rust::LANGUAGE.into()),
14982    )));
14983    let mut fake_servers = language_registry.register_fake_lsp(
14984        "Rust",
14985        FakeLspAdapter {
14986            capabilities: lsp::ServerCapabilities {
14987                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
14988                    first_trigger_character: "{".to_string(),
14989                    more_trigger_character: None,
14990                }),
14991                ..Default::default()
14992            },
14993            ..Default::default()
14994        },
14995    );
14996
14997    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14998
14999    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15000
15001    let worktree_id = workspace
15002        .update(cx, |workspace, _, cx| {
15003            workspace.project().update(cx, |project, cx| {
15004                project.worktrees(cx).next().unwrap().read(cx).id()
15005            })
15006        })
15007        .unwrap();
15008
15009    let buffer = project
15010        .update(cx, |project, cx| {
15011            project.open_local_buffer(path!("/a/main.rs"), cx)
15012        })
15013        .await
15014        .unwrap();
15015    let editor_handle = workspace
15016        .update(cx, |workspace, window, cx| {
15017            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
15018        })
15019        .unwrap()
15020        .await
15021        .unwrap()
15022        .downcast::<Editor>()
15023        .unwrap();
15024
15025    cx.executor().start_waiting();
15026    let fake_server = fake_servers.next().await.unwrap();
15027
15028    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
15029        |params, _| async move {
15030            assert_eq!(
15031                params.text_document_position.text_document.uri,
15032                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
15033            );
15034            assert_eq!(
15035                params.text_document_position.position,
15036                lsp::Position::new(0, 21),
15037            );
15038
15039            Ok(Some(vec![lsp::TextEdit {
15040                new_text: "]".to_string(),
15041                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15042            }]))
15043        },
15044    );
15045
15046    editor_handle.update_in(cx, |editor, window, cx| {
15047        window.focus(&editor.focus_handle(cx));
15048        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15049            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
15050        });
15051        editor.handle_input("{", window, cx);
15052    });
15053
15054    cx.executor().run_until_parked();
15055
15056    buffer.update(cx, |buffer, _| {
15057        assert_eq!(
15058            buffer.text(),
15059            "fn main() { let a = {5}; }",
15060            "No extra braces from on type formatting should appear in the buffer"
15061        )
15062    });
15063}
15064
15065#[gpui::test(iterations = 20, seeds(31))]
15066async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
15067    init_test(cx, |_| {});
15068
15069    let mut cx = EditorLspTestContext::new_rust(
15070        lsp::ServerCapabilities {
15071            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
15072                first_trigger_character: ".".to_string(),
15073                more_trigger_character: None,
15074            }),
15075            ..Default::default()
15076        },
15077        cx,
15078    )
15079    .await;
15080
15081    cx.update_buffer(|buffer, _| {
15082        // This causes autoindent to be async.
15083        buffer.set_sync_parse_timeout(Duration::ZERO)
15084    });
15085
15086    cx.set_state("fn c() {\n    d()ˇ\n}\n");
15087    cx.simulate_keystroke("\n");
15088    cx.run_until_parked();
15089
15090    let buffer_cloned =
15091        cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap().clone());
15092    let mut request =
15093        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
15094            let buffer_cloned = buffer_cloned.clone();
15095            async move {
15096                buffer_cloned.update(&mut cx, |buffer, _| {
15097                    assert_eq!(
15098                        buffer.text(),
15099                        "fn c() {\n    d()\n        .\n}\n",
15100                        "OnTypeFormatting should triggered after autoindent applied"
15101                    )
15102                })?;
15103
15104                Ok(Some(vec![]))
15105            }
15106        });
15107
15108    cx.simulate_keystroke(".");
15109    cx.run_until_parked();
15110
15111    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
15112    assert!(request.next().await.is_some());
15113    request.close();
15114    assert!(request.next().await.is_none());
15115}
15116
15117#[gpui::test]
15118async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
15119    init_test(cx, |_| {});
15120
15121    let fs = FakeFs::new(cx.executor());
15122    fs.insert_tree(
15123        path!("/a"),
15124        json!({
15125            "main.rs": "fn main() { let a = 5; }",
15126            "other.rs": "// Test file",
15127        }),
15128    )
15129    .await;
15130
15131    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15132
15133    let server_restarts = Arc::new(AtomicUsize::new(0));
15134    let closure_restarts = Arc::clone(&server_restarts);
15135    let language_server_name = "test language server";
15136    let language_name: LanguageName = "Rust".into();
15137
15138    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15139    language_registry.add(Arc::new(Language::new(
15140        LanguageConfig {
15141            name: language_name.clone(),
15142            matcher: LanguageMatcher {
15143                path_suffixes: vec!["rs".to_string()],
15144                ..Default::default()
15145            },
15146            ..Default::default()
15147        },
15148        Some(tree_sitter_rust::LANGUAGE.into()),
15149    )));
15150    let mut fake_servers = language_registry.register_fake_lsp(
15151        "Rust",
15152        FakeLspAdapter {
15153            name: language_server_name,
15154            initialization_options: Some(json!({
15155                "testOptionValue": true
15156            })),
15157            initializer: Some(Box::new(move |fake_server| {
15158                let task_restarts = Arc::clone(&closure_restarts);
15159                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
15160                    task_restarts.fetch_add(1, atomic::Ordering::Release);
15161                    futures::future::ready(Ok(()))
15162                });
15163            })),
15164            ..Default::default()
15165        },
15166    );
15167
15168    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15169    let _buffer = project
15170        .update(cx, |project, cx| {
15171            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
15172        })
15173        .await
15174        .unwrap();
15175    let _fake_server = fake_servers.next().await.unwrap();
15176    update_test_language_settings(cx, |language_settings| {
15177        language_settings.languages.0.insert(
15178            language_name.clone(),
15179            LanguageSettingsContent {
15180                tab_size: NonZeroU32::new(8),
15181                ..Default::default()
15182            },
15183        );
15184    });
15185    cx.executor().run_until_parked();
15186    assert_eq!(
15187        server_restarts.load(atomic::Ordering::Acquire),
15188        0,
15189        "Should not restart LSP server on an unrelated change"
15190    );
15191
15192    update_test_project_settings(cx, |project_settings| {
15193        project_settings.lsp.insert(
15194            "Some other server name".into(),
15195            LspSettings {
15196                binary: None,
15197                settings: None,
15198                initialization_options: Some(json!({
15199                    "some other init value": false
15200                })),
15201                enable_lsp_tasks: false,
15202            },
15203        );
15204    });
15205    cx.executor().run_until_parked();
15206    assert_eq!(
15207        server_restarts.load(atomic::Ordering::Acquire),
15208        0,
15209        "Should not restart LSP server on an unrelated LSP settings change"
15210    );
15211
15212    update_test_project_settings(cx, |project_settings| {
15213        project_settings.lsp.insert(
15214            language_server_name.into(),
15215            LspSettings {
15216                binary: None,
15217                settings: None,
15218                initialization_options: Some(json!({
15219                    "anotherInitValue": false
15220                })),
15221                enable_lsp_tasks: false,
15222            },
15223        );
15224    });
15225    cx.executor().run_until_parked();
15226    assert_eq!(
15227        server_restarts.load(atomic::Ordering::Acquire),
15228        1,
15229        "Should restart LSP server on a related LSP settings change"
15230    );
15231
15232    update_test_project_settings(cx, |project_settings| {
15233        project_settings.lsp.insert(
15234            language_server_name.into(),
15235            LspSettings {
15236                binary: None,
15237                settings: None,
15238                initialization_options: Some(json!({
15239                    "anotherInitValue": false
15240                })),
15241                enable_lsp_tasks: false,
15242            },
15243        );
15244    });
15245    cx.executor().run_until_parked();
15246    assert_eq!(
15247        server_restarts.load(atomic::Ordering::Acquire),
15248        1,
15249        "Should not restart LSP server on a related LSP settings change that is the same"
15250    );
15251
15252    update_test_project_settings(cx, |project_settings| {
15253        project_settings.lsp.insert(
15254            language_server_name.into(),
15255            LspSettings {
15256                binary: None,
15257                settings: None,
15258                initialization_options: None,
15259                enable_lsp_tasks: false,
15260            },
15261        );
15262    });
15263    cx.executor().run_until_parked();
15264    assert_eq!(
15265        server_restarts.load(atomic::Ordering::Acquire),
15266        2,
15267        "Should restart LSP server on another related LSP settings change"
15268    );
15269}
15270
15271#[gpui::test]
15272async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
15273    init_test(cx, |_| {});
15274
15275    let mut cx = EditorLspTestContext::new_rust(
15276        lsp::ServerCapabilities {
15277            completion_provider: Some(lsp::CompletionOptions {
15278                trigger_characters: Some(vec![".".to_string()]),
15279                resolve_provider: Some(true),
15280                ..Default::default()
15281            }),
15282            ..Default::default()
15283        },
15284        cx,
15285    )
15286    .await;
15287
15288    cx.set_state("fn main() { let a = 2ˇ; }");
15289    cx.simulate_keystroke(".");
15290    let completion_item = lsp::CompletionItem {
15291        label: "some".into(),
15292        kind: Some(lsp::CompletionItemKind::SNIPPET),
15293        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15294        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15295            kind: lsp::MarkupKind::Markdown,
15296            value: "```rust\nSome(2)\n```".to_string(),
15297        })),
15298        deprecated: Some(false),
15299        sort_text: Some("fffffff2".to_string()),
15300        filter_text: Some("some".to_string()),
15301        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15302        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15303            range: lsp::Range {
15304                start: lsp::Position {
15305                    line: 0,
15306                    character: 22,
15307                },
15308                end: lsp::Position {
15309                    line: 0,
15310                    character: 22,
15311                },
15312            },
15313            new_text: "Some(2)".to_string(),
15314        })),
15315        additional_text_edits: Some(vec![lsp::TextEdit {
15316            range: lsp::Range {
15317                start: lsp::Position {
15318                    line: 0,
15319                    character: 20,
15320                },
15321                end: lsp::Position {
15322                    line: 0,
15323                    character: 22,
15324                },
15325            },
15326            new_text: "".to_string(),
15327        }]),
15328        ..Default::default()
15329    };
15330
15331    let closure_completion_item = completion_item.clone();
15332    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15333        let task_completion_item = closure_completion_item.clone();
15334        async move {
15335            Ok(Some(lsp::CompletionResponse::Array(vec![
15336                task_completion_item,
15337            ])))
15338        }
15339    });
15340
15341    request.next().await;
15342
15343    cx.condition(|editor, _| editor.context_menu_visible())
15344        .await;
15345    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15346        editor
15347            .confirm_completion(&ConfirmCompletion::default(), window, cx)
15348            .unwrap()
15349    });
15350    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
15351
15352    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
15353        let task_completion_item = completion_item.clone();
15354        async move { Ok(task_completion_item) }
15355    })
15356    .next()
15357    .await
15358    .unwrap();
15359    apply_additional_edits.await.unwrap();
15360    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
15361}
15362
15363#[gpui::test]
15364async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
15365    init_test(cx, |_| {});
15366
15367    let mut cx = EditorLspTestContext::new_rust(
15368        lsp::ServerCapabilities {
15369            completion_provider: Some(lsp::CompletionOptions {
15370                trigger_characters: Some(vec![".".to_string()]),
15371                resolve_provider: Some(true),
15372                ..Default::default()
15373            }),
15374            ..Default::default()
15375        },
15376        cx,
15377    )
15378    .await;
15379
15380    cx.set_state("fn main() { let a = 2ˇ; }");
15381    cx.simulate_keystroke(".");
15382
15383    let item1 = lsp::CompletionItem {
15384        label: "method id()".to_string(),
15385        filter_text: Some("id".to_string()),
15386        detail: None,
15387        documentation: None,
15388        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15389            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15390            new_text: ".id".to_string(),
15391        })),
15392        ..lsp::CompletionItem::default()
15393    };
15394
15395    let item2 = lsp::CompletionItem {
15396        label: "other".to_string(),
15397        filter_text: Some("other".to_string()),
15398        detail: None,
15399        documentation: None,
15400        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15401            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15402            new_text: ".other".to_string(),
15403        })),
15404        ..lsp::CompletionItem::default()
15405    };
15406
15407    let item1 = item1.clone();
15408    cx.set_request_handler::<lsp::request::Completion, _, _>({
15409        let item1 = item1.clone();
15410        move |_, _, _| {
15411            let item1 = item1.clone();
15412            let item2 = item2.clone();
15413            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
15414        }
15415    })
15416    .next()
15417    .await;
15418
15419    cx.condition(|editor, _| editor.context_menu_visible())
15420        .await;
15421    cx.update_editor(|editor, _, _| {
15422        let context_menu = editor.context_menu.borrow_mut();
15423        let context_menu = context_menu
15424            .as_ref()
15425            .expect("Should have the context menu deployed");
15426        match context_menu {
15427            CodeContextMenu::Completions(completions_menu) => {
15428                let completions = completions_menu.completions.borrow_mut();
15429                assert_eq!(
15430                    completions
15431                        .iter()
15432                        .map(|completion| &completion.label.text)
15433                        .collect::<Vec<_>>(),
15434                    vec!["method id()", "other"]
15435                )
15436            }
15437            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15438        }
15439    });
15440
15441    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
15442        let item1 = item1.clone();
15443        move |_, item_to_resolve, _| {
15444            let item1 = item1.clone();
15445            async move {
15446                if item1 == item_to_resolve {
15447                    Ok(lsp::CompletionItem {
15448                        label: "method id()".to_string(),
15449                        filter_text: Some("id".to_string()),
15450                        detail: Some("Now resolved!".to_string()),
15451                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
15452                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15453                            range: lsp::Range::new(
15454                                lsp::Position::new(0, 22),
15455                                lsp::Position::new(0, 22),
15456                            ),
15457                            new_text: ".id".to_string(),
15458                        })),
15459                        ..lsp::CompletionItem::default()
15460                    })
15461                } else {
15462                    Ok(item_to_resolve)
15463                }
15464            }
15465        }
15466    })
15467    .next()
15468    .await
15469    .unwrap();
15470    cx.run_until_parked();
15471
15472    cx.update_editor(|editor, window, cx| {
15473        editor.context_menu_next(&Default::default(), window, cx);
15474    });
15475
15476    cx.update_editor(|editor, _, _| {
15477        let context_menu = editor.context_menu.borrow_mut();
15478        let context_menu = context_menu
15479            .as_ref()
15480            .expect("Should have the context menu deployed");
15481        match context_menu {
15482            CodeContextMenu::Completions(completions_menu) => {
15483                let completions = completions_menu.completions.borrow_mut();
15484                assert_eq!(
15485                    completions
15486                        .iter()
15487                        .map(|completion| &completion.label.text)
15488                        .collect::<Vec<_>>(),
15489                    vec!["method id() Now resolved!", "other"],
15490                    "Should update first completion label, but not second as the filter text did not match."
15491                );
15492            }
15493            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15494        }
15495    });
15496}
15497
15498#[gpui::test]
15499async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
15500    init_test(cx, |_| {});
15501    let mut cx = EditorLspTestContext::new_rust(
15502        lsp::ServerCapabilities {
15503            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
15504            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
15505            completion_provider: Some(lsp::CompletionOptions {
15506                resolve_provider: Some(true),
15507                ..Default::default()
15508            }),
15509            ..Default::default()
15510        },
15511        cx,
15512    )
15513    .await;
15514    cx.set_state(indoc! {"
15515        struct TestStruct {
15516            field: i32
15517        }
15518
15519        fn mainˇ() {
15520            let unused_var = 42;
15521            let test_struct = TestStruct { field: 42 };
15522        }
15523    "});
15524    let symbol_range = cx.lsp_range(indoc! {"
15525        struct TestStruct {
15526            field: i32
15527        }
15528
15529        «fn main»() {
15530            let unused_var = 42;
15531            let test_struct = TestStruct { field: 42 };
15532        }
15533    "});
15534    let mut hover_requests =
15535        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
15536            Ok(Some(lsp::Hover {
15537                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
15538                    kind: lsp::MarkupKind::Markdown,
15539                    value: "Function documentation".to_string(),
15540                }),
15541                range: Some(symbol_range),
15542            }))
15543        });
15544
15545    // Case 1: Test that code action menu hide hover popover
15546    cx.dispatch_action(Hover);
15547    hover_requests.next().await;
15548    cx.condition(|editor, _| editor.hover_state.visible()).await;
15549    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
15550        move |_, _, _| async move {
15551            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
15552                lsp::CodeAction {
15553                    title: "Remove unused variable".to_string(),
15554                    kind: Some(CodeActionKind::QUICKFIX),
15555                    edit: Some(lsp::WorkspaceEdit {
15556                        changes: Some(
15557                            [(
15558                                lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
15559                                vec![lsp::TextEdit {
15560                                    range: lsp::Range::new(
15561                                        lsp::Position::new(5, 4),
15562                                        lsp::Position::new(5, 27),
15563                                    ),
15564                                    new_text: "".to_string(),
15565                                }],
15566                            )]
15567                            .into_iter()
15568                            .collect(),
15569                        ),
15570                        ..Default::default()
15571                    }),
15572                    ..Default::default()
15573                },
15574            )]))
15575        },
15576    );
15577    cx.update_editor(|editor, window, cx| {
15578        editor.toggle_code_actions(
15579            &ToggleCodeActions {
15580                deployed_from: None,
15581                quick_launch: false,
15582            },
15583            window,
15584            cx,
15585        );
15586    });
15587    code_action_requests.next().await;
15588    cx.run_until_parked();
15589    cx.condition(|editor, _| editor.context_menu_visible())
15590        .await;
15591    cx.update_editor(|editor, _, _| {
15592        assert!(
15593            !editor.hover_state.visible(),
15594            "Hover popover should be hidden when code action menu is shown"
15595        );
15596        // Hide code actions
15597        editor.context_menu.take();
15598    });
15599
15600    // Case 2: Test that code completions hide hover popover
15601    cx.dispatch_action(Hover);
15602    hover_requests.next().await;
15603    cx.condition(|editor, _| editor.hover_state.visible()).await;
15604    let counter = Arc::new(AtomicUsize::new(0));
15605    let mut completion_requests =
15606        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15607            let counter = counter.clone();
15608            async move {
15609                counter.fetch_add(1, atomic::Ordering::Release);
15610                Ok(Some(lsp::CompletionResponse::Array(vec![
15611                    lsp::CompletionItem {
15612                        label: "main".into(),
15613                        kind: Some(lsp::CompletionItemKind::FUNCTION),
15614                        detail: Some("() -> ()".to_string()),
15615                        ..Default::default()
15616                    },
15617                    lsp::CompletionItem {
15618                        label: "TestStruct".into(),
15619                        kind: Some(lsp::CompletionItemKind::STRUCT),
15620                        detail: Some("struct TestStruct".to_string()),
15621                        ..Default::default()
15622                    },
15623                ])))
15624            }
15625        });
15626    cx.update_editor(|editor, window, cx| {
15627        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15628    });
15629    completion_requests.next().await;
15630    cx.condition(|editor, _| editor.context_menu_visible())
15631        .await;
15632    cx.update_editor(|editor, _, _| {
15633        assert!(
15634            !editor.hover_state.visible(),
15635            "Hover popover should be hidden when completion menu is shown"
15636        );
15637    });
15638}
15639
15640#[gpui::test]
15641async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
15642    init_test(cx, |_| {});
15643
15644    let mut cx = EditorLspTestContext::new_rust(
15645        lsp::ServerCapabilities {
15646            completion_provider: Some(lsp::CompletionOptions {
15647                trigger_characters: Some(vec![".".to_string()]),
15648                resolve_provider: Some(true),
15649                ..Default::default()
15650            }),
15651            ..Default::default()
15652        },
15653        cx,
15654    )
15655    .await;
15656
15657    cx.set_state("fn main() { let a = 2ˇ; }");
15658    cx.simulate_keystroke(".");
15659
15660    let unresolved_item_1 = lsp::CompletionItem {
15661        label: "id".to_string(),
15662        filter_text: Some("id".to_string()),
15663        detail: None,
15664        documentation: None,
15665        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15666            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15667            new_text: ".id".to_string(),
15668        })),
15669        ..lsp::CompletionItem::default()
15670    };
15671    let resolved_item_1 = lsp::CompletionItem {
15672        additional_text_edits: Some(vec![lsp::TextEdit {
15673            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
15674            new_text: "!!".to_string(),
15675        }]),
15676        ..unresolved_item_1.clone()
15677    };
15678    let unresolved_item_2 = lsp::CompletionItem {
15679        label: "other".to_string(),
15680        filter_text: Some("other".to_string()),
15681        detail: None,
15682        documentation: None,
15683        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15684            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15685            new_text: ".other".to_string(),
15686        })),
15687        ..lsp::CompletionItem::default()
15688    };
15689    let resolved_item_2 = lsp::CompletionItem {
15690        additional_text_edits: Some(vec![lsp::TextEdit {
15691            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
15692            new_text: "??".to_string(),
15693        }]),
15694        ..unresolved_item_2.clone()
15695    };
15696
15697    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
15698    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
15699    cx.lsp
15700        .server
15701        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
15702            let unresolved_item_1 = unresolved_item_1.clone();
15703            let resolved_item_1 = resolved_item_1.clone();
15704            let unresolved_item_2 = unresolved_item_2.clone();
15705            let resolved_item_2 = resolved_item_2.clone();
15706            let resolve_requests_1 = resolve_requests_1.clone();
15707            let resolve_requests_2 = resolve_requests_2.clone();
15708            move |unresolved_request, _| {
15709                let unresolved_item_1 = unresolved_item_1.clone();
15710                let resolved_item_1 = resolved_item_1.clone();
15711                let unresolved_item_2 = unresolved_item_2.clone();
15712                let resolved_item_2 = resolved_item_2.clone();
15713                let resolve_requests_1 = resolve_requests_1.clone();
15714                let resolve_requests_2 = resolve_requests_2.clone();
15715                async move {
15716                    if unresolved_request == unresolved_item_1 {
15717                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
15718                        Ok(resolved_item_1.clone())
15719                    } else if unresolved_request == unresolved_item_2 {
15720                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
15721                        Ok(resolved_item_2.clone())
15722                    } else {
15723                        panic!("Unexpected completion item {unresolved_request:?}")
15724                    }
15725                }
15726            }
15727        })
15728        .detach();
15729
15730    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15731        let unresolved_item_1 = unresolved_item_1.clone();
15732        let unresolved_item_2 = unresolved_item_2.clone();
15733        async move {
15734            Ok(Some(lsp::CompletionResponse::Array(vec![
15735                unresolved_item_1,
15736                unresolved_item_2,
15737            ])))
15738        }
15739    })
15740    .next()
15741    .await;
15742
15743    cx.condition(|editor, _| editor.context_menu_visible())
15744        .await;
15745    cx.update_editor(|editor, _, _| {
15746        let context_menu = editor.context_menu.borrow_mut();
15747        let context_menu = context_menu
15748            .as_ref()
15749            .expect("Should have the context menu deployed");
15750        match context_menu {
15751            CodeContextMenu::Completions(completions_menu) => {
15752                let completions = completions_menu.completions.borrow_mut();
15753                assert_eq!(
15754                    completions
15755                        .iter()
15756                        .map(|completion| &completion.label.text)
15757                        .collect::<Vec<_>>(),
15758                    vec!["id", "other"]
15759                )
15760            }
15761            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15762        }
15763    });
15764    cx.run_until_parked();
15765
15766    cx.update_editor(|editor, window, cx| {
15767        editor.context_menu_next(&ContextMenuNext, window, cx);
15768    });
15769    cx.run_until_parked();
15770    cx.update_editor(|editor, window, cx| {
15771        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
15772    });
15773    cx.run_until_parked();
15774    cx.update_editor(|editor, window, cx| {
15775        editor.context_menu_next(&ContextMenuNext, window, cx);
15776    });
15777    cx.run_until_parked();
15778    cx.update_editor(|editor, window, cx| {
15779        editor
15780            .compose_completion(&ComposeCompletion::default(), window, cx)
15781            .expect("No task returned")
15782    })
15783    .await
15784    .expect("Completion failed");
15785    cx.run_until_parked();
15786
15787    cx.update_editor(|editor, _, cx| {
15788        assert_eq!(
15789            resolve_requests_1.load(atomic::Ordering::Acquire),
15790            1,
15791            "Should always resolve once despite multiple selections"
15792        );
15793        assert_eq!(
15794            resolve_requests_2.load(atomic::Ordering::Acquire),
15795            1,
15796            "Should always resolve once after multiple selections and applying the completion"
15797        );
15798        assert_eq!(
15799            editor.text(cx),
15800            "fn main() { let a = ??.other; }",
15801            "Should use resolved data when applying the completion"
15802        );
15803    });
15804}
15805
15806#[gpui::test]
15807async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
15808    init_test(cx, |_| {});
15809
15810    let item_0 = lsp::CompletionItem {
15811        label: "abs".into(),
15812        insert_text: Some("abs".into()),
15813        data: Some(json!({ "very": "special"})),
15814        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
15815        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
15816            lsp::InsertReplaceEdit {
15817                new_text: "abs".to_string(),
15818                insert: lsp::Range::default(),
15819                replace: lsp::Range::default(),
15820            },
15821        )),
15822        ..lsp::CompletionItem::default()
15823    };
15824    let items = iter::once(item_0.clone())
15825        .chain((11..51).map(|i| lsp::CompletionItem {
15826            label: format!("item_{}", i),
15827            insert_text: Some(format!("item_{}", i)),
15828            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15829            ..lsp::CompletionItem::default()
15830        }))
15831        .collect::<Vec<_>>();
15832
15833    let default_commit_characters = vec!["?".to_string()];
15834    let default_data = json!({ "default": "data"});
15835    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
15836    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
15837    let default_edit_range = lsp::Range {
15838        start: lsp::Position {
15839            line: 0,
15840            character: 5,
15841        },
15842        end: lsp::Position {
15843            line: 0,
15844            character: 5,
15845        },
15846    };
15847
15848    let mut cx = EditorLspTestContext::new_rust(
15849        lsp::ServerCapabilities {
15850            completion_provider: Some(lsp::CompletionOptions {
15851                trigger_characters: Some(vec![".".to_string()]),
15852                resolve_provider: Some(true),
15853                ..Default::default()
15854            }),
15855            ..Default::default()
15856        },
15857        cx,
15858    )
15859    .await;
15860
15861    cx.set_state("fn main() { let a = 2ˇ; }");
15862    cx.simulate_keystroke(".");
15863
15864    let completion_data = default_data.clone();
15865    let completion_characters = default_commit_characters.clone();
15866    let completion_items = items.clone();
15867    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15868        let default_data = completion_data.clone();
15869        let default_commit_characters = completion_characters.clone();
15870        let items = completion_items.clone();
15871        async move {
15872            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15873                items,
15874                item_defaults: Some(lsp::CompletionListItemDefaults {
15875                    data: Some(default_data.clone()),
15876                    commit_characters: Some(default_commit_characters.clone()),
15877                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
15878                        default_edit_range,
15879                    )),
15880                    insert_text_format: Some(default_insert_text_format),
15881                    insert_text_mode: Some(default_insert_text_mode),
15882                }),
15883                ..lsp::CompletionList::default()
15884            })))
15885        }
15886    })
15887    .next()
15888    .await;
15889
15890    let resolved_items = Arc::new(Mutex::new(Vec::new()));
15891    cx.lsp
15892        .server
15893        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
15894            let closure_resolved_items = resolved_items.clone();
15895            move |item_to_resolve, _| {
15896                let closure_resolved_items = closure_resolved_items.clone();
15897                async move {
15898                    closure_resolved_items.lock().push(item_to_resolve.clone());
15899                    Ok(item_to_resolve)
15900                }
15901            }
15902        })
15903        .detach();
15904
15905    cx.condition(|editor, _| editor.context_menu_visible())
15906        .await;
15907    cx.run_until_parked();
15908    cx.update_editor(|editor, _, _| {
15909        let menu = editor.context_menu.borrow_mut();
15910        match menu.as_ref().expect("should have the completions menu") {
15911            CodeContextMenu::Completions(completions_menu) => {
15912                assert_eq!(
15913                    completions_menu
15914                        .entries
15915                        .borrow()
15916                        .iter()
15917                        .map(|mat| mat.string.clone())
15918                        .collect::<Vec<String>>(),
15919                    items
15920                        .iter()
15921                        .map(|completion| completion.label.clone())
15922                        .collect::<Vec<String>>()
15923                );
15924            }
15925            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
15926        }
15927    });
15928    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
15929    // with 4 from the end.
15930    assert_eq!(
15931        *resolved_items.lock(),
15932        [&items[0..16], &items[items.len() - 4..items.len()]]
15933            .concat()
15934            .iter()
15935            .cloned()
15936            .map(|mut item| {
15937                if item.data.is_none() {
15938                    item.data = Some(default_data.clone());
15939                }
15940                item
15941            })
15942            .collect::<Vec<lsp::CompletionItem>>(),
15943        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
15944    );
15945    resolved_items.lock().clear();
15946
15947    cx.update_editor(|editor, window, cx| {
15948        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
15949    });
15950    cx.run_until_parked();
15951    // Completions that have already been resolved are skipped.
15952    assert_eq!(
15953        *resolved_items.lock(),
15954        items[items.len() - 17..items.len() - 4]
15955            .iter()
15956            .cloned()
15957            .map(|mut item| {
15958                if item.data.is_none() {
15959                    item.data = Some(default_data.clone());
15960                }
15961                item
15962            })
15963            .collect::<Vec<lsp::CompletionItem>>()
15964    );
15965    resolved_items.lock().clear();
15966}
15967
15968#[gpui::test]
15969async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
15970    init_test(cx, |_| {});
15971
15972    let mut cx = EditorLspTestContext::new(
15973        Language::new(
15974            LanguageConfig {
15975                matcher: LanguageMatcher {
15976                    path_suffixes: vec!["jsx".into()],
15977                    ..Default::default()
15978                },
15979                overrides: [(
15980                    "element".into(),
15981                    LanguageConfigOverride {
15982                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
15983                        ..Default::default()
15984                    },
15985                )]
15986                .into_iter()
15987                .collect(),
15988                ..Default::default()
15989            },
15990            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
15991        )
15992        .with_override_query("(jsx_self_closing_element) @element")
15993        .unwrap(),
15994        lsp::ServerCapabilities {
15995            completion_provider: Some(lsp::CompletionOptions {
15996                trigger_characters: Some(vec![":".to_string()]),
15997                ..Default::default()
15998            }),
15999            ..Default::default()
16000        },
16001        cx,
16002    )
16003    .await;
16004
16005    cx.lsp
16006        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16007            Ok(Some(lsp::CompletionResponse::Array(vec![
16008                lsp::CompletionItem {
16009                    label: "bg-blue".into(),
16010                    ..Default::default()
16011                },
16012                lsp::CompletionItem {
16013                    label: "bg-red".into(),
16014                    ..Default::default()
16015                },
16016                lsp::CompletionItem {
16017                    label: "bg-yellow".into(),
16018                    ..Default::default()
16019                },
16020            ])))
16021        });
16022
16023    cx.set_state(r#"<p class="bgˇ" />"#);
16024
16025    // Trigger completion when typing a dash, because the dash is an extra
16026    // word character in the 'element' scope, which contains the cursor.
16027    cx.simulate_keystroke("-");
16028    cx.executor().run_until_parked();
16029    cx.update_editor(|editor, _, _| {
16030        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16031        {
16032            assert_eq!(
16033                completion_menu_entries(&menu),
16034                &["bg-blue", "bg-red", "bg-yellow"]
16035            );
16036        } else {
16037            panic!("expected completion menu to be open");
16038        }
16039    });
16040
16041    cx.simulate_keystroke("l");
16042    cx.executor().run_until_parked();
16043    cx.update_editor(|editor, _, _| {
16044        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16045        {
16046            assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
16047        } else {
16048            panic!("expected completion menu to be open");
16049        }
16050    });
16051
16052    // When filtering completions, consider the character after the '-' to
16053    // be the start of a subword.
16054    cx.set_state(r#"<p class="yelˇ" />"#);
16055    cx.simulate_keystroke("l");
16056    cx.executor().run_until_parked();
16057    cx.update_editor(|editor, _, _| {
16058        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16059        {
16060            assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
16061        } else {
16062            panic!("expected completion menu to be open");
16063        }
16064    });
16065}
16066
16067fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
16068    let entries = menu.entries.borrow();
16069    entries.iter().map(|mat| mat.string.clone()).collect()
16070}
16071
16072#[gpui::test]
16073async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
16074    init_test(cx, |settings| {
16075        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
16076            Formatter::Prettier,
16077        )))
16078    });
16079
16080    let fs = FakeFs::new(cx.executor());
16081    fs.insert_file(path!("/file.ts"), Default::default()).await;
16082
16083    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
16084    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16085
16086    language_registry.add(Arc::new(Language::new(
16087        LanguageConfig {
16088            name: "TypeScript".into(),
16089            matcher: LanguageMatcher {
16090                path_suffixes: vec!["ts".to_string()],
16091                ..Default::default()
16092            },
16093            ..Default::default()
16094        },
16095        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
16096    )));
16097    update_test_language_settings(cx, |settings| {
16098        settings.defaults.prettier = Some(PrettierSettings {
16099            allowed: true,
16100            ..PrettierSettings::default()
16101        });
16102    });
16103
16104    let test_plugin = "test_plugin";
16105    let _ = language_registry.register_fake_lsp(
16106        "TypeScript",
16107        FakeLspAdapter {
16108            prettier_plugins: vec![test_plugin],
16109            ..Default::default()
16110        },
16111    );
16112
16113    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
16114    let buffer = project
16115        .update(cx, |project, cx| {
16116            project.open_local_buffer(path!("/file.ts"), cx)
16117        })
16118        .await
16119        .unwrap();
16120
16121    let buffer_text = "one\ntwo\nthree\n";
16122    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16123    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16124    editor.update_in(cx, |editor, window, cx| {
16125        editor.set_text(buffer_text, window, cx)
16126    });
16127
16128    editor
16129        .update_in(cx, |editor, window, cx| {
16130            editor.perform_format(
16131                project.clone(),
16132                FormatTrigger::Manual,
16133                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
16134                window,
16135                cx,
16136            )
16137        })
16138        .unwrap()
16139        .await;
16140    assert_eq!(
16141        editor.update(cx, |editor, cx| editor.text(cx)),
16142        buffer_text.to_string() + prettier_format_suffix,
16143        "Test prettier formatting was not applied to the original buffer text",
16144    );
16145
16146    update_test_language_settings(cx, |settings| {
16147        settings.defaults.formatter = Some(SelectedFormatter::Auto)
16148    });
16149    let format = editor.update_in(cx, |editor, window, cx| {
16150        editor.perform_format(
16151            project.clone(),
16152            FormatTrigger::Manual,
16153            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
16154            window,
16155            cx,
16156        )
16157    });
16158    format.await.unwrap();
16159    assert_eq!(
16160        editor.update(cx, |editor, cx| editor.text(cx)),
16161        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
16162        "Autoformatting (via test prettier) was not applied to the original buffer text",
16163    );
16164}
16165
16166#[gpui::test]
16167async fn test_addition_reverts(cx: &mut TestAppContext) {
16168    init_test(cx, |_| {});
16169    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16170    let base_text = indoc! {r#"
16171        struct Row;
16172        struct Row1;
16173        struct Row2;
16174
16175        struct Row4;
16176        struct Row5;
16177        struct Row6;
16178
16179        struct Row8;
16180        struct Row9;
16181        struct Row10;"#};
16182
16183    // When addition hunks are not adjacent to carets, no hunk revert is performed
16184    assert_hunk_revert(
16185        indoc! {r#"struct Row;
16186                   struct Row1;
16187                   struct Row1.1;
16188                   struct Row1.2;
16189                   struct Row2;ˇ
16190
16191                   struct Row4;
16192                   struct Row5;
16193                   struct Row6;
16194
16195                   struct Row8;
16196                   ˇstruct Row9;
16197                   struct Row9.1;
16198                   struct Row9.2;
16199                   struct Row9.3;
16200                   struct Row10;"#},
16201        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
16202        indoc! {r#"struct Row;
16203                   struct Row1;
16204                   struct Row1.1;
16205                   struct Row1.2;
16206                   struct Row2;ˇ
16207
16208                   struct Row4;
16209                   struct Row5;
16210                   struct Row6;
16211
16212                   struct Row8;
16213                   ˇstruct Row9;
16214                   struct Row9.1;
16215                   struct Row9.2;
16216                   struct Row9.3;
16217                   struct Row10;"#},
16218        base_text,
16219        &mut cx,
16220    );
16221    // Same for selections
16222    assert_hunk_revert(
16223        indoc! {r#"struct Row;
16224                   struct Row1;
16225                   struct Row2;
16226                   struct Row2.1;
16227                   struct Row2.2;
16228                   «ˇ
16229                   struct Row4;
16230                   struct» Row5;
16231                   «struct Row6;
16232                   ˇ»
16233                   struct Row9.1;
16234                   struct Row9.2;
16235                   struct Row9.3;
16236                   struct Row8;
16237                   struct Row9;
16238                   struct Row10;"#},
16239        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
16240        indoc! {r#"struct Row;
16241                   struct Row1;
16242                   struct Row2;
16243                   struct Row2.1;
16244                   struct Row2.2;
16245                   «ˇ
16246                   struct Row4;
16247                   struct» Row5;
16248                   «struct Row6;
16249                   ˇ»
16250                   struct Row9.1;
16251                   struct Row9.2;
16252                   struct Row9.3;
16253                   struct Row8;
16254                   struct Row9;
16255                   struct Row10;"#},
16256        base_text,
16257        &mut cx,
16258    );
16259
16260    // When carets and selections intersect the addition hunks, those are reverted.
16261    // Adjacent carets got merged.
16262    assert_hunk_revert(
16263        indoc! {r#"struct Row;
16264                   ˇ// something on the top
16265                   struct Row1;
16266                   struct Row2;
16267                   struct Roˇw3.1;
16268                   struct Row2.2;
16269                   struct Row2.3;ˇ
16270
16271                   struct Row4;
16272                   struct ˇRow5.1;
16273                   struct Row5.2;
16274                   struct «Rowˇ»5.3;
16275                   struct Row5;
16276                   struct Row6;
16277                   ˇ
16278                   struct Row9.1;
16279                   struct «Rowˇ»9.2;
16280                   struct «ˇRow»9.3;
16281                   struct Row8;
16282                   struct Row9;
16283                   «ˇ// something on bottom»
16284                   struct Row10;"#},
16285        vec![
16286            DiffHunkStatusKind::Added,
16287            DiffHunkStatusKind::Added,
16288            DiffHunkStatusKind::Added,
16289            DiffHunkStatusKind::Added,
16290            DiffHunkStatusKind::Added,
16291        ],
16292        indoc! {r#"struct Row;
16293                   ˇstruct Row1;
16294                   struct Row2;
16295                   ˇ
16296                   struct Row4;
16297                   ˇstruct Row5;
16298                   struct Row6;
16299                   ˇ
16300                   ˇstruct Row8;
16301                   struct Row9;
16302                   ˇstruct Row10;"#},
16303        base_text,
16304        &mut cx,
16305    );
16306}
16307
16308#[gpui::test]
16309async fn test_modification_reverts(cx: &mut TestAppContext) {
16310    init_test(cx, |_| {});
16311    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16312    let base_text = indoc! {r#"
16313        struct Row;
16314        struct Row1;
16315        struct Row2;
16316
16317        struct Row4;
16318        struct Row5;
16319        struct Row6;
16320
16321        struct Row8;
16322        struct Row9;
16323        struct Row10;"#};
16324
16325    // Modification hunks behave the same as the addition ones.
16326    assert_hunk_revert(
16327        indoc! {r#"struct Row;
16328                   struct Row1;
16329                   struct Row33;
16330                   ˇ
16331                   struct Row4;
16332                   struct Row5;
16333                   struct Row6;
16334                   ˇ
16335                   struct Row99;
16336                   struct Row9;
16337                   struct Row10;"#},
16338        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
16339        indoc! {r#"struct Row;
16340                   struct Row1;
16341                   struct Row33;
16342                   ˇ
16343                   struct Row4;
16344                   struct Row5;
16345                   struct Row6;
16346                   ˇ
16347                   struct Row99;
16348                   struct Row9;
16349                   struct Row10;"#},
16350        base_text,
16351        &mut cx,
16352    );
16353    assert_hunk_revert(
16354        indoc! {r#"struct Row;
16355                   struct Row1;
16356                   struct Row33;
16357                   «ˇ
16358                   struct Row4;
16359                   struct» Row5;
16360                   «struct Row6;
16361                   ˇ»
16362                   struct Row99;
16363                   struct Row9;
16364                   struct Row10;"#},
16365        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
16366        indoc! {r#"struct Row;
16367                   struct Row1;
16368                   struct Row33;
16369                   «ˇ
16370                   struct Row4;
16371                   struct» Row5;
16372                   «struct Row6;
16373                   ˇ»
16374                   struct Row99;
16375                   struct Row9;
16376                   struct Row10;"#},
16377        base_text,
16378        &mut cx,
16379    );
16380
16381    assert_hunk_revert(
16382        indoc! {r#"ˇstruct Row1.1;
16383                   struct Row1;
16384                   «ˇstr»uct Row22;
16385
16386                   struct ˇRow44;
16387                   struct Row5;
16388                   struct «Rˇ»ow66;ˇ
16389
16390                   «struˇ»ct Row88;
16391                   struct Row9;
16392                   struct Row1011;ˇ"#},
16393        vec![
16394            DiffHunkStatusKind::Modified,
16395            DiffHunkStatusKind::Modified,
16396            DiffHunkStatusKind::Modified,
16397            DiffHunkStatusKind::Modified,
16398            DiffHunkStatusKind::Modified,
16399            DiffHunkStatusKind::Modified,
16400        ],
16401        indoc! {r#"struct Row;
16402                   ˇstruct Row1;
16403                   struct Row2;
16404                   ˇ
16405                   struct Row4;
16406                   ˇstruct Row5;
16407                   struct Row6;
16408                   ˇ
16409                   struct Row8;
16410                   ˇstruct Row9;
16411                   struct Row10;ˇ"#},
16412        base_text,
16413        &mut cx,
16414    );
16415}
16416
16417#[gpui::test]
16418async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
16419    init_test(cx, |_| {});
16420    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16421    let base_text = indoc! {r#"
16422        one
16423
16424        two
16425        three
16426        "#};
16427
16428    cx.set_head_text(base_text);
16429    cx.set_state("\nˇ\n");
16430    cx.executor().run_until_parked();
16431    cx.update_editor(|editor, _window, cx| {
16432        editor.expand_selected_diff_hunks(cx);
16433    });
16434    cx.executor().run_until_parked();
16435    cx.update_editor(|editor, window, cx| {
16436        editor.backspace(&Default::default(), window, cx);
16437    });
16438    cx.run_until_parked();
16439    cx.assert_state_with_diff(
16440        indoc! {r#"
16441
16442        - two
16443        - threeˇ
16444        +
16445        "#}
16446        .to_string(),
16447    );
16448}
16449
16450#[gpui::test]
16451async fn test_deletion_reverts(cx: &mut TestAppContext) {
16452    init_test(cx, |_| {});
16453    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16454    let base_text = indoc! {r#"struct Row;
16455struct Row1;
16456struct Row2;
16457
16458struct Row4;
16459struct Row5;
16460struct Row6;
16461
16462struct Row8;
16463struct Row9;
16464struct Row10;"#};
16465
16466    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
16467    assert_hunk_revert(
16468        indoc! {r#"struct Row;
16469                   struct Row2;
16470
16471                   ˇstruct Row4;
16472                   struct Row5;
16473                   struct Row6;
16474                   ˇ
16475                   struct Row8;
16476                   struct Row10;"#},
16477        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16478        indoc! {r#"struct Row;
16479                   struct Row2;
16480
16481                   ˇstruct Row4;
16482                   struct Row5;
16483                   struct Row6;
16484                   ˇ
16485                   struct Row8;
16486                   struct Row10;"#},
16487        base_text,
16488        &mut cx,
16489    );
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
16514    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
16515    assert_hunk_revert(
16516        indoc! {r#"struct Row;
16517                   ˇstruct Row2;
16518
16519                   struct Row4;
16520                   struct Row5;
16521                   struct Row6;
16522
16523                   struct Row8;ˇ
16524                   struct Row10;"#},
16525        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16526        indoc! {r#"struct Row;
16527                   struct Row1;
16528                   ˇstruct Row2;
16529
16530                   struct Row4;
16531                   struct Row5;
16532                   struct Row6;
16533
16534                   struct Row8;ˇ
16535                   struct Row9;
16536                   struct Row10;"#},
16537        base_text,
16538        &mut cx,
16539    );
16540    assert_hunk_revert(
16541        indoc! {r#"struct Row;
16542                   struct Row2«ˇ;
16543                   struct Row4;
16544                   struct» Row5;
16545                   «struct Row6;
16546
16547                   struct Row8;ˇ»
16548                   struct Row10;"#},
16549        vec![
16550            DiffHunkStatusKind::Deleted,
16551            DiffHunkStatusKind::Deleted,
16552            DiffHunkStatusKind::Deleted,
16553        ],
16554        indoc! {r#"struct Row;
16555                   struct Row1;
16556                   struct Row2«ˇ;
16557
16558                   struct Row4;
16559                   struct» Row5;
16560                   «struct Row6;
16561
16562                   struct Row8;ˇ»
16563                   struct Row9;
16564                   struct Row10;"#},
16565        base_text,
16566        &mut cx,
16567    );
16568}
16569
16570#[gpui::test]
16571async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
16572    init_test(cx, |_| {});
16573
16574    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
16575    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
16576    let base_text_3 =
16577        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
16578
16579    let text_1 = edit_first_char_of_every_line(base_text_1);
16580    let text_2 = edit_first_char_of_every_line(base_text_2);
16581    let text_3 = edit_first_char_of_every_line(base_text_3);
16582
16583    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
16584    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
16585    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
16586
16587    let multibuffer = cx.new(|cx| {
16588        let mut multibuffer = MultiBuffer::new(ReadWrite);
16589        multibuffer.push_excerpts(
16590            buffer_1.clone(),
16591            [
16592                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16593                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16594                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16595            ],
16596            cx,
16597        );
16598        multibuffer.push_excerpts(
16599            buffer_2.clone(),
16600            [
16601                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16602                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16603                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16604            ],
16605            cx,
16606        );
16607        multibuffer.push_excerpts(
16608            buffer_3.clone(),
16609            [
16610                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16611                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16612                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16613            ],
16614            cx,
16615        );
16616        multibuffer
16617    });
16618
16619    let fs = FakeFs::new(cx.executor());
16620    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
16621    let (editor, cx) = cx
16622        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
16623    editor.update_in(cx, |editor, _window, cx| {
16624        for (buffer, diff_base) in [
16625            (buffer_1.clone(), base_text_1),
16626            (buffer_2.clone(), base_text_2),
16627            (buffer_3.clone(), base_text_3),
16628        ] {
16629            let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
16630            editor
16631                .buffer
16632                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
16633        }
16634    });
16635    cx.executor().run_until_parked();
16636
16637    editor.update_in(cx, |editor, window, cx| {
16638        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}");
16639        editor.select_all(&SelectAll, window, cx);
16640        editor.git_restore(&Default::default(), window, cx);
16641    });
16642    cx.executor().run_until_parked();
16643
16644    // When all ranges are selected, all buffer hunks are reverted.
16645    editor.update(cx, |editor, cx| {
16646        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");
16647    });
16648    buffer_1.update(cx, |buffer, _| {
16649        assert_eq!(buffer.text(), base_text_1);
16650    });
16651    buffer_2.update(cx, |buffer, _| {
16652        assert_eq!(buffer.text(), base_text_2);
16653    });
16654    buffer_3.update(cx, |buffer, _| {
16655        assert_eq!(buffer.text(), base_text_3);
16656    });
16657
16658    editor.update_in(cx, |editor, window, cx| {
16659        editor.undo(&Default::default(), window, cx);
16660    });
16661
16662    editor.update_in(cx, |editor, window, cx| {
16663        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16664            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
16665        });
16666        editor.git_restore(&Default::default(), window, cx);
16667    });
16668
16669    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
16670    // but not affect buffer_2 and its related excerpts.
16671    editor.update(cx, |editor, cx| {
16672        assert_eq!(
16673            editor.text(cx),
16674            "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}"
16675        );
16676    });
16677    buffer_1.update(cx, |buffer, _| {
16678        assert_eq!(buffer.text(), base_text_1);
16679    });
16680    buffer_2.update(cx, |buffer, _| {
16681        assert_eq!(
16682            buffer.text(),
16683            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
16684        );
16685    });
16686    buffer_3.update(cx, |buffer, _| {
16687        assert_eq!(
16688            buffer.text(),
16689            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
16690        );
16691    });
16692
16693    fn edit_first_char_of_every_line(text: &str) -> String {
16694        text.split('\n')
16695            .map(|line| format!("X{}", &line[1..]))
16696            .collect::<Vec<_>>()
16697            .join("\n")
16698    }
16699}
16700
16701#[gpui::test]
16702async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
16703    init_test(cx, |_| {});
16704
16705    let cols = 4;
16706    let rows = 10;
16707    let sample_text_1 = sample_text(rows, cols, 'a');
16708    assert_eq!(
16709        sample_text_1,
16710        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
16711    );
16712    let sample_text_2 = sample_text(rows, cols, 'l');
16713    assert_eq!(
16714        sample_text_2,
16715        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
16716    );
16717    let sample_text_3 = sample_text(rows, cols, 'v');
16718    assert_eq!(
16719        sample_text_3,
16720        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
16721    );
16722
16723    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
16724    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
16725    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
16726
16727    let multi_buffer = cx.new(|cx| {
16728        let mut multibuffer = MultiBuffer::new(ReadWrite);
16729        multibuffer.push_excerpts(
16730            buffer_1.clone(),
16731            [
16732                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16733                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16734                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16735            ],
16736            cx,
16737        );
16738        multibuffer.push_excerpts(
16739            buffer_2.clone(),
16740            [
16741                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16742                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16743                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16744            ],
16745            cx,
16746        );
16747        multibuffer.push_excerpts(
16748            buffer_3.clone(),
16749            [
16750                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16751                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16752                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16753            ],
16754            cx,
16755        );
16756        multibuffer
16757    });
16758
16759    let fs = FakeFs::new(cx.executor());
16760    fs.insert_tree(
16761        "/a",
16762        json!({
16763            "main.rs": sample_text_1,
16764            "other.rs": sample_text_2,
16765            "lib.rs": sample_text_3,
16766        }),
16767    )
16768    .await;
16769    let project = Project::test(fs, ["/a".as_ref()], cx).await;
16770    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16771    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16772    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16773        Editor::new(
16774            EditorMode::full(),
16775            multi_buffer,
16776            Some(project.clone()),
16777            window,
16778            cx,
16779        )
16780    });
16781    let multibuffer_item_id = workspace
16782        .update(cx, |workspace, window, cx| {
16783            assert!(
16784                workspace.active_item(cx).is_none(),
16785                "active item should be None before the first item is added"
16786            );
16787            workspace.add_item_to_active_pane(
16788                Box::new(multi_buffer_editor.clone()),
16789                None,
16790                true,
16791                window,
16792                cx,
16793            );
16794            let active_item = workspace
16795                .active_item(cx)
16796                .expect("should have an active item after adding the multi buffer");
16797            assert!(
16798                !active_item.is_singleton(cx),
16799                "A multi buffer was expected to active after adding"
16800            );
16801            active_item.item_id()
16802        })
16803        .unwrap();
16804    cx.executor().run_until_parked();
16805
16806    multi_buffer_editor.update_in(cx, |editor, window, cx| {
16807        editor.change_selections(
16808            SelectionEffects::scroll(Autoscroll::Next),
16809            window,
16810            cx,
16811            |s| s.select_ranges(Some(1..2)),
16812        );
16813        editor.open_excerpts(&OpenExcerpts, window, cx);
16814    });
16815    cx.executor().run_until_parked();
16816    let first_item_id = workspace
16817        .update(cx, |workspace, window, cx| {
16818            let active_item = workspace
16819                .active_item(cx)
16820                .expect("should have an active item after navigating into the 1st buffer");
16821            let first_item_id = active_item.item_id();
16822            assert_ne!(
16823                first_item_id, multibuffer_item_id,
16824                "Should navigate into the 1st buffer and activate it"
16825            );
16826            assert!(
16827                active_item.is_singleton(cx),
16828                "New active item should be a singleton buffer"
16829            );
16830            assert_eq!(
16831                active_item
16832                    .act_as::<Editor>(cx)
16833                    .expect("should have navigated into an editor for the 1st buffer")
16834                    .read(cx)
16835                    .text(cx),
16836                sample_text_1
16837            );
16838
16839            workspace
16840                .go_back(workspace.active_pane().downgrade(), window, cx)
16841                .detach_and_log_err(cx);
16842
16843            first_item_id
16844        })
16845        .unwrap();
16846    cx.executor().run_until_parked();
16847    workspace
16848        .update(cx, |workspace, _, cx| {
16849            let active_item = workspace
16850                .active_item(cx)
16851                .expect("should have an active item after navigating back");
16852            assert_eq!(
16853                active_item.item_id(),
16854                multibuffer_item_id,
16855                "Should navigate back to the multi buffer"
16856            );
16857            assert!(!active_item.is_singleton(cx));
16858        })
16859        .unwrap();
16860
16861    multi_buffer_editor.update_in(cx, |editor, window, cx| {
16862        editor.change_selections(
16863            SelectionEffects::scroll(Autoscroll::Next),
16864            window,
16865            cx,
16866            |s| s.select_ranges(Some(39..40)),
16867        );
16868        editor.open_excerpts(&OpenExcerpts, window, cx);
16869    });
16870    cx.executor().run_until_parked();
16871    let second_item_id = workspace
16872        .update(cx, |workspace, window, cx| {
16873            let active_item = workspace
16874                .active_item(cx)
16875                .expect("should have an active item after navigating into the 2nd buffer");
16876            let second_item_id = active_item.item_id();
16877            assert_ne!(
16878                second_item_id, multibuffer_item_id,
16879                "Should navigate away from the multibuffer"
16880            );
16881            assert_ne!(
16882                second_item_id, first_item_id,
16883                "Should navigate into the 2nd buffer and activate it"
16884            );
16885            assert!(
16886                active_item.is_singleton(cx),
16887                "New active item should be a singleton buffer"
16888            );
16889            assert_eq!(
16890                active_item
16891                    .act_as::<Editor>(cx)
16892                    .expect("should have navigated into an editor")
16893                    .read(cx)
16894                    .text(cx),
16895                sample_text_2
16896            );
16897
16898            workspace
16899                .go_back(workspace.active_pane().downgrade(), window, cx)
16900                .detach_and_log_err(cx);
16901
16902            second_item_id
16903        })
16904        .unwrap();
16905    cx.executor().run_until_parked();
16906    workspace
16907        .update(cx, |workspace, _, cx| {
16908            let active_item = workspace
16909                .active_item(cx)
16910                .expect("should have an active item after navigating back from the 2nd buffer");
16911            assert_eq!(
16912                active_item.item_id(),
16913                multibuffer_item_id,
16914                "Should navigate back from the 2nd buffer to the multi buffer"
16915            );
16916            assert!(!active_item.is_singleton(cx));
16917        })
16918        .unwrap();
16919
16920    multi_buffer_editor.update_in(cx, |editor, window, cx| {
16921        editor.change_selections(
16922            SelectionEffects::scroll(Autoscroll::Next),
16923            window,
16924            cx,
16925            |s| s.select_ranges(Some(70..70)),
16926        );
16927        editor.open_excerpts(&OpenExcerpts, window, cx);
16928    });
16929    cx.executor().run_until_parked();
16930    workspace
16931        .update(cx, |workspace, window, cx| {
16932            let active_item = workspace
16933                .active_item(cx)
16934                .expect("should have an active item after navigating into the 3rd buffer");
16935            let third_item_id = active_item.item_id();
16936            assert_ne!(
16937                third_item_id, multibuffer_item_id,
16938                "Should navigate into the 3rd buffer and activate it"
16939            );
16940            assert_ne!(third_item_id, first_item_id);
16941            assert_ne!(third_item_id, second_item_id);
16942            assert!(
16943                active_item.is_singleton(cx),
16944                "New active item should be a singleton buffer"
16945            );
16946            assert_eq!(
16947                active_item
16948                    .act_as::<Editor>(cx)
16949                    .expect("should have navigated into an editor")
16950                    .read(cx)
16951                    .text(cx),
16952                sample_text_3
16953            );
16954
16955            workspace
16956                .go_back(workspace.active_pane().downgrade(), window, cx)
16957                .detach_and_log_err(cx);
16958        })
16959        .unwrap();
16960    cx.executor().run_until_parked();
16961    workspace
16962        .update(cx, |workspace, _, cx| {
16963            let active_item = workspace
16964                .active_item(cx)
16965                .expect("should have an active item after navigating back from the 3rd buffer");
16966            assert_eq!(
16967                active_item.item_id(),
16968                multibuffer_item_id,
16969                "Should navigate back from the 3rd buffer to the multi buffer"
16970            );
16971            assert!(!active_item.is_singleton(cx));
16972        })
16973        .unwrap();
16974}
16975
16976#[gpui::test]
16977async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16978    init_test(cx, |_| {});
16979
16980    let mut cx = EditorTestContext::new(cx).await;
16981
16982    let diff_base = r#"
16983        use some::mod;
16984
16985        const A: u32 = 42;
16986
16987        fn main() {
16988            println!("hello");
16989
16990            println!("world");
16991        }
16992        "#
16993    .unindent();
16994
16995    cx.set_state(
16996        &r#"
16997        use some::modified;
16998
16999        ˇ
17000        fn main() {
17001            println!("hello there");
17002
17003            println!("around the");
17004            println!("world");
17005        }
17006        "#
17007        .unindent(),
17008    );
17009
17010    cx.set_head_text(&diff_base);
17011    executor.run_until_parked();
17012
17013    cx.update_editor(|editor, window, cx| {
17014        editor.go_to_next_hunk(&GoToHunk, window, cx);
17015        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17016    });
17017    executor.run_until_parked();
17018    cx.assert_state_with_diff(
17019        r#"
17020          use some::modified;
17021
17022
17023          fn main() {
17024        -     println!("hello");
17025        + ˇ    println!("hello there");
17026
17027              println!("around the");
17028              println!("world");
17029          }
17030        "#
17031        .unindent(),
17032    );
17033
17034    cx.update_editor(|editor, window, cx| {
17035        for _ in 0..2 {
17036            editor.go_to_next_hunk(&GoToHunk, window, cx);
17037            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17038        }
17039    });
17040    executor.run_until_parked();
17041    cx.assert_state_with_diff(
17042        r#"
17043        - use some::mod;
17044        + ˇuse some::modified;
17045
17046
17047          fn main() {
17048        -     println!("hello");
17049        +     println!("hello there");
17050
17051        +     println!("around the");
17052              println!("world");
17053          }
17054        "#
17055        .unindent(),
17056    );
17057
17058    cx.update_editor(|editor, window, cx| {
17059        editor.go_to_next_hunk(&GoToHunk, window, cx);
17060        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17061    });
17062    executor.run_until_parked();
17063    cx.assert_state_with_diff(
17064        r#"
17065        - use some::mod;
17066        + use some::modified;
17067
17068        - const A: u32 = 42;
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.cancel(&Cancel, window, cx);
17083    });
17084
17085    cx.assert_state_with_diff(
17086        r#"
17087          use some::modified;
17088
17089          ˇ
17090          fn main() {
17091              println!("hello there");
17092
17093              println!("around the");
17094              println!("world");
17095          }
17096        "#
17097        .unindent(),
17098    );
17099}
17100
17101#[gpui::test]
17102async fn test_diff_base_change_with_expanded_diff_hunks(
17103    executor: BackgroundExecutor,
17104    cx: &mut TestAppContext,
17105) {
17106    init_test(cx, |_| {});
17107
17108    let mut cx = EditorTestContext::new(cx).await;
17109
17110    let diff_base = r#"
17111        use some::mod1;
17112        use some::mod2;
17113
17114        const A: u32 = 42;
17115        const B: u32 = 42;
17116        const C: u32 = 42;
17117
17118        fn main() {
17119            println!("hello");
17120
17121            println!("world");
17122        }
17123        "#
17124    .unindent();
17125
17126    cx.set_state(
17127        &r#"
17128        use some::mod2;
17129
17130        const A: u32 = 42;
17131        const C: u32 = 42;
17132
17133        fn main(ˇ) {
17134            //println!("hello");
17135
17136            println!("world");
17137            //
17138            //
17139        }
17140        "#
17141        .unindent(),
17142    );
17143
17144    cx.set_head_text(&diff_base);
17145    executor.run_until_parked();
17146
17147    cx.update_editor(|editor, window, cx| {
17148        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17149    });
17150    executor.run_until_parked();
17151    cx.assert_state_with_diff(
17152        r#"
17153        - use some::mod1;
17154          use some::mod2;
17155
17156          const A: u32 = 42;
17157        - const B: u32 = 42;
17158          const C: u32 = 42;
17159
17160          fn main(ˇ) {
17161        -     println!("hello");
17162        +     //println!("hello");
17163
17164              println!("world");
17165        +     //
17166        +     //
17167          }
17168        "#
17169        .unindent(),
17170    );
17171
17172    cx.set_head_text("new diff base!");
17173    executor.run_until_parked();
17174    cx.assert_state_with_diff(
17175        r#"
17176        - new diff base!
17177        + use some::mod2;
17178        +
17179        + const A: u32 = 42;
17180        + const C: u32 = 42;
17181        +
17182        + fn main(ˇ) {
17183        +     //println!("hello");
17184        +
17185        +     println!("world");
17186        +     //
17187        +     //
17188        + }
17189        "#
17190        .unindent(),
17191    );
17192}
17193
17194#[gpui::test]
17195async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
17196    init_test(cx, |_| {});
17197
17198    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
17199    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
17200    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
17201    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
17202    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
17203    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
17204
17205    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
17206    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
17207    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
17208
17209    let multi_buffer = cx.new(|cx| {
17210        let mut multibuffer = MultiBuffer::new(ReadWrite);
17211        multibuffer.push_excerpts(
17212            buffer_1.clone(),
17213            [
17214                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17215                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17216                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17217            ],
17218            cx,
17219        );
17220        multibuffer.push_excerpts(
17221            buffer_2.clone(),
17222            [
17223                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17224                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17225                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17226            ],
17227            cx,
17228        );
17229        multibuffer.push_excerpts(
17230            buffer_3.clone(),
17231            [
17232                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17233                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17234                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17235            ],
17236            cx,
17237        );
17238        multibuffer
17239    });
17240
17241    let editor =
17242        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
17243    editor
17244        .update(cx, |editor, _window, cx| {
17245            for (buffer, diff_base) in [
17246                (buffer_1.clone(), file_1_old),
17247                (buffer_2.clone(), file_2_old),
17248                (buffer_3.clone(), file_3_old),
17249            ] {
17250                let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
17251                editor
17252                    .buffer
17253                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
17254            }
17255        })
17256        .unwrap();
17257
17258    let mut cx = EditorTestContext::for_editor(editor, cx).await;
17259    cx.run_until_parked();
17260
17261    cx.assert_editor_state(
17262        &"
17263            ˇaaa
17264            ccc
17265            ddd
17266
17267            ggg
17268            hhh
17269
17270
17271            lll
17272            mmm
17273            NNN
17274
17275            qqq
17276            rrr
17277
17278            uuu
17279            111
17280            222
17281            333
17282
17283            666
17284            777
17285
17286            000
17287            !!!"
17288        .unindent(),
17289    );
17290
17291    cx.update_editor(|editor, window, cx| {
17292        editor.select_all(&SelectAll, window, cx);
17293        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17294    });
17295    cx.executor().run_until_parked();
17296
17297    cx.assert_state_with_diff(
17298        "
17299            «aaa
17300          - bbb
17301            ccc
17302            ddd
17303
17304            ggg
17305            hhh
17306
17307
17308            lll
17309            mmm
17310          - nnn
17311          + NNN
17312
17313            qqq
17314            rrr
17315
17316            uuu
17317            111
17318            222
17319            333
17320
17321          + 666
17322            777
17323
17324            000
17325            !!!ˇ»"
17326            .unindent(),
17327    );
17328}
17329
17330#[gpui::test]
17331async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
17332    init_test(cx, |_| {});
17333
17334    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
17335    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
17336
17337    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
17338    let multi_buffer = cx.new(|cx| {
17339        let mut multibuffer = MultiBuffer::new(ReadWrite);
17340        multibuffer.push_excerpts(
17341            buffer.clone(),
17342            [
17343                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
17344                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
17345                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
17346            ],
17347            cx,
17348        );
17349        multibuffer
17350    });
17351
17352    let editor =
17353        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
17354    editor
17355        .update(cx, |editor, _window, cx| {
17356            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
17357            editor
17358                .buffer
17359                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
17360        })
17361        .unwrap();
17362
17363    let mut cx = EditorTestContext::for_editor(editor, cx).await;
17364    cx.run_until_parked();
17365
17366    cx.update_editor(|editor, window, cx| {
17367        editor.expand_all_diff_hunks(&Default::default(), window, cx)
17368    });
17369    cx.executor().run_until_parked();
17370
17371    // When the start of a hunk coincides with the start of its excerpt,
17372    // the hunk is expanded. When the start of a a hunk is earlier than
17373    // the start of its excerpt, the hunk is not expanded.
17374    cx.assert_state_with_diff(
17375        "
17376            ˇaaa
17377          - bbb
17378          + BBB
17379
17380          - ddd
17381          - eee
17382          + DDD
17383          + EEE
17384            fff
17385
17386            iii
17387        "
17388        .unindent(),
17389    );
17390}
17391
17392#[gpui::test]
17393async fn test_edits_around_expanded_insertion_hunks(
17394    executor: BackgroundExecutor,
17395    cx: &mut TestAppContext,
17396) {
17397    init_test(cx, |_| {});
17398
17399    let mut cx = EditorTestContext::new(cx).await;
17400
17401    let diff_base = r#"
17402        use some::mod1;
17403        use some::mod2;
17404
17405        const A: u32 = 42;
17406
17407        fn main() {
17408            println!("hello");
17409
17410            println!("world");
17411        }
17412        "#
17413    .unindent();
17414    executor.run_until_parked();
17415    cx.set_state(
17416        &r#"
17417        use some::mod1;
17418        use some::mod2;
17419
17420        const A: u32 = 42;
17421        const B: u32 = 42;
17422        const C: u32 = 42;
17423        ˇ
17424
17425        fn main() {
17426            println!("hello");
17427
17428            println!("world");
17429        }
17430        "#
17431        .unindent(),
17432    );
17433
17434    cx.set_head_text(&diff_base);
17435    executor.run_until_parked();
17436
17437    cx.update_editor(|editor, window, cx| {
17438        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17439    });
17440    executor.run_until_parked();
17441
17442    cx.assert_state_with_diff(
17443        r#"
17444        use some::mod1;
17445        use some::mod2;
17446
17447        const A: u32 = 42;
17448      + const B: u32 = 42;
17449      + const C: u32 = 42;
17450      + ˇ
17451
17452        fn main() {
17453            println!("hello");
17454
17455            println!("world");
17456        }
17457      "#
17458        .unindent(),
17459    );
17460
17461    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
17462    executor.run_until_parked();
17463
17464    cx.assert_state_with_diff(
17465        r#"
17466        use some::mod1;
17467        use some::mod2;
17468
17469        const A: u32 = 42;
17470      + const B: u32 = 42;
17471      + const C: u32 = 42;
17472      + const D: 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 E: 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      + const E: u32 = 42;
17497      + ˇ
17498
17499        fn main() {
17500            println!("hello");
17501
17502            println!("world");
17503        }
17504      "#
17505        .unindent(),
17506    );
17507
17508    cx.update_editor(|editor, window, cx| {
17509        editor.delete_line(&DeleteLine, window, cx);
17510    });
17511    executor.run_until_parked();
17512
17513    cx.assert_state_with_diff(
17514        r#"
17515        use some::mod1;
17516        use some::mod2;
17517
17518        const A: u32 = 42;
17519      + const B: u32 = 42;
17520      + const C: u32 = 42;
17521      + const D: u32 = 42;
17522      + const E: u32 = 42;
17523        ˇ
17524        fn main() {
17525            println!("hello");
17526
17527            println!("world");
17528        }
17529      "#
17530        .unindent(),
17531    );
17532
17533    cx.update_editor(|editor, window, cx| {
17534        editor.move_up(&MoveUp, window, cx);
17535        editor.delete_line(&DeleteLine, window, cx);
17536        editor.move_up(&MoveUp, window, cx);
17537        editor.delete_line(&DeleteLine, window, cx);
17538        editor.move_up(&MoveUp, window, cx);
17539        editor.delete_line(&DeleteLine, window, cx);
17540    });
17541    executor.run_until_parked();
17542    cx.assert_state_with_diff(
17543        r#"
17544        use some::mod1;
17545        use some::mod2;
17546
17547        const A: u32 = 42;
17548      + const B: u32 = 42;
17549        ˇ
17550        fn main() {
17551            println!("hello");
17552
17553            println!("world");
17554        }
17555      "#
17556        .unindent(),
17557    );
17558
17559    cx.update_editor(|editor, window, cx| {
17560        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
17561        editor.delete_line(&DeleteLine, window, cx);
17562    });
17563    executor.run_until_parked();
17564    cx.assert_state_with_diff(
17565        r#"
17566        ˇ
17567        fn main() {
17568            println!("hello");
17569
17570            println!("world");
17571        }
17572      "#
17573        .unindent(),
17574    );
17575}
17576
17577#[gpui::test]
17578async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
17579    init_test(cx, |_| {});
17580
17581    let mut cx = EditorTestContext::new(cx).await;
17582    cx.set_head_text(indoc! { "
17583        one
17584        two
17585        three
17586        four
17587        five
17588        "
17589    });
17590    cx.set_state(indoc! { "
17591        one
17592        ˇthree
17593        five
17594    "});
17595    cx.run_until_parked();
17596    cx.update_editor(|editor, window, cx| {
17597        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17598    });
17599    cx.assert_state_with_diff(
17600        indoc! { "
17601        one
17602      - two
17603        ˇthree
17604      - four
17605        five
17606    "}
17607        .to_string(),
17608    );
17609    cx.update_editor(|editor, window, cx| {
17610        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17611    });
17612
17613    cx.assert_state_with_diff(
17614        indoc! { "
17615        one
17616        ˇthree
17617        five
17618    "}
17619        .to_string(),
17620    );
17621
17622    cx.set_state(indoc! { "
17623        one
17624        ˇTWO
17625        three
17626        four
17627        five
17628    "});
17629    cx.run_until_parked();
17630    cx.update_editor(|editor, window, cx| {
17631        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17632    });
17633
17634    cx.assert_state_with_diff(
17635        indoc! { "
17636            one
17637          - two
17638          + ˇTWO
17639            three
17640            four
17641            five
17642        "}
17643        .to_string(),
17644    );
17645    cx.update_editor(|editor, window, cx| {
17646        editor.move_up(&Default::default(), window, cx);
17647        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17648    });
17649    cx.assert_state_with_diff(
17650        indoc! { "
17651            one
17652            ˇTWO
17653            three
17654            four
17655            five
17656        "}
17657        .to_string(),
17658    );
17659}
17660
17661#[gpui::test]
17662async fn test_edits_around_expanded_deletion_hunks(
17663    executor: BackgroundExecutor,
17664    cx: &mut TestAppContext,
17665) {
17666    init_test(cx, |_| {});
17667
17668    let mut cx = EditorTestContext::new(cx).await;
17669
17670    let diff_base = r#"
17671        use some::mod1;
17672        use some::mod2;
17673
17674        const A: u32 = 42;
17675        const B: u32 = 42;
17676        const C: u32 = 42;
17677
17678
17679        fn main() {
17680            println!("hello");
17681
17682            println!("world");
17683        }
17684    "#
17685    .unindent();
17686    executor.run_until_parked();
17687    cx.set_state(
17688        &r#"
17689        use some::mod1;
17690        use some::mod2;
17691
17692        ˇconst B: u32 = 42;
17693        const C: u32 = 42;
17694
17695
17696        fn main() {
17697            println!("hello");
17698
17699            println!("world");
17700        }
17701        "#
17702        .unindent(),
17703    );
17704
17705    cx.set_head_text(&diff_base);
17706    executor.run_until_parked();
17707
17708    cx.update_editor(|editor, window, cx| {
17709        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17710    });
17711    executor.run_until_parked();
17712
17713    cx.assert_state_with_diff(
17714        r#"
17715        use some::mod1;
17716        use some::mod2;
17717
17718      - const A: u32 = 42;
17719        ˇconst B: u32 = 42;
17720        const C: u32 = 42;
17721
17722
17723        fn main() {
17724            println!("hello");
17725
17726            println!("world");
17727        }
17728      "#
17729        .unindent(),
17730    );
17731
17732    cx.update_editor(|editor, window, cx| {
17733        editor.delete_line(&DeleteLine, window, cx);
17734    });
17735    executor.run_until_parked();
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.handle_input("replacement", 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      + replacementˇ
17792
17793        fn main() {
17794            println!("hello");
17795
17796            println!("world");
17797        }
17798      "#
17799        .unindent(),
17800    );
17801}
17802
17803#[gpui::test]
17804async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17805    init_test(cx, |_| {});
17806
17807    let mut cx = EditorTestContext::new(cx).await;
17808
17809    let base_text = r#"
17810        one
17811        two
17812        three
17813        four
17814        five
17815    "#
17816    .unindent();
17817    executor.run_until_parked();
17818    cx.set_state(
17819        &r#"
17820        one
17821        two
17822        fˇour
17823        five
17824        "#
17825        .unindent(),
17826    );
17827
17828    cx.set_head_text(&base_text);
17829    executor.run_until_parked();
17830
17831    cx.update_editor(|editor, window, cx| {
17832        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17833    });
17834    executor.run_until_parked();
17835
17836    cx.assert_state_with_diff(
17837        r#"
17838          one
17839          two
17840        - three
17841          fˇour
17842          five
17843        "#
17844        .unindent(),
17845    );
17846
17847    cx.update_editor(|editor, window, cx| {
17848        editor.backspace(&Backspace, window, cx);
17849        editor.backspace(&Backspace, window, cx);
17850    });
17851    executor.run_until_parked();
17852    cx.assert_state_with_diff(
17853        r#"
17854          one
17855          two
17856        - threeˇ
17857        - four
17858        + our
17859          five
17860        "#
17861        .unindent(),
17862    );
17863}
17864
17865#[gpui::test]
17866async fn test_edit_after_expanded_modification_hunk(
17867    executor: BackgroundExecutor,
17868    cx: &mut TestAppContext,
17869) {
17870    init_test(cx, |_| {});
17871
17872    let mut cx = EditorTestContext::new(cx).await;
17873
17874    let diff_base = r#"
17875        use some::mod1;
17876        use some::mod2;
17877
17878        const A: u32 = 42;
17879        const B: u32 = 42;
17880        const C: u32 = 42;
17881        const D: u32 = 42;
17882
17883
17884        fn main() {
17885            println!("hello");
17886
17887            println!("world");
17888        }"#
17889    .unindent();
17890
17891    cx.set_state(
17892        &r#"
17893        use some::mod1;
17894        use some::mod2;
17895
17896        const A: u32 = 42;
17897        const B: u32 = 42;
17898        const C: u32 = 43ˇ
17899        const D: u32 = 42;
17900
17901
17902        fn main() {
17903            println!("hello");
17904
17905            println!("world");
17906        }"#
17907        .unindent(),
17908    );
17909
17910    cx.set_head_text(&diff_base);
17911    executor.run_until_parked();
17912    cx.update_editor(|editor, window, cx| {
17913        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17914    });
17915    executor.run_until_parked();
17916
17917    cx.assert_state_with_diff(
17918        r#"
17919        use some::mod1;
17920        use some::mod2;
17921
17922        const A: u32 = 42;
17923        const B: u32 = 42;
17924      - const C: u32 = 42;
17925      + const C: u32 = 43ˇ
17926        const D: u32 = 42;
17927
17928
17929        fn main() {
17930            println!("hello");
17931
17932            println!("world");
17933        }"#
17934        .unindent(),
17935    );
17936
17937    cx.update_editor(|editor, window, cx| {
17938        editor.handle_input("\nnew_line\n", window, cx);
17939    });
17940    executor.run_until_parked();
17941
17942    cx.assert_state_with_diff(
17943        r#"
17944        use some::mod1;
17945        use some::mod2;
17946
17947        const A: u32 = 42;
17948        const B: u32 = 42;
17949      - const C: u32 = 42;
17950      + const C: u32 = 43
17951      + new_line
17952      + ˇ
17953        const D: u32 = 42;
17954
17955
17956        fn main() {
17957            println!("hello");
17958
17959            println!("world");
17960        }"#
17961        .unindent(),
17962    );
17963}
17964
17965#[gpui::test]
17966async fn test_stage_and_unstage_added_file_hunk(
17967    executor: BackgroundExecutor,
17968    cx: &mut TestAppContext,
17969) {
17970    init_test(cx, |_| {});
17971
17972    let mut cx = EditorTestContext::new(cx).await;
17973    cx.update_editor(|editor, _, cx| {
17974        editor.set_expand_all_diff_hunks(cx);
17975    });
17976
17977    let working_copy = r#"
17978            ˇfn main() {
17979                println!("hello, world!");
17980            }
17981        "#
17982    .unindent();
17983
17984    cx.set_state(&working_copy);
17985    executor.run_until_parked();
17986
17987    cx.assert_state_with_diff(
17988        r#"
17989            + ˇfn main() {
17990            +     println!("hello, world!");
17991            + }
17992        "#
17993        .unindent(),
17994    );
17995    cx.assert_index_text(None);
17996
17997    cx.update_editor(|editor, window, cx| {
17998        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
17999    });
18000    executor.run_until_parked();
18001    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
18002    cx.assert_state_with_diff(
18003        r#"
18004            + ˇfn main() {
18005            +     println!("hello, world!");
18006            + }
18007        "#
18008        .unindent(),
18009    );
18010
18011    cx.update_editor(|editor, window, cx| {
18012        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18013    });
18014    executor.run_until_parked();
18015    cx.assert_index_text(None);
18016}
18017
18018async fn setup_indent_guides_editor(
18019    text: &str,
18020    cx: &mut TestAppContext,
18021) -> (BufferId, EditorTestContext) {
18022    init_test(cx, |_| {});
18023
18024    let mut cx = EditorTestContext::new(cx).await;
18025
18026    let buffer_id = cx.update_editor(|editor, window, cx| {
18027        editor.set_text(text, window, cx);
18028        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
18029
18030        buffer_ids[0]
18031    });
18032
18033    (buffer_id, cx)
18034}
18035
18036fn assert_indent_guides(
18037    range: Range<u32>,
18038    expected: Vec<IndentGuide>,
18039    active_indices: Option<Vec<usize>>,
18040    cx: &mut EditorTestContext,
18041) {
18042    let indent_guides = cx.update_editor(|editor, window, cx| {
18043        let snapshot = editor.snapshot(window, cx).display_snapshot;
18044        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
18045            editor,
18046            MultiBufferRow(range.start)..MultiBufferRow(range.end),
18047            true,
18048            &snapshot,
18049            cx,
18050        );
18051
18052        indent_guides.sort_by(|a, b| {
18053            a.depth.cmp(&b.depth).then(
18054                a.start_row
18055                    .cmp(&b.start_row)
18056                    .then(a.end_row.cmp(&b.end_row)),
18057            )
18058        });
18059        indent_guides
18060    });
18061
18062    if let Some(expected) = active_indices {
18063        let active_indices = cx.update_editor(|editor, window, cx| {
18064            let snapshot = editor.snapshot(window, cx).display_snapshot;
18065            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
18066        });
18067
18068        assert_eq!(
18069            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
18070            expected,
18071            "Active indent guide indices do not match"
18072        );
18073    }
18074
18075    assert_eq!(indent_guides, expected, "Indent guides do not match");
18076}
18077
18078fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
18079    IndentGuide {
18080        buffer_id,
18081        start_row: MultiBufferRow(start_row),
18082        end_row: MultiBufferRow(end_row),
18083        depth,
18084        tab_size: 4,
18085        settings: IndentGuideSettings {
18086            enabled: true,
18087            line_width: 1,
18088            active_line_width: 1,
18089            ..Default::default()
18090        },
18091    }
18092}
18093
18094#[gpui::test]
18095async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
18096    let (buffer_id, mut cx) = setup_indent_guides_editor(
18097        &"
18098        fn main() {
18099            let a = 1;
18100        }"
18101        .unindent(),
18102        cx,
18103    )
18104    .await;
18105
18106    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
18107}
18108
18109#[gpui::test]
18110async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
18111    let (buffer_id, mut cx) = setup_indent_guides_editor(
18112        &"
18113        fn main() {
18114            let a = 1;
18115            let b = 2;
18116        }"
18117        .unindent(),
18118        cx,
18119    )
18120    .await;
18121
18122    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
18123}
18124
18125#[gpui::test]
18126async fn test_indent_guide_nested(cx: &mut TestAppContext) {
18127    let (buffer_id, mut cx) = setup_indent_guides_editor(
18128        &"
18129        fn main() {
18130            let a = 1;
18131            if a == 3 {
18132                let b = 2;
18133            } else {
18134                let c = 3;
18135            }
18136        }"
18137        .unindent(),
18138        cx,
18139    )
18140    .await;
18141
18142    assert_indent_guides(
18143        0..8,
18144        vec![
18145            indent_guide(buffer_id, 1, 6, 0),
18146            indent_guide(buffer_id, 3, 3, 1),
18147            indent_guide(buffer_id, 5, 5, 1),
18148        ],
18149        None,
18150        &mut cx,
18151    );
18152}
18153
18154#[gpui::test]
18155async fn test_indent_guide_tab(cx: &mut TestAppContext) {
18156    let (buffer_id, mut cx) = setup_indent_guides_editor(
18157        &"
18158        fn main() {
18159            let a = 1;
18160                let b = 2;
18161            let c = 3;
18162        }"
18163        .unindent(),
18164        cx,
18165    )
18166    .await;
18167
18168    assert_indent_guides(
18169        0..5,
18170        vec![
18171            indent_guide(buffer_id, 1, 3, 0),
18172            indent_guide(buffer_id, 2, 2, 1),
18173        ],
18174        None,
18175        &mut cx,
18176    );
18177}
18178
18179#[gpui::test]
18180async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
18181    let (buffer_id, mut cx) = setup_indent_guides_editor(
18182        &"
18183        fn main() {
18184            let a = 1;
18185
18186            let c = 3;
18187        }"
18188        .unindent(),
18189        cx,
18190    )
18191    .await;
18192
18193    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
18194}
18195
18196#[gpui::test]
18197async fn test_indent_guide_complex(cx: &mut TestAppContext) {
18198    let (buffer_id, mut cx) = setup_indent_guides_editor(
18199        &"
18200        fn main() {
18201            let a = 1;
18202
18203            let c = 3;
18204
18205            if a == 3 {
18206                let b = 2;
18207            } else {
18208                let c = 3;
18209            }
18210        }"
18211        .unindent(),
18212        cx,
18213    )
18214    .await;
18215
18216    assert_indent_guides(
18217        0..11,
18218        vec![
18219            indent_guide(buffer_id, 1, 9, 0),
18220            indent_guide(buffer_id, 6, 6, 1),
18221            indent_guide(buffer_id, 8, 8, 1),
18222        ],
18223        None,
18224        &mut cx,
18225    );
18226}
18227
18228#[gpui::test]
18229async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
18230    let (buffer_id, mut cx) = setup_indent_guides_editor(
18231        &"
18232        fn main() {
18233            let a = 1;
18234
18235            let c = 3;
18236
18237            if a == 3 {
18238                let b = 2;
18239            } else {
18240                let c = 3;
18241            }
18242        }"
18243        .unindent(),
18244        cx,
18245    )
18246    .await;
18247
18248    assert_indent_guides(
18249        1..11,
18250        vec![
18251            indent_guide(buffer_id, 1, 9, 0),
18252            indent_guide(buffer_id, 6, 6, 1),
18253            indent_guide(buffer_id, 8, 8, 1),
18254        ],
18255        None,
18256        &mut cx,
18257    );
18258}
18259
18260#[gpui::test]
18261async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
18262    let (buffer_id, mut cx) = setup_indent_guides_editor(
18263        &"
18264        fn main() {
18265            let a = 1;
18266
18267            let c = 3;
18268
18269            if a == 3 {
18270                let b = 2;
18271            } else {
18272                let c = 3;
18273            }
18274        }"
18275        .unindent(),
18276        cx,
18277    )
18278    .await;
18279
18280    assert_indent_guides(
18281        1..10,
18282        vec![
18283            indent_guide(buffer_id, 1, 9, 0),
18284            indent_guide(buffer_id, 6, 6, 1),
18285            indent_guide(buffer_id, 8, 8, 1),
18286        ],
18287        None,
18288        &mut cx,
18289    );
18290}
18291
18292#[gpui::test]
18293async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
18294    let (buffer_id, mut cx) = setup_indent_guides_editor(
18295        &"
18296        fn main() {
18297            if a {
18298                b(
18299                    c,
18300                    d,
18301                )
18302            } else {
18303                e(
18304                    f
18305                )
18306            }
18307        }"
18308        .unindent(),
18309        cx,
18310    )
18311    .await;
18312
18313    assert_indent_guides(
18314        0..11,
18315        vec![
18316            indent_guide(buffer_id, 1, 10, 0),
18317            indent_guide(buffer_id, 2, 5, 1),
18318            indent_guide(buffer_id, 7, 9, 1),
18319            indent_guide(buffer_id, 3, 4, 2),
18320            indent_guide(buffer_id, 8, 8, 2),
18321        ],
18322        None,
18323        &mut cx,
18324    );
18325
18326    cx.update_editor(|editor, window, cx| {
18327        editor.fold_at(MultiBufferRow(2), window, cx);
18328        assert_eq!(
18329            editor.display_text(cx),
18330            "
18331            fn main() {
18332                if a {
18333                    b(⋯
18334                    )
18335                } else {
18336                    e(
18337                        f
18338                    )
18339                }
18340            }"
18341            .unindent()
18342        );
18343    });
18344
18345    assert_indent_guides(
18346        0..11,
18347        vec![
18348            indent_guide(buffer_id, 1, 10, 0),
18349            indent_guide(buffer_id, 2, 5, 1),
18350            indent_guide(buffer_id, 7, 9, 1),
18351            indent_guide(buffer_id, 8, 8, 2),
18352        ],
18353        None,
18354        &mut cx,
18355    );
18356}
18357
18358#[gpui::test]
18359async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
18360    let (buffer_id, mut cx) = setup_indent_guides_editor(
18361        &"
18362        block1
18363            block2
18364                block3
18365                    block4
18366            block2
18367        block1
18368        block1"
18369            .unindent(),
18370        cx,
18371    )
18372    .await;
18373
18374    assert_indent_guides(
18375        1..10,
18376        vec![
18377            indent_guide(buffer_id, 1, 4, 0),
18378            indent_guide(buffer_id, 2, 3, 1),
18379            indent_guide(buffer_id, 3, 3, 2),
18380        ],
18381        None,
18382        &mut cx,
18383    );
18384}
18385
18386#[gpui::test]
18387async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
18388    let (buffer_id, mut cx) = setup_indent_guides_editor(
18389        &"
18390        block1
18391            block2
18392                block3
18393
18394        block1
18395        block1"
18396            .unindent(),
18397        cx,
18398    )
18399    .await;
18400
18401    assert_indent_guides(
18402        0..6,
18403        vec![
18404            indent_guide(buffer_id, 1, 2, 0),
18405            indent_guide(buffer_id, 2, 2, 1),
18406        ],
18407        None,
18408        &mut cx,
18409    );
18410}
18411
18412#[gpui::test]
18413async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
18414    let (buffer_id, mut cx) = setup_indent_guides_editor(
18415        &"
18416        function component() {
18417        \treturn (
18418        \t\t\t
18419        \t\t<div>
18420        \t\t\t<abc></abc>
18421        \t\t</div>
18422        \t)
18423        }"
18424        .unindent(),
18425        cx,
18426    )
18427    .await;
18428
18429    assert_indent_guides(
18430        0..8,
18431        vec![
18432            indent_guide(buffer_id, 1, 6, 0),
18433            indent_guide(buffer_id, 2, 5, 1),
18434            indent_guide(buffer_id, 4, 4, 2),
18435        ],
18436        None,
18437        &mut cx,
18438    );
18439}
18440
18441#[gpui::test]
18442async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
18443    let (buffer_id, mut cx) = setup_indent_guides_editor(
18444        &"
18445        function component() {
18446        \treturn (
18447        \t
18448        \t\t<div>
18449        \t\t\t<abc></abc>
18450        \t\t</div>
18451        \t)
18452        }"
18453        .unindent(),
18454        cx,
18455    )
18456    .await;
18457
18458    assert_indent_guides(
18459        0..8,
18460        vec![
18461            indent_guide(buffer_id, 1, 6, 0),
18462            indent_guide(buffer_id, 2, 5, 1),
18463            indent_guide(buffer_id, 4, 4, 2),
18464        ],
18465        None,
18466        &mut cx,
18467    );
18468}
18469
18470#[gpui::test]
18471async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
18472    let (buffer_id, mut cx) = setup_indent_guides_editor(
18473        &"
18474        block1
18475
18476
18477
18478            block2
18479        "
18480        .unindent(),
18481        cx,
18482    )
18483    .await;
18484
18485    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
18486}
18487
18488#[gpui::test]
18489async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
18490    let (buffer_id, mut cx) = setup_indent_guides_editor(
18491        &"
18492        def a:
18493        \tb = 3
18494        \tif True:
18495        \t\tc = 4
18496        \t\td = 5
18497        \tprint(b)
18498        "
18499        .unindent(),
18500        cx,
18501    )
18502    .await;
18503
18504    assert_indent_guides(
18505        0..6,
18506        vec![
18507            indent_guide(buffer_id, 1, 5, 0),
18508            indent_guide(buffer_id, 3, 4, 1),
18509        ],
18510        None,
18511        &mut cx,
18512    );
18513}
18514
18515#[gpui::test]
18516async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
18517    let (buffer_id, mut cx) = setup_indent_guides_editor(
18518        &"
18519    fn main() {
18520        let a = 1;
18521    }"
18522        .unindent(),
18523        cx,
18524    )
18525    .await;
18526
18527    cx.update_editor(|editor, window, cx| {
18528        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18529            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18530        });
18531    });
18532
18533    assert_indent_guides(
18534        0..3,
18535        vec![indent_guide(buffer_id, 1, 1, 0)],
18536        Some(vec![0]),
18537        &mut cx,
18538    );
18539}
18540
18541#[gpui::test]
18542async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
18543    let (buffer_id, mut cx) = setup_indent_guides_editor(
18544        &"
18545    fn main() {
18546        if 1 == 2 {
18547            let a = 1;
18548        }
18549    }"
18550        .unindent(),
18551        cx,
18552    )
18553    .await;
18554
18555    cx.update_editor(|editor, window, cx| {
18556        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18557            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18558        });
18559    });
18560
18561    assert_indent_guides(
18562        0..4,
18563        vec![
18564            indent_guide(buffer_id, 1, 3, 0),
18565            indent_guide(buffer_id, 2, 2, 1),
18566        ],
18567        Some(vec![1]),
18568        &mut cx,
18569    );
18570
18571    cx.update_editor(|editor, window, cx| {
18572        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18573            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
18574        });
18575    });
18576
18577    assert_indent_guides(
18578        0..4,
18579        vec![
18580            indent_guide(buffer_id, 1, 3, 0),
18581            indent_guide(buffer_id, 2, 2, 1),
18582        ],
18583        Some(vec![1]),
18584        &mut cx,
18585    );
18586
18587    cx.update_editor(|editor, window, cx| {
18588        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18589            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
18590        });
18591    });
18592
18593    assert_indent_guides(
18594        0..4,
18595        vec![
18596            indent_guide(buffer_id, 1, 3, 0),
18597            indent_guide(buffer_id, 2, 2, 1),
18598        ],
18599        Some(vec![0]),
18600        &mut cx,
18601    );
18602}
18603
18604#[gpui::test]
18605async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
18606    let (buffer_id, mut cx) = setup_indent_guides_editor(
18607        &"
18608    fn main() {
18609        let a = 1;
18610
18611        let b = 2;
18612    }"
18613        .unindent(),
18614        cx,
18615    )
18616    .await;
18617
18618    cx.update_editor(|editor, window, cx| {
18619        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18620            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
18621        });
18622    });
18623
18624    assert_indent_guides(
18625        0..5,
18626        vec![indent_guide(buffer_id, 1, 3, 0)],
18627        Some(vec![0]),
18628        &mut cx,
18629    );
18630}
18631
18632#[gpui::test]
18633async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
18634    let (buffer_id, mut cx) = setup_indent_guides_editor(
18635        &"
18636    def m:
18637        a = 1
18638        pass"
18639            .unindent(),
18640        cx,
18641    )
18642    .await;
18643
18644    cx.update_editor(|editor, window, cx| {
18645        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18646            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18647        });
18648    });
18649
18650    assert_indent_guides(
18651        0..3,
18652        vec![indent_guide(buffer_id, 1, 2, 0)],
18653        Some(vec![0]),
18654        &mut cx,
18655    );
18656}
18657
18658#[gpui::test]
18659async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
18660    init_test(cx, |_| {});
18661    let mut cx = EditorTestContext::new(cx).await;
18662    let text = indoc! {
18663        "
18664        impl A {
18665            fn b() {
18666                0;
18667                3;
18668                5;
18669                6;
18670                7;
18671            }
18672        }
18673        "
18674    };
18675    let base_text = indoc! {
18676        "
18677        impl A {
18678            fn b() {
18679                0;
18680                1;
18681                2;
18682                3;
18683                4;
18684            }
18685            fn c() {
18686                5;
18687                6;
18688                7;
18689            }
18690        }
18691        "
18692    };
18693
18694    cx.update_editor(|editor, window, cx| {
18695        editor.set_text(text, window, cx);
18696
18697        editor.buffer().update(cx, |multibuffer, cx| {
18698            let buffer = multibuffer.as_singleton().unwrap();
18699            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
18700
18701            multibuffer.set_all_diff_hunks_expanded(cx);
18702            multibuffer.add_diff(diff, cx);
18703
18704            buffer.read(cx).remote_id()
18705        })
18706    });
18707    cx.run_until_parked();
18708
18709    cx.assert_state_with_diff(
18710        indoc! { "
18711          impl A {
18712              fn b() {
18713                  0;
18714        -         1;
18715        -         2;
18716                  3;
18717        -         4;
18718        -     }
18719        -     fn c() {
18720                  5;
18721                  6;
18722                  7;
18723              }
18724          }
18725          ˇ"
18726        }
18727        .to_string(),
18728    );
18729
18730    let mut actual_guides = cx.update_editor(|editor, window, cx| {
18731        editor
18732            .snapshot(window, cx)
18733            .buffer_snapshot
18734            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
18735            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
18736            .collect::<Vec<_>>()
18737    });
18738    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
18739    assert_eq!(
18740        actual_guides,
18741        vec![
18742            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
18743            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
18744            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
18745        ]
18746    );
18747}
18748
18749#[gpui::test]
18750async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18751    init_test(cx, |_| {});
18752    let mut cx = EditorTestContext::new(cx).await;
18753
18754    let diff_base = r#"
18755        a
18756        b
18757        c
18758        "#
18759    .unindent();
18760
18761    cx.set_state(
18762        &r#"
18763        ˇA
18764        b
18765        C
18766        "#
18767        .unindent(),
18768    );
18769    cx.set_head_text(&diff_base);
18770    cx.update_editor(|editor, window, cx| {
18771        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18772    });
18773    executor.run_until_parked();
18774
18775    let both_hunks_expanded = r#"
18776        - a
18777        + ˇA
18778          b
18779        - c
18780        + C
18781        "#
18782    .unindent();
18783
18784    cx.assert_state_with_diff(both_hunks_expanded.clone());
18785
18786    let hunk_ranges = cx.update_editor(|editor, window, cx| {
18787        let snapshot = editor.snapshot(window, cx);
18788        let hunks = editor
18789            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18790            .collect::<Vec<_>>();
18791        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
18792        let buffer_id = hunks[0].buffer_id;
18793        hunks
18794            .into_iter()
18795            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
18796            .collect::<Vec<_>>()
18797    });
18798    assert_eq!(hunk_ranges.len(), 2);
18799
18800    cx.update_editor(|editor, _, cx| {
18801        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18802    });
18803    executor.run_until_parked();
18804
18805    let second_hunk_expanded = r#"
18806          ˇA
18807          b
18808        - c
18809        + C
18810        "#
18811    .unindent();
18812
18813    cx.assert_state_with_diff(second_hunk_expanded);
18814
18815    cx.update_editor(|editor, _, cx| {
18816        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18817    });
18818    executor.run_until_parked();
18819
18820    cx.assert_state_with_diff(both_hunks_expanded.clone());
18821
18822    cx.update_editor(|editor, _, cx| {
18823        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
18824    });
18825    executor.run_until_parked();
18826
18827    let first_hunk_expanded = r#"
18828        - a
18829        + ˇA
18830          b
18831          C
18832        "#
18833    .unindent();
18834
18835    cx.assert_state_with_diff(first_hunk_expanded);
18836
18837    cx.update_editor(|editor, _, cx| {
18838        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
18839    });
18840    executor.run_until_parked();
18841
18842    cx.assert_state_with_diff(both_hunks_expanded);
18843
18844    cx.set_state(
18845        &r#"
18846        ˇA
18847        b
18848        "#
18849        .unindent(),
18850    );
18851    cx.run_until_parked();
18852
18853    // TODO this cursor position seems bad
18854    cx.assert_state_with_diff(
18855        r#"
18856        - ˇa
18857        + A
18858          b
18859        "#
18860        .unindent(),
18861    );
18862
18863    cx.update_editor(|editor, window, cx| {
18864        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18865    });
18866
18867    cx.assert_state_with_diff(
18868        r#"
18869            - ˇa
18870            + A
18871              b
18872            - c
18873            "#
18874        .unindent(),
18875    );
18876
18877    let hunk_ranges = cx.update_editor(|editor, window, cx| {
18878        let snapshot = editor.snapshot(window, cx);
18879        let hunks = editor
18880            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18881            .collect::<Vec<_>>();
18882        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
18883        let buffer_id = hunks[0].buffer_id;
18884        hunks
18885            .into_iter()
18886            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
18887            .collect::<Vec<_>>()
18888    });
18889    assert_eq!(hunk_ranges.len(), 2);
18890
18891    cx.update_editor(|editor, _, cx| {
18892        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
18893    });
18894    executor.run_until_parked();
18895
18896    cx.assert_state_with_diff(
18897        r#"
18898        - ˇa
18899        + A
18900          b
18901        "#
18902        .unindent(),
18903    );
18904}
18905
18906#[gpui::test]
18907async fn test_toggle_deletion_hunk_at_start_of_file(
18908    executor: BackgroundExecutor,
18909    cx: &mut TestAppContext,
18910) {
18911    init_test(cx, |_| {});
18912    let mut cx = EditorTestContext::new(cx).await;
18913
18914    let diff_base = r#"
18915        a
18916        b
18917        c
18918        "#
18919    .unindent();
18920
18921    cx.set_state(
18922        &r#"
18923        ˇb
18924        c
18925        "#
18926        .unindent(),
18927    );
18928    cx.set_head_text(&diff_base);
18929    cx.update_editor(|editor, window, cx| {
18930        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18931    });
18932    executor.run_until_parked();
18933
18934    let hunk_expanded = r#"
18935        - a
18936          ˇb
18937          c
18938        "#
18939    .unindent();
18940
18941    cx.assert_state_with_diff(hunk_expanded.clone());
18942
18943    let hunk_ranges = cx.update_editor(|editor, window, cx| {
18944        let snapshot = editor.snapshot(window, cx);
18945        let hunks = editor
18946            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18947            .collect::<Vec<_>>();
18948        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
18949        let buffer_id = hunks[0].buffer_id;
18950        hunks
18951            .into_iter()
18952            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
18953            .collect::<Vec<_>>()
18954    });
18955    assert_eq!(hunk_ranges.len(), 1);
18956
18957    cx.update_editor(|editor, _, cx| {
18958        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18959    });
18960    executor.run_until_parked();
18961
18962    let hunk_collapsed = r#"
18963          ˇb
18964          c
18965        "#
18966    .unindent();
18967
18968    cx.assert_state_with_diff(hunk_collapsed);
18969
18970    cx.update_editor(|editor, _, cx| {
18971        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18972    });
18973    executor.run_until_parked();
18974
18975    cx.assert_state_with_diff(hunk_expanded.clone());
18976}
18977
18978#[gpui::test]
18979async fn test_display_diff_hunks(cx: &mut TestAppContext) {
18980    init_test(cx, |_| {});
18981
18982    let fs = FakeFs::new(cx.executor());
18983    fs.insert_tree(
18984        path!("/test"),
18985        json!({
18986            ".git": {},
18987            "file-1": "ONE\n",
18988            "file-2": "TWO\n",
18989            "file-3": "THREE\n",
18990        }),
18991    )
18992    .await;
18993
18994    fs.set_head_for_repo(
18995        path!("/test/.git").as_ref(),
18996        &[
18997            ("file-1".into(), "one\n".into()),
18998            ("file-2".into(), "two\n".into()),
18999            ("file-3".into(), "three\n".into()),
19000        ],
19001        "deadbeef",
19002    );
19003
19004    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
19005    let mut buffers = vec![];
19006    for i in 1..=3 {
19007        let buffer = project
19008            .update(cx, |project, cx| {
19009                let path = format!(path!("/test/file-{}"), i);
19010                project.open_local_buffer(path, cx)
19011            })
19012            .await
19013            .unwrap();
19014        buffers.push(buffer);
19015    }
19016
19017    let multibuffer = cx.new(|cx| {
19018        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
19019        multibuffer.set_all_diff_hunks_expanded(cx);
19020        for buffer in &buffers {
19021            let snapshot = buffer.read(cx).snapshot();
19022            multibuffer.set_excerpts_for_path(
19023                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
19024                buffer.clone(),
19025                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
19026                DEFAULT_MULTIBUFFER_CONTEXT,
19027                cx,
19028            );
19029        }
19030        multibuffer
19031    });
19032
19033    let editor = cx.add_window(|window, cx| {
19034        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
19035    });
19036    cx.run_until_parked();
19037
19038    let snapshot = editor
19039        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19040        .unwrap();
19041    let hunks = snapshot
19042        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
19043        .map(|hunk| match hunk {
19044            DisplayDiffHunk::Unfolded {
19045                display_row_range, ..
19046            } => display_row_range,
19047            DisplayDiffHunk::Folded { .. } => unreachable!(),
19048        })
19049        .collect::<Vec<_>>();
19050    assert_eq!(
19051        hunks,
19052        [
19053            DisplayRow(2)..DisplayRow(4),
19054            DisplayRow(7)..DisplayRow(9),
19055            DisplayRow(12)..DisplayRow(14),
19056        ]
19057    );
19058}
19059
19060#[gpui::test]
19061async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
19062    init_test(cx, |_| {});
19063
19064    let mut cx = EditorTestContext::new(cx).await;
19065    cx.set_head_text(indoc! { "
19066        one
19067        two
19068        three
19069        four
19070        five
19071        "
19072    });
19073    cx.set_index_text(indoc! { "
19074        one
19075        two
19076        three
19077        four
19078        five
19079        "
19080    });
19081    cx.set_state(indoc! {"
19082        one
19083        TWO
19084        ˇTHREE
19085        FOUR
19086        five
19087    "});
19088    cx.run_until_parked();
19089    cx.update_editor(|editor, window, cx| {
19090        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19091    });
19092    cx.run_until_parked();
19093    cx.assert_index_text(Some(indoc! {"
19094        one
19095        TWO
19096        THREE
19097        FOUR
19098        five
19099    "}));
19100    cx.set_state(indoc! { "
19101        one
19102        TWO
19103        ˇTHREE-HUNDRED
19104        FOUR
19105        five
19106    "});
19107    cx.run_until_parked();
19108    cx.update_editor(|editor, window, cx| {
19109        let snapshot = editor.snapshot(window, cx);
19110        let hunks = editor
19111            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19112            .collect::<Vec<_>>();
19113        assert_eq!(hunks.len(), 1);
19114        assert_eq!(
19115            hunks[0].status(),
19116            DiffHunkStatus {
19117                kind: DiffHunkStatusKind::Modified,
19118                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
19119            }
19120        );
19121
19122        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19123    });
19124    cx.run_until_parked();
19125    cx.assert_index_text(Some(indoc! {"
19126        one
19127        TWO
19128        THREE-HUNDRED
19129        FOUR
19130        five
19131    "}));
19132}
19133
19134#[gpui::test]
19135fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
19136    init_test(cx, |_| {});
19137
19138    let editor = cx.add_window(|window, cx| {
19139        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
19140        build_editor(buffer, window, cx)
19141    });
19142
19143    let render_args = Arc::new(Mutex::new(None));
19144    let snapshot = editor
19145        .update(cx, |editor, window, cx| {
19146            let snapshot = editor.buffer().read(cx).snapshot(cx);
19147            let range =
19148                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
19149
19150            struct RenderArgs {
19151                row: MultiBufferRow,
19152                folded: bool,
19153                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
19154            }
19155
19156            let crease = Crease::inline(
19157                range,
19158                FoldPlaceholder::test(),
19159                {
19160                    let toggle_callback = render_args.clone();
19161                    move |row, folded, callback, _window, _cx| {
19162                        *toggle_callback.lock() = Some(RenderArgs {
19163                            row,
19164                            folded,
19165                            callback,
19166                        });
19167                        div()
19168                    }
19169                },
19170                |_row, _folded, _window, _cx| div(),
19171            );
19172
19173            editor.insert_creases(Some(crease), cx);
19174            let snapshot = editor.snapshot(window, cx);
19175            let _div = snapshot.render_crease_toggle(
19176                MultiBufferRow(1),
19177                false,
19178                cx.entity().clone(),
19179                window,
19180                cx,
19181            );
19182            snapshot
19183        })
19184        .unwrap();
19185
19186    let render_args = render_args.lock().take().unwrap();
19187    assert_eq!(render_args.row, MultiBufferRow(1));
19188    assert!(!render_args.folded);
19189    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
19190
19191    cx.update_window(*editor, |_, window, cx| {
19192        (render_args.callback)(true, window, cx)
19193    })
19194    .unwrap();
19195    let snapshot = editor
19196        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19197        .unwrap();
19198    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
19199
19200    cx.update_window(*editor, |_, window, cx| {
19201        (render_args.callback)(false, window, cx)
19202    })
19203    .unwrap();
19204    let snapshot = editor
19205        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19206        .unwrap();
19207    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
19208}
19209
19210#[gpui::test]
19211async fn test_input_text(cx: &mut TestAppContext) {
19212    init_test(cx, |_| {});
19213    let mut cx = EditorTestContext::new(cx).await;
19214
19215    cx.set_state(
19216        &r#"ˇone
19217        two
19218
19219        three
19220        fourˇ
19221        five
19222
19223        siˇx"#
19224            .unindent(),
19225    );
19226
19227    cx.dispatch_action(HandleInput(String::new()));
19228    cx.assert_editor_state(
19229        &r#"ˇone
19230        two
19231
19232        three
19233        fourˇ
19234        five
19235
19236        siˇx"#
19237            .unindent(),
19238    );
19239
19240    cx.dispatch_action(HandleInput("AAAA".to_string()));
19241    cx.assert_editor_state(
19242        &r#"AAAAˇone
19243        two
19244
19245        three
19246        fourAAAAˇ
19247        five
19248
19249        siAAAAˇx"#
19250            .unindent(),
19251    );
19252}
19253
19254#[gpui::test]
19255async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
19256    init_test(cx, |_| {});
19257
19258    let mut cx = EditorTestContext::new(cx).await;
19259    cx.set_state(
19260        r#"let foo = 1;
19261let foo = 2;
19262let foo = 3;
19263let fooˇ = 4;
19264let foo = 5;
19265let foo = 6;
19266let foo = 7;
19267let foo = 8;
19268let foo = 9;
19269let foo = 10;
19270let foo = 11;
19271let foo = 12;
19272let foo = 13;
19273let foo = 14;
19274let foo = 15;"#,
19275    );
19276
19277    cx.update_editor(|e, window, cx| {
19278        assert_eq!(
19279            e.next_scroll_position,
19280            NextScrollCursorCenterTopBottom::Center,
19281            "Default next scroll direction is center",
19282        );
19283
19284        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19285        assert_eq!(
19286            e.next_scroll_position,
19287            NextScrollCursorCenterTopBottom::Top,
19288            "After center, next scroll direction should be top",
19289        );
19290
19291        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19292        assert_eq!(
19293            e.next_scroll_position,
19294            NextScrollCursorCenterTopBottom::Bottom,
19295            "After top, next scroll direction should be bottom",
19296        );
19297
19298        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19299        assert_eq!(
19300            e.next_scroll_position,
19301            NextScrollCursorCenterTopBottom::Center,
19302            "After bottom, scrolling should start over",
19303        );
19304
19305        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19306        assert_eq!(
19307            e.next_scroll_position,
19308            NextScrollCursorCenterTopBottom::Top,
19309            "Scrolling continues if retriggered fast enough"
19310        );
19311    });
19312
19313    cx.executor()
19314        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
19315    cx.executor().run_until_parked();
19316    cx.update_editor(|e, _, _| {
19317        assert_eq!(
19318            e.next_scroll_position,
19319            NextScrollCursorCenterTopBottom::Center,
19320            "If scrolling is not triggered fast enough, it should reset"
19321        );
19322    });
19323}
19324
19325#[gpui::test]
19326async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
19327    init_test(cx, |_| {});
19328    let mut cx = EditorLspTestContext::new_rust(
19329        lsp::ServerCapabilities {
19330            definition_provider: Some(lsp::OneOf::Left(true)),
19331            references_provider: Some(lsp::OneOf::Left(true)),
19332            ..lsp::ServerCapabilities::default()
19333        },
19334        cx,
19335    )
19336    .await;
19337
19338    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
19339        let go_to_definition = cx
19340            .lsp
19341            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
19342                move |params, _| async move {
19343                    if empty_go_to_definition {
19344                        Ok(None)
19345                    } else {
19346                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
19347                            uri: params.text_document_position_params.text_document.uri,
19348                            range: lsp::Range::new(
19349                                lsp::Position::new(4, 3),
19350                                lsp::Position::new(4, 6),
19351                            ),
19352                        })))
19353                    }
19354                },
19355            );
19356        let references = cx
19357            .lsp
19358            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
19359                Ok(Some(vec![lsp::Location {
19360                    uri: params.text_document_position.text_document.uri,
19361                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
19362                }]))
19363            });
19364        (go_to_definition, references)
19365    };
19366
19367    cx.set_state(
19368        &r#"fn one() {
19369            let mut a = ˇtwo();
19370        }
19371
19372        fn two() {}"#
19373            .unindent(),
19374    );
19375    set_up_lsp_handlers(false, &mut cx);
19376    let navigated = cx
19377        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19378        .await
19379        .expect("Failed to navigate to definition");
19380    assert_eq!(
19381        navigated,
19382        Navigated::Yes,
19383        "Should have navigated to definition from the GetDefinition response"
19384    );
19385    cx.assert_editor_state(
19386        &r#"fn one() {
19387            let mut a = two();
19388        }
19389
19390        fn «twoˇ»() {}"#
19391            .unindent(),
19392    );
19393
19394    let editors = cx.update_workspace(|workspace, _, cx| {
19395        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19396    });
19397    cx.update_editor(|_, _, test_editor_cx| {
19398        assert_eq!(
19399            editors.len(),
19400            1,
19401            "Initially, only one, test, editor should be open in the workspace"
19402        );
19403        assert_eq!(
19404            test_editor_cx.entity(),
19405            editors.last().expect("Asserted len is 1").clone()
19406        );
19407    });
19408
19409    set_up_lsp_handlers(true, &mut cx);
19410    let navigated = cx
19411        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19412        .await
19413        .expect("Failed to navigate to lookup references");
19414    assert_eq!(
19415        navigated,
19416        Navigated::Yes,
19417        "Should have navigated to references as a fallback after empty GoToDefinition response"
19418    );
19419    // We should not change the selections in the existing file,
19420    // if opening another milti buffer with the references
19421    cx.assert_editor_state(
19422        &r#"fn one() {
19423            let mut a = two();
19424        }
19425
19426        fn «twoˇ»() {}"#
19427            .unindent(),
19428    );
19429    let editors = cx.update_workspace(|workspace, _, cx| {
19430        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19431    });
19432    cx.update_editor(|_, _, test_editor_cx| {
19433        assert_eq!(
19434            editors.len(),
19435            2,
19436            "After falling back to references search, we open a new editor with the results"
19437        );
19438        let references_fallback_text = editors
19439            .into_iter()
19440            .find(|new_editor| *new_editor != test_editor_cx.entity())
19441            .expect("Should have one non-test editor now")
19442            .read(test_editor_cx)
19443            .text(test_editor_cx);
19444        assert_eq!(
19445            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
19446            "Should use the range from the references response and not the GoToDefinition one"
19447        );
19448    });
19449}
19450
19451#[gpui::test]
19452async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
19453    init_test(cx, |_| {});
19454    cx.update(|cx| {
19455        let mut editor_settings = EditorSettings::get_global(cx).clone();
19456        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
19457        EditorSettings::override_global(editor_settings, cx);
19458    });
19459    let mut cx = EditorLspTestContext::new_rust(
19460        lsp::ServerCapabilities {
19461            definition_provider: Some(lsp::OneOf::Left(true)),
19462            references_provider: Some(lsp::OneOf::Left(true)),
19463            ..lsp::ServerCapabilities::default()
19464        },
19465        cx,
19466    )
19467    .await;
19468    let original_state = r#"fn one() {
19469        let mut a = ˇtwo();
19470    }
19471
19472    fn two() {}"#
19473        .unindent();
19474    cx.set_state(&original_state);
19475
19476    let mut go_to_definition = cx
19477        .lsp
19478        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
19479            move |_, _| async move { Ok(None) },
19480        );
19481    let _references = cx
19482        .lsp
19483        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
19484            panic!("Should not call for references with no go to definition fallback")
19485        });
19486
19487    let navigated = cx
19488        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19489        .await
19490        .expect("Failed to navigate to lookup references");
19491    go_to_definition
19492        .next()
19493        .await
19494        .expect("Should have called the go_to_definition handler");
19495
19496    assert_eq!(
19497        navigated,
19498        Navigated::No,
19499        "Should have navigated to references as a fallback after empty GoToDefinition response"
19500    );
19501    cx.assert_editor_state(&original_state);
19502    let editors = cx.update_workspace(|workspace, _, cx| {
19503        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19504    });
19505    cx.update_editor(|_, _, _| {
19506        assert_eq!(
19507            editors.len(),
19508            1,
19509            "After unsuccessful fallback, no other editor should have been opened"
19510        );
19511    });
19512}
19513
19514#[gpui::test]
19515async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
19516    init_test(cx, |_| {});
19517
19518    let language = Arc::new(Language::new(
19519        LanguageConfig::default(),
19520        Some(tree_sitter_rust::LANGUAGE.into()),
19521    ));
19522
19523    let text = r#"
19524        #[cfg(test)]
19525        mod tests() {
19526            #[test]
19527            fn runnable_1() {
19528                let a = 1;
19529            }
19530
19531            #[test]
19532            fn runnable_2() {
19533                let a = 1;
19534                let b = 2;
19535            }
19536        }
19537    "#
19538    .unindent();
19539
19540    let fs = FakeFs::new(cx.executor());
19541    fs.insert_file("/file.rs", Default::default()).await;
19542
19543    let project = Project::test(fs, ["/a".as_ref()], cx).await;
19544    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19545    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19546    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
19547    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
19548
19549    let editor = cx.new_window_entity(|window, cx| {
19550        Editor::new(
19551            EditorMode::full(),
19552            multi_buffer,
19553            Some(project.clone()),
19554            window,
19555            cx,
19556        )
19557    });
19558
19559    editor.update_in(cx, |editor, window, cx| {
19560        let snapshot = editor.buffer().read(cx).snapshot(cx);
19561        editor.tasks.insert(
19562            (buffer.read(cx).remote_id(), 3),
19563            RunnableTasks {
19564                templates: vec![],
19565                offset: snapshot.anchor_before(43),
19566                column: 0,
19567                extra_variables: HashMap::default(),
19568                context_range: BufferOffset(43)..BufferOffset(85),
19569            },
19570        );
19571        editor.tasks.insert(
19572            (buffer.read(cx).remote_id(), 8),
19573            RunnableTasks {
19574                templates: vec![],
19575                offset: snapshot.anchor_before(86),
19576                column: 0,
19577                extra_variables: HashMap::default(),
19578                context_range: BufferOffset(86)..BufferOffset(191),
19579            },
19580        );
19581
19582        // Test finding task when cursor is inside function body
19583        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19584            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
19585        });
19586        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
19587        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
19588
19589        // Test finding task when cursor is on function name
19590        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19591            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
19592        });
19593        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
19594        assert_eq!(row, 8, "Should find task when cursor is on function name");
19595    });
19596}
19597
19598#[gpui::test]
19599async fn test_folding_buffers(cx: &mut TestAppContext) {
19600    init_test(cx, |_| {});
19601
19602    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
19603    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
19604    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
19605
19606    let fs = FakeFs::new(cx.executor());
19607    fs.insert_tree(
19608        path!("/a"),
19609        json!({
19610            "first.rs": sample_text_1,
19611            "second.rs": sample_text_2,
19612            "third.rs": sample_text_3,
19613        }),
19614    )
19615    .await;
19616    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19617    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19618    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19619    let worktree = project.update(cx, |project, cx| {
19620        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19621        assert_eq!(worktrees.len(), 1);
19622        worktrees.pop().unwrap()
19623    });
19624    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19625
19626    let buffer_1 = project
19627        .update(cx, |project, cx| {
19628            project.open_buffer((worktree_id, "first.rs"), cx)
19629        })
19630        .await
19631        .unwrap();
19632    let buffer_2 = project
19633        .update(cx, |project, cx| {
19634            project.open_buffer((worktree_id, "second.rs"), cx)
19635        })
19636        .await
19637        .unwrap();
19638    let buffer_3 = project
19639        .update(cx, |project, cx| {
19640            project.open_buffer((worktree_id, "third.rs"), cx)
19641        })
19642        .await
19643        .unwrap();
19644
19645    let multi_buffer = cx.new(|cx| {
19646        let mut multi_buffer = MultiBuffer::new(ReadWrite);
19647        multi_buffer.push_excerpts(
19648            buffer_1.clone(),
19649            [
19650                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19651                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19652                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19653            ],
19654            cx,
19655        );
19656        multi_buffer.push_excerpts(
19657            buffer_2.clone(),
19658            [
19659                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19660                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19661                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19662            ],
19663            cx,
19664        );
19665        multi_buffer.push_excerpts(
19666            buffer_3.clone(),
19667            [
19668                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19669                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19670                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19671            ],
19672            cx,
19673        );
19674        multi_buffer
19675    });
19676    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19677        Editor::new(
19678            EditorMode::full(),
19679            multi_buffer.clone(),
19680            Some(project.clone()),
19681            window,
19682            cx,
19683        )
19684    });
19685
19686    assert_eq!(
19687        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19688        "\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",
19689    );
19690
19691    multi_buffer_editor.update(cx, |editor, cx| {
19692        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
19693    });
19694    assert_eq!(
19695        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19696        "\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",
19697        "After folding the first buffer, its text should not be displayed"
19698    );
19699
19700    multi_buffer_editor.update(cx, |editor, cx| {
19701        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
19702    });
19703    assert_eq!(
19704        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19705        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
19706        "After folding the second buffer, its text should not be displayed"
19707    );
19708
19709    multi_buffer_editor.update(cx, |editor, cx| {
19710        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
19711    });
19712    assert_eq!(
19713        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19714        "\n\n\n\n\n",
19715        "After folding the third buffer, its text should not be displayed"
19716    );
19717
19718    // Emulate selection inside the fold logic, that should work
19719    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19720        editor
19721            .snapshot(window, cx)
19722            .next_line_boundary(Point::new(0, 4));
19723    });
19724
19725    multi_buffer_editor.update(cx, |editor, cx| {
19726        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
19727    });
19728    assert_eq!(
19729        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19730        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
19731        "After unfolding the second buffer, its text should be displayed"
19732    );
19733
19734    // Typing inside of buffer 1 causes that buffer to be unfolded.
19735    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19736        assert_eq!(
19737            multi_buffer
19738                .read(cx)
19739                .snapshot(cx)
19740                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
19741                .collect::<String>(),
19742            "bbbb"
19743        );
19744        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
19745            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
19746        });
19747        editor.handle_input("B", window, cx);
19748    });
19749
19750    assert_eq!(
19751        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19752        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
19753        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
19754    );
19755
19756    multi_buffer_editor.update(cx, |editor, cx| {
19757        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
19758    });
19759    assert_eq!(
19760        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19761        "\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",
19762        "After unfolding the all buffers, all original text should be displayed"
19763    );
19764}
19765
19766#[gpui::test]
19767async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
19768    init_test(cx, |_| {});
19769
19770    let sample_text_1 = "1111\n2222\n3333".to_string();
19771    let sample_text_2 = "4444\n5555\n6666".to_string();
19772    let sample_text_3 = "7777\n8888\n9999".to_string();
19773
19774    let fs = FakeFs::new(cx.executor());
19775    fs.insert_tree(
19776        path!("/a"),
19777        json!({
19778            "first.rs": sample_text_1,
19779            "second.rs": sample_text_2,
19780            "third.rs": sample_text_3,
19781        }),
19782    )
19783    .await;
19784    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19785    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19786    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19787    let worktree = project.update(cx, |project, cx| {
19788        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19789        assert_eq!(worktrees.len(), 1);
19790        worktrees.pop().unwrap()
19791    });
19792    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19793
19794    let buffer_1 = project
19795        .update(cx, |project, cx| {
19796            project.open_buffer((worktree_id, "first.rs"), cx)
19797        })
19798        .await
19799        .unwrap();
19800    let buffer_2 = project
19801        .update(cx, |project, cx| {
19802            project.open_buffer((worktree_id, "second.rs"), cx)
19803        })
19804        .await
19805        .unwrap();
19806    let buffer_3 = project
19807        .update(cx, |project, cx| {
19808            project.open_buffer((worktree_id, "third.rs"), cx)
19809        })
19810        .await
19811        .unwrap();
19812
19813    let multi_buffer = cx.new(|cx| {
19814        let mut multi_buffer = MultiBuffer::new(ReadWrite);
19815        multi_buffer.push_excerpts(
19816            buffer_1.clone(),
19817            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19818            cx,
19819        );
19820        multi_buffer.push_excerpts(
19821            buffer_2.clone(),
19822            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19823            cx,
19824        );
19825        multi_buffer.push_excerpts(
19826            buffer_3.clone(),
19827            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19828            cx,
19829        );
19830        multi_buffer
19831    });
19832
19833    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19834        Editor::new(
19835            EditorMode::full(),
19836            multi_buffer,
19837            Some(project.clone()),
19838            window,
19839            cx,
19840        )
19841    });
19842
19843    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
19844    assert_eq!(
19845        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19846        full_text,
19847    );
19848
19849    multi_buffer_editor.update(cx, |editor, cx| {
19850        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
19851    });
19852    assert_eq!(
19853        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19854        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
19855        "After folding the first buffer, its text should not be displayed"
19856    );
19857
19858    multi_buffer_editor.update(cx, |editor, cx| {
19859        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
19860    });
19861
19862    assert_eq!(
19863        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19864        "\n\n\n\n\n\n7777\n8888\n9999",
19865        "After folding the second buffer, its text should not be displayed"
19866    );
19867
19868    multi_buffer_editor.update(cx, |editor, cx| {
19869        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
19870    });
19871    assert_eq!(
19872        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19873        "\n\n\n\n\n",
19874        "After folding the third buffer, its text should not be displayed"
19875    );
19876
19877    multi_buffer_editor.update(cx, |editor, cx| {
19878        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
19879    });
19880    assert_eq!(
19881        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19882        "\n\n\n\n4444\n5555\n6666\n\n",
19883        "After unfolding the second buffer, its text should be displayed"
19884    );
19885
19886    multi_buffer_editor.update(cx, |editor, cx| {
19887        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
19888    });
19889    assert_eq!(
19890        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19891        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
19892        "After unfolding the first buffer, its text should be displayed"
19893    );
19894
19895    multi_buffer_editor.update(cx, |editor, cx| {
19896        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
19897    });
19898    assert_eq!(
19899        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19900        full_text,
19901        "After unfolding all buffers, all original text should be displayed"
19902    );
19903}
19904
19905#[gpui::test]
19906async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
19907    init_test(cx, |_| {});
19908
19909    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
19910
19911    let fs = FakeFs::new(cx.executor());
19912    fs.insert_tree(
19913        path!("/a"),
19914        json!({
19915            "main.rs": sample_text,
19916        }),
19917    )
19918    .await;
19919    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19920    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19921    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19922    let worktree = project.update(cx, |project, cx| {
19923        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19924        assert_eq!(worktrees.len(), 1);
19925        worktrees.pop().unwrap()
19926    });
19927    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19928
19929    let buffer_1 = project
19930        .update(cx, |project, cx| {
19931            project.open_buffer((worktree_id, "main.rs"), cx)
19932        })
19933        .await
19934        .unwrap();
19935
19936    let multi_buffer = cx.new(|cx| {
19937        let mut multi_buffer = MultiBuffer::new(ReadWrite);
19938        multi_buffer.push_excerpts(
19939            buffer_1.clone(),
19940            [ExcerptRange::new(
19941                Point::new(0, 0)
19942                    ..Point::new(
19943                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
19944                        0,
19945                    ),
19946            )],
19947            cx,
19948        );
19949        multi_buffer
19950    });
19951    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19952        Editor::new(
19953            EditorMode::full(),
19954            multi_buffer,
19955            Some(project.clone()),
19956            window,
19957            cx,
19958        )
19959    });
19960
19961    let selection_range = Point::new(1, 0)..Point::new(2, 0);
19962    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19963        enum TestHighlight {}
19964        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
19965        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
19966        editor.highlight_text::<TestHighlight>(
19967            vec![highlight_range.clone()],
19968            HighlightStyle::color(Hsla::green()),
19969            cx,
19970        );
19971        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19972            s.select_ranges(Some(highlight_range))
19973        });
19974    });
19975
19976    let full_text = format!("\n\n{sample_text}");
19977    assert_eq!(
19978        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19979        full_text,
19980    );
19981}
19982
19983#[gpui::test]
19984async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
19985    init_test(cx, |_| {});
19986    cx.update(|cx| {
19987        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
19988            "keymaps/default-linux.json",
19989            cx,
19990        )
19991        .unwrap();
19992        cx.bind_keys(default_key_bindings);
19993    });
19994
19995    let (editor, cx) = cx.add_window_view(|window, cx| {
19996        let multi_buffer = MultiBuffer::build_multi(
19997            [
19998                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
19999                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
20000                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
20001                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
20002            ],
20003            cx,
20004        );
20005        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
20006
20007        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
20008        // fold all but the second buffer, so that we test navigating between two
20009        // adjacent folded buffers, as well as folded buffers at the start and
20010        // end the multibuffer
20011        editor.fold_buffer(buffer_ids[0], cx);
20012        editor.fold_buffer(buffer_ids[2], cx);
20013        editor.fold_buffer(buffer_ids[3], cx);
20014
20015        editor
20016    });
20017    cx.simulate_resize(size(px(1000.), px(1000.)));
20018
20019    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
20020    cx.assert_excerpts_with_selections(indoc! {"
20021        [EXCERPT]
20022        ˇ[FOLDED]
20023        [EXCERPT]
20024        a1
20025        b1
20026        [EXCERPT]
20027        [FOLDED]
20028        [EXCERPT]
20029        [FOLDED]
20030        "
20031    });
20032    cx.simulate_keystroke("down");
20033    cx.assert_excerpts_with_selections(indoc! {"
20034        [EXCERPT]
20035        [FOLDED]
20036        [EXCERPT]
20037        ˇa1
20038        b1
20039        [EXCERPT]
20040        [FOLDED]
20041        [EXCERPT]
20042        [FOLDED]
20043        "
20044    });
20045    cx.simulate_keystroke("down");
20046    cx.assert_excerpts_with_selections(indoc! {"
20047        [EXCERPT]
20048        [FOLDED]
20049        [EXCERPT]
20050        a1
20051        ˇb1
20052        [EXCERPT]
20053        [FOLDED]
20054        [EXCERPT]
20055        [FOLDED]
20056        "
20057    });
20058    cx.simulate_keystroke("down");
20059    cx.assert_excerpts_with_selections(indoc! {"
20060        [EXCERPT]
20061        [FOLDED]
20062        [EXCERPT]
20063        a1
20064        b1
20065        ˇ[EXCERPT]
20066        [FOLDED]
20067        [EXCERPT]
20068        [FOLDED]
20069        "
20070    });
20071    cx.simulate_keystroke("down");
20072    cx.assert_excerpts_with_selections(indoc! {"
20073        [EXCERPT]
20074        [FOLDED]
20075        [EXCERPT]
20076        a1
20077        b1
20078        [EXCERPT]
20079        ˇ[FOLDED]
20080        [EXCERPT]
20081        [FOLDED]
20082        "
20083    });
20084    for _ in 0..5 {
20085        cx.simulate_keystroke("down");
20086        cx.assert_excerpts_with_selections(indoc! {"
20087            [EXCERPT]
20088            [FOLDED]
20089            [EXCERPT]
20090            a1
20091            b1
20092            [EXCERPT]
20093            [FOLDED]
20094            [EXCERPT]
20095            ˇ[FOLDED]
20096            "
20097        });
20098    }
20099
20100    cx.simulate_keystroke("up");
20101    cx.assert_excerpts_with_selections(indoc! {"
20102        [EXCERPT]
20103        [FOLDED]
20104        [EXCERPT]
20105        a1
20106        b1
20107        [EXCERPT]
20108        ˇ[FOLDED]
20109        [EXCERPT]
20110        [FOLDED]
20111        "
20112    });
20113    cx.simulate_keystroke("up");
20114    cx.assert_excerpts_with_selections(indoc! {"
20115        [EXCERPT]
20116        [FOLDED]
20117        [EXCERPT]
20118        a1
20119        b1
20120        ˇ[EXCERPT]
20121        [FOLDED]
20122        [EXCERPT]
20123        [FOLDED]
20124        "
20125    });
20126    cx.simulate_keystroke("up");
20127    cx.assert_excerpts_with_selections(indoc! {"
20128        [EXCERPT]
20129        [FOLDED]
20130        [EXCERPT]
20131        a1
20132        ˇb1
20133        [EXCERPT]
20134        [FOLDED]
20135        [EXCERPT]
20136        [FOLDED]
20137        "
20138    });
20139    cx.simulate_keystroke("up");
20140    cx.assert_excerpts_with_selections(indoc! {"
20141        [EXCERPT]
20142        [FOLDED]
20143        [EXCERPT]
20144        ˇa1
20145        b1
20146        [EXCERPT]
20147        [FOLDED]
20148        [EXCERPT]
20149        [FOLDED]
20150        "
20151    });
20152    for _ in 0..5 {
20153        cx.simulate_keystroke("up");
20154        cx.assert_excerpts_with_selections(indoc! {"
20155            [EXCERPT]
20156            ˇ[FOLDED]
20157            [EXCERPT]
20158            a1
20159            b1
20160            [EXCERPT]
20161            [FOLDED]
20162            [EXCERPT]
20163            [FOLDED]
20164            "
20165        });
20166    }
20167}
20168
20169#[gpui::test]
20170async fn test_inline_completion_text(cx: &mut TestAppContext) {
20171    init_test(cx, |_| {});
20172
20173    // Simple insertion
20174    assert_highlighted_edits(
20175        "Hello, world!",
20176        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
20177        true,
20178        cx,
20179        |highlighted_edits, cx| {
20180            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
20181            assert_eq!(highlighted_edits.highlights.len(), 1);
20182            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
20183            assert_eq!(
20184                highlighted_edits.highlights[0].1.background_color,
20185                Some(cx.theme().status().created_background)
20186            );
20187        },
20188    )
20189    .await;
20190
20191    // Replacement
20192    assert_highlighted_edits(
20193        "This is a test.",
20194        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
20195        false,
20196        cx,
20197        |highlighted_edits, cx| {
20198            assert_eq!(highlighted_edits.text, "That is a test.");
20199            assert_eq!(highlighted_edits.highlights.len(), 1);
20200            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
20201            assert_eq!(
20202                highlighted_edits.highlights[0].1.background_color,
20203                Some(cx.theme().status().created_background)
20204            );
20205        },
20206    )
20207    .await;
20208
20209    // Multiple edits
20210    assert_highlighted_edits(
20211        "Hello, world!",
20212        vec![
20213            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
20214            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
20215        ],
20216        false,
20217        cx,
20218        |highlighted_edits, cx| {
20219            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
20220            assert_eq!(highlighted_edits.highlights.len(), 2);
20221            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
20222            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
20223            assert_eq!(
20224                highlighted_edits.highlights[0].1.background_color,
20225                Some(cx.theme().status().created_background)
20226            );
20227            assert_eq!(
20228                highlighted_edits.highlights[1].1.background_color,
20229                Some(cx.theme().status().created_background)
20230            );
20231        },
20232    )
20233    .await;
20234
20235    // Multiple lines with edits
20236    assert_highlighted_edits(
20237        "First line\nSecond line\nThird line\nFourth line",
20238        vec![
20239            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
20240            (
20241                Point::new(2, 0)..Point::new(2, 10),
20242                "New third line".to_string(),
20243            ),
20244            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
20245        ],
20246        false,
20247        cx,
20248        |highlighted_edits, cx| {
20249            assert_eq!(
20250                highlighted_edits.text,
20251                "Second modified\nNew third line\nFourth updated line"
20252            );
20253            assert_eq!(highlighted_edits.highlights.len(), 3);
20254            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
20255            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
20256            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
20257            for highlight in &highlighted_edits.highlights {
20258                assert_eq!(
20259                    highlight.1.background_color,
20260                    Some(cx.theme().status().created_background)
20261                );
20262            }
20263        },
20264    )
20265    .await;
20266}
20267
20268#[gpui::test]
20269async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
20270    init_test(cx, |_| {});
20271
20272    // Deletion
20273    assert_highlighted_edits(
20274        "Hello, world!",
20275        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
20276        true,
20277        cx,
20278        |highlighted_edits, cx| {
20279            assert_eq!(highlighted_edits.text, "Hello, world!");
20280            assert_eq!(highlighted_edits.highlights.len(), 1);
20281            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
20282            assert_eq!(
20283                highlighted_edits.highlights[0].1.background_color,
20284                Some(cx.theme().status().deleted_background)
20285            );
20286        },
20287    )
20288    .await;
20289
20290    // Insertion
20291    assert_highlighted_edits(
20292        "Hello, world!",
20293        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
20294        true,
20295        cx,
20296        |highlighted_edits, cx| {
20297            assert_eq!(highlighted_edits.highlights.len(), 1);
20298            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
20299            assert_eq!(
20300                highlighted_edits.highlights[0].1.background_color,
20301                Some(cx.theme().status().created_background)
20302            );
20303        },
20304    )
20305    .await;
20306}
20307
20308async fn assert_highlighted_edits(
20309    text: &str,
20310    edits: Vec<(Range<Point>, String)>,
20311    include_deletions: bool,
20312    cx: &mut TestAppContext,
20313    assertion_fn: impl Fn(HighlightedText, &App),
20314) {
20315    let window = cx.add_window(|window, cx| {
20316        let buffer = MultiBuffer::build_simple(text, cx);
20317        Editor::new(EditorMode::full(), buffer, None, window, cx)
20318    });
20319    let cx = &mut VisualTestContext::from_window(*window, cx);
20320
20321    let (buffer, snapshot) = window
20322        .update(cx, |editor, _window, cx| {
20323            (
20324                editor.buffer().clone(),
20325                editor.buffer().read(cx).snapshot(cx),
20326            )
20327        })
20328        .unwrap();
20329
20330    let edits = edits
20331        .into_iter()
20332        .map(|(range, edit)| {
20333            (
20334                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
20335                edit,
20336            )
20337        })
20338        .collect::<Vec<_>>();
20339
20340    let text_anchor_edits = edits
20341        .clone()
20342        .into_iter()
20343        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
20344        .collect::<Vec<_>>();
20345
20346    let edit_preview = window
20347        .update(cx, |_, _window, cx| {
20348            buffer
20349                .read(cx)
20350                .as_singleton()
20351                .unwrap()
20352                .read(cx)
20353                .preview_edits(text_anchor_edits.into(), cx)
20354        })
20355        .unwrap()
20356        .await;
20357
20358    cx.update(|_window, cx| {
20359        let highlighted_edits = inline_completion_edit_text(
20360            &snapshot.as_singleton().unwrap().2,
20361            &edits,
20362            &edit_preview,
20363            include_deletions,
20364            cx,
20365        );
20366        assertion_fn(highlighted_edits, cx)
20367    });
20368}
20369
20370#[track_caller]
20371fn assert_breakpoint(
20372    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
20373    path: &Arc<Path>,
20374    expected: Vec<(u32, Breakpoint)>,
20375) {
20376    if expected.len() == 0usize {
20377        assert!(!breakpoints.contains_key(path), "{}", path.display());
20378    } else {
20379        let mut breakpoint = breakpoints
20380            .get(path)
20381            .unwrap()
20382            .into_iter()
20383            .map(|breakpoint| {
20384                (
20385                    breakpoint.row,
20386                    Breakpoint {
20387                        message: breakpoint.message.clone(),
20388                        state: breakpoint.state,
20389                        condition: breakpoint.condition.clone(),
20390                        hit_condition: breakpoint.hit_condition.clone(),
20391                    },
20392                )
20393            })
20394            .collect::<Vec<_>>();
20395
20396        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
20397
20398        assert_eq!(expected, breakpoint);
20399    }
20400}
20401
20402fn add_log_breakpoint_at_cursor(
20403    editor: &mut Editor,
20404    log_message: &str,
20405    window: &mut Window,
20406    cx: &mut Context<Editor>,
20407) {
20408    let (anchor, bp) = editor
20409        .breakpoints_at_cursors(window, cx)
20410        .first()
20411        .and_then(|(anchor, bp)| {
20412            if let Some(bp) = bp {
20413                Some((*anchor, bp.clone()))
20414            } else {
20415                None
20416            }
20417        })
20418        .unwrap_or_else(|| {
20419            let cursor_position: Point = editor.selections.newest(cx).head();
20420
20421            let breakpoint_position = editor
20422                .snapshot(window, cx)
20423                .display_snapshot
20424                .buffer_snapshot
20425                .anchor_before(Point::new(cursor_position.row, 0));
20426
20427            (breakpoint_position, Breakpoint::new_log(&log_message))
20428        });
20429
20430    editor.edit_breakpoint_at_anchor(
20431        anchor,
20432        bp,
20433        BreakpointEditAction::EditLogMessage(log_message.into()),
20434        cx,
20435    );
20436}
20437
20438#[gpui::test]
20439async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
20440    init_test(cx, |_| {});
20441
20442    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20443    let fs = FakeFs::new(cx.executor());
20444    fs.insert_tree(
20445        path!("/a"),
20446        json!({
20447            "main.rs": sample_text,
20448        }),
20449    )
20450    .await;
20451    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20452    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20453    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20454
20455    let fs = FakeFs::new(cx.executor());
20456    fs.insert_tree(
20457        path!("/a"),
20458        json!({
20459            "main.rs": sample_text,
20460        }),
20461    )
20462    .await;
20463    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20464    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20465    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20466    let worktree_id = workspace
20467        .update(cx, |workspace, _window, cx| {
20468            workspace.project().update(cx, |project, cx| {
20469                project.worktrees(cx).next().unwrap().read(cx).id()
20470            })
20471        })
20472        .unwrap();
20473
20474    let buffer = project
20475        .update(cx, |project, cx| {
20476            project.open_buffer((worktree_id, "main.rs"), cx)
20477        })
20478        .await
20479        .unwrap();
20480
20481    let (editor, cx) = cx.add_window_view(|window, cx| {
20482        Editor::new(
20483            EditorMode::full(),
20484            MultiBuffer::build_from_buffer(buffer, cx),
20485            Some(project.clone()),
20486            window,
20487            cx,
20488        )
20489    });
20490
20491    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20492    let abs_path = project.read_with(cx, |project, cx| {
20493        project
20494            .absolute_path(&project_path, cx)
20495            .map(|path_buf| Arc::from(path_buf.to_owned()))
20496            .unwrap()
20497    });
20498
20499    // assert we can add breakpoint on the first line
20500    editor.update_in(cx, |editor, window, cx| {
20501        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20502        editor.move_to_end(&MoveToEnd, window, cx);
20503        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20504    });
20505
20506    let breakpoints = editor.update(cx, |editor, cx| {
20507        editor
20508            .breakpoint_store()
20509            .as_ref()
20510            .unwrap()
20511            .read(cx)
20512            .all_source_breakpoints(cx)
20513            .clone()
20514    });
20515
20516    assert_eq!(1, breakpoints.len());
20517    assert_breakpoint(
20518        &breakpoints,
20519        &abs_path,
20520        vec![
20521            (0, Breakpoint::new_standard()),
20522            (3, Breakpoint::new_standard()),
20523        ],
20524    );
20525
20526    editor.update_in(cx, |editor, window, cx| {
20527        editor.move_to_beginning(&MoveToBeginning, window, cx);
20528        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20529    });
20530
20531    let breakpoints = editor.update(cx, |editor, cx| {
20532        editor
20533            .breakpoint_store()
20534            .as_ref()
20535            .unwrap()
20536            .read(cx)
20537            .all_source_breakpoints(cx)
20538            .clone()
20539    });
20540
20541    assert_eq!(1, breakpoints.len());
20542    assert_breakpoint(
20543        &breakpoints,
20544        &abs_path,
20545        vec![(3, Breakpoint::new_standard())],
20546    );
20547
20548    editor.update_in(cx, |editor, window, cx| {
20549        editor.move_to_end(&MoveToEnd, window, cx);
20550        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20551    });
20552
20553    let breakpoints = editor.update(cx, |editor, cx| {
20554        editor
20555            .breakpoint_store()
20556            .as_ref()
20557            .unwrap()
20558            .read(cx)
20559            .all_source_breakpoints(cx)
20560            .clone()
20561    });
20562
20563    assert_eq!(0, breakpoints.len());
20564    assert_breakpoint(&breakpoints, &abs_path, vec![]);
20565}
20566
20567#[gpui::test]
20568async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
20569    init_test(cx, |_| {});
20570
20571    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20572
20573    let fs = FakeFs::new(cx.executor());
20574    fs.insert_tree(
20575        path!("/a"),
20576        json!({
20577            "main.rs": sample_text,
20578        }),
20579    )
20580    .await;
20581    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20582    let (workspace, cx) =
20583        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
20584
20585    let worktree_id = workspace.update(cx, |workspace, cx| {
20586        workspace.project().update(cx, |project, cx| {
20587            project.worktrees(cx).next().unwrap().read(cx).id()
20588        })
20589    });
20590
20591    let buffer = project
20592        .update(cx, |project, cx| {
20593            project.open_buffer((worktree_id, "main.rs"), cx)
20594        })
20595        .await
20596        .unwrap();
20597
20598    let (editor, cx) = cx.add_window_view(|window, cx| {
20599        Editor::new(
20600            EditorMode::full(),
20601            MultiBuffer::build_from_buffer(buffer, cx),
20602            Some(project.clone()),
20603            window,
20604            cx,
20605        )
20606    });
20607
20608    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20609    let abs_path = project.read_with(cx, |project, cx| {
20610        project
20611            .absolute_path(&project_path, cx)
20612            .map(|path_buf| Arc::from(path_buf.to_owned()))
20613            .unwrap()
20614    });
20615
20616    editor.update_in(cx, |editor, window, cx| {
20617        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
20618    });
20619
20620    let breakpoints = editor.update(cx, |editor, cx| {
20621        editor
20622            .breakpoint_store()
20623            .as_ref()
20624            .unwrap()
20625            .read(cx)
20626            .all_source_breakpoints(cx)
20627            .clone()
20628    });
20629
20630    assert_breakpoint(
20631        &breakpoints,
20632        &abs_path,
20633        vec![(0, Breakpoint::new_log("hello world"))],
20634    );
20635
20636    // Removing a log message from a log breakpoint should remove it
20637    editor.update_in(cx, |editor, window, cx| {
20638        add_log_breakpoint_at_cursor(editor, "", window, cx);
20639    });
20640
20641    let breakpoints = editor.update(cx, |editor, cx| {
20642        editor
20643            .breakpoint_store()
20644            .as_ref()
20645            .unwrap()
20646            .read(cx)
20647            .all_source_breakpoints(cx)
20648            .clone()
20649    });
20650
20651    assert_breakpoint(&breakpoints, &abs_path, vec![]);
20652
20653    editor.update_in(cx, |editor, window, cx| {
20654        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20655        editor.move_to_end(&MoveToEnd, window, cx);
20656        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20657        // Not adding a log message to a standard breakpoint shouldn't remove it
20658        add_log_breakpoint_at_cursor(editor, "", window, cx);
20659    });
20660
20661    let breakpoints = editor.update(cx, |editor, cx| {
20662        editor
20663            .breakpoint_store()
20664            .as_ref()
20665            .unwrap()
20666            .read(cx)
20667            .all_source_breakpoints(cx)
20668            .clone()
20669    });
20670
20671    assert_breakpoint(
20672        &breakpoints,
20673        &abs_path,
20674        vec![
20675            (0, Breakpoint::new_standard()),
20676            (3, Breakpoint::new_standard()),
20677        ],
20678    );
20679
20680    editor.update_in(cx, |editor, window, cx| {
20681        add_log_breakpoint_at_cursor(editor, "hello world", 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_log("hello world")),
20700        ],
20701    );
20702
20703    editor.update_in(cx, |editor, window, cx| {
20704        add_log_breakpoint_at_cursor(editor, "hello Earth!!", 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 Earth!!")),
20723        ],
20724    );
20725}
20726
20727/// This also tests that Editor::breakpoint_at_cursor_head is working properly
20728/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
20729/// or when breakpoints were placed out of order. This tests for a regression too
20730#[gpui::test]
20731async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
20732    init_test(cx, |_| {});
20733
20734    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20735    let fs = FakeFs::new(cx.executor());
20736    fs.insert_tree(
20737        path!("/a"),
20738        json!({
20739            "main.rs": sample_text,
20740        }),
20741    )
20742    .await;
20743    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20744    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20745    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20746
20747    let fs = FakeFs::new(cx.executor());
20748    fs.insert_tree(
20749        path!("/a"),
20750        json!({
20751            "main.rs": sample_text,
20752        }),
20753    )
20754    .await;
20755    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20756    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20757    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20758    let worktree_id = workspace
20759        .update(cx, |workspace, _window, cx| {
20760            workspace.project().update(cx, |project, cx| {
20761                project.worktrees(cx).next().unwrap().read(cx).id()
20762            })
20763        })
20764        .unwrap();
20765
20766    let buffer = project
20767        .update(cx, |project, cx| {
20768            project.open_buffer((worktree_id, "main.rs"), cx)
20769        })
20770        .await
20771        .unwrap();
20772
20773    let (editor, cx) = cx.add_window_view(|window, cx| {
20774        Editor::new(
20775            EditorMode::full(),
20776            MultiBuffer::build_from_buffer(buffer, cx),
20777            Some(project.clone()),
20778            window,
20779            cx,
20780        )
20781    });
20782
20783    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20784    let abs_path = project.read_with(cx, |project, cx| {
20785        project
20786            .absolute_path(&project_path, cx)
20787            .map(|path_buf| Arc::from(path_buf.to_owned()))
20788            .unwrap()
20789    });
20790
20791    // assert we can add breakpoint on the first line
20792    editor.update_in(cx, |editor, window, cx| {
20793        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20794        editor.move_to_end(&MoveToEnd, window, cx);
20795        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20796        editor.move_up(&MoveUp, window, cx);
20797        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20798    });
20799
20800    let breakpoints = editor.update(cx, |editor, cx| {
20801        editor
20802            .breakpoint_store()
20803            .as_ref()
20804            .unwrap()
20805            .read(cx)
20806            .all_source_breakpoints(cx)
20807            .clone()
20808    });
20809
20810    assert_eq!(1, breakpoints.len());
20811    assert_breakpoint(
20812        &breakpoints,
20813        &abs_path,
20814        vec![
20815            (0, Breakpoint::new_standard()),
20816            (2, Breakpoint::new_standard()),
20817            (3, Breakpoint::new_standard()),
20818        ],
20819    );
20820
20821    editor.update_in(cx, |editor, window, cx| {
20822        editor.move_to_beginning(&MoveToBeginning, window, cx);
20823        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20824        editor.move_to_end(&MoveToEnd, window, cx);
20825        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20826        // Disabling a breakpoint that doesn't exist should do nothing
20827        editor.move_up(&MoveUp, window, cx);
20828        editor.move_up(&MoveUp, window, cx);
20829        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20830    });
20831
20832    let breakpoints = editor.update(cx, |editor, cx| {
20833        editor
20834            .breakpoint_store()
20835            .as_ref()
20836            .unwrap()
20837            .read(cx)
20838            .all_source_breakpoints(cx)
20839            .clone()
20840    });
20841
20842    let disable_breakpoint = {
20843        let mut bp = Breakpoint::new_standard();
20844        bp.state = BreakpointState::Disabled;
20845        bp
20846    };
20847
20848    assert_eq!(1, breakpoints.len());
20849    assert_breakpoint(
20850        &breakpoints,
20851        &abs_path,
20852        vec![
20853            (0, disable_breakpoint.clone()),
20854            (2, Breakpoint::new_standard()),
20855            (3, disable_breakpoint.clone()),
20856        ],
20857    );
20858
20859    editor.update_in(cx, |editor, window, cx| {
20860        editor.move_to_beginning(&MoveToBeginning, window, cx);
20861        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
20862        editor.move_to_end(&MoveToEnd, window, cx);
20863        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
20864        editor.move_up(&MoveUp, window, cx);
20865        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20866    });
20867
20868    let breakpoints = editor.update(cx, |editor, cx| {
20869        editor
20870            .breakpoint_store()
20871            .as_ref()
20872            .unwrap()
20873            .read(cx)
20874            .all_source_breakpoints(cx)
20875            .clone()
20876    });
20877
20878    assert_eq!(1, breakpoints.len());
20879    assert_breakpoint(
20880        &breakpoints,
20881        &abs_path,
20882        vec![
20883            (0, Breakpoint::new_standard()),
20884            (2, disable_breakpoint),
20885            (3, Breakpoint::new_standard()),
20886        ],
20887    );
20888}
20889
20890#[gpui::test]
20891async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
20892    init_test(cx, |_| {});
20893    let capabilities = lsp::ServerCapabilities {
20894        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
20895            prepare_provider: Some(true),
20896            work_done_progress_options: Default::default(),
20897        })),
20898        ..Default::default()
20899    };
20900    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
20901
20902    cx.set_state(indoc! {"
20903        struct Fˇoo {}
20904    "});
20905
20906    cx.update_editor(|editor, _, cx| {
20907        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
20908        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
20909        editor.highlight_background::<DocumentHighlightRead>(
20910            &[highlight_range],
20911            |theme| theme.colors().editor_document_highlight_read_background,
20912            cx,
20913        );
20914    });
20915
20916    let mut prepare_rename_handler = cx
20917        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
20918            move |_, _, _| async move {
20919                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
20920                    start: lsp::Position {
20921                        line: 0,
20922                        character: 7,
20923                    },
20924                    end: lsp::Position {
20925                        line: 0,
20926                        character: 10,
20927                    },
20928                })))
20929            },
20930        );
20931    let prepare_rename_task = cx
20932        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
20933        .expect("Prepare rename was not started");
20934    prepare_rename_handler.next().await.unwrap();
20935    prepare_rename_task.await.expect("Prepare rename failed");
20936
20937    let mut rename_handler =
20938        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
20939            let edit = lsp::TextEdit {
20940                range: lsp::Range {
20941                    start: lsp::Position {
20942                        line: 0,
20943                        character: 7,
20944                    },
20945                    end: lsp::Position {
20946                        line: 0,
20947                        character: 10,
20948                    },
20949                },
20950                new_text: "FooRenamed".to_string(),
20951            };
20952            Ok(Some(lsp::WorkspaceEdit::new(
20953                // Specify the same edit twice
20954                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
20955            )))
20956        });
20957    let rename_task = cx
20958        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
20959        .expect("Confirm rename was not started");
20960    rename_handler.next().await.unwrap();
20961    rename_task.await.expect("Confirm rename failed");
20962    cx.run_until_parked();
20963
20964    // Despite two edits, only one is actually applied as those are identical
20965    cx.assert_editor_state(indoc! {"
20966        struct FooRenamedˇ {}
20967    "});
20968}
20969
20970#[gpui::test]
20971async fn test_rename_without_prepare(cx: &mut TestAppContext) {
20972    init_test(cx, |_| {});
20973    // These capabilities indicate that the server does not support prepare rename.
20974    let capabilities = lsp::ServerCapabilities {
20975        rename_provider: Some(lsp::OneOf::Left(true)),
20976        ..Default::default()
20977    };
20978    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
20979
20980    cx.set_state(indoc! {"
20981        struct Fˇoo {}
20982    "});
20983
20984    cx.update_editor(|editor, _window, cx| {
20985        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
20986        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
20987        editor.highlight_background::<DocumentHighlightRead>(
20988            &[highlight_range],
20989            |theme| theme.colors().editor_document_highlight_read_background,
20990            cx,
20991        );
20992    });
20993
20994    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
20995        .expect("Prepare rename was not started")
20996        .await
20997        .expect("Prepare rename failed");
20998
20999    let mut rename_handler =
21000        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
21001            let edit = lsp::TextEdit {
21002                range: lsp::Range {
21003                    start: lsp::Position {
21004                        line: 0,
21005                        character: 7,
21006                    },
21007                    end: lsp::Position {
21008                        line: 0,
21009                        character: 10,
21010                    },
21011                },
21012                new_text: "FooRenamed".to_string(),
21013            };
21014            Ok(Some(lsp::WorkspaceEdit::new(
21015                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
21016            )))
21017        });
21018    let rename_task = cx
21019        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
21020        .expect("Confirm rename was not started");
21021    rename_handler.next().await.unwrap();
21022    rename_task.await.expect("Confirm rename failed");
21023    cx.run_until_parked();
21024
21025    // Correct range is renamed, as `surrounding_word` is used to find it.
21026    cx.assert_editor_state(indoc! {"
21027        struct FooRenamedˇ {}
21028    "});
21029}
21030
21031#[gpui::test]
21032async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
21033    init_test(cx, |_| {});
21034    let mut cx = EditorTestContext::new(cx).await;
21035
21036    let language = Arc::new(
21037        Language::new(
21038            LanguageConfig::default(),
21039            Some(tree_sitter_html::LANGUAGE.into()),
21040        )
21041        .with_brackets_query(
21042            r#"
21043            ("<" @open "/>" @close)
21044            ("</" @open ">" @close)
21045            ("<" @open ">" @close)
21046            ("\"" @open "\"" @close)
21047            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
21048        "#,
21049        )
21050        .unwrap(),
21051    );
21052    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
21053
21054    cx.set_state(indoc! {"
21055        <span>ˇ</span>
21056    "});
21057    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21058    cx.assert_editor_state(indoc! {"
21059        <span>
21060        ˇ
21061        </span>
21062    "});
21063
21064    cx.set_state(indoc! {"
21065        <span><span></span>ˇ</span>
21066    "});
21067    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21068    cx.assert_editor_state(indoc! {"
21069        <span><span></span>
21070        ˇ</span>
21071    "});
21072
21073    cx.set_state(indoc! {"
21074        <span>ˇ
21075        </span>
21076    "});
21077    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21078    cx.assert_editor_state(indoc! {"
21079        <span>
21080        ˇ
21081        </span>
21082    "});
21083}
21084
21085#[gpui::test(iterations = 10)]
21086async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
21087    init_test(cx, |_| {});
21088
21089    let fs = FakeFs::new(cx.executor());
21090    fs.insert_tree(
21091        path!("/dir"),
21092        json!({
21093            "a.ts": "a",
21094        }),
21095    )
21096    .await;
21097
21098    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
21099    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21100    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21101
21102    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21103    language_registry.add(Arc::new(Language::new(
21104        LanguageConfig {
21105            name: "TypeScript".into(),
21106            matcher: LanguageMatcher {
21107                path_suffixes: vec!["ts".to_string()],
21108                ..Default::default()
21109            },
21110            ..Default::default()
21111        },
21112        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
21113    )));
21114    let mut fake_language_servers = language_registry.register_fake_lsp(
21115        "TypeScript",
21116        FakeLspAdapter {
21117            capabilities: lsp::ServerCapabilities {
21118                code_lens_provider: Some(lsp::CodeLensOptions {
21119                    resolve_provider: Some(true),
21120                }),
21121                execute_command_provider: Some(lsp::ExecuteCommandOptions {
21122                    commands: vec!["_the/command".to_string()],
21123                    ..lsp::ExecuteCommandOptions::default()
21124                }),
21125                ..lsp::ServerCapabilities::default()
21126            },
21127            ..FakeLspAdapter::default()
21128        },
21129    );
21130
21131    let (buffer, _handle) = project
21132        .update(cx, |p, cx| {
21133            p.open_local_buffer_with_lsp(path!("/dir/a.ts"), cx)
21134        })
21135        .await
21136        .unwrap();
21137    cx.executor().run_until_parked();
21138
21139    let fake_server = fake_language_servers.next().await.unwrap();
21140
21141    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
21142    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
21143    drop(buffer_snapshot);
21144    let actions = cx
21145        .update_window(*workspace, |_, window, cx| {
21146            project.code_actions(&buffer, anchor..anchor, window, cx)
21147        })
21148        .unwrap();
21149
21150    fake_server
21151        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
21152            Ok(Some(vec![
21153                lsp::CodeLens {
21154                    range: lsp::Range::default(),
21155                    command: Some(lsp::Command {
21156                        title: "Code lens command".to_owned(),
21157                        command: "_the/command".to_owned(),
21158                        arguments: None,
21159                    }),
21160                    data: None,
21161                },
21162                lsp::CodeLens {
21163                    range: lsp::Range::default(),
21164                    command: Some(lsp::Command {
21165                        title: "Command not in capabilities".to_owned(),
21166                        command: "not in capabilities".to_owned(),
21167                        arguments: None,
21168                    }),
21169                    data: None,
21170                },
21171                lsp::CodeLens {
21172                    range: lsp::Range {
21173                        start: lsp::Position {
21174                            line: 1,
21175                            character: 1,
21176                        },
21177                        end: lsp::Position {
21178                            line: 1,
21179                            character: 1,
21180                        },
21181                    },
21182                    command: Some(lsp::Command {
21183                        title: "Command not in range".to_owned(),
21184                        command: "_the/command".to_owned(),
21185                        arguments: None,
21186                    }),
21187                    data: None,
21188                },
21189            ]))
21190        })
21191        .next()
21192        .await;
21193
21194    let actions = actions.await.unwrap();
21195    assert_eq!(
21196        actions.len(),
21197        1,
21198        "Should have only one valid action for the 0..0 range"
21199    );
21200    let action = actions[0].clone();
21201    let apply = project.update(cx, |project, cx| {
21202        project.apply_code_action(buffer.clone(), action, true, cx)
21203    });
21204
21205    // Resolving the code action does not populate its edits. In absence of
21206    // edits, we must execute the given command.
21207    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
21208        |mut lens, _| async move {
21209            let lens_command = lens.command.as_mut().expect("should have a command");
21210            assert_eq!(lens_command.title, "Code lens command");
21211            lens_command.arguments = Some(vec![json!("the-argument")]);
21212            Ok(lens)
21213        },
21214    );
21215
21216    // While executing the command, the language server sends the editor
21217    // a `workspaceEdit` request.
21218    fake_server
21219        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
21220            let fake = fake_server.clone();
21221            move |params, _| {
21222                assert_eq!(params.command, "_the/command");
21223                let fake = fake.clone();
21224                async move {
21225                    fake.server
21226                        .request::<lsp::request::ApplyWorkspaceEdit>(
21227                            lsp::ApplyWorkspaceEditParams {
21228                                label: None,
21229                                edit: lsp::WorkspaceEdit {
21230                                    changes: Some(
21231                                        [(
21232                                            lsp::Url::from_file_path(path!("/dir/a.ts")).unwrap(),
21233                                            vec![lsp::TextEdit {
21234                                                range: lsp::Range::new(
21235                                                    lsp::Position::new(0, 0),
21236                                                    lsp::Position::new(0, 0),
21237                                                ),
21238                                                new_text: "X".into(),
21239                                            }],
21240                                        )]
21241                                        .into_iter()
21242                                        .collect(),
21243                                    ),
21244                                    ..Default::default()
21245                                },
21246                            },
21247                        )
21248                        .await
21249                        .into_response()
21250                        .unwrap();
21251                    Ok(Some(json!(null)))
21252                }
21253            }
21254        })
21255        .next()
21256        .await;
21257
21258    // Applying the code lens command returns a project transaction containing the edits
21259    // sent by the language server in its `workspaceEdit` request.
21260    let transaction = apply.await.unwrap();
21261    assert!(transaction.0.contains_key(&buffer));
21262    buffer.update(cx, |buffer, cx| {
21263        assert_eq!(buffer.text(), "Xa");
21264        buffer.undo(cx);
21265        assert_eq!(buffer.text(), "a");
21266    });
21267}
21268
21269#[gpui::test]
21270async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
21271    init_test(cx, |_| {});
21272
21273    let fs = FakeFs::new(cx.executor());
21274    let main_text = r#"fn main() {
21275println!("1");
21276println!("2");
21277println!("3");
21278println!("4");
21279println!("5");
21280}"#;
21281    let lib_text = "mod foo {}";
21282    fs.insert_tree(
21283        path!("/a"),
21284        json!({
21285            "lib.rs": lib_text,
21286            "main.rs": main_text,
21287        }),
21288    )
21289    .await;
21290
21291    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21292    let (workspace, cx) =
21293        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21294    let worktree_id = workspace.update(cx, |workspace, cx| {
21295        workspace.project().update(cx, |project, cx| {
21296            project.worktrees(cx).next().unwrap().read(cx).id()
21297        })
21298    });
21299
21300    let expected_ranges = vec![
21301        Point::new(0, 0)..Point::new(0, 0),
21302        Point::new(1, 0)..Point::new(1, 1),
21303        Point::new(2, 0)..Point::new(2, 2),
21304        Point::new(3, 0)..Point::new(3, 3),
21305    ];
21306
21307    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21308    let editor_1 = workspace
21309        .update_in(cx, |workspace, window, cx| {
21310            workspace.open_path(
21311                (worktree_id, "main.rs"),
21312                Some(pane_1.downgrade()),
21313                true,
21314                window,
21315                cx,
21316            )
21317        })
21318        .unwrap()
21319        .await
21320        .downcast::<Editor>()
21321        .unwrap();
21322    pane_1.update(cx, |pane, cx| {
21323        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21324        open_editor.update(cx, |editor, cx| {
21325            assert_eq!(
21326                editor.display_text(cx),
21327                main_text,
21328                "Original main.rs text on initial open",
21329            );
21330            assert_eq!(
21331                editor
21332                    .selections
21333                    .all::<Point>(cx)
21334                    .into_iter()
21335                    .map(|s| s.range())
21336                    .collect::<Vec<_>>(),
21337                vec![Point::zero()..Point::zero()],
21338                "Default selections on initial open",
21339            );
21340        })
21341    });
21342    editor_1.update_in(cx, |editor, window, cx| {
21343        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21344            s.select_ranges(expected_ranges.clone());
21345        });
21346    });
21347
21348    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
21349        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
21350    });
21351    let editor_2 = workspace
21352        .update_in(cx, |workspace, window, cx| {
21353            workspace.open_path(
21354                (worktree_id, "main.rs"),
21355                Some(pane_2.downgrade()),
21356                true,
21357                window,
21358                cx,
21359            )
21360        })
21361        .unwrap()
21362        .await
21363        .downcast::<Editor>()
21364        .unwrap();
21365    pane_2.update(cx, |pane, cx| {
21366        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21367        open_editor.update(cx, |editor, cx| {
21368            assert_eq!(
21369                editor.display_text(cx),
21370                main_text,
21371                "Original main.rs text on initial open in another panel",
21372            );
21373            assert_eq!(
21374                editor
21375                    .selections
21376                    .all::<Point>(cx)
21377                    .into_iter()
21378                    .map(|s| s.range())
21379                    .collect::<Vec<_>>(),
21380                vec![Point::zero()..Point::zero()],
21381                "Default selections on initial open in another panel",
21382            );
21383        })
21384    });
21385
21386    editor_2.update_in(cx, |editor, window, cx| {
21387        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
21388    });
21389
21390    let _other_editor_1 = workspace
21391        .update_in(cx, |workspace, window, cx| {
21392            workspace.open_path(
21393                (worktree_id, "lib.rs"),
21394                Some(pane_1.downgrade()),
21395                true,
21396                window,
21397                cx,
21398            )
21399        })
21400        .unwrap()
21401        .await
21402        .downcast::<Editor>()
21403        .unwrap();
21404    pane_1
21405        .update_in(cx, |pane, window, cx| {
21406            pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
21407        })
21408        .await
21409        .unwrap();
21410    drop(editor_1);
21411    pane_1.update(cx, |pane, cx| {
21412        pane.active_item()
21413            .unwrap()
21414            .downcast::<Editor>()
21415            .unwrap()
21416            .update(cx, |editor, cx| {
21417                assert_eq!(
21418                    editor.display_text(cx),
21419                    lib_text,
21420                    "Other file should be open and active",
21421                );
21422            });
21423        assert_eq!(pane.items().count(), 1, "No other editors should be open");
21424    });
21425
21426    let _other_editor_2 = workspace
21427        .update_in(cx, |workspace, window, cx| {
21428            workspace.open_path(
21429                (worktree_id, "lib.rs"),
21430                Some(pane_2.downgrade()),
21431                true,
21432                window,
21433                cx,
21434            )
21435        })
21436        .unwrap()
21437        .await
21438        .downcast::<Editor>()
21439        .unwrap();
21440    pane_2
21441        .update_in(cx, |pane, window, cx| {
21442            pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
21443        })
21444        .await
21445        .unwrap();
21446    drop(editor_2);
21447    pane_2.update(cx, |pane, cx| {
21448        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21449        open_editor.update(cx, |editor, cx| {
21450            assert_eq!(
21451                editor.display_text(cx),
21452                lib_text,
21453                "Other file should be open and active in another panel too",
21454            );
21455        });
21456        assert_eq!(
21457            pane.items().count(),
21458            1,
21459            "No other editors should be open in another pane",
21460        );
21461    });
21462
21463    let _editor_1_reopened = workspace
21464        .update_in(cx, |workspace, window, cx| {
21465            workspace.open_path(
21466                (worktree_id, "main.rs"),
21467                Some(pane_1.downgrade()),
21468                true,
21469                window,
21470                cx,
21471            )
21472        })
21473        .unwrap()
21474        .await
21475        .downcast::<Editor>()
21476        .unwrap();
21477    let _editor_2_reopened = workspace
21478        .update_in(cx, |workspace, window, cx| {
21479            workspace.open_path(
21480                (worktree_id, "main.rs"),
21481                Some(pane_2.downgrade()),
21482                true,
21483                window,
21484                cx,
21485            )
21486        })
21487        .unwrap()
21488        .await
21489        .downcast::<Editor>()
21490        .unwrap();
21491    pane_1.update(cx, |pane, cx| {
21492        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21493        open_editor.update(cx, |editor, cx| {
21494            assert_eq!(
21495                editor.display_text(cx),
21496                main_text,
21497                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
21498            );
21499            assert_eq!(
21500                editor
21501                    .selections
21502                    .all::<Point>(cx)
21503                    .into_iter()
21504                    .map(|s| s.range())
21505                    .collect::<Vec<_>>(),
21506                expected_ranges,
21507                "Previous editor in the 1st panel had selections and should get them restored on reopen",
21508            );
21509        })
21510    });
21511    pane_2.update(cx, |pane, cx| {
21512        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21513        open_editor.update(cx, |editor, cx| {
21514            assert_eq!(
21515                editor.display_text(cx),
21516                r#"fn main() {
21517⋯rintln!("1");
21518⋯intln!("2");
21519⋯ntln!("3");
21520println!("4");
21521println!("5");
21522}"#,
21523                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
21524            );
21525            assert_eq!(
21526                editor
21527                    .selections
21528                    .all::<Point>(cx)
21529                    .into_iter()
21530                    .map(|s| s.range())
21531                    .collect::<Vec<_>>(),
21532                vec![Point::zero()..Point::zero()],
21533                "Previous editor in the 2nd pane had no selections changed hence should restore none",
21534            );
21535        })
21536    });
21537}
21538
21539#[gpui::test]
21540async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
21541    init_test(cx, |_| {});
21542
21543    let fs = FakeFs::new(cx.executor());
21544    let main_text = r#"fn main() {
21545println!("1");
21546println!("2");
21547println!("3");
21548println!("4");
21549println!("5");
21550}"#;
21551    let lib_text = "mod foo {}";
21552    fs.insert_tree(
21553        path!("/a"),
21554        json!({
21555            "lib.rs": lib_text,
21556            "main.rs": main_text,
21557        }),
21558    )
21559    .await;
21560
21561    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21562    let (workspace, cx) =
21563        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21564    let worktree_id = workspace.update(cx, |workspace, cx| {
21565        workspace.project().update(cx, |project, cx| {
21566            project.worktrees(cx).next().unwrap().read(cx).id()
21567        })
21568    });
21569
21570    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21571    let editor = workspace
21572        .update_in(cx, |workspace, window, cx| {
21573            workspace.open_path(
21574                (worktree_id, "main.rs"),
21575                Some(pane.downgrade()),
21576                true,
21577                window,
21578                cx,
21579            )
21580        })
21581        .unwrap()
21582        .await
21583        .downcast::<Editor>()
21584        .unwrap();
21585    pane.update(cx, |pane, cx| {
21586        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21587        open_editor.update(cx, |editor, cx| {
21588            assert_eq!(
21589                editor.display_text(cx),
21590                main_text,
21591                "Original main.rs text on initial open",
21592            );
21593        })
21594    });
21595    editor.update_in(cx, |editor, window, cx| {
21596        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
21597    });
21598
21599    cx.update_global(|store: &mut SettingsStore, cx| {
21600        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
21601            s.restore_on_file_reopen = Some(false);
21602        });
21603    });
21604    editor.update_in(cx, |editor, window, cx| {
21605        editor.fold_ranges(
21606            vec![
21607                Point::new(1, 0)..Point::new(1, 1),
21608                Point::new(2, 0)..Point::new(2, 2),
21609                Point::new(3, 0)..Point::new(3, 3),
21610            ],
21611            false,
21612            window,
21613            cx,
21614        );
21615    });
21616    pane.update_in(cx, |pane, window, cx| {
21617        pane.close_all_items(&CloseAllItems::default(), window, cx)
21618    })
21619    .await
21620    .unwrap();
21621    pane.update(cx, |pane, _| {
21622        assert!(pane.active_item().is_none());
21623    });
21624    cx.update_global(|store: &mut SettingsStore, cx| {
21625        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
21626            s.restore_on_file_reopen = Some(true);
21627        });
21628    });
21629
21630    let _editor_reopened = workspace
21631        .update_in(cx, |workspace, window, cx| {
21632            workspace.open_path(
21633                (worktree_id, "main.rs"),
21634                Some(pane.downgrade()),
21635                true,
21636                window,
21637                cx,
21638            )
21639        })
21640        .unwrap()
21641        .await
21642        .downcast::<Editor>()
21643        .unwrap();
21644    pane.update(cx, |pane, cx| {
21645        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21646        open_editor.update(cx, |editor, cx| {
21647            assert_eq!(
21648                editor.display_text(cx),
21649                main_text,
21650                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
21651            );
21652        })
21653    });
21654}
21655
21656#[gpui::test]
21657async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
21658    struct EmptyModalView {
21659        focus_handle: gpui::FocusHandle,
21660    }
21661    impl EventEmitter<DismissEvent> for EmptyModalView {}
21662    impl Render for EmptyModalView {
21663        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
21664            div()
21665        }
21666    }
21667    impl Focusable for EmptyModalView {
21668        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
21669            self.focus_handle.clone()
21670        }
21671    }
21672    impl workspace::ModalView for EmptyModalView {}
21673    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
21674        EmptyModalView {
21675            focus_handle: cx.focus_handle(),
21676        }
21677    }
21678
21679    init_test(cx, |_| {});
21680
21681    let fs = FakeFs::new(cx.executor());
21682    let project = Project::test(fs, [], cx).await;
21683    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21684    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
21685    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21686    let editor = cx.new_window_entity(|window, cx| {
21687        Editor::new(
21688            EditorMode::full(),
21689            buffer,
21690            Some(project.clone()),
21691            window,
21692            cx,
21693        )
21694    });
21695    workspace
21696        .update(cx, |workspace, window, cx| {
21697            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
21698        })
21699        .unwrap();
21700    editor.update_in(cx, |editor, window, cx| {
21701        editor.open_context_menu(&OpenContextMenu, window, cx);
21702        assert!(editor.mouse_context_menu.is_some());
21703    });
21704    workspace
21705        .update(cx, |workspace, window, cx| {
21706            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
21707        })
21708        .unwrap();
21709    cx.read(|cx| {
21710        assert!(editor.read(cx).mouse_context_menu.is_none());
21711    });
21712}
21713
21714#[gpui::test]
21715async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
21716    init_test(cx, |_| {});
21717
21718    let fs = FakeFs::new(cx.executor());
21719    fs.insert_file(path!("/file.html"), Default::default())
21720        .await;
21721
21722    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
21723
21724    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21725    let html_language = Arc::new(Language::new(
21726        LanguageConfig {
21727            name: "HTML".into(),
21728            matcher: LanguageMatcher {
21729                path_suffixes: vec!["html".to_string()],
21730                ..LanguageMatcher::default()
21731            },
21732            brackets: BracketPairConfig {
21733                pairs: vec![BracketPair {
21734                    start: "<".into(),
21735                    end: ">".into(),
21736                    close: true,
21737                    ..Default::default()
21738                }],
21739                ..Default::default()
21740            },
21741            ..Default::default()
21742        },
21743        Some(tree_sitter_html::LANGUAGE.into()),
21744    ));
21745    language_registry.add(html_language);
21746    let mut fake_servers = language_registry.register_fake_lsp(
21747        "HTML",
21748        FakeLspAdapter {
21749            capabilities: lsp::ServerCapabilities {
21750                completion_provider: Some(lsp::CompletionOptions {
21751                    resolve_provider: Some(true),
21752                    ..Default::default()
21753                }),
21754                ..Default::default()
21755            },
21756            ..Default::default()
21757        },
21758    );
21759
21760    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21761    let cx = &mut VisualTestContext::from_window(*workspace, cx);
21762
21763    let worktree_id = workspace
21764        .update(cx, |workspace, _window, cx| {
21765            workspace.project().update(cx, |project, cx| {
21766                project.worktrees(cx).next().unwrap().read(cx).id()
21767            })
21768        })
21769        .unwrap();
21770    project
21771        .update(cx, |project, cx| {
21772            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
21773        })
21774        .await
21775        .unwrap();
21776    let editor = workspace
21777        .update(cx, |workspace, window, cx| {
21778            workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
21779        })
21780        .unwrap()
21781        .await
21782        .unwrap()
21783        .downcast::<Editor>()
21784        .unwrap();
21785
21786    let fake_server = fake_servers.next().await.unwrap();
21787    editor.update_in(cx, |editor, window, cx| {
21788        editor.set_text("<ad></ad>", window, cx);
21789        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21790            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
21791        });
21792        let Some((buffer, _)) = editor
21793            .buffer
21794            .read(cx)
21795            .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
21796        else {
21797            panic!("Failed to get buffer for selection position");
21798        };
21799        let buffer = buffer.read(cx);
21800        let buffer_id = buffer.remote_id();
21801        let opening_range =
21802            buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3));
21803        let closing_range =
21804            buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8));
21805        let mut linked_ranges = HashMap::default();
21806        linked_ranges.insert(
21807            buffer_id,
21808            vec![(opening_range.clone(), vec![closing_range.clone()])],
21809        );
21810        editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
21811    });
21812    let mut completion_handle =
21813        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
21814            Ok(Some(lsp::CompletionResponse::Array(vec![
21815                lsp::CompletionItem {
21816                    label: "head".to_string(),
21817                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21818                        lsp::InsertReplaceEdit {
21819                            new_text: "head".to_string(),
21820                            insert: lsp::Range::new(
21821                                lsp::Position::new(0, 1),
21822                                lsp::Position::new(0, 3),
21823                            ),
21824                            replace: lsp::Range::new(
21825                                lsp::Position::new(0, 1),
21826                                lsp::Position::new(0, 3),
21827                            ),
21828                        },
21829                    )),
21830                    ..Default::default()
21831                },
21832            ])))
21833        });
21834    editor.update_in(cx, |editor, window, cx| {
21835        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
21836    });
21837    cx.run_until_parked();
21838    completion_handle.next().await.unwrap();
21839    editor.update(cx, |editor, _| {
21840        assert!(
21841            editor.context_menu_visible(),
21842            "Completion menu should be visible"
21843        );
21844    });
21845    editor.update_in(cx, |editor, window, cx| {
21846        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
21847    });
21848    cx.executor().run_until_parked();
21849    editor.update(cx, |editor, cx| {
21850        assert_eq!(editor.text(cx), "<head></head>");
21851    });
21852}
21853
21854#[gpui::test]
21855async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
21856    init_test(cx, |_| {});
21857
21858    let fs = FakeFs::new(cx.executor());
21859    fs.insert_tree(
21860        path!("/root"),
21861        json!({
21862            "a": {
21863                "main.rs": "fn main() {}",
21864            },
21865            "foo": {
21866                "bar": {
21867                    "external_file.rs": "pub mod external {}",
21868                }
21869            }
21870        }),
21871    )
21872    .await;
21873
21874    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
21875    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21876    language_registry.add(rust_lang());
21877    let _fake_servers = language_registry.register_fake_lsp(
21878        "Rust",
21879        FakeLspAdapter {
21880            ..FakeLspAdapter::default()
21881        },
21882    );
21883    let (workspace, cx) =
21884        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21885    let worktree_id = workspace.update(cx, |workspace, cx| {
21886        workspace.project().update(cx, |project, cx| {
21887            project.worktrees(cx).next().unwrap().read(cx).id()
21888        })
21889    });
21890
21891    let assert_language_servers_count =
21892        |expected: usize, context: &str, cx: &mut VisualTestContext| {
21893            project.update(cx, |project, cx| {
21894                let current = project
21895                    .lsp_store()
21896                    .read(cx)
21897                    .as_local()
21898                    .unwrap()
21899                    .language_servers
21900                    .len();
21901                assert_eq!(expected, current, "{context}");
21902            });
21903        };
21904
21905    assert_language_servers_count(
21906        0,
21907        "No servers should be running before any file is open",
21908        cx,
21909    );
21910    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21911    let main_editor = workspace
21912        .update_in(cx, |workspace, window, cx| {
21913            workspace.open_path(
21914                (worktree_id, "main.rs"),
21915                Some(pane.downgrade()),
21916                true,
21917                window,
21918                cx,
21919            )
21920        })
21921        .unwrap()
21922        .await
21923        .downcast::<Editor>()
21924        .unwrap();
21925    pane.update(cx, |pane, cx| {
21926        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21927        open_editor.update(cx, |editor, cx| {
21928            assert_eq!(
21929                editor.display_text(cx),
21930                "fn main() {}",
21931                "Original main.rs text on initial open",
21932            );
21933        });
21934        assert_eq!(open_editor, main_editor);
21935    });
21936    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
21937
21938    let external_editor = workspace
21939        .update_in(cx, |workspace, window, cx| {
21940            workspace.open_abs_path(
21941                PathBuf::from("/root/foo/bar/external_file.rs"),
21942                OpenOptions::default(),
21943                window,
21944                cx,
21945            )
21946        })
21947        .await
21948        .expect("opening external file")
21949        .downcast::<Editor>()
21950        .expect("downcasted external file's open element to editor");
21951    pane.update(cx, |pane, cx| {
21952        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21953        open_editor.update(cx, |editor, cx| {
21954            assert_eq!(
21955                editor.display_text(cx),
21956                "pub mod external {}",
21957                "External file is open now",
21958            );
21959        });
21960        assert_eq!(open_editor, external_editor);
21961    });
21962    assert_language_servers_count(
21963        1,
21964        "Second, external, *.rs file should join the existing server",
21965        cx,
21966    );
21967
21968    pane.update_in(cx, |pane, window, cx| {
21969        pane.close_active_item(&CloseActiveItem::default(), window, cx)
21970    })
21971    .await
21972    .unwrap();
21973    pane.update_in(cx, |pane, window, cx| {
21974        pane.navigate_backward(window, cx);
21975    });
21976    cx.run_until_parked();
21977    pane.update(cx, |pane, cx| {
21978        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21979        open_editor.update(cx, |editor, cx| {
21980            assert_eq!(
21981                editor.display_text(cx),
21982                "pub mod external {}",
21983                "External file is open now",
21984            );
21985        });
21986    });
21987    assert_language_servers_count(
21988        1,
21989        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
21990        cx,
21991    );
21992
21993    cx.update(|_, cx| {
21994        workspace::reload(&workspace::Reload::default(), cx);
21995    });
21996    assert_language_servers_count(
21997        1,
21998        "After reloading the worktree with local and external files opened, only one project should be started",
21999        cx,
22000    );
22001}
22002
22003#[gpui::test]
22004async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
22005    init_test(cx, |_| {});
22006
22007    let mut cx = EditorTestContext::new(cx).await;
22008    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22009    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22010
22011    // test cursor move to start of each line on tab
22012    // for `if`, `elif`, `else`, `while`, `with` and `for`
22013    cx.set_state(indoc! {"
22014        def main():
22015        ˇ    for item in items:
22016        ˇ        while item.active:
22017        ˇ            if item.value > 10:
22018        ˇ                continue
22019        ˇ            elif item.value < 0:
22020        ˇ                break
22021        ˇ            else:
22022        ˇ                with item.context() as ctx:
22023        ˇ                    yield count
22024        ˇ        else:
22025        ˇ            log('while else')
22026        ˇ    else:
22027        ˇ        log('for else')
22028    "});
22029    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22030    cx.assert_editor_state(indoc! {"
22031        def main():
22032            ˇfor item in items:
22033                ˇwhile item.active:
22034                    ˇif item.value > 10:
22035                        ˇcontinue
22036                    ˇelif item.value < 0:
22037                        ˇbreak
22038                    ˇelse:
22039                        ˇwith item.context() as ctx:
22040                            ˇyield count
22041                ˇelse:
22042                    ˇlog('while else')
22043            ˇelse:
22044                ˇlog('for else')
22045    "});
22046    // test relative indent is preserved when tab
22047    // for `if`, `elif`, `else`, `while`, `with` and `for`
22048    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22049    cx.assert_editor_state(indoc! {"
22050        def main():
22051                ˇfor item in items:
22052                    ˇwhile item.active:
22053                        ˇif item.value > 10:
22054                            ˇcontinue
22055                        ˇelif item.value < 0:
22056                            ˇbreak
22057                        ˇelse:
22058                            ˇwith item.context() as ctx:
22059                                ˇyield count
22060                    ˇelse:
22061                        ˇlog('while else')
22062                ˇelse:
22063                    ˇlog('for else')
22064    "});
22065
22066    // test cursor move to start of each line on tab
22067    // for `try`, `except`, `else`, `finally`, `match` and `def`
22068    cx.set_state(indoc! {"
22069        def main():
22070        ˇ    try:
22071        ˇ        fetch()
22072        ˇ    except ValueError:
22073        ˇ        handle_error()
22074        ˇ    else:
22075        ˇ        match value:
22076        ˇ            case _:
22077        ˇ    finally:
22078        ˇ        def status():
22079        ˇ            return 0
22080    "});
22081    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22082    cx.assert_editor_state(indoc! {"
22083        def main():
22084            ˇtry:
22085                ˇfetch()
22086            ˇexcept ValueError:
22087                ˇhandle_error()
22088            ˇelse:
22089                ˇmatch value:
22090                    ˇcase _:
22091            ˇfinally:
22092                ˇdef status():
22093                    ˇreturn 0
22094    "});
22095    // test relative indent is preserved when tab
22096    // for `try`, `except`, `else`, `finally`, `match` and `def`
22097    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22098    cx.assert_editor_state(indoc! {"
22099        def main():
22100                ˇtry:
22101                    ˇfetch()
22102                ˇexcept ValueError:
22103                    ˇhandle_error()
22104                ˇelse:
22105                    ˇmatch value:
22106                        ˇcase _:
22107                ˇfinally:
22108                    ˇdef status():
22109                        ˇreturn 0
22110    "});
22111}
22112
22113#[gpui::test]
22114async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
22115    init_test(cx, |_| {});
22116
22117    let mut cx = EditorTestContext::new(cx).await;
22118    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22119    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22120
22121    // test `else` auto outdents when typed inside `if` block
22122    cx.set_state(indoc! {"
22123        def main():
22124            if i == 2:
22125                return
22126                ˇ
22127    "});
22128    cx.update_editor(|editor, window, cx| {
22129        editor.handle_input("else:", window, cx);
22130    });
22131    cx.assert_editor_state(indoc! {"
22132        def main():
22133            if i == 2:
22134                return
22135            else:ˇ
22136    "});
22137
22138    // test `except` auto outdents when typed inside `try` block
22139    cx.set_state(indoc! {"
22140        def main():
22141            try:
22142                i = 2
22143                ˇ
22144    "});
22145    cx.update_editor(|editor, window, cx| {
22146        editor.handle_input("except:", window, cx);
22147    });
22148    cx.assert_editor_state(indoc! {"
22149        def main():
22150            try:
22151                i = 2
22152            except:ˇ
22153    "});
22154
22155    // test `else` auto outdents when typed inside `except` block
22156    cx.set_state(indoc! {"
22157        def main():
22158            try:
22159                i = 2
22160            except:
22161                j = 2
22162                ˇ
22163    "});
22164    cx.update_editor(|editor, window, cx| {
22165        editor.handle_input("else:", window, cx);
22166    });
22167    cx.assert_editor_state(indoc! {"
22168        def main():
22169            try:
22170                i = 2
22171            except:
22172                j = 2
22173            else:ˇ
22174    "});
22175
22176    // test `finally` auto outdents when typed inside `else` block
22177    cx.set_state(indoc! {"
22178        def main():
22179            try:
22180                i = 2
22181            except:
22182                j = 2
22183            else:
22184                k = 2
22185                ˇ
22186    "});
22187    cx.update_editor(|editor, window, cx| {
22188        editor.handle_input("finally:", 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                k = 2
22198            finally:ˇ
22199    "});
22200
22201    // test `else` does not outdents when typed inside `except` block right after for block
22202    cx.set_state(indoc! {"
22203        def main():
22204            try:
22205                i = 2
22206            except:
22207                for i in range(n):
22208                    pass
22209                ˇ
22210    "});
22211    cx.update_editor(|editor, window, cx| {
22212        editor.handle_input("else:", window, cx);
22213    });
22214    cx.assert_editor_state(indoc! {"
22215        def main():
22216            try:
22217                i = 2
22218            except:
22219                for i in range(n):
22220                    pass
22221                else:ˇ
22222    "});
22223
22224    // test `finally` auto outdents when typed inside `else` block right after for block
22225    cx.set_state(indoc! {"
22226        def main():
22227            try:
22228                i = 2
22229            except:
22230                j = 2
22231            else:
22232                for i in range(n):
22233                    pass
22234                ˇ
22235    "});
22236    cx.update_editor(|editor, window, cx| {
22237        editor.handle_input("finally:", window, cx);
22238    });
22239    cx.assert_editor_state(indoc! {"
22240        def main():
22241            try:
22242                i = 2
22243            except:
22244                j = 2
22245            else:
22246                for i in range(n):
22247                    pass
22248            finally:ˇ
22249    "});
22250
22251    // test `except` outdents to inner "try" block
22252    cx.set_state(indoc! {"
22253        def main():
22254            try:
22255                i = 2
22256                if i == 2:
22257                    try:
22258                        i = 3
22259                        ˇ
22260    "});
22261    cx.update_editor(|editor, window, cx| {
22262        editor.handle_input("except:", window, cx);
22263    });
22264    cx.assert_editor_state(indoc! {"
22265        def main():
22266            try:
22267                i = 2
22268                if i == 2:
22269                    try:
22270                        i = 3
22271                    except:ˇ
22272    "});
22273
22274    // test `except` outdents to outer "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 `else` stays at correct indent when typed after `for` block
22298    cx.set_state(indoc! {"
22299        def main():
22300            for i in range(10):
22301                if i == 3:
22302                    break
22303            ˇ
22304    "});
22305    cx.update_editor(|editor, window, cx| {
22306        editor.handle_input("else:", window, cx);
22307    });
22308    cx.assert_editor_state(indoc! {"
22309        def main():
22310            for i in range(10):
22311                if i == 3:
22312                    break
22313            else:ˇ
22314    "});
22315
22316    // test does not outdent on typing after line with square brackets
22317    cx.set_state(indoc! {"
22318        def f() -> list[str]:
22319            ˇ
22320    "});
22321    cx.update_editor(|editor, window, cx| {
22322        editor.handle_input("a", window, cx);
22323    });
22324    cx.assert_editor_state(indoc! {"
22325        def f() -> list[str]:
2232622327    "});
22328}
22329
22330#[gpui::test]
22331async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
22332    init_test(cx, |_| {});
22333    update_test_language_settings(cx, |settings| {
22334        settings.defaults.extend_comment_on_newline = Some(false);
22335    });
22336    let mut cx = EditorTestContext::new(cx).await;
22337    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22338    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22339
22340    // test correct indent after newline on comment
22341    cx.set_state(indoc! {"
22342        # COMMENT:ˇ
22343    "});
22344    cx.update_editor(|editor, window, cx| {
22345        editor.newline(&Newline, window, cx);
22346    });
22347    cx.assert_editor_state(indoc! {"
22348        # COMMENT:
22349        ˇ
22350    "});
22351
22352    // test correct indent after newline in brackets
22353    cx.set_state(indoc! {"
22354        {ˇ}
22355    "});
22356    cx.update_editor(|editor, window, cx| {
22357        editor.newline(&Newline, window, cx);
22358    });
22359    cx.run_until_parked();
22360    cx.assert_editor_state(indoc! {"
22361        {
22362            ˇ
22363        }
22364    "});
22365
22366    cx.set_state(indoc! {"
22367        (ˇ)
22368    "});
22369    cx.update_editor(|editor, window, cx| {
22370        editor.newline(&Newline, window, cx);
22371    });
22372    cx.run_until_parked();
22373    cx.assert_editor_state(indoc! {"
22374        (
22375            ˇ
22376        )
22377    "});
22378
22379    // do not indent after empty lists or dictionaries
22380    cx.set_state(indoc! {"
22381        a = []ˇ
22382    "});
22383    cx.update_editor(|editor, window, cx| {
22384        editor.newline(&Newline, window, cx);
22385    });
22386    cx.run_until_parked();
22387    cx.assert_editor_state(indoc! {"
22388        a = []
22389        ˇ
22390    "});
22391}
22392
22393fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
22394    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
22395    point..point
22396}
22397
22398#[track_caller]
22399fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
22400    let (text, ranges) = marked_text_ranges(marked_text, true);
22401    assert_eq!(editor.text(cx), text);
22402    assert_eq!(
22403        editor.selections.ranges(cx),
22404        ranges,
22405        "Assert selections are {}",
22406        marked_text
22407    );
22408}
22409
22410pub fn handle_signature_help_request(
22411    cx: &mut EditorLspTestContext,
22412    mocked_response: lsp::SignatureHelp,
22413) -> impl Future<Output = ()> + use<> {
22414    let mut request =
22415        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
22416            let mocked_response = mocked_response.clone();
22417            async move { Ok(Some(mocked_response)) }
22418        });
22419
22420    async move {
22421        request.next().await;
22422    }
22423}
22424
22425#[track_caller]
22426pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
22427    cx.update_editor(|editor, _, _| {
22428        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
22429            let entries = menu.entries.borrow();
22430            let entries = entries
22431                .iter()
22432                .map(|entry| entry.string.as_str())
22433                .collect::<Vec<_>>();
22434            assert_eq!(entries, expected);
22435        } else {
22436            panic!("Expected completions menu");
22437        }
22438    });
22439}
22440
22441/// Handle completion request passing a marked string specifying where the completion
22442/// should be triggered from using '|' character, what range should be replaced, and what completions
22443/// should be returned using '<' and '>' to delimit the range.
22444///
22445/// Also see `handle_completion_request_with_insert_and_replace`.
22446#[track_caller]
22447pub fn handle_completion_request(
22448    marked_string: &str,
22449    completions: Vec<&'static str>,
22450    is_incomplete: bool,
22451    counter: Arc<AtomicUsize>,
22452    cx: &mut EditorLspTestContext,
22453) -> impl Future<Output = ()> {
22454    let complete_from_marker: TextRangeMarker = '|'.into();
22455    let replace_range_marker: TextRangeMarker = ('<', '>').into();
22456    let (_, mut marked_ranges) = marked_text_ranges_by(
22457        marked_string,
22458        vec![complete_from_marker.clone(), replace_range_marker.clone()],
22459    );
22460
22461    let complete_from_position =
22462        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
22463    let replace_range =
22464        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
22465
22466    let mut request =
22467        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
22468            let completions = completions.clone();
22469            counter.fetch_add(1, atomic::Ordering::Release);
22470            async move {
22471                assert_eq!(params.text_document_position.text_document.uri, url.clone());
22472                assert_eq!(
22473                    params.text_document_position.position,
22474                    complete_from_position
22475                );
22476                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
22477                    is_incomplete: is_incomplete,
22478                    item_defaults: None,
22479                    items: completions
22480                        .iter()
22481                        .map(|completion_text| lsp::CompletionItem {
22482                            label: completion_text.to_string(),
22483                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
22484                                range: replace_range,
22485                                new_text: completion_text.to_string(),
22486                            })),
22487                            ..Default::default()
22488                        })
22489                        .collect(),
22490                })))
22491            }
22492        });
22493
22494    async move {
22495        request.next().await;
22496    }
22497}
22498
22499/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
22500/// given instead, which also contains an `insert` range.
22501///
22502/// This function uses markers to define ranges:
22503/// - `|` marks the cursor position
22504/// - `<>` marks the replace range
22505/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
22506pub fn handle_completion_request_with_insert_and_replace(
22507    cx: &mut EditorLspTestContext,
22508    marked_string: &str,
22509    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
22510    counter: Arc<AtomicUsize>,
22511) -> impl Future<Output = ()> {
22512    let complete_from_marker: TextRangeMarker = '|'.into();
22513    let replace_range_marker: TextRangeMarker = ('<', '>').into();
22514    let insert_range_marker: TextRangeMarker = ('{', '}').into();
22515
22516    let (_, mut marked_ranges) = marked_text_ranges_by(
22517        marked_string,
22518        vec![
22519            complete_from_marker.clone(),
22520            replace_range_marker.clone(),
22521            insert_range_marker.clone(),
22522        ],
22523    );
22524
22525    let complete_from_position =
22526        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
22527    let replace_range =
22528        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
22529
22530    let insert_range = match marked_ranges.remove(&insert_range_marker) {
22531        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
22532        _ => lsp::Range {
22533            start: replace_range.start,
22534            end: complete_from_position,
22535        },
22536    };
22537
22538    let mut request =
22539        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
22540            let completions = completions.clone();
22541            counter.fetch_add(1, atomic::Ordering::Release);
22542            async move {
22543                assert_eq!(params.text_document_position.text_document.uri, url.clone());
22544                assert_eq!(
22545                    params.text_document_position.position, complete_from_position,
22546                    "marker `|` position doesn't match",
22547                );
22548                Ok(Some(lsp::CompletionResponse::Array(
22549                    completions
22550                        .iter()
22551                        .map(|(label, new_text)| lsp::CompletionItem {
22552                            label: label.to_string(),
22553                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22554                                lsp::InsertReplaceEdit {
22555                                    insert: insert_range,
22556                                    replace: replace_range,
22557                                    new_text: new_text.to_string(),
22558                                },
22559                            )),
22560                            ..Default::default()
22561                        })
22562                        .collect(),
22563                )))
22564            }
22565        });
22566
22567    async move {
22568        request.next().await;
22569    }
22570}
22571
22572fn handle_resolve_completion_request(
22573    cx: &mut EditorLspTestContext,
22574    edits: Option<Vec<(&'static str, &'static str)>>,
22575) -> impl Future<Output = ()> {
22576    let edits = edits.map(|edits| {
22577        edits
22578            .iter()
22579            .map(|(marked_string, new_text)| {
22580                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
22581                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
22582                lsp::TextEdit::new(replace_range, new_text.to_string())
22583            })
22584            .collect::<Vec<_>>()
22585    });
22586
22587    let mut request =
22588        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
22589            let edits = edits.clone();
22590            async move {
22591                Ok(lsp::CompletionItem {
22592                    additional_text_edits: edits,
22593                    ..Default::default()
22594                })
22595            }
22596        });
22597
22598    async move {
22599        request.next().await;
22600    }
22601}
22602
22603pub(crate) fn update_test_language_settings(
22604    cx: &mut TestAppContext,
22605    f: impl Fn(&mut AllLanguageSettingsContent),
22606) {
22607    cx.update(|cx| {
22608        SettingsStore::update_global(cx, |store, cx| {
22609            store.update_user_settings::<AllLanguageSettings>(cx, f);
22610        });
22611    });
22612}
22613
22614pub(crate) fn update_test_project_settings(
22615    cx: &mut TestAppContext,
22616    f: impl Fn(&mut ProjectSettings),
22617) {
22618    cx.update(|cx| {
22619        SettingsStore::update_global(cx, |store, cx| {
22620            store.update_user_settings::<ProjectSettings>(cx, f);
22621        });
22622    });
22623}
22624
22625pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
22626    cx.update(|cx| {
22627        assets::Assets.load_test_fonts(cx);
22628        let store = SettingsStore::test(cx);
22629        cx.set_global(store);
22630        theme::init(theme::LoadThemes::JustBase, cx);
22631        release_channel::init(SemanticVersion::default(), cx);
22632        client::init_settings(cx);
22633        language::init(cx);
22634        Project::init_settings(cx);
22635        workspace::init_settings(cx);
22636        crate::init(cx);
22637    });
22638
22639    update_test_language_settings(cx, f);
22640}
22641
22642#[track_caller]
22643fn assert_hunk_revert(
22644    not_reverted_text_with_selections: &str,
22645    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
22646    expected_reverted_text_with_selections: &str,
22647    base_text: &str,
22648    cx: &mut EditorLspTestContext,
22649) {
22650    cx.set_state(not_reverted_text_with_selections);
22651    cx.set_head_text(base_text);
22652    cx.executor().run_until_parked();
22653
22654    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
22655        let snapshot = editor.snapshot(window, cx);
22656        let reverted_hunk_statuses = snapshot
22657            .buffer_snapshot
22658            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
22659            .map(|hunk| hunk.status().kind)
22660            .collect::<Vec<_>>();
22661
22662        editor.git_restore(&Default::default(), window, cx);
22663        reverted_hunk_statuses
22664    });
22665    cx.executor().run_until_parked();
22666    cx.assert_editor_state(expected_reverted_text_with_selections);
22667    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
22668}
22669
22670#[gpui::test(iterations = 10)]
22671async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
22672    init_test(cx, |_| {});
22673
22674    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
22675    let counter = diagnostic_requests.clone();
22676
22677    let fs = FakeFs::new(cx.executor());
22678    fs.insert_tree(
22679        path!("/a"),
22680        json!({
22681            "first.rs": "fn main() { let a = 5; }",
22682            "second.rs": "// Test file",
22683        }),
22684    )
22685    .await;
22686
22687    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22688    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22689    let cx = &mut VisualTestContext::from_window(*workspace, cx);
22690
22691    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22692    language_registry.add(rust_lang());
22693    let mut fake_servers = language_registry.register_fake_lsp(
22694        "Rust",
22695        FakeLspAdapter {
22696            capabilities: lsp::ServerCapabilities {
22697                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
22698                    lsp::DiagnosticOptions {
22699                        identifier: None,
22700                        inter_file_dependencies: true,
22701                        workspace_diagnostics: true,
22702                        work_done_progress_options: Default::default(),
22703                    },
22704                )),
22705                ..Default::default()
22706            },
22707            ..Default::default()
22708        },
22709    );
22710
22711    let editor = workspace
22712        .update(cx, |workspace, window, cx| {
22713            workspace.open_abs_path(
22714                PathBuf::from(path!("/a/first.rs")),
22715                OpenOptions::default(),
22716                window,
22717                cx,
22718            )
22719        })
22720        .unwrap()
22721        .await
22722        .unwrap()
22723        .downcast::<Editor>()
22724        .unwrap();
22725    let fake_server = fake_servers.next().await.unwrap();
22726    let server_id = fake_server.server.server_id();
22727    let mut first_request = fake_server
22728        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
22729            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
22730            let result_id = Some(new_result_id.to_string());
22731            assert_eq!(
22732                params.text_document.uri,
22733                lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
22734            );
22735            async move {
22736                Ok(lsp::DocumentDiagnosticReportResult::Report(
22737                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
22738                        related_documents: None,
22739                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
22740                            items: Vec::new(),
22741                            result_id,
22742                        },
22743                    }),
22744                ))
22745            }
22746        });
22747
22748    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
22749        project.update(cx, |project, cx| {
22750            let buffer_id = editor
22751                .read(cx)
22752                .buffer()
22753                .read(cx)
22754                .as_singleton()
22755                .expect("created a singleton buffer")
22756                .read(cx)
22757                .remote_id();
22758            let buffer_result_id = project
22759                .lsp_store()
22760                .read(cx)
22761                .result_id(server_id, buffer_id, cx);
22762            assert_eq!(expected, buffer_result_id);
22763        });
22764    };
22765
22766    ensure_result_id(None, cx);
22767    cx.executor().advance_clock(Duration::from_millis(60));
22768    cx.executor().run_until_parked();
22769    assert_eq!(
22770        diagnostic_requests.load(atomic::Ordering::Acquire),
22771        1,
22772        "Opening file should trigger diagnostic request"
22773    );
22774    first_request
22775        .next()
22776        .await
22777        .expect("should have sent the first diagnostics pull request");
22778    ensure_result_id(Some("1".to_string()), cx);
22779
22780    // Editing should trigger diagnostics
22781    editor.update_in(cx, |editor, window, cx| {
22782        editor.handle_input("2", window, cx)
22783    });
22784    cx.executor().advance_clock(Duration::from_millis(60));
22785    cx.executor().run_until_parked();
22786    assert_eq!(
22787        diagnostic_requests.load(atomic::Ordering::Acquire),
22788        2,
22789        "Editing should trigger diagnostic request"
22790    );
22791    ensure_result_id(Some("2".to_string()), cx);
22792
22793    // Moving cursor should not trigger diagnostic request
22794    editor.update_in(cx, |editor, window, cx| {
22795        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22796            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
22797        });
22798    });
22799    cx.executor().advance_clock(Duration::from_millis(60));
22800    cx.executor().run_until_parked();
22801    assert_eq!(
22802        diagnostic_requests.load(atomic::Ordering::Acquire),
22803        2,
22804        "Cursor movement should not trigger diagnostic request"
22805    );
22806    ensure_result_id(Some("2".to_string()), cx);
22807    // Multiple rapid edits should be debounced
22808    for _ in 0..5 {
22809        editor.update_in(cx, |editor, window, cx| {
22810            editor.handle_input("x", window, cx)
22811        });
22812    }
22813    cx.executor().advance_clock(Duration::from_millis(60));
22814    cx.executor().run_until_parked();
22815
22816    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
22817    assert!(
22818        final_requests <= 4,
22819        "Multiple rapid edits should be debounced (got {final_requests} requests)",
22820    );
22821    ensure_result_id(Some(final_requests.to_string()), cx);
22822}
22823
22824#[gpui::test]
22825async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
22826    // Regression test for issue #11671
22827    // Previously, adding a cursor after moving multiple cursors would reset
22828    // the cursor count instead of adding to the existing cursors.
22829    init_test(cx, |_| {});
22830    let mut cx = EditorTestContext::new(cx).await;
22831
22832    // Create a simple buffer with cursor at start
22833    cx.set_state(indoc! {"
22834        ˇaaaa
22835        bbbb
22836        cccc
22837        dddd
22838        eeee
22839        ffff
22840        gggg
22841        hhhh"});
22842
22843    // Add 2 cursors below (so we have 3 total)
22844    cx.update_editor(|editor, window, cx| {
22845        editor.add_selection_below(&Default::default(), window, cx);
22846        editor.add_selection_below(&Default::default(), window, cx);
22847    });
22848
22849    // Verify we have 3 cursors
22850    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
22851    assert_eq!(
22852        initial_count, 3,
22853        "Should have 3 cursors after adding 2 below"
22854    );
22855
22856    // Move down one line
22857    cx.update_editor(|editor, window, cx| {
22858        editor.move_down(&MoveDown, window, cx);
22859    });
22860
22861    // Add another cursor below
22862    cx.update_editor(|editor, window, cx| {
22863        editor.add_selection_below(&Default::default(), window, cx);
22864    });
22865
22866    // Should now have 4 cursors (3 original + 1 new)
22867    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
22868    assert_eq!(
22869        final_count, 4,
22870        "Should have 4 cursors after moving and adding another"
22871    );
22872}
22873
22874#[gpui::test(iterations = 10)]
22875async fn test_document_colors(cx: &mut TestAppContext) {
22876    let expected_color = Rgba {
22877        r: 0.33,
22878        g: 0.33,
22879        b: 0.33,
22880        a: 0.33,
22881    };
22882
22883    init_test(cx, |_| {});
22884
22885    let fs = FakeFs::new(cx.executor());
22886    fs.insert_tree(
22887        path!("/a"),
22888        json!({
22889            "first.rs": "fn main() { let a = 5; }",
22890        }),
22891    )
22892    .await;
22893
22894    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22895    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22896    let cx = &mut VisualTestContext::from_window(*workspace, cx);
22897
22898    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22899    language_registry.add(rust_lang());
22900    let mut fake_servers = language_registry.register_fake_lsp(
22901        "Rust",
22902        FakeLspAdapter {
22903            capabilities: lsp::ServerCapabilities {
22904                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
22905                ..lsp::ServerCapabilities::default()
22906            },
22907            name: "rust-analyzer",
22908            ..FakeLspAdapter::default()
22909        },
22910    );
22911    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
22912        "Rust",
22913        FakeLspAdapter {
22914            capabilities: lsp::ServerCapabilities {
22915                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
22916                ..lsp::ServerCapabilities::default()
22917            },
22918            name: "not-rust-analyzer",
22919            ..FakeLspAdapter::default()
22920        },
22921    );
22922
22923    let editor = workspace
22924        .update(cx, |workspace, window, cx| {
22925            workspace.open_abs_path(
22926                PathBuf::from(path!("/a/first.rs")),
22927                OpenOptions::default(),
22928                window,
22929                cx,
22930            )
22931        })
22932        .unwrap()
22933        .await
22934        .unwrap()
22935        .downcast::<Editor>()
22936        .unwrap();
22937    let fake_language_server = fake_servers.next().await.unwrap();
22938    let fake_language_server_without_capabilities =
22939        fake_servers_without_capabilities.next().await.unwrap();
22940    let requests_made = Arc::new(AtomicUsize::new(0));
22941    let closure_requests_made = Arc::clone(&requests_made);
22942    let mut color_request_handle = fake_language_server
22943        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
22944            let requests_made = Arc::clone(&closure_requests_made);
22945            async move {
22946                assert_eq!(
22947                    params.text_document.uri,
22948                    lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
22949                );
22950                requests_made.fetch_add(1, atomic::Ordering::Release);
22951                Ok(vec![
22952                    lsp::ColorInformation {
22953                        range: lsp::Range {
22954                            start: lsp::Position {
22955                                line: 0,
22956                                character: 0,
22957                            },
22958                            end: lsp::Position {
22959                                line: 0,
22960                                character: 1,
22961                            },
22962                        },
22963                        color: lsp::Color {
22964                            red: 0.33,
22965                            green: 0.33,
22966                            blue: 0.33,
22967                            alpha: 0.33,
22968                        },
22969                    },
22970                    lsp::ColorInformation {
22971                        range: lsp::Range {
22972                            start: lsp::Position {
22973                                line: 0,
22974                                character: 0,
22975                            },
22976                            end: lsp::Position {
22977                                line: 0,
22978                                character: 1,
22979                            },
22980                        },
22981                        color: lsp::Color {
22982                            red: 0.33,
22983                            green: 0.33,
22984                            blue: 0.33,
22985                            alpha: 0.33,
22986                        },
22987                    },
22988                ])
22989            }
22990        });
22991
22992    let _handle = fake_language_server_without_capabilities
22993        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
22994            panic!("Should not be called");
22995        });
22996    cx.executor().advance_clock(Duration::from_millis(100));
22997    color_request_handle.next().await.unwrap();
22998    cx.run_until_parked();
22999    assert_eq!(
23000        1,
23001        requests_made.load(atomic::Ordering::Acquire),
23002        "Should query for colors once per editor open"
23003    );
23004    editor.update_in(cx, |editor, _, cx| {
23005        assert_eq!(
23006            vec![expected_color],
23007            extract_color_inlays(editor, cx),
23008            "Should have an initial inlay"
23009        );
23010    });
23011
23012    // opening another file in a split should not influence the LSP query counter
23013    workspace
23014        .update(cx, |workspace, window, cx| {
23015            assert_eq!(
23016                workspace.panes().len(),
23017                1,
23018                "Should have one pane with one editor"
23019            );
23020            workspace.move_item_to_pane_in_direction(
23021                &MoveItemToPaneInDirection {
23022                    direction: SplitDirection::Right,
23023                    focus: false,
23024                    clone: true,
23025                },
23026                window,
23027                cx,
23028            );
23029        })
23030        .unwrap();
23031    cx.run_until_parked();
23032    workspace
23033        .update(cx, |workspace, _, cx| {
23034            let panes = workspace.panes();
23035            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
23036            for pane in panes {
23037                let editor = pane
23038                    .read(cx)
23039                    .active_item()
23040                    .and_then(|item| item.downcast::<Editor>())
23041                    .expect("Should have opened an editor in each split");
23042                let editor_file = editor
23043                    .read(cx)
23044                    .buffer()
23045                    .read(cx)
23046                    .as_singleton()
23047                    .expect("test deals with singleton buffers")
23048                    .read(cx)
23049                    .file()
23050                    .expect("test buffese should have a file")
23051                    .path();
23052                assert_eq!(
23053                    editor_file.as_ref(),
23054                    Path::new("first.rs"),
23055                    "Both editors should be opened for the same file"
23056                )
23057            }
23058        })
23059        .unwrap();
23060
23061    cx.executor().advance_clock(Duration::from_millis(500));
23062    let save = editor.update_in(cx, |editor, window, cx| {
23063        editor.move_to_end(&MoveToEnd, window, cx);
23064        editor.handle_input("dirty", window, cx);
23065        editor.save(
23066            SaveOptions {
23067                format: true,
23068                autosave: true,
23069            },
23070            project.clone(),
23071            window,
23072            cx,
23073        )
23074    });
23075    save.await.unwrap();
23076
23077    color_request_handle.next().await.unwrap();
23078    cx.run_until_parked();
23079    assert_eq!(
23080        3,
23081        requests_made.load(atomic::Ordering::Acquire),
23082        "Should query for colors once per save and once per formatting after save"
23083    );
23084
23085    drop(editor);
23086    let close = workspace
23087        .update(cx, |workspace, window, cx| {
23088            workspace.active_pane().update(cx, |pane, cx| {
23089                pane.close_active_item(&CloseActiveItem::default(), window, cx)
23090            })
23091        })
23092        .unwrap();
23093    close.await.unwrap();
23094    let close = workspace
23095        .update(cx, |workspace, window, cx| {
23096            workspace.active_pane().update(cx, |pane, cx| {
23097                pane.close_active_item(&CloseActiveItem::default(), window, cx)
23098            })
23099        })
23100        .unwrap();
23101    close.await.unwrap();
23102    assert_eq!(
23103        3,
23104        requests_made.load(atomic::Ordering::Acquire),
23105        "After saving and closing all editors, no extra requests should be made"
23106    );
23107    workspace
23108        .update(cx, |workspace, _, cx| {
23109            assert!(
23110                workspace.active_item(cx).is_none(),
23111                "Should close all editors"
23112            )
23113        })
23114        .unwrap();
23115
23116    workspace
23117        .update(cx, |workspace, window, cx| {
23118            workspace.active_pane().update(cx, |pane, cx| {
23119                pane.navigate_backward(window, cx);
23120            })
23121        })
23122        .unwrap();
23123    cx.executor().advance_clock(Duration::from_millis(100));
23124    cx.run_until_parked();
23125    let editor = workspace
23126        .update(cx, |workspace, _, cx| {
23127            workspace
23128                .active_item(cx)
23129                .expect("Should have reopened the editor again after navigating back")
23130                .downcast::<Editor>()
23131                .expect("Should be an editor")
23132        })
23133        .unwrap();
23134    color_request_handle.next().await.unwrap();
23135    assert_eq!(
23136        3,
23137        requests_made.load(atomic::Ordering::Acquire),
23138        "Cache should be reused on buffer close and reopen"
23139    );
23140    editor.update(cx, |editor, cx| {
23141        assert_eq!(
23142            vec![expected_color],
23143            extract_color_inlays(editor, cx),
23144            "Should have an initial inlay"
23145        );
23146    });
23147}
23148
23149#[gpui::test]
23150async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
23151    init_test(cx, |_| {});
23152    let (editor, cx) = cx.add_window_view(Editor::single_line);
23153    editor.update_in(cx, |editor, window, cx| {
23154        editor.set_text("oops\n\nwow\n", window, cx)
23155    });
23156    cx.run_until_parked();
23157    editor.update(cx, |editor, cx| {
23158        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
23159    });
23160    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
23161    cx.run_until_parked();
23162    editor.update(cx, |editor, cx| {
23163        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
23164    });
23165}
23166
23167#[track_caller]
23168fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
23169    editor
23170        .all_inlays(cx)
23171        .into_iter()
23172        .filter_map(|inlay| inlay.get_color())
23173        .map(Rgba::from)
23174        .collect()
23175}